v4
This commit is contained in:
4
.env.example
Normal file
4
.env.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
VITE_WHATSAPP_PHONE=971564148980
|
||||||
|
VITE_BRAND_PRIMARY=#FA2D39
|
||||||
|
VITE_BRAND_DARK=#101840
|
||||||
|
VITE_BRAND_NAVY=#0a004c
|
||||||
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,24 +1,4 @@
|
|||||||
# Logs
|
node_modules/
|
||||||
logs
|
dist/
|
||||||
*.log
|
.env
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|||||||
39
package-lock.json
generated
39
package-lock.json
generated
@@ -8,14 +8,13 @@
|
|||||||
"name": "react-app",
|
"name": "react-app",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"react-router-dom": "^7.17.0"
|
"react-router-dom": "^7.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/react": "^19.2.14",
|
|
||||||
"@types/react-dom": "^19.2.3",
|
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"eslint": "^10.3.0",
|
"eslint": "^10.3.0",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
@@ -426,6 +425,15 @@
|
|||||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-free": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-Yw4Qa43P6e4Xwh0TwEUkHQNRJInWaqIBo73VJmns3j2AIlZPAvUcR4yGIxCPqPRCgyJ5KIVIalF/I1GxhZ/Kgw==",
|
||||||
|
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@@ -885,26 +893,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
|
||||||
"version": "19.2.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
|
|
||||||
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"csstype": "^3.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/react-dom": {
|
|
||||||
"version": "19.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
|
||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^19.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz",
|
||||||
@@ -1097,13 +1085,6 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/csstype": {
|
|
||||||
"version": "3.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
|
|||||||
@@ -10,14 +10,13 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^7.3.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"react-router-dom": "^7.17.0"
|
"react-router-dom": "^7.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/react": "^19.2.14",
|
|
||||||
"@types/react-dom": "^19.2.3",
|
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"eslint": "^10.3.0",
|
"eslint": "^10.3.0",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2")}
|
||||||
|
@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2")}
|
||||||
|
@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2")}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Font Awesome Pro 6.0.0-beta3 by @fontawesome - https://fontawesome.com
|
* Font Awesome Pro 6.0.0-beta3 by @fontawesome - https://fontawesome.com
|
||||||
* License - https://fontawesome.com/license (Commercial License)
|
* License - https://fontawesome.com/license (Commercial License)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
12
src/App.jsx
12
src/App.jsx
@@ -1,7 +1,9 @@
|
|||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import Layout from './components/Layout'
|
import Layout from './components/Layout'
|
||||||
|
import Loading from './components/Loading'
|
||||||
|
import ErrorBoundary from './components/ErrorBoundary'
|
||||||
|
import { BRAND_PRIMARY } from './config'
|
||||||
|
|
||||||
const Home = lazy(() => import('./pages/Home'))
|
const Home = lazy(() => import('./pages/Home'))
|
||||||
const About = lazy(() => import('./pages/About'))
|
const About = lazy(() => import('./pages/About'))
|
||||||
@@ -15,7 +17,7 @@ function NotFound() {
|
|||||||
<div style={{ padding: '120px 24px', textAlign: 'center' }}>
|
<div style={{ padding: '120px 24px', textAlign: 'center' }}>
|
||||||
<h1>404 — Page Not Found</h1>
|
<h1>404 — Page Not Found</h1>
|
||||||
<p>The page you're looking for doesn't exist.</p>
|
<p>The page you're looking for doesn't exist.</p>
|
||||||
<Link to="/" style={{ color: '#FA2D39' }}>← Back to Home</Link>
|
<Link to="/" style={{ color: BRAND_PRIMARY }}>← Back to Home</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -23,7 +25,8 @@ function NotFound() {
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Suspense fallback={null}>
|
<ErrorBoundary>
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<Layout />}>
|
<Route element={<Layout />}>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
@@ -36,6 +39,7 @@ export default function App() {
|
|||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/components/ErrorBoundary.jsx
Normal file
39
src/components/ErrorBoundary.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Component } from 'react'
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = { hasError: false, error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error) {
|
||||||
|
return { hasError: true, error }
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error, info) {
|
||||||
|
console.error('ErrorBoundary caught:', error, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '80px 24px', textAlign: 'center' }}>
|
||||||
|
<h1>Something went wrong</h1>
|
||||||
|
<p style={{ color: '#666', marginBottom: 24 }}>
|
||||||
|
{this.state.error?.message || 'An unexpected error occurred'}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => { this.setState({ hasError: false, error: null }) }}
|
||||||
|
style={{
|
||||||
|
padding: '12px 24px', background: '#FA2D39', color: '#fff',
|
||||||
|
border: 'none', borderRadius: 6, cursor: 'pointer', fontSize: 16
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Try again
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.props.children
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { useRef } from 'react'
|
|
||||||
import footerHtml from '../data/layout-footer.html?raw'
|
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
|
||||||
|
|
||||||
export default function Footer() {
|
|
||||||
const ref = useRef(null)
|
|
||||||
useNavFix(ref)
|
|
||||||
return <div ref={ref} dangerouslySetInnerHTML={{ __html: footerHtml }} />
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { useRef } from 'react'
|
|
||||||
import headerHtml from '../data/layout-header.html?raw'
|
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
|
||||||
|
|
||||||
export default function Header() {
|
|
||||||
const ref = useRef(null)
|
|
||||||
useNavFix(ref)
|
|
||||||
return <div ref={ref} dangerouslySetInnerHTML={{ __html: headerHtml }} />
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { Outlet, useLocation } from 'react-router-dom'
|
import { Outlet, useLocation } from 'react-router-dom'
|
||||||
import Header from './Header'
|
import PageShell from './PageShell'
|
||||||
import Footer from './Footer'
|
import headerHtml from '../data/layout-header.html?raw'
|
||||||
|
import footerHtml from '../data/layout-footer.html?raw'
|
||||||
import WhatsAppButton from './WhatsAppButton'
|
import WhatsAppButton from './WhatsAppButton'
|
||||||
import useThemeAssets from '../hooks/useThemeAssets'
|
import useThemeAssets from '../hooks/useThemeAssets'
|
||||||
import useLayoutInit from '../hooks/useLayoutInit'
|
import useLayoutInit from '../hooks/useLayoutInit'
|
||||||
|
import { PAGE_IDS } from '../config'
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
@@ -18,11 +20,11 @@ export default function Layout() {
|
|||||||
if (removed) return
|
if (removed) return
|
||||||
removed = true
|
removed = true
|
||||||
if (window.__removePreloader) {
|
if (window.__removePreloader) {
|
||||||
setTimeout(window.__removePreloader, 800)
|
window.__removePreloader()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const tryRemove = () => {
|
const tryRemove = () => {
|
||||||
if (window.__removePreloader && window.__cssReady && window.__layoutReady && document.readyState === 'complete') {
|
if (window.__removePreloader && window.__cssReady && window.__layoutReady) {
|
||||||
removePreloader()
|
removePreloader()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -33,7 +35,7 @@ export default function Layout() {
|
|||||||
document.addEventListener('css-ready', onReady)
|
document.addEventListener('css-ready', onReady)
|
||||||
window.addEventListener('layout-ready', onReady)
|
window.addEventListener('layout-ready', onReady)
|
||||||
window.addEventListener('load', onReady)
|
window.addEventListener('load', onReady)
|
||||||
const fallback = setTimeout(removePreloader, 15000)
|
const fallback = setTimeout(removePreloader, 5000)
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('css-ready', onReady)
|
document.removeEventListener('css-ready', onReady)
|
||||||
window.removeEventListener('layout-ready', onReady)
|
window.removeEventListener('layout-ready', onReady)
|
||||||
@@ -45,11 +47,8 @@ export default function Layout() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
|
|
||||||
const pageIds = {
|
|
||||||
'/': 23, '/about': 31, '/service': 32, '/project': 36, '/contact': 48,
|
|
||||||
}
|
|
||||||
const path = pathname.replace(/\/$/, '') || '/'
|
const path = pathname.replace(/\/$/, '') || '/'
|
||||||
const pageId = pageIds[path] || 23
|
const pageId = PAGE_IDS[path] || 23
|
||||||
const isHome = path === '/'
|
const isHome = path === '/'
|
||||||
|
|
||||||
if (window.elementorFrontendConfig) {
|
if (window.elementorFrontendConfig) {
|
||||||
@@ -66,36 +65,12 @@ export default function Layout() {
|
|||||||
].filter(Boolean).join(' ')
|
].filter(Boolean).join(' ')
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const style = document.createElement('style')
|
|
||||||
style.id = 'rakar_opt-dynamic-css'
|
|
||||||
style.textContent = `
|
|
||||||
:root{--icon-font:"Font Awesome 6 Free";--fa-style-family:"Font Awesome 6 Free";}
|
|
||||||
.header-logo .logo img{width:260px;}
|
|
||||||
.th-menu-wrapper .mobile-logo img{width:180px;}
|
|
||||||
.th-menu-wrapper .mobile-logo{background-color:#0a004c;}
|
|
||||||
.breadcumb-wrapper{background-repeat:no-repeat;background-position:center center;background-image:url('/wp-content/uploads/2024/07/breadcumb-bg.jpg');background-size:cover;}
|
|
||||||
.hero-inner{position:relative;}
|
|
||||||
.hero-inner::before{content:'';position:absolute;inset:0;background:linear-gradient(90deg,rgba(0,0,0,0.7) 0%,rgba(0,0,0,0.3) 100%);z-index:1;pointer-events:none;}
|
|
||||||
.hero-inner .container{position:relative;z-index:2;}
|
|
||||||
.hero-style5 .sub-title2.sub{color:#fff !important;}
|
|
||||||
.hero-style5 .hero-title,.hero-style5 .hero-text{color:#fff !important;}
|
|
||||||
.slider-area .slider-arrow{display:inline-flex;align-items:center;justify-content:center;width:50px;height:50px;border-radius:50%;background:rgba(255,255,255,0.2);color:#fff;border:none;cursor:pointer;transition:0.3s;font-size:18px;}
|
|
||||||
.slider-area .slider-arrow:hover{background:#FA2D39;}
|
|
||||||
.slider-area .slider-controller{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:40px;}
|
|
||||||
.slider-area .slider-pagination{display:flex;align-items:center;gap:6px;}
|
|
||||||
.slider-area .slider-pagination button{width:12px;height:12px;border-radius:50%;border:none;background:#999;opacity:0.4;cursor:pointer;padding:0;transition:0.3s;}
|
|
||||||
.slider-area .slider-pagination button.active{opacity:1;background:#FA2D39;}
|
|
||||||
`
|
|
||||||
document.head.appendChild(style)
|
|
||||||
return () => style.remove()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<PageShell html={headerHtml} />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<Footer />
|
<PageShell html={footerHtml} />
|
||||||
<WhatsAppButton />
|
<WhatsAppButton />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
10
src/components/Loading.jsx
Normal file
10
src/components/Loading.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
height: '80vh', color: '#fff', fontSize: '18px',
|
||||||
|
}}>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
8
src/components/PageShell.jsx
Normal file
8
src/components/PageShell.jsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { useRef } from 'react'
|
||||||
|
import { useNavFix } from '../hooks/useNavFix'
|
||||||
|
|
||||||
|
export default function PageShell({ html }) {
|
||||||
|
const ref = useRef(null)
|
||||||
|
useNavFix(ref)
|
||||||
|
return <div ref={ref} dangerouslySetInnerHTML={{ __html: html }} />
|
||||||
|
}
|
||||||
@@ -1,17 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { WHATSAPP_PHONE } from '../config'
|
||||||
|
const WHATSAPP_URL = `https://wa.me/${WHATSAPP_PHONE}`
|
||||||
const PHONE = '971564148980'
|
|
||||||
const WHATSAPP_URL = `https://wa.me/${PHONE}`
|
|
||||||
|
|
||||||
export default function WhatsAppButton() {
|
export default function WhatsAppButton() {
|
||||||
const [visible, setVisible] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setVisible(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!visible) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
|
|||||||
23
src/config.js
Normal file
23
src/config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// App configuration
|
||||||
|
export const WHATSAPP_PHONE = import.meta.env.VITE_WHATSAPP_PHONE || '971564148980'
|
||||||
|
export const BRAND_PRIMARY = import.meta.env.VITE_BRAND_PRIMARY || '#FA2D39'
|
||||||
|
export const BRAND_DARK = import.meta.env.VITE_BRAND_DARK || '#101840'
|
||||||
|
export const BRAND_NAVY = import.meta.env.VITE_BRAND_NAVY || '#0a004c'
|
||||||
|
|
||||||
|
export const PAGE_IDS = {
|
||||||
|
'/': 23, '/about': 31, '/service': 32, '/project': 36, '/contact': 48,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SPA_ROUTES = ['/', '/about', '/service', '/project', '/contact']
|
||||||
|
|
||||||
|
export const POST_ID_TO_SLUG = {
|
||||||
|
35: 'electro-mechanical-works',
|
||||||
|
3159: 'landscaping-fencing-works',
|
||||||
|
3160: 'metal-construction-fabrications',
|
||||||
|
3172: 'building-contracting-works',
|
||||||
|
3179: 'interior-fit-out-works',
|
||||||
|
3186: 'chiller-hot-cool-solutions',
|
||||||
|
3192: 'misting-solutions',
|
||||||
|
3199: 'adiabatic-pre-cooling-system',
|
||||||
|
3206: 'building-management-system',
|
||||||
|
}
|
||||||
16
src/css/dynamic.css
Normal file
16
src/css/dynamic.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
:root{--icon-font:"Font Awesome 6 Free";--fa-style-family:"Font Awesome 6 Free";}
|
||||||
|
.header-logo .logo img{width:260px;}
|
||||||
|
.th-menu-wrapper .mobile-logo img{width:180px;}
|
||||||
|
.th-menu-wrapper .mobile-logo{background-color:var(--brand-navy, #0a004c);}
|
||||||
|
.breadcumb-wrapper{background-repeat:no-repeat;background-position:center center;background-image:url('/wp-content/uploads/2024/07/breadcumb-bg.jpg');background-size:cover;}
|
||||||
|
.hero-inner{position:relative;background-color:var(--brand-dark, #101840);}
|
||||||
|
.hero-inner::before{content:'';position:absolute;inset:0;background:linear-gradient(90deg,rgba(0,0,0,0.7) 0%,rgba(0,0,0,0.3) 100%);z-index:1;pointer-events:none;}
|
||||||
|
.hero-inner .container{position:relative;z-index:2;}
|
||||||
|
.hero-style5 .sub-title2.sub{color:#fff !important;}
|
||||||
|
.hero-style5 .hero-title,.hero-style5 .hero-text{color:#fff !important;}
|
||||||
|
.slider-area .slider-arrow{display:inline-flex;align-items:center;justify-content:center;width:50px;height:50px;border-radius:50%;background:rgba(255,255,255,0.2);color:#fff;border:none;cursor:pointer;transition:0.3s;font-size:18px;}
|
||||||
|
.slider-area .slider-arrow:hover{background:var(--brand-primary, #FA2D39);}
|
||||||
|
.slider-area .slider-controller{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:40px;}
|
||||||
|
.slider-area .slider-pagination{display:flex;align-items:center;gap:6px;}
|
||||||
|
.slider-area .slider-pagination button{width:12px;height:12px;border-radius:50%;border:none;background:#999;opacity:0.4;cursor:pointer;padding:0;transition:0.3s;}
|
||||||
|
.slider-area .slider-pagination button.active{opacity:1;background:var(--brand-primary, #FA2D39);}
|
||||||
File diff suppressed because one or more lines are too long
5
src/css/variables.css
Normal file
5
src/css/variables.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
:root {
|
||||||
|
--brand-primary: #FA2D39;
|
||||||
|
--brand-dark: #101840;
|
||||||
|
--brand-navy: #0a004c;
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ export default function useLayoutInit() {
|
|||||||
// Destroy ALL existing swiper instances to prevent duplicates
|
// Destroy ALL existing swiper instances to prevent duplicates
|
||||||
document.querySelectorAll('.swiper').forEach(el => {
|
document.querySelectorAll('.swiper').forEach(el => {
|
||||||
if (el.swiper) {
|
if (el.swiper) {
|
||||||
try { el.swiper.destroy(true, true) } catch (e) {}
|
try { el.swiper.destroy(true, true) } catch (e) { console.warn('useLayoutInit: swiper destroy failed:', e) }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export default function useLayoutInit() {
|
|||||||
}
|
}
|
||||||
rafId = requestAnimationFrame(poll)
|
rafId = requestAnimationFrame(poll)
|
||||||
|
|
||||||
// Fallback timeout (8 seconds)
|
// Fallback timeout (3 seconds)
|
||||||
const fallback = setTimeout(() => {
|
const fallback = setTimeout(() => {
|
||||||
if (!destroyed) {
|
if (!destroyed) {
|
||||||
window.__layoutReady = true
|
window.__layoutReady = true
|
||||||
@@ -63,7 +63,7 @@ export default function useLayoutInit() {
|
|||||||
destroyed = true
|
destroyed = true
|
||||||
if (rafId) cancelAnimationFrame(rafId)
|
if (rafId) cancelAnimationFrame(rafId)
|
||||||
}
|
}
|
||||||
}, 8000)
|
}, 3000)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
destroyed = true
|
destroyed = true
|
||||||
|
|||||||
@@ -1,20 +1,6 @@
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import { SPA_ROUTES, POST_ID_TO_SLUG } from '../config'
|
||||||
const postIdToSlug = {
|
|
||||||
35: 'electro-mechanical-works',
|
|
||||||
3159: 'landscaping-fencing-works',
|
|
||||||
3160: 'metal-construction-fabrications',
|
|
||||||
3172: 'building-contracting-works',
|
|
||||||
3179: 'interior-fit-out-works',
|
|
||||||
3186: 'chiller-hot-cool-solutions',
|
|
||||||
3192: 'misting-solutions',
|
|
||||||
3199: 'adiabatic-pre-cooling-system',
|
|
||||||
3206: 'building-management-system',
|
|
||||||
}
|
|
||||||
|
|
||||||
// Known SPA routes
|
|
||||||
const spaRoutes = ['/', '/about', '/service', '/project', '/contact']
|
|
||||||
|
|
||||||
export function useNavFix(ref) {
|
export function useNavFix(ref) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -28,7 +14,7 @@ export function useNavFix(ref) {
|
|||||||
if (!link) return
|
if (!link) return
|
||||||
|
|
||||||
let href = link.getAttribute('href')
|
let href = link.getAttribute('href')
|
||||||
if (!href || href === '#' || href.startsWith('#')) return
|
if (!href || href.startsWith('#')) return
|
||||||
|
|
||||||
// External links (http/https) — open in new tab if different host
|
// External links (http/https) — open in new tab if different host
|
||||||
if (href.startsWith('http')) {
|
if (href.startsWith('http')) {
|
||||||
@@ -38,7 +24,7 @@ export function useNavFix(ref) {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
window.open(href, '_blank', 'noopener,noreferrer')
|
window.open(href, '_blank', 'noopener,noreferrer')
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) { console.warn('useNavFix: failed to parse URL:', href) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +37,7 @@ export function useNavFix(ref) {
|
|||||||
// Handle ?p= service links → SPA route
|
// Handle ?p= service links → SPA route
|
||||||
const serviceMatch = route.match(/service\?p=(\d+)/)
|
const serviceMatch = route.match(/service\?p=(\d+)/)
|
||||||
if (serviceMatch) {
|
if (serviceMatch) {
|
||||||
const slug = postIdToSlug[serviceMatch[1]]
|
const slug = POST_ID_TO_SLUG[serviceMatch[1]]
|
||||||
if (slug) {
|
if (slug) {
|
||||||
navigate('/service/' + slug)
|
navigate('/service/' + slug)
|
||||||
} else {
|
} else {
|
||||||
@@ -71,7 +57,7 @@ export function useNavFix(ref) {
|
|||||||
|
|
||||||
// If route matches known SPA routes or starts with /service/, navigate
|
// If route matches known SPA routes or starts with /service/, navigate
|
||||||
const normalizedRoute = '/' + route
|
const normalizedRoute = '/' + route
|
||||||
if (spaRoutes.includes(normalizedRoute) || normalizedRoute.startsWith('/service/')) {
|
if (SPA_ROUTES.includes(normalizedRoute) || normalizedRoute.startsWith('/service/')) {
|
||||||
navigate(normalizedRoute)
|
navigate(normalizedRoute)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
/* Reset - all styles come from theme CSS loaded in index.html */
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#root {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
import './css/variables.css'
|
||||||
import './css/kit.css'
|
import './css/kit.css'
|
||||||
import './css/theme.css'
|
import './css/theme.css'
|
||||||
import './css/layout.css'
|
import './css/layout.css'
|
||||||
import './css/pages.css'
|
import './css/pages.css'
|
||||||
import './css/services.css'
|
import './css/services.css'
|
||||||
|
import './css/dynamic.css'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useRef } from 'react'
|
|
||||||
import contentHtml from '../data/about-content.html?raw'
|
import contentHtml from '../data/about-content.html?raw'
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
import PageShell from '../components/PageShell'
|
||||||
|
|
||||||
export default function About() {
|
export default function About() {
|
||||||
const ref = useRef(null)
|
return <PageShell html={contentHtml} />
|
||||||
useNavFix(ref)
|
|
||||||
return <div ref={ref} dangerouslySetInnerHTML={{ __html: contentHtml }} />
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useRef } from 'react'
|
|
||||||
import contentHtml from '../data/contact-content.html?raw'
|
import contentHtml from '../data/contact-content.html?raw'
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
import PageShell from '../components/PageShell'
|
||||||
|
|
||||||
export default function Contact() {
|
export default function Contact() {
|
||||||
const ref = useRef(null)
|
return <PageShell html={contentHtml} />
|
||||||
useNavFix(ref)
|
|
||||||
return <div ref={ref} dangerouslySetInnerHTML={{ __html: contentHtml }} />
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useRef } from 'react'
|
|
||||||
import contentHtml from '../data/home-content.html?raw'
|
import contentHtml from '../data/home-content.html?raw'
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
import PageShell from '../components/PageShell'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const ref = useRef(null)
|
return <PageShell html={contentHtml} />
|
||||||
useNavFix(ref)
|
|
||||||
return <div ref={ref} dangerouslySetInnerHTML={{ __html: contentHtml }} />
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useRef } from 'react'
|
|
||||||
import contentHtml from '../data/project-content.html?raw'
|
import contentHtml from '../data/project-content.html?raw'
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
import PageShell from '../components/PageShell'
|
||||||
|
|
||||||
export default function Project() {
|
export default function Project() {
|
||||||
const ref = useRef(null)
|
return <PageShell html={contentHtml} />
|
||||||
useNavFix(ref)
|
|
||||||
return <div ref={ref} dangerouslySetInnerHTML={{ __html: contentHtml }} />
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,11 @@ import { useRef, useEffect } from 'react'
|
|||||||
import servicePages from '../data/servicePages'
|
import servicePages from '../data/servicePages'
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
import { useNavFix } from '../hooks/useNavFix'
|
||||||
|
|
||||||
const serviceSlugs = Object.keys(servicePages)
|
|
||||||
|
|
||||||
export default function ServiceDetail() {
|
export default function ServiceDetail() {
|
||||||
const { slug } = useParams()
|
const { slug } = useParams()
|
||||||
const ref = useRef(null)
|
const ref = useRef(null)
|
||||||
useNavFix(ref)
|
useNavFix(ref)
|
||||||
|
|
||||||
// Debug: log what we're looking up
|
|
||||||
console.log('ServiceDetail: slug=%s, available=%d keys, found=%s', slug, serviceSlugs.length, slug ? (slug in servicePages) : false)
|
|
||||||
|
|
||||||
const page = slug ? servicePages[slug] : null
|
const page = slug ? servicePages[slug] : null
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,5 +33,3 @@ export default function ServiceDetail() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { serviceSlugs }
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useRef } from 'react'
|
|
||||||
import contentHtml from '../data/service-content.html?raw'
|
import contentHtml from '../data/service-content.html?raw'
|
||||||
import { useNavFix } from '../hooks/useNavFix'
|
import PageShell from '../components/PageShell'
|
||||||
|
|
||||||
export default function Services() {
|
export default function Services() {
|
||||||
const ref = useRef(null)
|
return <PageShell html={contentHtml} />
|
||||||
useNavFix(ref)
|
|
||||||
return <div ref={ref} dangerouslySetInnerHTML={{ __html: contentHtml }} />
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user