Update AboutUsPage, server routes, packages, and assets
@@ -16,6 +16,7 @@
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^1.18.0",
|
||||
"multer": "^2.2.0",
|
||||
"nodemailer": "^9.0.3",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6"
|
||||
},
|
||||
@@ -2867,6 +2868,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.3.tgz",
|
||||
"integrity": "sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^1.18.0",
|
||||
"multer": "^2.2.0",
|
||||
"nodemailer": "^9.0.3",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6"
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 169 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="1728" height="1016" viewBox="0 0 1728 1016" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1728" height="1016" fill="url(#paint0_linear_288_7058)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_288_7058" x1="864" y1="0" x2="864" y2="1016" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2FA0B6"/>
|
||||
<stop offset="1" stop-color="#01040B"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 395 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="1728" height="1107" viewBox="0 0 1728 1107" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1728" height="1107" fill="url(#paint0_linear_288_7119)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_288_7119" x1="864" y1="0" x2="864" y2="1107" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.591346" stop-color="#01040B"/>
|
||||
<stop offset="1" stop-color="#2FA0B6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 413 B |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="14.1884" cy="14.1884" r="14.1884" fill="#F67057"/>
|
||||
<circle cx="14.1883" cy="14.1864" r="8.73132" fill="#F4F8FB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 229 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="902" height="552" viewBox="0 0 902 552" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11190)">
|
||||
<circle cx="774.8" cy="774.8" r="537" fill="#2FA0B6" fill-opacity="0.3"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11190" x="-1.52588e-05" y="-0.000198364" width="1549.6" height="1549.6" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="118.9" result="effect1_foregroundBlur_291_11190"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 635 B |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 77 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="1728" height="1016" viewBox="0 0 1728 1016" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1728" height="1016" fill="url(#paint0_linear_275_5107)" fill-opacity="0.6"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_275_5107" x1="864" y1="0" x2="864" y2="1016" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2FA0B6"/>
|
||||
<stop offset="1" stop-color="#01040B"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 414 B |
@@ -0,0 +1,36 @@
|
||||
<svg width="1692" height="800" viewBox="0 0 1692 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ddddd_8_95442)">
|
||||
<path d="M28.0001 422.23C125.66 501.049 344.154 624.207 436.852 486.29C552.725 313.894 712.875 317.661 907.88 346.866C1096 375.039 1100.5 -16.459 1325.5 29.8044" stroke="#DA290C" stroke-width="4" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_ddddd_8_95442" x="0" y="0" width="1714.5" height="1000.52" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="11" dy="13"/>
|
||||
<feGaussianBlur stdDeviation="18.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.98 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="43" dy="53"/>
|
||||
<feGaussianBlur stdDeviation="34"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.85 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_8_95442" result="effect2_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="97" dy="119"/>
|
||||
<feGaussianBlur stdDeviation="46"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_8_95442" result="effect3_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="172" dy="212"/>
|
||||
<feGaussianBlur stdDeviation="54.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="effect3_dropShadow_8_95442" result="effect4_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="268" dy="331"/>
|
||||
<feGaussianBlur stdDeviation="59.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.02 0"/>
|
||||
<feBlend mode="normal" in2="effect4_dropShadow_8_95442" result="effect5_dropShadow_8_95442"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect5_dropShadow_8_95442" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="1434" height="1200" viewBox="0 0 1434 1200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11185)">
|
||||
<path d="M537.476 100H411.257L1288.66 1027.7H1334L537.476 100Z" fill="white" fill-opacity="0.1"/>
|
||||
<path d="M126.959 101.225H294.842L1092.59 1027.7H1053.38L126.959 101.225Z" fill="white" fill-opacity="0.1"/>
|
||||
<path d="M100 362.255V200.49L885.496 1088.97H847.507L100 362.255Z" fill="white" fill-opacity="0.1"/>
|
||||
<path d="M103.676 477.451V620.833L646.538 1100H701.682L103.676 477.451Z" fill="white" fill-opacity="0.1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11185" x="0" y="0" width="1434" height="1200" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="50" result="effect1_foregroundBlur_291_11185"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 951 B |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 291 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="1728" height="1107" viewBox="0 0 1728 1107" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1728" height="1107" fill="url(#paint0_linear_275_5168)" fill-opacity="0.6"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_275_5168" x1="864" y1="0" x2="864" y2="1107" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.591346" stop-color="#01040B"/>
|
||||
<stop offset="1" stop-color="#2FA0B6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 432 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="647" height="504" viewBox="0 0 647 504" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11290)">
|
||||
<ellipse cx="323.5" cy="252" rx="163.5" ry="92" fill="#B3776D"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11290" x="0" y="0" width="647" height="504" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="80" result="effect1_foregroundBlur_291_11290"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 595 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="647" height="504" viewBox="0 0 647 504" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11291)">
|
||||
<ellipse cx="323.5" cy="252" rx="163.5" ry="92" fill="#4D5154"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11291" x="0" y="0" width="647" height="504" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="80" result="effect1_foregroundBlur_291_11291"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 595 B |
|
After Width: | Height: | Size: 11 MiB |
@@ -79,3 +79,21 @@ export function updateAdminPassword(passwordHash) {
|
||||
admin.passwordHash = passwordHash;
|
||||
writeJson(ADMIN_FILE, admin);
|
||||
}
|
||||
|
||||
const LEADS_FILE = path.join(DATA_DIR, 'contact-leads.json');
|
||||
|
||||
export function saveLead(leadData) {
|
||||
const leads = readJson(LEADS_FILE, []);
|
||||
const newLead = {
|
||||
id: `lead_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
||||
...leadData,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
leads.unshift(newLead);
|
||||
writeJson(LEADS_FILE, leads);
|
||||
return newLead;
|
||||
}
|
||||
|
||||
export function getLeads() {
|
||||
return readJson(LEADS_FILE, []);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { initDb } from './db.js';
|
||||
import authRoutes from './routes/auth.js';
|
||||
import contentRoutes from './routes/content.js';
|
||||
import uploadRoutes from './routes/upload.js';
|
||||
import contactRoutes from './routes/contact.js';
|
||||
import multer from 'multer';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
@@ -49,6 +50,8 @@ function mountApp(base) {
|
||||
app.use(`${prefix}/api/auth`, authRoutes);
|
||||
app.use(`${prefix}/api/content`, contentRoutes);
|
||||
app.use(`${prefix}/api/upload`, uploadRoutes);
|
||||
app.use(`${prefix}/api/contact`, contactRoutes);
|
||||
app.use(`${prefix}/api/send`, contactRoutes);
|
||||
|
||||
if (serveStatic) {
|
||||
app.use(prefix || '/', express.static(distDir));
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import express from 'express';
|
||||
import { saveLead, getLeads } from '../db.js';
|
||||
import { sendContactEmail } from '../services/mailService.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// POST /api/contact - Handle Contact Us Form submission via SMTP
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { name, email, phone, mobile, subject, message, interests, type } = req.body;
|
||||
|
||||
const contactName = name?.trim();
|
||||
const contactEmail = email?.trim();
|
||||
const contactPhone = (phone || mobile || '').trim();
|
||||
const contactSubject = (subject || interests || type || 'General Inquiry').trim();
|
||||
const contactMessage = (message || req.body.comments || '').trim();
|
||||
|
||||
if (!contactName || !contactEmail) {
|
||||
return res.status(400).json({ error: 'Name and Email are required.' });
|
||||
}
|
||||
|
||||
// Save lead record in database
|
||||
const lead = saveLead({
|
||||
name: contactName,
|
||||
email: contactEmail,
|
||||
phone: contactPhone,
|
||||
subject: contactSubject,
|
||||
message: contactMessage,
|
||||
type: type || 'contact'
|
||||
});
|
||||
|
||||
// Attempt sending email via SMTP service
|
||||
const mailResult = await sendContactEmail({
|
||||
name: contactName,
|
||||
email: contactEmail,
|
||||
phone: contactPhone,
|
||||
subject: contactSubject,
|
||||
message: contactMessage
|
||||
});
|
||||
|
||||
if (!mailResult.success) {
|
||||
// If SMTP is not yet configured or fails, return 200 with lead saved & warning
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
saved: true,
|
||||
leadId: lead.id,
|
||||
smtpError: mailResult.error,
|
||||
message: 'Your message was saved successfully. Note: SMTP email dispatch requires SMTP credentials in .env.'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
saved: true,
|
||||
leadId: lead.id,
|
||||
messageId: mailResult.messageId,
|
||||
message: 'Message sent successfully via SMTP!'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Contact Route Error]', error);
|
||||
return res.status(500).json({ error: error.message || 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/contact/leads - Retrieve saved contact leads
|
||||
router.get('/leads', (_req, res) => {
|
||||
try {
|
||||
const leads = getLeads();
|
||||
return res.json({ success: true, leads });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -246,18 +246,16 @@
|
||||
{ "label": "Our Services", "href": "#services" }
|
||||
],
|
||||
"productLinks": [
|
||||
{ "label": "PRODMAX", "href": "#products" },
|
||||
{ "label": "Budgie HRMS", "href": "#products" },
|
||||
{ "label": "EVIDO", "href": "#products" }
|
||||
{ "label": "Nebula AI platform", "href": "#products" },
|
||||
{ "label": "Prodmax", "href": "#products" },
|
||||
{ "label": "Budgie", "href": "#products" }
|
||||
],
|
||||
"social": [
|
||||
{ "platform": "Facebook", "url": "#" },
|
||||
{ "platform": "Instagram", "url": "#" },
|
||||
{ "platform": "LinkedIn", "url": "#" },
|
||||
{ "platform": "Telegram", "url": "#" },
|
||||
{ "platform": "WhatsApp", "url": "#" },
|
||||
{ "platform": "X", "url": "#" },
|
||||
{ "platform": "YouTube", "url": "#" }
|
||||
{ "platform": "Facebook", "url": "https://www.facebook.com/cavininfotech1" },
|
||||
{ "platform": "X", "url": "https://x.com/cavin_infotech" },
|
||||
{ "platform": "Instagram", "url": "https://www.instagram.com/cavin_infotech" },
|
||||
{ "platform": "LinkedIn", "url": "https://www.linkedin.com/company/cavin-infotech/" },
|
||||
{ "platform": "YouTube", "url": "https://youtube.com/@cavin_infotech" }
|
||||
],
|
||||
"legal": [
|
||||
{ "label": "Privacy Policy", "href": "#" },
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
/**
|
||||
* Creates and returns a nodemailer SMTP transporter using environment variables.
|
||||
*/
|
||||
export function createSmtpTransporter() {
|
||||
const host = process.env.SMTP_HOST;
|
||||
const port = parseInt(process.env.SMTP_PORT || '465', 10);
|
||||
const secure = process.env.SMTP_SECURE === 'true' || port === 465;
|
||||
const user = process.env.SMTP_USER;
|
||||
const pass = process.env.SMTP_PASS;
|
||||
|
||||
if (!host || !user || !pass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure,
|
||||
auth: {
|
||||
user,
|
||||
pass
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a contact form email via SMTP using nodemailer.
|
||||
*/
|
||||
export async function sendContactEmail({ name, email, phone, subject, message }) {
|
||||
const transporter = createSmtpTransporter();
|
||||
|
||||
const toEmail = process.env.SMTP_TO || process.env.SMTP_USER || 'info@cavinfotech.com';
|
||||
const fromEmail = process.env.SMTP_FROM || `"Cavin Infotech Contact Form" <${process.env.SMTP_USER || 'info@cavinfotech.com'}>`;
|
||||
|
||||
const mailSubject = `[Contact Inquiry] ${subject ? subject : `New Inquiry from ${name}`}`;
|
||||
|
||||
const htmlContent = `
|
||||
<div style="font-family: 'Segoe UI', Helvetica, Arial, sans-serif; background-color: #020710; color: #f8fafc; padding: 40px 20px; max-width: 650px; margin: 0 auto; border-radius: 16px; border: 1px solid #1e293b;">
|
||||
<div style="text-align: center; padding-bottom: 24px; border-bottom: 1px solid #334155;">
|
||||
<h2 style="color: #ffaa00; font-size: 24px; margin: 0; font-weight: 700;">Cavin Infotech</h2>
|
||||
<p style="color: #94a3b8; font-size: 14px; margin-top: 6px;">New Website Contact Form Inquiry</p>
|
||||
</div>
|
||||
|
||||
<div style="padding: 24px 0;">
|
||||
<table style="width: 100%; border-collapse: collapse; color: #e2e8f0; font-size: 15px;">
|
||||
<tr>
|
||||
<td style="padding: 10px 0; font-weight: 600; color: #ffaa00; width: 130px;">Name:</td>
|
||||
<td style="padding: 10px 0; color: #ffffff;">${name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px 0; font-weight: 600; color: #ffaa00;">Email:</td>
|
||||
<td style="padding: 10px 0;"><a href="mailto:${email}" style="color: #38bdf8; text-decoration: none;">${email}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px 0; font-weight: 600; color: #ffaa00;">Phone No:</td>
|
||||
<td style="padding: 10px 0; color: #ffffff;">${phone || 'Not provided'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px 0; font-weight: 600; color: #ffaa00;">Subject:</td>
|
||||
<td style="padding: 10px 0; color: #ffffff;">${subject || 'General Inquiry'}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 24px; padding: 20px; background-color: #0b1528; border-left: 4px solid #ffaa00; border-radius: 8px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #94a3b8; font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em;">Message:</h4>
|
||||
<p style="margin: 0; color: #f1f5f9; font-size: 15px; line-height: 1.6; white-space: pre-line;">${message}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; padding-top: 20px; border-top: 1px solid #334155; color: #64748b; font-size: 12px;">
|
||||
Submitted on ${new Date().toLocaleString()} from Cavin Infotech Contact Us Page
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const textContent = `
|
||||
New Contact Inquiry - Cavin Infotech
|
||||
|
||||
Name: ${name}
|
||||
Email: ${email}
|
||||
Phone: ${phone || 'Not provided'}
|
||||
Subject: ${subject || 'General Inquiry'}
|
||||
|
||||
Message:
|
||||
${message}
|
||||
|
||||
Submitted on: ${new Date().toLocaleString()}
|
||||
`;
|
||||
|
||||
if (!transporter) {
|
||||
console.warn('[SMTP Service] SMTP credentials not set in environment (SMTP_HOST, SMTP_USER, SMTP_PASS).');
|
||||
return {
|
||||
success: false,
|
||||
error: 'SMTP credentials not configured on the server. Please set SMTP_HOST, SMTP_USER, and SMTP_PASS in .env file.'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const info = await transporter.sendMail({
|
||||
from: fromEmail,
|
||||
to: toEmail,
|
||||
replyTo: `"${name}" <${email}>`,
|
||||
subject: mailSubject,
|
||||
text: textContent,
|
||||
html: htmlContent
|
||||
});
|
||||
|
||||
console.log('[SMTP Service] Contact email sent via SMTP:', info.messageId);
|
||||
return { success: true, messageId: info.messageId };
|
||||
} catch (err) {
|
||||
console.error('[SMTP Service] Failed to send email via SMTP:', err);
|
||||
return { success: false, error: err.message || 'SMTP sending failed' };
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import ServiceDial from './components/ServiceDial';
|
||||
import FeaturedGallery from './components/FeaturedGallery';
|
||||
import ProductCard from './components/ProductCard';
|
||||
import ContactSection from './components/ContactSection';
|
||||
import AboutUsPage from './components/AboutUsPage';
|
||||
|
||||
// Reusable Scroll Reveal Wrapper with Reduced Motion Support
|
||||
function Reveal({ children, delay = 0, style }) {
|
||||
@@ -377,7 +378,9 @@ export default function App({ previewMode = false }) {
|
||||
const [activeSection, setActiveSection] = useState('home');
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const hash = typeof window !== 'undefined' ? window.location.hash : '';
|
||||
return hash === '#contact' || hash === '#contactus' ? 'contact' : 'home';
|
||||
if (hash === '#contact' || hash === '#contactus') return 'contact';
|
||||
if (hash === '#about' || hash === '#aboutus') return 'about';
|
||||
return 'home';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -387,6 +390,10 @@ export default function App({ previewMode = false }) {
|
||||
setCurrentPage('contact');
|
||||
setActiveSection('contactus');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else if (hash === '#about' || hash === '#aboutus') {
|
||||
setCurrentPage('about');
|
||||
setActiveSection('aboutus');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} else if (hash === '#home' || hash === '') {
|
||||
setCurrentPage('home');
|
||||
}
|
||||
@@ -481,6 +488,7 @@ export default function App({ previewMode = false }) {
|
||||
|
||||
const getActiveNavItem = () => {
|
||||
if (currentPage === 'contact') return 'Contact Us';
|
||||
if (currentPage === 'about') return 'About Us';
|
||||
switch (activeSection) {
|
||||
case 'home':
|
||||
return 'Home';
|
||||
@@ -560,6 +568,14 @@ export default function App({ previewMode = false }) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item === 'About Us') {
|
||||
setCurrentPage('about');
|
||||
setActiveSection('aboutus');
|
||||
window.location.hash = 'aboutus';
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
return;
|
||||
}
|
||||
|
||||
const sectionMap = {
|
||||
Home: 'home',
|
||||
'About Us': 'aboutus',
|
||||
@@ -820,6 +836,8 @@ export default function App({ previewMode = false }) {
|
||||
)}
|
||||
{currentPage === 'contact' ? (
|
||||
<ContactSection isMobile={isMobile} onOpenStrategy={() => setIsStrategyOpen(true)} />
|
||||
) : currentPage === 'about' ? (
|
||||
<AboutUsPage isMobile={isMobile} onOpenStrategy={() => setIsStrategyOpen(true)} onNavigate={handleNavClick} />
|
||||
) : (
|
||||
<>
|
||||
{/* Main Homepage Content */}
|
||||
@@ -1788,32 +1806,36 @@ export default function App({ previewMode = false }) {
|
||||
{/* Products Suite */}
|
||||
<section id="products" style={{
|
||||
padding: isMobile ? '4rem 1.25rem' : '6rem 4rem',
|
||||
backgroundImage: `url(${assetUrl('/assets/products_bg.svg')})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
background: 'linear-gradient(180deg, #2791a5 0%, #0f4652 35%, #020710 100%)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* Soft gradient from bottom to top: light bottom fading to 0 opacity at top */}
|
||||
<div style={{
|
||||
{/* Wave Pattern Overlay */}
|
||||
<img
|
||||
src={assetUrl('/assets/Wave Pattern 2.svg')}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
top: '-50%',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(0deg, #ffffff 0%, rgba(255, 255, 255, 0.85) 45%, rgba(255, 255, 255, 0.25) 75%, rgba(255, 255, 255, 0) 100%)',
|
||||
width: '100%',
|
||||
height: '180%',
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center top',
|
||||
opacity: 0.42,
|
||||
pointerEvents: 'none',
|
||||
zIndex: 1
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
<div style={{ maxWidth: '1440px', margin: '0 auto', width: '100%', position: 'relative', zIndex: 10 }}>
|
||||
{/* Header - Left Aligned matching screenshot */}
|
||||
<Reveal>
|
||||
<div style={{ textAlign: 'left', marginBottom: '3.5rem', position: 'relative', zIndex: 10 }}>
|
||||
<h2 style={{ fontSize: 'clamp(2.2rem, 3.2vw, 2.8rem)', fontWeight: 800, color: '#0f172a', margin: '0 0 0.4rem 0', letterSpacing: '-0.02em' }}>
|
||||
<h2 style={{ fontSize: 'clamp(2.2rem, 3.2vw, 2.8rem)', fontWeight: 500, color: '#ffffff', margin: '0 0 0.4rem 0', letterSpacing: '-0.02em' }}>
|
||||
{products.title || 'Our Products Suite'}
|
||||
</h2>
|
||||
<span style={{ color: '#64748b', fontSize: '0.78rem', fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.08em', display: 'block' }}>
|
||||
<span style={{ color: '#8c9ba5', fontSize: '0.78rem', fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.08em', display: 'block' }}>
|
||||
{products.eyebrow || 'ONE ECOSYSTEM. EXPONENTIAL IMPACT.'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1827,7 +1849,7 @@ export default function App({ previewMode = false }) {
|
||||
alignItems: 'stretch',
|
||||
position: 'relative',
|
||||
zIndex: 10,
|
||||
marginBottom: '4rem'
|
||||
marginBottom: '2.5rem'
|
||||
}}>
|
||||
{products.items.map((item, index) => (
|
||||
<ProductCard
|
||||
@@ -1839,6 +1861,56 @@ export default function App({ previewMode = false }) {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Sub-text below product cards */}
|
||||
<p style={{
|
||||
color: '#8c9ba5',
|
||||
fontSize: '0.88rem',
|
||||
textAlign: 'center',
|
||||
maxWidth: '850px',
|
||||
margin: '0 auto 1.8rem auto',
|
||||
lineHeight: '1.5',
|
||||
fontWeight: 400
|
||||
}}>
|
||||
Plus many more specialized solutions: LiveWire, Nucleus, Digital Meeting Agenda, SEC, XR Workbench, TMS, and others - purpose-built for manufacturing and enterprise excellence.
|
||||
</p>
|
||||
|
||||
{/* Bottom CTA Button */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scrollToSection('contactus')}
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.6rem',
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
border: '1.5px solid rgba(255, 255, 255, 0.35)',
|
||||
boxShadow: '0 8px 25px rgba(0, 0, 0, 0.3)',
|
||||
borderRadius: '50px',
|
||||
color: '#ffffff',
|
||||
padding: '0.75rem 2rem',
|
||||
fontSize: '0.92rem',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.25s ease',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.12)';
|
||||
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.6)';
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)';
|
||||
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.35)';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}
|
||||
>
|
||||
<span>Explore Our Full Product Suite</span>
|
||||
<ArrowRight size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2069,20 +2141,21 @@ export default function App({ previewMode = false }) {
|
||||
<div style={{
|
||||
background: 'linear-gradient(135deg, #092244 0%, #05162d 100%)',
|
||||
borderRadius: '20px',
|
||||
padding: '2.5rem 1.8rem',
|
||||
padding: '2.2rem 1.6rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2rem',
|
||||
marginBottom: '3rem',
|
||||
gap: '1.2rem',
|
||||
marginBottom: '2.5rem',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 20px 40px rgba(0,0,0,0.2)'
|
||||
boxShadow: '0 20px 40px rgba(0,0,0,0.25)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.08)'
|
||||
}}>
|
||||
<div>
|
||||
<h2 style={{ fontSize: '1.8rem', fontWeight: 500, color: '#ffffff', lineHeight: 1.2, marginBottom: '1rem', letterSpacing: '-0.02em' }}>
|
||||
<h2 style={{ fontSize: '1.65rem', fontWeight: 600, color: '#ffffff', lineHeight: 1.25, marginBottom: '0.8rem', letterSpacing: '-0.02em' }}>
|
||||
Let's Build What's Next -<br/>Together.
|
||||
</h2>
|
||||
<p style={{ fontSize: '0.88rem', fontWeight: 500, lineHeight: 1.55, color: '#cbd5e1', marginBottom: '2rem', textTransform: 'uppercase', letterSpacing: '0.06em' }}>
|
||||
<p style={{ fontSize: '0.82rem', fontWeight: 500, lineHeight: 1.55, color: '#cbd5e1', marginBottom: '1.8rem', textTransform: 'uppercase', letterSpacing: '0.06em' }}>
|
||||
{footer.cta.body}
|
||||
</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.9rem' }}>
|
||||
@@ -2098,45 +2171,14 @@ export default function App({ previewMode = false }) {
|
||||
fontSize: '0.88rem',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
width: '100%',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{footer.cta.primaryButton}
|
||||
</button>
|
||||
{/* Secondary button commented out per user request
|
||||
<button
|
||||
onClick={() => setIsStrategyOpen(true)}
|
||||
style={{
|
||||
background: '#e8f1f8',
|
||||
border: 'none',
|
||||
color: '#2a4a6c',
|
||||
padding: '0.85rem 1.6rem',
|
||||
borderRadius: '50px',
|
||||
fontSize: '0.88rem',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{footer.cta.secondaryButton}
|
||||
</button>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Phone mockup container on mobile */}
|
||||
<div style={{ width: '100%', height: '180px', position: 'relative', overflow: 'hidden', borderRadius: '12px' }}>
|
||||
<img
|
||||
src={assetUrl(footer.cta.image)}
|
||||
alt="Phone preview"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: '-20px',
|
||||
top: '-90px',
|
||||
width: '280px',
|
||||
height: 'auto',
|
||||
maxWidth: 'none',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
) : (
|
||||
@@ -2209,34 +2251,6 @@ export default function App({ previewMode = false }) {
|
||||
>
|
||||
{footer.cta.primaryButton}
|
||||
</button>
|
||||
{/* Secondary button commented out per user request
|
||||
<button
|
||||
onClick={() => setIsStrategyOpen(true)}
|
||||
style={{
|
||||
background: '#e8f1f8',
|
||||
border: 'none',
|
||||
color: '#2a4a6c',
|
||||
padding: '0.85rem 1.8rem',
|
||||
borderRadius: '50px',
|
||||
fontSize: 'clamp(0.75rem, 0.95vw, 0.88rem)',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.25s ease',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
e.currentTarget.style.background = '#ffffff';
|
||||
e.currentTarget.style.boxShadow = '0 8px 20px rgba(0, 0, 0, 0.1)';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.background = '#e8f1f8';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}}
|
||||
>
|
||||
{footer.cta.secondaryButton}
|
||||
</button>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2247,20 +2261,20 @@ export default function App({ previewMode = false }) {
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: isMobile ? '1fr 1fr' : '180px 180px 1fr',
|
||||
gap: isMobile ? '1.5rem' : '1.25rem',
|
||||
paddingBottom: '2.5rem',
|
||||
gap: isMobile ? '2rem 1.5rem' : '1.25rem',
|
||||
paddingBottom: isMobile ? '2rem' : '2.5rem',
|
||||
borderBottom: '1px solid rgba(30,41,59,0.12)',
|
||||
marginBottom: '1.5rem',
|
||||
}}>
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h5 style={{ fontSize: '0.7rem', fontWeight: 800, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#1e293b', marginBottom: '1rem' }}>Quick Links</h5>
|
||||
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '0.35rem', padding: 0, margin: 0 }}>
|
||||
<h5 style={{ fontSize: '0.75rem', fontWeight: 800, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#1e293b', marginBottom: '1rem' }}>Quick Links</h5>
|
||||
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '0.45rem', padding: 0, margin: 0 }}>
|
||||
<li>
|
||||
<a href="#home" onClick={e => {
|
||||
e.preventDefault();
|
||||
scrollToSection('home');
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.84rem', transition: 'color 0.2s' }}
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.85rem', transition: 'color 0.2s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = '#1e40af'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>Home</a>
|
||||
</li>
|
||||
@@ -2268,7 +2282,7 @@ export default function App({ previewMode = false }) {
|
||||
<a href="#certifications" onClick={e => {
|
||||
e.preventDefault();
|
||||
scrollToSection('certifications');
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.84rem', transition: 'color 0.2s' }}
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.85rem', transition: 'color 0.2s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = '#1e40af'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>About Us</a>
|
||||
</li>
|
||||
@@ -2276,7 +2290,7 @@ export default function App({ previewMode = false }) {
|
||||
<a href="#services" onClick={e => {
|
||||
e.preventDefault();
|
||||
scrollToSection('services');
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.84rem', transition: 'color 0.2s' }}
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.85rem', transition: 'color 0.2s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = '#1e40af'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>Our Services</a>
|
||||
</li>
|
||||
@@ -2285,36 +2299,43 @@ export default function App({ previewMode = false }) {
|
||||
|
||||
{/* Products */}
|
||||
<div>
|
||||
<h5 style={{ fontSize: '0.7rem', fontWeight: 800, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#1e293b', marginBottom: '1rem' }}>Products</h5>
|
||||
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '0.35rem', padding: 0, margin: 0 }}>
|
||||
{['PRODMAX', 'Budgie HRMS', 'EVIDO'].map(l => (
|
||||
<h5 style={{ fontSize: '0.75rem', fontWeight: 800, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#1e293b', marginBottom: '1rem' }}>Products</h5>
|
||||
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '0.45rem', padding: 0, margin: 0 }}>
|
||||
{(footer.productLinks && footer.productLinks.length ? footer.productLinks.map(p => p.label) : ['Nebula AI platform', 'Prodmax', 'Budgie']).map(l => (
|
||||
<li key={l}><a href="#products" onClick={e => {
|
||||
e.preventDefault();
|
||||
scrollToSection('products');
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.84rem', transition: 'color 0.2s' }}
|
||||
}} style={{ color: '#475569', textDecoration: 'none', fontSize: '0.85rem', transition: 'color 0.2s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = '#1e40af'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>{l}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Social Links Column */}
|
||||
<div style={{ gridColumn: isMobile ? 'span 2' : 'auto' }}>
|
||||
<h5 style={{ fontSize: '0.7rem', fontWeight: 800, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#1e40af', marginBottom: '1rem' }}>Social Links</h5>
|
||||
<h5 style={{ fontSize: '0.75rem', fontWeight: 800, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#1e40af', marginBottom: '1rem' }}>Social Links</h5>
|
||||
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
{[
|
||||
{ label: 'Facebook', d: 'M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z' },
|
||||
{ label: 'Instagram', d: 'M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.051.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z' },
|
||||
{ label: 'LinkedIn', d: 'M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z' },
|
||||
{ label: 'Telegram', d: 'M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.562 8.161c-.18.717-.962 4.084-1.362 5.462-.168.58-.506.772-.712.791-.45.041-.79-.297-1.225-.583-.681-.447-1.066-.723-1.727-1.158-.764-.502-.269-.778.167-1.23.114-.118 2.096-1.922 2.134-2.083.005-.02.009-.095-.036-.135-.045-.04-.112-.026-.16-.015-.069.016-1.171.745-3.305 2.186-.313.215-.596.321-.85.315-.281-.006-.822-.153-1.225-.284-.494-.16-.887-.246-.853-.518.178-.142.716-.291 1.6-.447 5.485-2.385 9.143-3.957 10.973-4.717.868-.362 1.8-.425 2.39-.425.13 0 .42.03.606.18.156.126.2.296.21.425.013.167.013.435-.008.685z' },
|
||||
{ label: 'WhatsApp', d: 'M12.008 22c-1.948 0-3.855-.522-5.504-1.508l-.395-.235-4.093 1.073 1.092-3.993-.257-.408C1.815 15.222 1.25 13.064 12.008 12c5.514 0 10.008 4.486 10.008 10S17.522 22 12.008 22zm8.487-18.49C18.232 1.257 15.258 0 12.008 0 5.394 0 .02 5.373.017 11.99c0 2.115.553 4.18 1.603 5.996L0 24l6.155-1.615c1.768.964 3.753 1.472 5.845 1.474v-.002c6.611 0 11.986-5.375 11.99-11.994.002-3.206-1.242-6.22-3.497-8.48zM17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.063 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.715 2.006-1.404.248-.69.248-1.278.173-1.402-.074-.125-.272-.199-.57-.348z' },
|
||||
{ label: 'X', d: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z' },
|
||||
{ label: 'YouTube', d: 'M23.498 6.163a3.003 3.003 0 0 0-2.11-2.11C19.53 3.545 12 3.545 12 3.545s-7.53 0-9.388.508a3.003 3.003 0 0 0-2.11 2.11C0 8.017 0 12 0 12s0 3.983.502 5.837a3.003 3.003 0 0 0 2.11 2.11c1.858.508 9.388.508 9.388.508s7.53 0 9.388-.508a3.003 3.003 0 0 0 2.11-2.11C24 15.983 24 12 24 12s0-3.983-.502-5.837zM9.545 15.568V8.432L15.818 12l-6.273 3.568z' },
|
||||
].map(({ label, d }) => (
|
||||
<a key={label} href="#" aria-label={label}
|
||||
{(footer.social && footer.social.length ? footer.social : [
|
||||
{ platform: 'Facebook', url: 'https://www.facebook.com/cavininfotech1' },
|
||||
{ platform: 'X', url: 'https://x.com/cavin_infotech' },
|
||||
{ platform: 'Instagram', url: 'https://www.instagram.com/cavin_infotech' },
|
||||
{ platform: 'LinkedIn', url: 'https://www.linkedin.com/company/cavin-infotech/' },
|
||||
{ platform: 'YouTube', url: 'https://youtube.com/@cavin_infotech' }
|
||||
]).map(({ platform, url }) => {
|
||||
const paths = {
|
||||
Facebook: 'M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z',
|
||||
Instagram: 'M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.051.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm6.406-11.845a1.44 1.44 0 1 0 0 2.881 1.44 1.44 0 0 0 0-2.881z',
|
||||
LinkedIn: 'M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z',
|
||||
X: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z',
|
||||
YouTube: 'M23.498 6.163a3.003 3.003 0 0 0-2.11-2.11C19.53 3.545 12 3.545 12 3.545s-7.53 0-9.388.508a3.003 3.003 0 0 0-2.11 2.11C0 8.017 0 12 0 12s0 3.983.502 5.837a3.003 3.003 0 0 0 2.11 2.11c1.858.508 9.388.508 9.388.508s7.53 0 9.388-.508a3.003 3.003 0 0 0 2.11-2.11C24 15.983 24 12 24 12s0-3.983-.502-5.837zM9.545 15.568V8.432L15.818 12l-6.273 3.568z'
|
||||
};
|
||||
const d = paths[platform] || paths.Facebook;
|
||||
|
||||
return (
|
||||
<a key={platform} href={url || '#'} target="_blank" rel="noopener noreferrer" aria-label={platform}
|
||||
style={{
|
||||
width: '34px', height: '34px', borderRadius: '50%',
|
||||
width: '36px', height: '36px', borderRadius: '50%',
|
||||
background: 'rgba(30,41,59,0.08)', border: '1px solid rgba(30,41,59,0.12)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
transition: 'all 0.2s ease', color: '#475569',
|
||||
@@ -2326,17 +2347,23 @@ export default function App({ previewMode = false }) {
|
||||
<path d={d} />
|
||||
</svg>
|
||||
</a>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Legal / Copyright Bottom Bar */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: '1rem',
|
||||
marginTop: '1.5rem',
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
alignItems: isMobile ? 'flex-start' : 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: isMobile ? '0.75rem' : '1.5rem',
|
||||
fontSize: '0.82rem',
|
||||
color: '#475569',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
||||
<a href="#" style={{ color: '#475569', textDecoration: 'none', fontWeight: 500, transition: 'color 0.2s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = '#1e40af'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>Privacy Policy</a>
|
||||
@@ -2344,14 +2371,12 @@ export default function App({ previewMode = false }) {
|
||||
<a href="#" style={{ color: '#475569', textDecoration: 'none', fontWeight: 500, transition: 'color 0.2s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.color = '#1e40af'}
|
||||
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>Terms & Conditions</a>
|
||||
<span style={{ color: '#cbd5e1' }}>•</span>
|
||||
<span style={{ fontSize: '0.78rem', color: '#334155' }}>
|
||||
</div>
|
||||
<span style={{ fontSize: '0.78rem', color: '#64748b' }}>
|
||||
Copyrights reserved to CAVIN INFOTECH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{/* Careers Modal Popup */}
|
||||
|
||||
|
After Width: | Height: | Size: 169 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="1728" height="1016" viewBox="0 0 1728 1016" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1728" height="1016" fill="url(#paint0_linear_291_10455)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_291_10455" x1="864" y1="0" x2="864" y2="1016" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2FA0B6"/>
|
||||
<stop offset="1" stop-color="#01040B"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 397 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="1728" height="1107" viewBox="0 0 1728 1107" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1728" height="1107" fill="url(#paint0_linear_291_8788)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_291_8788" x1="864" y1="0" x2="864" y2="1107" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.591346" stop-color="#01040B"/>
|
||||
<stop offset="1" stop-color="#2FA0B6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 413 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="14.1884" cy="14.1884" r="14.1884" fill="#F67057"/>
|
||||
<circle cx="14.1883" cy="14.1864" r="8.73132" fill="#F4F8FB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 229 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="902" height="552" viewBox="0 0 902 552" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11190)">
|
||||
<circle cx="774.8" cy="774.8" r="537" fill="#2FA0B6" fill-opacity="0.3"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11190" x="-1.52588e-05" y="-0.000198364" width="1549.6" height="1549.6" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="118.9" result="effect1_foregroundBlur_291_11190"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 635 B |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 77 KiB |
@@ -0,0 +1,36 @@
|
||||
<svg width="1692" height="800" viewBox="0 0 1692 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ddddd_8_95442)">
|
||||
<path d="M28.0001 422.23C125.66 501.049 344.154 624.207 436.852 486.29C552.725 313.894 712.875 317.661 907.88 346.866C1096 375.039 1100.5 -16.459 1325.5 29.8044" stroke="#DA290C" stroke-width="4" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_ddddd_8_95442" x="0" y="0" width="1714.5" height="1000.52" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="11" dy="13"/>
|
||||
<feGaussianBlur stdDeviation="18.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.98 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="43" dy="53"/>
|
||||
<feGaussianBlur stdDeviation="34"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.85 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_8_95442" result="effect2_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="97" dy="119"/>
|
||||
<feGaussianBlur stdDeviation="46"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_8_95442" result="effect3_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="172" dy="212"/>
|
||||
<feGaussianBlur stdDeviation="54.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="effect3_dropShadow_8_95442" result="effect4_dropShadow_8_95442"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="268" dy="331"/>
|
||||
<feGaussianBlur stdDeviation="59.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.713726 0 0 0 0 0.333333 0 0 0 0 0.262745 0 0 0 0.02 0"/>
|
||||
<feBlend mode="normal" in2="effect4_dropShadow_8_95442" result="effect5_dropShadow_8_95442"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect5_dropShadow_8_95442" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="1434" height="1200" viewBox="0 0 1434 1200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11185)">
|
||||
<path d="M537.476 100H411.257L1288.66 1027.7H1334L537.476 100Z" fill="white" fill-opacity="0.1"/>
|
||||
<path d="M126.959 101.225H294.842L1092.59 1027.7H1053.38L126.959 101.225Z" fill="white" fill-opacity="0.1"/>
|
||||
<path d="M100 362.255V200.49L885.496 1088.97H847.507L100 362.255Z" fill="white" fill-opacity="0.1"/>
|
||||
<path d="M103.676 477.451V620.833L646.538 1100H701.682L103.676 477.451Z" fill="white" fill-opacity="0.1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11185" x="0" y="0" width="1434" height="1200" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="50" result="effect1_foregroundBlur_291_11185"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 951 B |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 291 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg width="647" height="504" viewBox="0 0 647 504" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11290)">
|
||||
<ellipse cx="323.5" cy="252" rx="163.5" ry="92" fill="#B3776D"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11290" x="0" y="0" width="647" height="504" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="80" result="effect1_foregroundBlur_291_11290"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 595 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="647" height="504" viewBox="0 0 647 504" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_291_11291)">
|
||||
<ellipse cx="323.5" cy="252" rx="163.5" ry="92" fill="#4D5154"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_291_11291" x="0" y="0" width="647" height="504" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="80" result="effect1_foregroundBlur_291_11291"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 595 B |
|
After Width: | Height: | Size: 11 MiB |
@@ -31,8 +31,7 @@ export default function ContactSection({ isMobile, onOpenStrategy }) {
|
||||
setErrorMsg('');
|
||||
|
||||
try {
|
||||
// Try sending via API endpoint
|
||||
const response = await fetch('/api/send', {
|
||||
let response = await fetch('/api/contact', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -40,21 +39,36 @@ export default function ContactSection({ isMobile, onOpenStrategy }) {
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
phone: formData.phone,
|
||||
interests: formData.subject,
|
||||
subject: formData.subject,
|
||||
message: formData.message
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
if (!response.ok) {
|
||||
response = await fetch('/api/send', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
type: 'contact',
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
phone: formData.phone,
|
||||
subject: formData.subject,
|
||||
message: formData.message
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const resData = await response.json().catch(() => ({}));
|
||||
|
||||
if (response.ok && resData.success) {
|
||||
setSubmitted(true);
|
||||
setFormData({ name: '', email: '', phone: '', subject: '', message: '' });
|
||||
} else {
|
||||
// Fallback smooth demo success if backend API is not configured with live EmailJS keys
|
||||
setSubmitted(true);
|
||||
setFormData({ name: '', email: '', phone: '', subject: '', message: '' });
|
||||
setErrorMsg(resData.error || resData.smtpError || 'Failed to send message. Please check SMTP configuration.');
|
||||
}
|
||||
} catch {
|
||||
// Graceful fallback for local development without server
|
||||
} catch (err) {
|
||||
console.error('Contact submit error:', err);
|
||||
setSubmitted(true);
|
||||
setFormData({ name: '', email: '', phone: '', subject: '', message: '' });
|
||||
} finally {
|
||||
|
||||
@@ -27,17 +27,22 @@ export default function FeaturedGallery({ gallery }) {
|
||||
}, [activeGalleryIndex, galleryData.length]);
|
||||
|
||||
return (
|
||||
<section id="featured-gallery" style={{ padding: isMobile ? '4rem 1.25rem' : '6rem 4rem', background: '#020710', position: 'relative' }}>
|
||||
<section id="featured-gallery" style={{
|
||||
padding: isMobile ? '4rem 1.25rem' : '6rem 4rem 4rem 4rem',
|
||||
background: 'linear-gradient(180deg, #020710 0%, #0f4652 65%, #2791a5 100%)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div style={{ maxWidth: '1440px', margin: '0 auto', width: '100%', position: 'relative', zIndex: 10 }}>
|
||||
<div style={{ marginBottom: '3.5rem' }}>
|
||||
<span style={{ color: '#ffaa00', fontSize: '0.9rem', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.1em' }}>
|
||||
{header.eyebrow}
|
||||
</span>
|
||||
<h2 style={{ fontSize: 'clamp(2rem, 3vw, 2.5rem)', fontWeight: 800, marginTop: '0.5rem', color: '#ffffff' }}>
|
||||
{header.title}
|
||||
<div style={{ marginBottom: '3rem' }}>
|
||||
<h2 style={{ fontSize: 'clamp(2.2rem, 3.2vw, 2.8rem)', fontWeight: 500, color: '#ffffff', margin: '0 0 0.4rem 0', letterSpacing: '-0.02em' }}>
|
||||
Case Studies
|
||||
</h2>
|
||||
<p style={{ color: '#94a3b8', fontSize: '1rem', marginTop: '0.75rem', maxWidth: '600px', lineHeight: '1.5' }}>
|
||||
{header.subtitle}
|
||||
<span style={{ color: '#8c9ba5', fontSize: '0.78rem', fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.08em', display: 'block', marginBottom: '0.6rem' }}>
|
||||
BEFORE VS. AFTER: THE NUMBERS DON'T LIE.
|
||||
</span>
|
||||
<p style={{ color: '#94a3b8', fontSize: '0.95rem', marginTop: '0.5rem', maxWidth: '720px', lineHeight: '1.5', fontWeight: 400 }}>
|
||||
Discover how leading manufacturers are using AI, SAP, and smart factory technologies to boost productivity, reduce costs, and gain competitive advantage.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -46,6 +51,48 @@ export default function FeaturedGallery({ gallery }) {
|
||||
activeIdx={activeGalleryIndex}
|
||||
setActiveIdx={setActiveGalleryIndex}
|
||||
/>
|
||||
|
||||
{/* Bottom CTA Button matching design screenshot */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '3rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const footer = document.getElementById('contactus');
|
||||
if (footer) {
|
||||
footer.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.6rem',
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
border: '1.5px solid rgba(255, 255, 255, 0.35)',
|
||||
boxShadow: '0 8px 25px rgba(0, 0, 0, 0.3)',
|
||||
borderRadius: '50px',
|
||||
color: '#ffffff',
|
||||
padding: '0.75rem 2rem',
|
||||
fontSize: '0.92rem',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.25s ease',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.12)';
|
||||
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.6)';
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)';
|
||||
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.35)';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}
|
||||
>
|
||||
<span>Explore Success Stories</span>
|
||||
<ArrowRight size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@ import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { assetUrl } from '../lib/assetUrl';
|
||||
|
||||
const ACCENT_GRADIENTS = [
|
||||
'linear-gradient(0deg, #ffffff 0%, rgba(255, 255, 255, 0.95) 50%, rgba(235, 252, 240, 0.5) 100%)',
|
||||
'linear-gradient(0deg, #ffffff 0%, rgba(255, 255, 255, 0.95) 50%, rgba(250, 243, 255, 0.5) 100%)',
|
||||
'linear-gradient(0deg, #ffffff 0%, rgba(255, 255, 255, 0.95) 50%, rgba(254, 252, 232, 0.5) 100%)',
|
||||
'linear-gradient(0deg, #ffffff 0%, #ffffff 50%, #e6f7ef 100%)',
|
||||
'linear-gradient(0deg, #ffffff 0%, #ffffff 50%, #f7effe 100%)',
|
||||
'linear-gradient(0deg, #ffffff 0%, #ffffff 50%, #fefce8 100%)',
|
||||
];
|
||||
|
||||
export default function ProductCard({ product, isMobile, delay = 0, index = 0 }) {
|
||||
|
||||