Add spam review functionality to contact form and update translations
- Introduced a new spamReview property in the Form interface to handle manual review requests for messages flagged as spam. - Updated the Form component to display a spam warning and a manual review form with dynamic labels and placeholders based on translations. - Enhanced the Tailwind CSS styles for responsive subtitles across various components. - Added corresponding translations for the spam review feature in English, Dutch, German, and French, ensuring consistency and clarity in messaging. - Updated various components to integrate the new spamReview functionality, improving user experience and interaction.
This commit is contained in:
@@ -26,6 +26,11 @@
|
|||||||
.text-muted {
|
.text-muted {
|
||||||
color: var(--aw-color-text-muted);
|
color: var(--aw-color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive subtitle utility */
|
||||||
|
.subtitle-responsive {
|
||||||
|
@apply text-base md:text-lg lg:text-xl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
import type { Form as Props } from '~/types';
|
import type { Form as Props } from '~/types';
|
||||||
import Button from '~/components/ui/Button.astro';
|
import Button from '~/components/ui/Button.astro';
|
||||||
|
|
||||||
const { inputs, textarea, disclaimer, button = 'Contact us', description = '' } = Astro.props;
|
const { inputs, textarea, disclaimer, button = 'Contact us', description = '', spamReview } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -15,14 +15,7 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' }
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<form
|
<form id="contact-form" name="contact" method="POST" action="/api/contact" class="needs-validation" novalidate>
|
||||||
id="contact-form"
|
|
||||||
name="contact"
|
|
||||||
method="POST"
|
|
||||||
action="/api/contact"
|
|
||||||
class="needs-validation"
|
|
||||||
novalidate
|
|
||||||
>
|
|
||||||
<!-- Form status messages -->
|
<!-- Form status messages -->
|
||||||
<div id="form-success" class="hidden mb-6 p-4 bg-green-100 border border-green-200 text-green-700 rounded-lg">
|
<div id="form-success" class="hidden mb-6 p-4 bg-green-100 border border-green-200 text-green-700 rounded-lg">
|
||||||
Your message has been sent successfully. We will get back to you soon!
|
Your message has been sent successfully. We will get back to you soon!
|
||||||
@@ -132,23 +125,51 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' }
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Manual Review UI -->
|
<!-- Manual Review UI -->
|
||||||
<div id="spam-warning" style="display:none;" class="max-w-xl mx-auto rounded-lg backdrop-blur-sm bg-white/15 dark:bg-slate-900 border border-gray-200 dark:border-gray-700 shadow-md p-4 sm:p-6 lg:p-8 w-full mb-6">
|
<div
|
||||||
|
id="spam-warning"
|
||||||
|
style="display:none;"
|
||||||
|
class="max-w-xl mx-auto rounded-lg backdrop-blur-sm bg-white/15 dark:bg-slate-900 border border-gray-200 dark:border-gray-700 shadow-md p-4 sm:p-6 lg:p-8 w-full mb-6"
|
||||||
|
>
|
||||||
<p class="mb-4 text-lg font-medium text-gray-800 dark:text-gray-100">
|
<p class="mb-4 text-lg font-medium text-gray-800 dark:text-gray-100">
|
||||||
Your message was detected as spam and was not sent.<br>
|
{spamReview?.title}<br />
|
||||||
<span class="text-base font-normal text-gray-600 dark:text-gray-300">If you believe this is a mistake, you can request a manual review.</span>
|
<span class="text-base font-normal text-gray-600 dark:text-gray-300">{spamReview?.description}</span>
|
||||||
</p>
|
</p>
|
||||||
<form id="manual-review-form" class="space-y-4">
|
<form id="manual-review-form" class="space-y-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="manual-email">Please re-enter your email address for confirmation</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="manual-email"
|
||||||
<input type="email" id="manual-email" required placeholder="Enter your email address again" class="py-3 px-4 block w-full text-md rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-slate-900" />
|
>{spamReview?.emailLabel}</label
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="manual-justification">Why is this not spam? <span class="text-gray-400">(optional)</span></label>
|
>
|
||||||
<textarea id="manual-justification" placeholder="Explain why your message is legitimate..." class="py-3 px-4 block w-full text-md rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-slate-900"></textarea>
|
<input
|
||||||
|
type="email"
|
||||||
|
id="manual-email"
|
||||||
|
required
|
||||||
|
placeholder={spamReview?.emailPlaceholder}
|
||||||
|
class="py-3 px-4 block w-full text-md rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-slate-900"
|
||||||
|
/>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="manual-justification"
|
||||||
|
>{spamReview?.justificationLabel} <span class="text-gray-400">{spamReview?.justificationOptional}</span></label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
id="manual-justification"
|
||||||
|
placeholder={spamReview?.justificationPlaceholder}
|
||||||
|
class="py-3 px-4 block w-full text-md rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-slate-900"
|
||||||
|
></textarea>
|
||||||
<input type="hidden" id="manual-token" />
|
<input type="hidden" id="manual-token" />
|
||||||
<button type="submit" class="mt-2 w-full py-3 px-4 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors">Request Manual Review</button>
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="mt-2 w-full py-3 px-4 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-semibold transition-colors"
|
||||||
|
>{spamReview?.button}</button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
<div id="manual-review-result" class="mt-4 text-center text-green-700 dark:text-green-400 font-medium"></div>
|
<div id="manual-review-result" class="mt-4 text-center text-green-700 dark:text-green-400 font-medium"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// TypeScript: declare the property on window
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__originalEmail?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
async function setCsrfToken() {
|
async function setCsrfToken() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/contact?csrf=true');
|
const res = await fetch('/api/contact?csrf=true');
|
||||||
@@ -156,7 +177,7 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' }
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const csrfInput = document.getElementById('csrf_token');
|
const csrfInput = document.getElementById('csrf_token');
|
||||||
if (csrfInput && data.csrfToken) {
|
if (csrfInput && data.csrfToken) {
|
||||||
csrfInput.value = data.csrfToken;
|
(csrfInput as HTMLInputElement).value = data.csrfToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -195,9 +216,11 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' }
|
|||||||
const spamWarning = document.getElementById('spam-warning');
|
const spamWarning = document.getElementById('spam-warning');
|
||||||
const manualEmail = document.getElementById('manual-email') as HTMLInputElement | null;
|
const manualEmail = document.getElementById('manual-email') as HTMLInputElement | null;
|
||||||
const manualToken = document.getElementById('manual-token') as HTMLInputElement | null;
|
const manualToken = document.getElementById('manual-token') as HTMLInputElement | null;
|
||||||
|
// Store the original email in a variable (not in the input)
|
||||||
|
window.__originalEmail = String(formData.get('email'));
|
||||||
if (spamWarning && manualEmail && manualToken) {
|
if (spamWarning && manualEmail && manualToken) {
|
||||||
spamWarning.style.display = 'block';
|
spamWarning.style.display = 'block';
|
||||||
manualEmail.value = String(formData.get('email'));
|
manualEmail.value = '';
|
||||||
manualToken.value = result.token;
|
manualToken.value = result.token;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -236,7 +259,11 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' }
|
|||||||
const email = manualEmail.value;
|
const email = manualEmail.value;
|
||||||
const justification = manualJustification.value;
|
const justification = manualJustification.value;
|
||||||
const token = manualToken.value;
|
const token = manualToken.value;
|
||||||
|
// Check if the entered email matches the original
|
||||||
|
if (typeof window.__originalEmail !== 'undefined' && email !== window.__originalEmail) {
|
||||||
|
resultDiv.textContent = 'Email addresses do not match.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
const res = await fetch('/api/contact/manual-review', {
|
const res = await fetch('/api/contact/manual-review', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -244,7 +271,8 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' }
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
resultDiv.textContent = 'Your request for manual review has been submitted. Thank you!';
|
resultDiv.textContent =
|
||||||
|
spamReview?.resultSuccess || 'Your request for manual review has been submitted. Thank you!';
|
||||||
// Hide spam-warning and show the normal form again after a short delay
|
// Hide spam-warning and show the normal form again after a short delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (spamWarning) spamWarning.style.display = 'none';
|
if (spamWarning) spamWarning.style.display = 'none';
|
||||||
@@ -255,7 +283,8 @@ const { inputs, textarea, disclaimer, button = 'Contact us', description = '' }
|
|||||||
resultDiv.textContent = '';
|
resultDiv.textContent = '';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
resultDiv.textContent = data.error || 'There was an error submitting your manual review request.';
|
resultDiv.textContent =
|
||||||
|
data.error || spamReview?.resultError || 'There was an error submitting your manual review request.';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ const {
|
|||||||
const {
|
const {
|
||||||
container: containerClass = 'max-w-3xl',
|
container: containerClass = 'max-w-3xl',
|
||||||
title: titleClass = 'text-3xl md:text-4xl ',
|
title: titleClass = 'text-3xl md:text-4xl ',
|
||||||
subtitle: subtitleClass = 'text-xl',
|
subtitle: subtitleClass = 'text-base md:text-lg lg:text-xl',
|
||||||
} = classes;
|
} = classes;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -36,7 +36,7 @@ const {
|
|||||||
classes={{
|
classes={{
|
||||||
container: 'mb-0 md:mb-0',
|
container: 'mb-0 md:mb-0',
|
||||||
title: 'text-4xl md:text-4xl font-bold tracking-tighter mb-4 font-heading',
|
title: 'text-4xl md:text-4xl font-bold tracking-tighter mb-4 font-heading',
|
||||||
subtitle: 'text-xl text-muted dark:text-slate-400',
|
subtitle: 'text-base md:text-lg lg:text-xl text-muted dark:text-slate-400',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
|
@@ -18,6 +18,7 @@ const {
|
|||||||
isDark = false,
|
isDark = false,
|
||||||
classes = {},
|
classes = {},
|
||||||
bg = await Astro.slots.render('bg'),
|
bg = await Astro.slots.render('bg'),
|
||||||
|
spamReview,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ const {
|
|||||||
disclaimer={disclaimer}
|
disclaimer={disclaimer}
|
||||||
button={button}
|
button={button}
|
||||||
description={description}
|
description={description}
|
||||||
|
spamReview={spamReview}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@@ -38,7 +38,7 @@ const {
|
|||||||
classes={{
|
classes={{
|
||||||
container: 'max-w-xl sm:mx-auto lg:max-w-2xl',
|
container: 'max-w-xl sm:mx-auto lg:max-w-2xl',
|
||||||
title: 'text-4xl md:text-5xl font-bold tracking-tighter mb-4 font-heading',
|
title: 'text-4xl md:text-5xl font-bold tracking-tighter mb-4 font-heading',
|
||||||
subtitle: 'max-w-3xl mx-auto sm:text-center text-xl text-muted dark:text-slate-400',
|
subtitle: 'max-w-3xl mx-auto sm:text-center text-base md:text-lg lg:text-xl text-muted dark:text-slate-400',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="mx-auto max-w-7xl p-4 md:px-8">
|
<div class="mx-auto max-w-7xl p-4 md:px-8">
|
||||||
|
@@ -57,7 +57,7 @@ const {
|
|||||||
{
|
{
|
||||||
subtitle && (
|
subtitle && (
|
||||||
<p
|
<p
|
||||||
class="text-xl mb-6 dark:text-slate-300 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade content-backdrop p-2 rounded-md"
|
class="text-base md:text-lg lg:text-xl mb-6 dark:text-slate-300 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade content-backdrop p-2 rounded-md"
|
||||||
set:html={subtitle}
|
set:html={subtitle}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@@ -50,7 +50,7 @@ const {
|
|||||||
{
|
{
|
||||||
subtitle && (
|
subtitle && (
|
||||||
<p
|
<p
|
||||||
class="text-xl text-muted mb-6 dark:text-slate-300 content-backdrop p-2 rounded-md"
|
class="text-base md:text-lg lg:text-xl text-muted mb-6 dark:text-slate-300 content-backdrop p-2 rounded-md"
|
||||||
set:html={subtitle}
|
set:html={subtitle}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@@ -47,7 +47,7 @@ const {
|
|||||||
{
|
{
|
||||||
subtitle && (
|
subtitle && (
|
||||||
<p
|
<p
|
||||||
class="text-xl text-muted mb-6 dark:text-slate-300 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
|
class="text-base md:text-lg lg:text-xl text-muted mb-6 dark:text-slate-300 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
|
||||||
set:html={subtitle}
|
set:html={subtitle}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@@ -67,7 +67,7 @@ const timelineItems = items.map((item) => {
|
|||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
{subtitle && <p class={`${compact ? 'text-lg' : 'text-xl'} text-muted dark:text-slate-400`}>{subtitle}</p>}
|
{subtitle && <p class={`${compact ? 'text-base md:text-lg' : 'text-base md:text-lg lg:text-xl'} text-muted dark:text-slate-400`}>{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -45,6 +45,18 @@ export interface ExplosiveHomepageTranslation {
|
|||||||
};
|
};
|
||||||
disclaimer: string;
|
disclaimer: string;
|
||||||
};
|
};
|
||||||
|
spamReview: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
emailLabel: string;
|
||||||
|
emailPlaceholder: string;
|
||||||
|
justificationLabel: string;
|
||||||
|
justificationOptional: string;
|
||||||
|
justificationPlaceholder: string;
|
||||||
|
button: string;
|
||||||
|
resultSuccess: string;
|
||||||
|
resultError: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const supportedLanguages = ['en', 'nl', 'de', 'fr'] as const;
|
export const supportedLanguages = ['en', 'nl', 'de', 'fr'] as const;
|
||||||
@@ -136,6 +148,18 @@ export const explosiveHomepageTranslations: Record<string, ExplosiveHomepageTran
|
|||||||
},
|
},
|
||||||
disclaimer: '🔒 Your information stays secure. No spam — just results.',
|
disclaimer: '🔒 Your information stays secure. No spam — just results.',
|
||||||
},
|
},
|
||||||
|
spamReview: {
|
||||||
|
title: "Your message was detected as spam and was not sent.",
|
||||||
|
description: "If you believe this is a mistake, you can request a manual review.",
|
||||||
|
emailLabel: "Please re-enter your email address for confirmation",
|
||||||
|
emailPlaceholder: "Enter your email address again",
|
||||||
|
justificationLabel: "Why is this not spam?",
|
||||||
|
justificationOptional: "(optional)",
|
||||||
|
justificationPlaceholder: "Explain why your message is legitimate...",
|
||||||
|
button: "Request Manual Review",
|
||||||
|
resultSuccess: "Your request for manual review has been submitted. Thank you!",
|
||||||
|
resultError: "There was an error submitting your manual review request."
|
||||||
|
},
|
||||||
},
|
},
|
||||||
nl: {
|
nl: {
|
||||||
hero: {
|
hero: {
|
||||||
@@ -221,6 +245,18 @@ export const explosiveHomepageTranslations: Record<string, ExplosiveHomepageTran
|
|||||||
},
|
},
|
||||||
disclaimer: '🔒 Jouw gegevens blijven privé. Geen spam – alleen oplossingen.',
|
disclaimer: '🔒 Jouw gegevens blijven privé. Geen spam – alleen oplossingen.',
|
||||||
},
|
},
|
||||||
|
spamReview: {
|
||||||
|
title: "Je bericht is als spam gemarkeerd en niet verzonden.",
|
||||||
|
description: "Denk je dat dit onterecht is? Vraag dan een handmatige beoordeling aan.",
|
||||||
|
emailLabel: "Voer je e-mailadres opnieuw in ter bevestiging",
|
||||||
|
emailPlaceholder: "Voer opnieuw je e-mailadres in",
|
||||||
|
justificationLabel: "Waarom is dit geen spam?",
|
||||||
|
justificationOptional: "(optioneel)",
|
||||||
|
justificationPlaceholder: "Leg uit waarom je bericht legitiem is...",
|
||||||
|
button: "Handmatige beoordeling aanvragen",
|
||||||
|
resultSuccess: "Je verzoek voor handmatige beoordeling is ingediend. Bedankt!",
|
||||||
|
resultError: "Er is een fout opgetreden bij het verzenden van je verzoek."
|
||||||
|
},
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
hero: {
|
hero: {
|
||||||
@@ -306,6 +342,18 @@ export const explosiveHomepageTranslations: Record<string, ExplosiveHomepageTran
|
|||||||
},
|
},
|
||||||
disclaimer: '🔒 Ihre Daten sind bei uns sicher. Kein Spam – nur Lösungen.',
|
disclaimer: '🔒 Ihre Daten sind bei uns sicher. Kein Spam – nur Lösungen.',
|
||||||
},
|
},
|
||||||
|
spamReview: {
|
||||||
|
title: "Ihre Nachricht wurde als Spam erkannt und nicht versendet.",
|
||||||
|
description: "Wenn Sie der Meinung sind, dass es sich um einen Fehler handelt, können Sie eine manuelle Prüfung anfordern.",
|
||||||
|
emailLabel: "Bitte geben Sie zur Bestätigung Ihre E-Mail-Adresse erneut ein",
|
||||||
|
emailPlaceholder: "E-Mail-Adresse erneut eingeben",
|
||||||
|
justificationLabel: "Warum ist das kein Spam?",
|
||||||
|
justificationOptional: "(optional)",
|
||||||
|
justificationPlaceholder: "Erklären Sie, warum Ihre Nachricht legitim ist...",
|
||||||
|
button: "Manuelle Prüfung anfordern",
|
||||||
|
resultSuccess: "Ihre Anfrage zur manuellen Prüfung wurde übermittelt. Vielen Dank!",
|
||||||
|
resultError: "Beim Absenden Ihrer Anfrage ist ein Fehler aufgetreten."
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fr: {
|
fr: {
|
||||||
hero: {
|
hero: {
|
||||||
@@ -390,5 +438,17 @@ export const explosiveHomepageTranslations: Record<string, ExplosiveHomepageTran
|
|||||||
},
|
},
|
||||||
disclaimer: '🔒 Vos données sont confidentielles. Aucune publicité – uniquement des solutions concrètes.',
|
disclaimer: '🔒 Vos données sont confidentielles. Aucune publicité – uniquement des solutions concrètes.',
|
||||||
},
|
},
|
||||||
|
spamReview: {
|
||||||
|
title: "Votre message a été détecté comme spam et n’a pas été envoyé.",
|
||||||
|
description: "Si vous pensez qu’il s’agit d’une erreur, vous pouvez demander une révision manuelle.",
|
||||||
|
emailLabel: "Veuillez saisir à nouveau votre adresse e-mail pour confirmation",
|
||||||
|
emailPlaceholder: "Saisissez à nouveau votre adresse e-mail",
|
||||||
|
justificationLabel: "Pourquoi ce message n’est-il pas du spam ?",
|
||||||
|
justificationOptional: "(optionnel)",
|
||||||
|
justificationPlaceholder: "Expliquez pourquoi votre message est légitime...",
|
||||||
|
button: "Demander une révision manuelle",
|
||||||
|
resultSuccess: "Votre demande de révision manuelle a été envoyée. Merci !",
|
||||||
|
resultError: "Une erreur s’est produite lors de l’envoi de votre demande."
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -208,5 +208,6 @@ const metadata = {
|
|||||||
}}
|
}}
|
||||||
description={t.homepage?.contact?.description ||
|
description={t.homepage?.contact?.description ||
|
||||||
"I'll respond to your message as soon as possible. You can also connect with me on LinkedIn or GitHub."}
|
"I'll respond to your message as soon as possible. You can also connect with me on LinkedIn or GitHub."}
|
||||||
|
spamReview={t.homepage?.spamReview}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@@ -466,6 +466,10 @@ const metadata = {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cta-subtitle {
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -608,7 +612,7 @@ const metadata = {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Fragment slot="title">
|
<Fragment slot="title">
|
||||||
<div class="text-6xl font-black mb-6">
|
<div class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black mb-6">
|
||||||
<span class="text-transparent bg-clip-text bg-gradient-to-r from-pink-400 via-purple-400 to-blue-400">
|
<span class="text-transparent bg-clip-text bg-gradient-to-r from-pink-400 via-purple-400 to-blue-400">
|
||||||
{explosive.cta.title}
|
{explosive.cta.title}
|
||||||
</span>
|
</span>
|
||||||
@@ -654,6 +658,7 @@ const metadata = {
|
|||||||
classes={{
|
classes={{
|
||||||
container: 'glass-vibrant'
|
container: 'glass-vibrant'
|
||||||
}}
|
}}
|
||||||
|
spamReview={explosive.spamReview}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
@@ -163,5 +163,6 @@ const metadata = {
|
|||||||
}}
|
}}
|
||||||
description={t.homepage?.contact?.description ||
|
description={t.homepage?.contact?.description ||
|
||||||
"I'll respond to your message as soon as possible. You can also connect with me on LinkedIn or GitHub."}
|
"I'll respond to your message as soon as possible. You can also connect with me on LinkedIn or GitHub."}
|
||||||
|
spamReview={t.homepage?.spamReview}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
12
src/types.d.ts
vendored
12
src/types.d.ts
vendored
@@ -213,6 +213,18 @@ export interface Form {
|
|||||||
disclaimer?: Disclaimer;
|
disclaimer?: Disclaimer;
|
||||||
button?: string;
|
button?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
spamReview?: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
emailLabel: string;
|
||||||
|
emailPlaceholder: string;
|
||||||
|
justificationLabel: string;
|
||||||
|
justificationOptional: string;
|
||||||
|
justificationPlaceholder: string;
|
||||||
|
button: string;
|
||||||
|
resultSuccess: string;
|
||||||
|
resultError: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// WIDGETS
|
// WIDGETS
|
||||||
|
Reference in New Issue
Block a user