Initial commit

This commit is contained in:
2026-07-10 13:42:16 +05:30
commit e0c244eb03
34 changed files with 7774 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
import { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import { Send, CheckCircle2, AlertCircle } from "lucide-react";
const initialState = {
name: "",
email: "",
mobile: "",
companyName: "",
designation: "",
message: ""
};
export default function ContactForm() {
const [form, setForm] = useState(initialState);
const [status, setStatus] = useState("idle");
const [errorMsg, setErrorMsg] = useState("");
const handleChange = (e) => {
const { name, value } = e.target;
setForm((f) => ({ ...f, [name]: value }));
};
const handleSubmit = async (e) => {
e.preventDefault();
setStatus("sending");
setErrorMsg("");
try {
await new Promise((resolve) => setTimeout(resolve, 1200));
setStatus("sent");
setForm(initialState);
} catch (err) {
setStatus("error");
setErrorMsg("Something went wrong. Please try again.");
}
};
return <div className="relative w-full">
<AnimatePresence mode="wait">
{status === "sent" ? <motion.div
key="success"
initial={{ opacity: 0, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
transition={{ duration: 0.3 }}
className="p-8 md:p-12 flex flex-col items-center text-center bg-white border border-slate-100 rounded-2xl shadow-xl"
>
<div className="w-14 h-14 rounded-full bg-emerald-50 flex items-center justify-center mb-5 border border-emerald-100">
<CheckCircle2 className="w-7 h-7 text-emerald-600" />
</div>
<h3 className="font-heading text-xl text-slate-900 font-extrabold tracking-tight">Message Received</h3>
<p className="text-slate-500 mt-3 max-w-md font-light leading-relaxed text-sm">
Thank you for reaching out to Masar NDT. Our expert engineering team will review your enquiry and get back to you within 24 hours.
</p>
<button
className="mt-8 text-xs text-white bg-orange-500 hover:bg-orange-600 px-8 py-3.5 font-bold tracking-wider uppercase rounded-xl transition-all shadow-md hover:shadow-lg hover:-translate-y-0.5 duration-200"
onClick={() => setStatus("idle")}
>
Send another message
</button>
</motion.div> : <motion.form
key="form"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
onSubmit={handleSubmit}
className="p-6 md:p-8 space-y-6 bg-white border border-slate-100 shadow-xl rounded-2xl"
>
<div className="grid sm:grid-cols-2 gap-6">
<Field label="Your Name" name="name" value={form.name} onChange={handleChange} required />
<Field label="Email Address" name="email" type="email" value={form.email} onChange={handleChange} required />
<Field label="Mobile Number" name="mobile" type="tel" value={form.mobile} onChange={handleChange} />
<Field label="Company Name" name="companyName" value={form.companyName} onChange={handleChange} />
</div>
<Field label="Designation / Job Title" name="designation" value={form.designation} onChange={handleChange} />
<div className="flex flex-col">
<label htmlFor="message" className="text-[11px] font-bold text-slate-700 uppercase tracking-wider mb-2 font-heading">
Message / Scope Details *
</label>
<textarea
id="message"
name="message"
rows={4}
value={form.message}
onChange={handleChange}
required
className="w-full bg-slate-50/50 border border-slate-200 hover:border-orange-500/30 rounded-xl px-4 py-3 text-sm text-slate-900 focus:border-orange-500 focus:bg-white focus:ring-2 focus:ring-orange-500/10 outline-none transition-all resize-y font-normal leading-relaxed"
placeholder="Describe your NDT, inspection, or technical manpower requirements..."
/>
</div>
{status === "error" && <div className="flex items-center gap-2.5 text-sm text-red-600 bg-red-50 border border-red-100 p-4 rounded-xl">
<AlertCircle className="w-4 h-4 shrink-0" />
<p>{errorMsg}</p>
</div>}
<button
type="submit"
disabled={status === "sending"}
className="w-full sm:w-auto flex items-center justify-center gap-2 bg-orange-500 hover:bg-orange-600 text-white font-bold text-xs tracking-wider uppercase px-8 py-4 rounded-xl transition-all duration-200 disabled:opacity-60 shadow-lg hover:shadow-orange-500/10 hover:-translate-y-0.5 active:translate-y-0"
>
{status === "sending" ? <>
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<span>Submitting</span>
</> : <>
<span>Submit Enquiry</span>
<Send className="w-3.5 h-3.5" />
</>}
</button>
</motion.form>}
</AnimatePresence>
</div>;
}
function Field({ label, name, value, onChange, type = "text", required = false }) {
return <div className="flex flex-col">
<label htmlFor={name} className="text-[11px] font-bold text-slate-700 uppercase tracking-wider mb-2 font-heading">
{label} {required && <span className="text-orange-500">*</span>}
</label>
<input
id={name}
name={name}
type={type}
value={value}
onChange={onChange}
required={required}
className="w-full bg-slate-50/50 border border-slate-200 hover:border-orange-500/30 rounded-xl px-4 py-3 text-sm text-slate-900 focus:border-orange-500 focus:bg-white focus:ring-2 focus:ring-orange-500/10 outline-none transition-all font-normal"
/>
</div>;
}
+127
View File
@@ -0,0 +1,127 @@
import { Link } from "react-router-dom";
import { serviceCategories, offices, company } from "../data/content";
import { Mail, Phone, MapPin, Clock } from "lucide-react";
export default function Footer() {
return <footer className="bg-slate-950 text-slate-200 mt-24 border-t border-slate-900">
{
/* Upper Grid Area */
}
<div className="max-w-7xl mx-auto px-5 md:px-8 py-16 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-12">
{
/* Col 1: Overview */
}
<div className="space-y-5">
<Link to="/" className="flex items-center gap-2">
<span className="w-4 h-4 rounded-sm bg-orange-500 shadow-md" />
<span className="font-heading font-extrabold uppercase tracking-wider text-white text-lg">
Masar<span className="text-orange-500"> NDT</span>
</span>
</Link>
<p className="text-xs leading-relaxed text-slate-400 font-light">
{company.intro}
</p>
<p className="text-[10px] text-slate-500 font-bold uppercase tracking-wider font-heading">
A member of {company.legalName}
</p>
<div className="flex items-center gap-3.5 pt-2">
<a href="#" className="p-2.5 bg-slate-900 hover:bg-orange-500 hover:text-white transition-all rounded-lg border border-slate-800" aria-label="LinkedIn">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fillRule="evenodd" 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-.779-1.75-1.75s.784-1.75 1.75-1.75 1.75.779 1.75 1.75-.784 1.75-1.75 1.75zm13.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" clipRule="evenodd" />
</svg>
</a>
<a href="#" className="p-2.5 bg-slate-900 hover:bg-orange-500 hover:text-white transition-all rounded-lg border border-slate-800" aria-label="Facebook">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fillRule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clipRule="evenodd" />
</svg>
</a>
</div>
</div>
{
/* Col 2: Services */
}
<div>
<h3 className="font-heading text-xs font-bold text-white uppercase tracking-wider mb-5 border-l-2 border-orange-500 pl-3">
Services
</h3>
<ul className="space-y-3 text-xs text-slate-400">
{serviceCategories.map((s) => <li key={s.id}>
<Link to={s.path} className="hover:text-orange-500 transition-colors duration-200 block py-0.5 font-medium">
{s.title}
</Link>
</li>)}
</ul>
</div>
{
/* Col 3: Quick Links */
}
<div>
<h3 className="font-heading text-xs font-bold text-white uppercase tracking-wider mb-5 border-l-2 border-orange-500 pl-3">
Quick Links
</h3>
<ul className="space-y-3 text-xs text-slate-400">
<li><Link to="/about" className="hover:text-orange-500 transition-colors block py-0.5 font-medium">About Us</Link></li>
<li><Link to="/industries" className="hover:text-orange-500 transition-colors block py-0.5 font-medium">Industries Served</Link></li>
<li><Link to="/projects" className="hover:text-orange-500 transition-colors block py-0.5 font-medium">Project Showcase</Link></li>
<li><Link to="/gallery" className="hover:text-orange-500 transition-colors block py-0.5 font-medium">Media Gallery</Link></li>
<li><Link to="/contact" className="hover:text-orange-500 transition-colors block py-0.5 font-medium">Get in Touch</Link></li>
</ul>
</div>
{
/* Col 4: Contact & Hours */
}
<div className="space-y-6">
<div>
<h3 className="font-heading text-xs font-bold text-white uppercase tracking-wider mb-5 border-l-2 border-orange-500 pl-3">
Contact Info
</h3>
<div className="space-y-3.5 text-xs text-slate-400">
<div className="flex items-start gap-2.5">
<MapPin className="w-4 h-4 text-orange-500 shrink-0 mt-0.5" />
<span className="leading-relaxed font-normal">{offices[0].lines.join(", ")}</span>
</div>
<a href="tel:+966571033252" className="flex items-center gap-2.5 hover:text-orange-500 transition-colors font-semibold">
<Phone className="w-4 h-4 text-orange-500 shrink-0" />
<span>+966 57 103 3252</span>
</a>
<a href="mailto:sales@masarndt.com" className="flex items-center gap-2.5 hover:text-orange-500 transition-colors font-medium">
<Mail className="w-4 h-4 text-orange-500 shrink-0" />
<span className="break-all text-orange-500 font-semibold">{offices[0].email}</span>
</a>
</div>
</div>
<div>
<h3 className="font-heading text-xs font-bold text-white uppercase tracking-wider mb-3 border-l-2 border-orange-500 pl-3">
Business Hours
</h3>
<div className="flex items-center gap-2.5 text-xs text-slate-400 font-light">
<Clock className="w-4 h-4 text-orange-500 shrink-0" />
<span className="font-medium">Sun - Thu: 8:00 AM - 5:00 PM</span>
</div>
</div>
</div>
</div>
{
/* Bottom Copyright Area */
}
<div className="border-t border-slate-900 bg-slate-950 py-8 text-xs text-slate-500">
<div className="max-w-7xl mx-auto px-5 md:px-8 flex flex-col sm:flex-row justify-between items-center gap-4 text-center sm:text-left">
<span className="font-light">
&copy; {(/* @__PURE__ */ new Date()).getFullYear()} Masar NDT. All rights reserved. Managed by AKMEC Group.
</span>
<div className="flex flex-wrap justify-center gap-4 font-bold text-[10px] uppercase tracking-wider">
<span className="text-orange-500">ISO 9001:2015 Approved</span>
<span className="text-slate-800">|</span>
<Link to="/contact" className="hover:text-orange-400 transition-colors">Privacy Policy</Link>
<span className="text-slate-800">|</span>
<Link to="/contact" className="hover:text-orange-400 transition-colors">Terms of Use</Link>
</div>
</div>
</div>
</footer>;
}
+508
View File
@@ -0,0 +1,508 @@
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "motion/react";
import { ShieldCheck, Cpu, Database, Gauge, RefreshCw, Zap, CheckCircle } from "lucide-react";
const assets = [
{ id: "tube", name: "Heat Exchanger Tubes", icon: Cpu, description: "Tubular bundles in condensers, boilers, or evaporators." },
{ id: "vessel", name: "Pressure Vessels & Shells", icon: Database, description: "Process drums, reactors, and high-pressure tanks." },
{ id: "pipe", name: "Process & Utility Piping", icon: Gauge, description: "Cross-country pipelines, dead-legs, and small-bore systems." },
{ id: "tank", name: "Storage Tank Bottoms", icon: ShieldCheck, description: "Crude and product storage tanks with potential ground-side corrosion." }
];
const concerns = [
{ id: "thinning", name: "Wall Thinning & Erosion", description: "Internal or external metal loss from corrosive flow." },
{ id: "cracks", name: "Surface & Welds Micro-cracks", description: "Fatigue cracking or thermal stress in weld joints." },
{ id: "coating", name: "Coating Holiday / Voids", description: "Microscopic pinholes in protective epoxy layers." },
{ id: "alloy", name: "Alloy Composition Mix-up", description: "Incorrect filler metal or grade installation." }
];
const getRecommendation = (assetId, concernId) => {
if (assetId === "tube") {
if (concernId === "thinning") {
return {
technique: "Remote Field Eddy Current Testing (RFET) & NFT",
techCode: "RFET / NFT / IRIS",
standards: ["ASME Section V Article 8", "ASTM E2096", "API 570"],
prepRequired: ["Hydro-jetting cleaning (280\u2013560 kg/cm\xB2)", "Dummy probe sizing check", "Drying of tubes"],
advantage: "Highly sensitive to wall loss in ferromagnetic boiler and air-cooler tubes.",
simulatedSignalName: "Impedance Phase Shift Anomaly",
simulatedSignalDesc: "Indicates localized pitting near support baffle.",
dangerLevel: "High"
};
}
if (concernId === "cracks") {
return {
technique: "Eddy Current Testing (ECT)",
techCode: "ECT Tube Inspection",
standards: ["ASME Section V Article 8", "ASTM E309", "API 510"],
prepRequired: ["Internal brushing", "Calibration reference standard match", "Removal of scale"],
advantage: "Extremely fast (up to 2m/s) detection of fine longitudinal cracks in non-ferrous tubing.",
simulatedSignalName: "Lissajous Loop Phase Deflection",
simulatedSignalDesc: "Reveals shallow inner-diameter surface breaking cracks.",
dangerLevel: "High"
};
}
if (concernId === "coating") {
return {
technique: "Specialized Internal Visual & Boroscopic Scan",
techCode: "VT-Boroscopy",
standards: ["ASME Section V Article 9", "API 510"],
prepRequired: ["Total de-watering", "Dual-axis high-definition probe calibration"],
advantage: "Visual verification of surface scaling and protective lining discoloration.",
simulatedSignalName: "Optical Surface Mapping Anomaly",
simulatedSignalDesc: "Visible pinhole lining breach with iron-sulfide deposits.",
dangerLevel: "Medium"
};
}
return {
technique: "Positive Material Identification (PMI Spectroscopy)",
techCode: "XRF / LIBS PMI",
standards: ["API RP 578", "ASME Section II"],
prepRequired: ["Oxide film sanding", "Surface temperature stability"],
advantage: "Direct chemical verification of tube-to-tubesheet alloy grades.",
simulatedSignalName: "X-Ray Emission Spectrum Peak",
simulatedSignalDesc: "Identified trace elements conforming to Monel 400 specification.",
dangerLevel: "Medium"
};
}
if (assetId === "vessel") {
if (concernId === "thinning") {
return {
technique: "Ultrasonic Corrosion Mapping & Thickness Testing",
techCode: "UT-Corrosion Mapping",
standards: ["ASME Section V Article 5", "API 510", "API 579-1 FFS"],
prepRequired: ["Removal of loose rust/peeling paint", "High-temp couplant application", "Grid marking"],
advantage: "Provides absolute remaining wall thickness calculations for Fitness-For-Service.",
simulatedSignalName: "A-Scan Signal Time-of-Flight Shift",
simulatedSignalDesc: "Significant shift in back-wall echo indicates localized wear loop.",
dangerLevel: "High"
};
}
if (concernId === "cracks") {
return {
technique: "Wet Fluorescent Magnetic Particle Testing (WFMT) or PAUT",
techCode: "WFMT / Phased Array UT",
standards: ["ASME Section V Article 4 & 7", "ASTM E1444", "API 510"],
prepRequired: ["Grease degreasing", "UV black-light assembly setup", "Yoke calibration check"],
advantage: "Detects extreme fine environmental stress corrosion cracks (SCC) in vessel welds.",
simulatedSignalName: "Linear Magnetic Particle Indication",
simulatedSignalDesc: "Fine transverse weld toe crack highlighted under UV spectrum.",
dangerLevel: "High"
};
}
if (concernId === "coating") {
return {
technique: "High-Voltage Holiday / Spark Testing",
techCode: "HV Holiday Detection",
standards: ["NACE SP0188", "ASTM D5162"],
prepRequired: ["Lining curing verification", "Ground wire connection to bare steel", "Dry surface state"],
advantage: "Uncovers invisible microscopic voids in internal rubber/glass-flake linings.",
simulatedSignalName: "Audible Continuous Spark Arc",
simulatedSignalDesc: "Voltage drop at 15kV identifies lining puncture.",
dangerLevel: "Medium"
};
}
return {
technique: "Portable PMI Spectroscopy",
techCode: "PMI Verification",
standards: ["API RP 578", "ASME Section II"],
prepRequired: ["Bare metal contact grinding", "Standards reference calibration"],
advantage: "Ensures replacement shell plate or weld filler matches material datasheet specifications.",
simulatedSignalName: "Chromium-Nickel Spectral Signal",
simulatedSignalDesc: "Conforms to SS316L; matches code verification guidelines.",
dangerLevel: "Medium"
};
}
if (assetId === "pipe") {
if (concernId === "thinning") {
return {
technique: "Manual & Automated Ultrasonic Thickness Testing (UT-T)",
techCode: "UT Wall-Thickness",
standards: ["ASME Section V Article 5", "API 570", "API 574"],
prepRequired: ["Couplant brush-on", "Scale scraping of test point", "Zero-block calibration"],
advantage: "Fast, precise remaining-life estimations in designated corrosion monitoring locations (CML).",
simulatedSignalName: "UT Echo Loss",
simulatedSignalDesc: "Reduced wall-thickness detected inside dead-leg bend.",
dangerLevel: "High"
};
}
if (concernId === "cracks") {
return {
technique: "Ultrasonic Shear-Wave / PAUT Inspections",
techCode: "PAUT Weld Inspection",
standards: ["ASME Section V Article 4", "API 570", "AWS D1.1"],
prepRequired: ["Weld crown preparation", "Couplant liquid application", "Wedge scan path design"],
advantage: "Volumetric weld-integrity scan; maps complete flaw depth, length, and slope.",
simulatedSignalName: "B-Scan Cross-Section Reflection",
simulatedSignalDesc: "Lack of root fusion anomaly identified at the 4-inch weld seam.",
dangerLevel: "High"
};
}
if (concernId === "coating") {
return {
technique: "Low-Voltage Sponge / Wet Sponge Holiday Testing",
techCode: "LV Holiday Testing",
standards: ["ASTM G62", "NACE SP0188"],
prepRequired: ["Tap water wetting with surfactant", "Lining surface drying"],
advantage: "Non-destructive testing for delicate pipeline paint or thin epoxy liners.",
simulatedSignalName: "Audio Signal Indicator Trigger",
simulatedSignalDesc: "Resistance drop reveals pinhole breach on the external pipe wrap.",
dangerLevel: "Medium"
};
}
return {
technique: "Alloy Verification & Weld PMI Check",
techCode: "XRF Alloy Analyzer",
standards: ["API RP 578", "ASME Section IX"],
prepRequired: ["Sanding of paint down to bare steel", "Analyzer sensor clean"],
advantage: "Rapid verification of pipe fitting schedules and flange material compatibility.",
simulatedSignalName: "Spectral Peak Verification",
simulatedSignalDesc: "Conforms to ASTM A106 Grade B carbon steel specification.",
dangerLevel: "Low"
};
}
if (concernId === "thinning") {
return {
technique: "Magnetic Flux Leakage (MFL) Tank Bottom Floor Scanner",
techCode: "MFL Scanning",
standards: ["API 653 Annex C", "ASME Section V Article 16"],
prepRequired: ["Comprehensive tank cleaning & sludge removal", "Floor drying", "Removal of heavy scale"],
advantage: "Rapidly scans 100% of tank bottom plates for soil-side or product-side pitting.",
simulatedSignalName: "MFL Flux Leakage Peak",
simulatedSignalDesc: "High flux escape indicates ~45% bottom plate loss from the bottom soil side.",
dangerLevel: "High"
};
}
if (concernId === "cracks") {
return {
technique: "Vacuum Box Testing & Dye Penetrant (PT)",
techCode: "Vacuum Box & PT Weld",
standards: ["ASME Section V Article 10", "API 653"],
prepRequired: ["Weld slag wire-brushing", "Liquid soap-bubble detergent prep", "Sufficient lighting"],
advantage: "Provides 100% immediate bubble verification of potential annular plate weld seam leaks.",
simulatedSignalName: "Detergent Bubble Indication",
simulatedSignalDesc: "Active bubble train stream points to a fine through-wall pinhole weld leak.",
dangerLevel: "High"
};
}
if (concernId === "coating") {
return {
technique: "High-Voltage Pulse Holiday Testing",
techCode: "Pulse Holiday NDT",
standards: ["NACE SP0188", "API 652"],
prepRequired: ["Coating curing check", "Continuous wire loop ground installation", "Dust wipe down"],
advantage: "Safely checks heavy, multi-layer reinforced tank fiber lining systems.",
simulatedSignalName: "High-Voltage Arc Spark",
simulatedSignalDesc: "Discharge arc points to holiday location in sump lining weld.",
dangerLevel: "Medium"
};
}
return {
technique: "XRF Alloy Analysis & Hardness Audit",
techCode: "PMI Hardness Testing",
standards: ["API 653", "ASTM E110"],
prepRequired: ["Sanding weld crown flat", "Surface cleaning"],
advantage: "Validates mechanical strength properties of heavy structural shell plate welds.",
simulatedSignalName: "Brinell Hardness Value Return",
simulatedSignalDesc: "185 HBW reading confirms material toughness falls in safe operating zone.",
dangerLevel: "Medium"
};
};
export default function InteractiveNdtAdvisor() {
const [selectedAsset, setSelectedAsset] = useState("tube");
const [selectedConcern, setSelectedConcern] = useState("thinning");
const [isScanning, setIsScanning] = useState(false);
const [scanProgress, setScanProgress] = useState(0);
const [showResult, setShowResult] = useState(true);
const [scanComplete, setScanComplete] = useState(false);
const recommendation = getRecommendation(selectedAsset, selectedConcern);
const handleScan = () => {
setIsScanning(true);
setScanProgress(0);
setScanComplete(false);
};
useEffect(() => {
let interval;
if (isScanning) {
interval = setInterval(() => {
setScanProgress((prev) => {
if (prev >= 100) {
clearInterval(interval);
setIsScanning(false);
setScanComplete(true);
return 100;
}
return prev + 4;
});
}, 50);
}
return () => clearInterval(interval);
}, [isScanning]);
const CurrentAssetIcon = assets.find((a) => a.id === selectedAsset)?.icon || Cpu;
return <div id="ndt-advisor-widget" className="bg-white border border-slate-200 rounded-3xl shadow-xl overflow-hidden max-w-6xl mx-auto my-6">
<div className="grid lg:grid-cols-12">
{
/* Left Side: Control Center (7 cols) */
}
<div className="lg:col-span-7 p-6 sm:p-10 border-b lg:border-b-0 lg:border-r border-slate-100 flex flex-col justify-between">
<div>
<div className="flex items-center gap-2 mb-4">
<span className="w-2.5 h-2.5 bg-orange-500 rounded-full animate-pulse" />
<span className="font-mono text-[10px] font-bold text-orange-500 uppercase tracking-widest">
Interactive Engineering System
</span>
</div>
<h3 className="font-heading text-2xl sm:text-3xl font-extrabold text-slate-900 tracking-tight">
NDT Strategy & Calibration Configurator
</h3>
<p className="text-xs text-slate-500 font-light mt-2 max-w-xl leading-relaxed">
Select an industrial asset category and primary mechanical concern to generate a compliant code procedure recommendation and execute a simulated instrument calibration scan.
</p>
{
/* Asset Selector */
}
<div className="mt-8">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-wider font-heading block mb-3">
1. Select Asset Geometry
</label>
<div className="grid grid-cols-2 gap-3">
{assets.map((asset) => {
const IconComp = asset.icon;
const isSelected = selectedAsset === asset.id;
return <button
key={asset.id}
onClick={() => {
setSelectedAsset(asset.id);
setScanComplete(false);
}}
className={`p-4 rounded-2xl border text-left transition-all duration-200 ${isSelected ? "bg-slate-900 text-white border-slate-900 shadow-md" : "bg-slate-50 text-slate-700 border-slate-200/60 hover:bg-slate-100/70"}`}
>
<div className="flex items-center gap-3">
<div className={`p-2 rounded-xl ${isSelected ? "bg-orange-500 text-white" : "bg-slate-200/60 text-slate-600"}`}>
<IconComp className="w-4 h-4" />
</div>
<div>
<p className="text-xs font-bold font-heading">{asset.name}</p>
</div>
</div>
</button>;
})}
</div>
</div>
{
/* Concern Selector */
}
<div className="mt-6">
<label className="text-[11px] font-bold text-slate-400 uppercase tracking-wider font-heading block mb-3">
2. Primary Concern or Damage Loop
</label>
<div className="grid grid-cols-2 gap-3">
{concerns.map((concern) => {
const isSelected = selectedConcern === concern.id;
return <button
key={concern.id}
onClick={() => {
setSelectedConcern(concern.id);
setScanComplete(false);
}}
className={`p-4 rounded-2xl border text-left transition-all duration-200 ${isSelected ? "bg-orange-500 text-white border-orange-500 shadow-md" : "bg-slate-50 text-slate-700 border-slate-200/60 hover:bg-slate-100/70"}`}
>
<h4 className="text-xs font-bold font-heading">{concern.name}</h4>
<p className={`text-[10px] mt-1 leading-normal ${isSelected ? "text-orange-100" : "text-slate-400"}`}>
{concern.description}
</p>
</button>;
})}
</div>
</div>
</div>
{
/* Action Footer */
}
<div className="mt-10 pt-6 border-t border-slate-100 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center text-slate-500 text-xs font-mono font-bold">
i
</div>
<p className="text-[10px] text-slate-400 font-light max-w-xs leading-normal">
Recommendations are vetted by our ASNT Level III and API 510/570 planning committees.
</p>
</div>
<button
onClick={handleScan}
disabled={isScanning}
className={`w-full sm:w-auto bg-slate-900 text-white hover:bg-slate-800 disabled:opacity-50 font-bold text-xs px-6 py-3.5 rounded-xl uppercase tracking-wider transition-all duration-200 flex items-center justify-center gap-2 shadow-md`}
>
{isScanning ? <>
<RefreshCw className="w-4 h-4 animate-spin text-white" />
<span>Scanning Instrument...</span>
</> : <>
<Zap className="w-4 h-4 text-orange-400 fill-orange-400 animate-pulse" />
<span>Calibrate & Test Probe</span>
</>}
</button>
</div>
</div>
{
/* Right Side: Compliance Sheet & Live Scan Screen (5 cols) */
}
<div className="lg:col-span-5 p-6 sm:p-10 bg-slate-950 text-white flex flex-col justify-between relative overflow-hidden">
{
/* Subtle tech grid background */
}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#1e293b_1px,transparent_1px),linear-gradient(to_bottom,#1e293b_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)] opacity-20 pointer-events-none" />
<div className="relative z-10 space-y-6">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<span className="font-mono text-xs font-bold text-orange-400 uppercase tracking-widest">
Compliance Spec Sheet
</span>
<span className={`text-[9px] font-mono uppercase tracking-widest px-2 py-0.5 rounded border ${recommendation.dangerLevel === "High" ? "bg-red-500/10 text-red-400 border-red-500/20" : "bg-amber-500/10 text-amber-400 border-amber-500/20"}`}>
Critical Level: {recommendation.dangerLevel}
</span>
</div>
{
/* Simulated Live Scan Screen */
}
<div className="bg-slate-900/90 border border-slate-800 rounded-2xl p-5 overflow-hidden font-mono relative">
<div className="flex items-center justify-between text-[10px] text-slate-400 mb-4 pb-2 border-b border-slate-800/60">
<span className="flex items-center gap-1.5">
<span className={`w-1.5 h-1.5 rounded-full ${isScanning ? "bg-orange-500 animate-ping" : "bg-green-500"}`} />
{isScanning ? "SIGNAL ACQUISITION ACTIVE" : "CALIBRATED & READY"}
</span>
<span>CH_A: 5.0 MHz</span>
</div>
{
/* Scan visualization box */
}
<div className="h-28 flex items-center justify-center relative bg-slate-950 rounded-lg overflow-hidden border border-slate-800/40">
{
/* Simulated wave or scan line */
}
<AnimatePresence>
{isScanning ? <motion.div
key="scanning"
className="absolute inset-0 flex flex-col justify-center px-4"
>
{
/* Animated wave path */
}
<svg viewBox="0 0 300 80" className="w-full h-full text-orange-500 stroke-2 fill-none">
<path d={`M 0,40 Q 30,20 60,40 T 120,40 T 180,${40 - scanProgress / 3} T 240,40 T 300,40`} className="animate-pulse" stroke="currentColor" />
</svg>
{
/* Scanning laser sweep */
}
<motion.div
className="absolute top-0 bottom-0 w-0.5 bg-orange-400 shadow-[0_0_10px_#f97316] z-10"
animate={{ left: ["0%", "100%", "0%"] }}
transition={{ repeat: Infinity, duration: 1.5, ease: "linear" }}
/>
</motion.div> : scanComplete ? <motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="absolute inset-0 bg-orange-500/5 p-3 flex flex-col justify-between"
>
<div className="flex items-start justify-between text-[9px]">
<span className="text-orange-400 font-bold"> DETECTED ANOMALY</span>
<span className="text-slate-400">POS: 184.2 mm</span>
</div>
{
/* Anomaly wave signal with a massive spike */
}
<svg viewBox="0 0 300 60" className="w-full h-10 text-orange-500 stroke-2 fill-none">
<path d="M 0,30 L 80,30 L 100,10 L 115,50 L 130,30 L 160,30 L 175,5 L 185,55 L 195,5 L 210,30 L 300,30" stroke="currentColor" />
</svg>
<div className="text-[10px] text-orange-300 leading-normal font-sans bg-orange-500/10 border border-orange-500/20 rounded p-1.5 mt-1">
<strong>{recommendation.simulatedSignalName}:</strong> {recommendation.simulatedSignalDesc}
</div>
</motion.div> : <div className="text-center text-xs text-slate-500 flex flex-col items-center gap-2">
<CurrentAssetIcon className="w-8 h-8 text-slate-600 animate-pulse" />
<span>Press Calibrate & Test Probe to capture NDT signals</span>
</div>}
</AnimatePresence>
{
/* Progress Overlay */
}
{isScanning && <div className="absolute inset-0 bg-slate-950/80 flex flex-col items-center justify-center p-4">
<span className="text-xs font-bold text-orange-400 mb-2 font-mono">SWEEPING ASSET... {scanProgress}%</span>
<div className="w-44 h-1.5 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-orange-500" style={{ width: `${scanProgress}%` }} />
</div>
</div>}
</div>
</div>
{
/* Core Recommendation Specs */
}
<div className="space-y-4">
<div>
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider block">Recommended NDT Technique</span>
<p className="text-sm font-bold text-white mt-1 flex items-center gap-1.5">
<CheckCircle className="w-4.5 h-4.5 text-orange-500 shrink-0" />
{recommendation.technique}
</p>
<p className="text-xs text-slate-400 font-light mt-1">{recommendation.advantage}</p>
</div>
<div className="grid grid-cols-2 gap-4 pt-2 border-t border-slate-900">
<div>
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider block">Procedural Standards</span>
<div className="mt-1 flex flex-col gap-1">
{recommendation.standards.map((s) => <span key={s} className="font-mono text-[10px] text-orange-300 block bg-orange-500/5 py-0.5 px-1.5 rounded border border-orange-500/10 w-fit">
{s}
</span>)}
</div>
</div>
<div>
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider block">Calibration Code</span>
<span className="font-mono text-xs font-bold text-slate-200 mt-1 block">
{recommendation.techCode}
</span>
</div>
</div>
{
/* Prep Work Required */
}
<div className="pt-4 border-t border-slate-900">
<span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider block">Mandatory Field Preparations</span>
<ul className="mt-2 space-y-1.5 text-xs text-slate-300 font-light list-none">
{recommendation.prepRequired.map((p, idx) => <li key={idx} className="flex items-start gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-orange-500 mt-1.5 shrink-0" />
<span>{p}</span>
</li>)}
</ul>
</div>
</div>
</div>
<div className="mt-8 pt-4 border-t border-slate-800 text-center relative z-10">
<p className="text-[11px] text-slate-400 font-light">
Need custom procedures drafted for this asset configuration?
</p>
<a
href="/contact"
className="inline-flex items-center gap-1.5 text-[11px] font-bold text-orange-400 hover:text-orange-300 uppercase tracking-wider mt-2 group"
>
<span>Consult Our Level III Engineers</span>
<span className="group-hover:translate-x-1 transition-transform"></span>
</a>
</div>
</div>
</div>
</div>;
}
+74
View File
@@ -0,0 +1,74 @@
import { motion } from "motion/react";
import { Check, X, Info } from "lucide-react";
export default function MethodCard({ method }) {
return <motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="bg-white border border-slate-100 p-6 md:p-8 rounded-2xl shadow-lg hover:shadow-xl hover:border-orange-500/20 transition-all duration-300 flex flex-col relative overflow-hidden group hover:-translate-y-1"
>
{
/* Decorative accent top-line */
}
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-orange-500/10 via-orange-500 to-orange-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
{
/* Code Badge */
}
<span className="absolute top-6 right-6 font-mono text-xs font-semibold text-slate-500 bg-slate-50 px-3 py-1 rounded-full border border-slate-100 tracking-wider">
METHOD {method.code}
</span>
<h3 className="font-heading text-xl md:text-2xl font-bold text-slate-900 group-hover:text-orange-500 transition-colors duration-200 mt-2 pr-28">
{method.name}
</h3>
<p className="mt-4 text-slate-500 text-sm leading-relaxed font-light">
{method.description}
</p>
{method.note && <div className="mt-5 flex gap-3.5 items-start bg-amber-50/40 border-l-4 border-amber-500 p-4 rounded-xl">
<Info className="w-4 h-4 text-amber-600 shrink-0 mt-0.5" />
<p className="text-xs text-amber-800 leading-relaxed font-normal">
{method.note}
</p>
</div>}
{(method.advantages || method.limitations) && <div className="grid sm:grid-cols-2 gap-6 mt-6 pt-5 border-t border-slate-50">
{method.advantages && <div>
<p className="font-heading text-xs font-bold tracking-wider text-slate-900 mb-3.5 uppercase">Advantages</p>
<ul className="space-y-2.5">
{method.advantages.map((a) => <li key={a} className="text-xs text-slate-600 flex gap-2.5 items-start font-light">
<span className="w-5 h-5 rounded-full bg-emerald-50 text-emerald-600 flex items-center justify-center shrink-0 mt-0.5 border border-emerald-100/50">
<Check className="w-3 h-3" />
</span>
<span>{a}</span>
</li>)}
</ul>
</div>}
{method.limitations && <div>
<p className="font-heading text-xs font-bold tracking-wider text-slate-500 mb-3.5 uppercase">Limitations</p>
<ul className="space-y-2.5">
{method.limitations.map((l) => <li key={l} className="text-xs text-slate-500 flex gap-2.5 items-start font-light">
<span className="w-5 h-5 rounded-full bg-rose-50 text-rose-500 flex items-center justify-center shrink-0 mt-0.5 border border-rose-100/50">
<X className="w-3 h-3" />
</span>
<span>{l}</span>
</li>)}
</ul>
</div>}
</div>}
{method.uses && <div className="mt-6 pt-5 border-t border-slate-50">
<p className="font-heading text-[10px] font-bold tracking-wider text-slate-800 mb-3 uppercase">Key Applications</p>
<div className="flex flex-wrap gap-2">
{method.uses.map((u) => <span
key={u}
className="text-xs border border-slate-200 bg-slate-50 text-slate-600 px-3.5 py-1.5 rounded-lg hover:border-orange-500/40 hover:text-orange-500 transition-all duration-200 cursor-default"
>
{u}
</span>)}
</div>
</div>}
</motion.div>;
}
+195
View File
@@ -0,0 +1,195 @@
import { useState, useEffect } from "react";
import { NavLink, Link, useLocation } from "react-router-dom";
import { motion, AnimatePresence } from "motion/react";
import { ChevronDown, Menu, X, Phone, Mail, MapPin, ArrowRight } from "lucide-react";
import { serviceCategories } from "../data/content";
const navLinkClass = ({ isActive }) => `relative text-sm font-semibold tracking-wide py-1.5 transition-all duration-200 ${isActive ? "text-orange-500 font-bold border-b-2 border-orange-500" : "text-slate-600 hover:text-orange-500"}`;
export default function Navbar() {
const [open, setOpen] = useState(false);
const [servicesOpen, setServicesOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const location = useLocation();
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 40);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
setOpen(false);
setServicesOpen(false);
}, [location]);
return <header className="w-full z-50 flex flex-col transition-all duration-300">
{
/* Top bar with contact info */
}
<div
className={`bg-slate-950 border-b border-slate-800 text-slate-300 py-2.5 px-5 md:px-8 transition-all duration-300 ${isScrolled ? "h-0 py-0 overflow-hidden opacity-0 border-none" : "h-auto opacity-100"}`}
>
<div className="max-w-7xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-3">
<div className="flex flex-wrap justify-center sm:justify-start items-center gap-5 text-xs">
<a href="tel:+966571033252" className="flex items-center gap-2 hover:text-orange-500 transition-colors font-medium">
<Phone className="w-3.5 h-3.5 text-orange-500 shrink-0" />
<span>+966 57 103 3252</span>
</a>
<a href="mailto:sales@masarndt.com" className="flex items-center gap-2 hover:text-orange-500 transition-colors font-medium">
<Mail className="w-3.5 h-3.5 text-orange-500 shrink-0" />
<span>sales@masarndt.com</span>
</a>
<span className="flex items-center gap-2 text-slate-400">
<MapPin className="w-3.5 h-3.5 text-orange-500 shrink-0" />
<span>Jubail, Saudi Arabia</span>
</span>
</div>
<div className="flex items-center gap-4 text-xs font-medium">
<span className="font-bold text-white uppercase text-[9px] tracking-wider bg-slate-900 border border-slate-800 px-2 py-0.5 rounded">
ISO 9001:2015 Approved
</span>
<span className="text-slate-800">|</span>
<Link to="/contact" className="text-slate-400 hover:text-orange-500 transition-colors">Support</Link>
</div>
</div>
</div>
{
/* Main navigation bar (Sticky) */
}
<div
className={`w-full bg-white transition-all duration-300 ${isScrolled ? "fixed top-0 shadow-lg border-b border-slate-100 py-3.5" : "relative py-5 border-b border-slate-100"}`}
>
<div className="max-w-7xl mx-auto px-5 md:px-8 flex items-center justify-between">
{
/* Brand Logo */
}
<Link to="/" className="flex items-center gap-2.5 shrink-0">
<span className="w-5 h-5 rounded bg-orange-500 shadow-md flex items-center justify-center font-bold text-white text-[10px]" aria-hidden="true">M</span>
<span className="font-heading font-extrabold uppercase tracking-wide text-slate-900 text-xl sm:text-2xl">
Masar<span className="text-orange-500 font-black"> NDT</span>
</span>
</Link>
{
/* Desktop Navigation Links */
}
<nav className="hidden lg:flex items-center gap-8">
<NavLink to="/" className={navLinkClass} end>Home</NavLink>
<NavLink to="/about" className={navLinkClass}>About</NavLink>
{
/* Services Dropdown */
}
<div
className="relative"
onMouseEnter={() => setServicesOpen(true)}
onMouseLeave={() => setServicesOpen(false)}
>
<button
className="text-sm font-semibold tracking-wide text-slate-600 hover:text-orange-500 flex items-center gap-1 transition-colors py-1.5"
onClick={() => setServicesOpen(!servicesOpen)}
>
Services
<ChevronDown className={`w-4 h-4 transition-transform duration-200 ${servicesOpen ? "rotate-180 text-orange-500" : "text-slate-400"}`} />
</button>
<AnimatePresence>
{servicesOpen && <motion.div
initial={{ opacity: 0, y: 10, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.98 }}
transition={{ duration: 0.15 }}
className="absolute left-0 top-full mt-2 w-80 bg-white border border-slate-100 shadow-xl p-2 rounded-xl z-50"
>
<div className="grid gap-1">
{serviceCategories.map((s) => <NavLink
key={s.id}
to={s.path}
className={({ isActive }) => `group flex flex-col p-3 rounded-lg transition-all duration-200 ${isActive ? "bg-slate-50 border-l-4 border-orange-500" : "hover:bg-slate-50"}`}
onClick={() => setServicesOpen(false)}
>
<span className="text-[9px] font-bold text-orange-500 uppercase tracking-widest">{s.tag}</span>
<span className="text-sm font-bold text-slate-900 group-hover:text-orange-500 transition-colors flex items-center mt-0.5 font-heading">
{s.title}
<ArrowRight className="w-4 h-4 opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all ml-auto text-orange-500" />
</span>
</NavLink>)}
</div>
</motion.div>}
</AnimatePresence>
</div>
<NavLink to="/industries" className={navLinkClass}>Industries</NavLink>
<NavLink to="/projects" className={navLinkClass}>Projects</NavLink>
<NavLink to="/gallery" className={navLinkClass}>Gallery</NavLink>
<NavLink to="/contact" className={navLinkClass}>Contact</NavLink>
</nav>
{
/* Right Action Button & Mobile Trigger */
}
<div className="flex items-center gap-4">
<Link
to="/contact"
className="hidden sm:inline-block bg-orange-500 hover:bg-orange-600 text-white font-bold text-xs px-6 py-3.5 rounded-xl tracking-wider uppercase shadow-md hover:shadow-orange-500/10 hover:-translate-y-0.5 active:translate-y-0 transition-all duration-200"
>
Get a Quote
</Link>
<button
className="lg:hidden p-2.5 text-slate-950 hover:text-orange-500 border border-slate-100 rounded-xl transition-colors"
onClick={() => setOpen(!open)}
aria-label="Toggle navigation menu"
aria-expanded={open}
>
{open ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
</div>
{
/* Mobile Drawer Menu */
}
<AnimatePresence>
{open && <motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
className="lg:hidden w-full bg-white border-b border-slate-100 px-6 py-6 flex flex-col gap-4 overflow-y-auto max-h-[calc(100vh-80px)] z-40 absolute top-full left-0 shadow-2xl"
>
<NavLink to="/" className="text-sm font-bold text-slate-800 hover:text-orange-500 font-heading" end onClick={() => setOpen(false)}>Home</NavLink>
<NavLink to="/about" className="text-sm font-bold text-slate-800 hover:text-orange-500 font-heading" onClick={() => setOpen(false)}>About</NavLink>
<div className="h-px bg-slate-100 my-1" />
<p className="text-[10px] font-bold text-orange-500 uppercase tracking-widest font-heading">Services</p>
<div className="grid gap-2 pl-3 border-l-2 border-orange-500 ml-1">
{serviceCategories.map((s) => <NavLink
key={s.id}
to={s.path}
className={({ isActive }) => `text-sm font-bold transition-colors py-1 font-heading ${isActive ? "text-orange-500" : "text-slate-800 hover:text-orange-500"}`}
onClick={() => setOpen(false)}
>
{s.title}
</NavLink>)}
</div>
<div className="h-px bg-slate-100 my-1" />
<NavLink to="/industries" className="text-sm font-bold text-slate-800 hover:text-orange-500 font-heading" onClick={() => setOpen(false)}>Industries</NavLink>
<NavLink to="/projects" className="text-sm font-bold text-slate-800 hover:text-orange-500 font-heading" onClick={() => setOpen(false)}>Projects</NavLink>
<NavLink to="/gallery" className="text-sm font-bold text-slate-800 hover:text-orange-500 font-heading" onClick={() => setOpen(false)}>Gallery</NavLink>
<NavLink to="/contact" className="text-sm font-bold text-slate-800 hover:text-orange-500 font-heading" onClick={() => setOpen(false)}>Contact</NavLink>
<Link
to="/contact"
className="mt-4 text-center bg-orange-500 hover:bg-orange-600 text-white font-bold text-xs py-4 rounded-xl tracking-wider uppercase shadow-md transition-all font-heading"
onClick={() => setOpen(false)}
>
Get a Quote
</Link>
</motion.div>}
</AnimatePresence>
</div>
</header>;
}
+22
View File
@@ -0,0 +1,22 @@
import { motion } from "motion/react";
export default function SectionHeader({ tag, title, description, align = "left", dark = false }) {
const isCenter = align === "center";
return <motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
className={`max-w-4xl ${isCenter ? "mx-auto text-center flex flex-col items-center" : ""} mb-12`}
>
{tag && <p className="font-mono text-[10px] font-bold tracking-widest text-orange-500 mb-3 flex items-center gap-2 uppercase">
<span className="w-4 h-px bg-orange-500/40 inline-block" />
{tag}
</p>}
<h2 className={`font-heading text-3xl md:text-4xl lg:text-5xl font-extrabold tracking-tight leading-[1.1] ${dark ? "text-white" : "text-slate-900"}`}>
{title}
</h2>
{description && <p className={`mt-4 leading-relaxed text-sm md:text-base max-w-2xl font-light ${dark ? "text-slate-300" : "text-slate-500"}`}>
{description}
</p>}
</motion.div>;
}
+66
View File
@@ -0,0 +1,66 @@
import { Link } from "react-router-dom";
import { motion } from "motion/react";
import { ArrowRight, ScanSearch, Zap, Activity, DatabaseZap, UserCheck } from "lucide-react";
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.08
}
}
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" } }
};
const serviceIcons = [ScanSearch, Zap, Activity, DatabaseZap, UserCheck];
export default function ServiceCategoryGrid({ categories }) {
return <motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-50px" }}
className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8"
>
{categories.map((s, idx) => {
const Icon = serviceIcons[idx] || ScanSearch;
return <motion.div key={s.id} variants={itemVariants}>
<Link
to={s.path}
className="group block bg-white border border-slate-100 p-8 flex flex-col justify-between min-h-[300px] rounded-2xl transition-all duration-300 relative overflow-hidden shadow-md hover:shadow-xl hover:border-orange-500/20 hover:-translate-y-1"
>
{
/* Top Accent line */
}
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-orange-500/20 via-orange-500 to-orange-500/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
<div className="relative z-10">
<div className="flex items-center justify-between">
<div className="w-12 h-12 rounded-xl bg-orange-500/10 text-orange-600 flex items-center justify-center shadow-sm group-hover:scale-110 group-hover:rotate-3 transition-all duration-300">
<Icon className="w-5 h-5" />
</div>
<p className="font-mono text-[10px] font-bold tracking-wider text-orange-500 bg-orange-500/5 px-2.5 py-1 rounded-full">{s.tag}</p>
</div>
<h3 className="font-heading text-xl font-bold mt-6 text-slate-900 group-hover:text-orange-500 transition-colors duration-300">
{s.title}
</h3>
<p className="text-sm text-slate-500 group-hover:text-slate-600 mt-3 leading-relaxed font-light transition-colors duration-300">
{s.summary}
</p>
</div>
<div className="mt-8 pt-5 border-t border-slate-50 flex items-center justify-between relative z-10">
<span className="text-xs font-bold text-slate-800 group-hover:text-orange-500 transition-colors duration-200 uppercase tracking-wider">
View division scope
</span>
<div className="w-8 h-8 rounded-full bg-slate-50 text-slate-700 flex items-center justify-center group-hover:bg-orange-500 group-hover:text-white transition-all duration-300 shadow-sm">
<ArrowRight className="w-4 h-4 transition-transform duration-200 group-hover:translate-x-0.5" />
</div>
</div>
</Link>
</motion.div>;
})}
</motion.div>;
}