- Convert Promist CTA from button (.promist-btn + arrow) to a plain image link (.promist-link) — no button chrome, no hover transform - Fix dead hamburger: React onClick handlers (idempotent add/remove of th-body-visible) on hamburger, close X, and overlay, so the drawer opens/closes regardless of the theme jQuery binding state - Vertically center hamburger + Profen + Promist in one row on mobile - Hide .menu-wrap SVG mask wings on mobile (they overlapped Promist) - Show Promist column on mobile (d-block d-lg-none d-xl-block) - All changes scoped to @media (max-width:767px) or the Promist link base styles; desktop (>=768px) untouched
Profen Engineering — React SPA
A React single-page application that recreates profeneng.com (WordPress + Elementor + Rakar theme) as a fast, portable frontend.
Architecture
src/
├── main.jsx Entry — imports CSS (variables → kit → theme → layout → pages → services → dynamic), mounts React
├── App.jsx Router (BrowserRouter, Suspense, ErrorBoundary, lazy-loaded pages, 404 route)
├── config.js Central config: WhatsApp phone, brand colors, PAGE_IDS, SPA_ROUTES, POST_ID_TO_SLUG, SERVICE_SIDEBAR_ITEMS, MENU_ITEM_IDS
├── components/
│ ├── Layout.jsx Shared layout (Header + Outlet + Footer + WhatsAppButton + preloader + page‑id body class)
│ ├── SafeHtml.jsx Parses HTML strings into React elements via html-react-parser (replaces dangerouslySetInnerHTML)
│ ├── ErrorBoundary.jsx Catches render crashes, shows Try Again button
│ ├── Loading.jsx Spinner for Suspense fallback
│ ├── WhatsAppButton.jsx Floating WhatsApp widget
│ └── sections/
│ ├── Header.jsx Proper React component with <Link> nav, active state from useLocation()
│ ├── Footer.jsx Proper React component with contact data, social links, service links
│ ├── Breadcrumb.jsx Reusable breadcrumb trail
│ ├── SectionTitle.jsx Section heading with subtitle & icon
│ ├── Button.jsx Reusable styled button
│ ├── PhoneWidget.jsx Phone call widget
│ ├── FeatureCards.jsx Feature card grid
│ ├── ContactForm.jsx "Get a Quote" form
│ ├── ContactInfo.jsx Address/phone/email display
│ ├── GoogleMap.jsx Embedded Google Maps iframe
│ ├── Divider.jsx Section divider
│ ├── ProjectFilterGrid.jsx Filterable project grid
│ ├── ServiceContentBlocks.jsx Renders structured service content (paragraphs, headings, lists)
│ ├── ServiceGridSection.jsx Service listing grid
│ ├── ServiceBreadcrumb.jsx Service detail breadcrumb
│ ├── ServiceImage.jsx Service detail featured image
│ ├── ServiceBody.jsx Service detail body (content blocks)
│ └── ServiceSidebar.jsx Service detail sidebar nav
├── pages/
│ ├── Home.jsx Page: / — uses SafeHtml (parses home-content.html)
│ ├── About.jsx Page: /about — uses SafeHtml (parses about-content.html)
│ ├── Services.jsx Page: /service — uses SafeHtml (parses service-content.html)
│ ├── ServiceDetail.jsx Page: /service/:slug — fully componentized, reads services.json
│ ├── Project.jsx Page: /project — fully componentized, uses ProjectFilterGrid
│ └── Contact.jsx Page: /contact — fully componentized, uses section components
├── hooks/
│ ├── useLayoutInit.js Re-initializes Elementor theme JS on route change (requestAnimationFrame polling)
│ ├── useNavFix.js Intercepts links inside a ref for proper SPA routing
│ └── useThemeAssets.js Injects <meta> and <link> tags (OG, canonical, favicon) on mount
├── data/ Content sources
│ ├── home-content.html Home page HTML content (parsed by SafeHtml)
│ ├── about-content.html About page HTML content (parsed by SafeHtml)
│ ├── service-content.html Services listing HTML content (parsed by SafeHtml)
│ └── services.json Structured data for 9 service detail pages (contentBlocks, images, metadata)
└── css/ Build-time CSS imports (all bundled into one index-*.css)
├── variables.css :root CSS custom properties for brand colors (imported first)
├── kit.css Elementor kit CSS (global styles)
├── theme.css Rakar theme overrides
├── layout.css Header (elementor-945) + Footer (elementor-1098 / elementor-128) CSS
├── pages.css Combined CSS for all 5 main pages
├── services.css Combined CSS for all 9 service detail pages
├── dynamic.css Dynamic overrides using var(--brand-*) with fallbacks
└── pages/ Individual page CSS files (development reference — edit these, then regenerate combined)
├── page-23.css Home page
├── page-31.css About page
├── page-32.css Services listing
├── page-36.css Project page
├── page-48.css Contact page
└── service-*.css Individual service detail pages (9 files)
Strategy: Hybrid React + WordPress Content
The app uses a hybrid approach:
- Componentized pages (Contact, Project, ServiceDetail) use proper React section components with structured data — no HTML strings.
- Transitional pages (Home, About, Services) render raw Elementor HTML from
src/data/*.htmlfiles, parsed at runtime byhtml-react-parserviaSafeHtml.jsx— nodangerouslySetInnerHTMLanywhere in the app. - Styling comes from two places:
- Build-time CSS imports (
src/css/*.css) — Elementor's page-specific CSS bundled by Vite - Runtime assets from
public/wp-content/— theme stylesheets, fonts, images
- Build-time CSS imports (
- Behavior comes from the original Rakar theme JS loaded via
<script>tags inindex.html - Theme re-initialization on route change is handled by
useLayoutInit.jsviarequestAnimationFramepolling
Why This Works
Elementor pages are self-contained: each page has a unique CSS class (.elementor-<postId>) and the HTML references it. By loading ALL page CSS at once and the correct page HTML per route, we get pixel-perfect WordPress output without running PHP.
Migration Path
The codebase is in active migration from "raw Elementor HTML" to "proper React components":
| Page | Approach | Status |
|---|---|---|
| Contact | Full React components (ContactForm, ContactInfo, GoogleMap) | ✅ Complete |
| Project | Full React components (ProjectFilterGrid) | ✅ Complete |
| ServiceDetail | Structured data from services.json + React components |
✅ Complete |
| Home | SafeHtml parsing home-content.html |
🔄 Transitional |
| About | SafeHtml parsing about-content.html |
🔄 Transitional |
| Services | SafeHtml parsing service-content.html |
🔄 Transitional |
Section Components & Data Flow
Componentized Pages (Contact, Project, ServiceDetail)
These pages compose reusable section components rather than rendering raw HTML:
Contact.jsx
├── Breadcrumb props: title, items[]
├── SectionTitle props: subtitle, heading, alignment
├── ContactInfo props: items[] ({ icon, label, value, link })
├── ContactForm props: heading
├── GoogleMap props: src, title
└── Divider
ServiceDetail.jsx
├── ServiceBreadcrumb props: title
├── ServiceImage props: src, srcSet, width, height
├── ServiceBody props: blocks[] (from services.json → contentBlocks)
└── ServiceSidebar props: activeSlug (renders SERVICE_SIDEBAR_ITEMS from config.js)
Project.jsx
└── ProjectFilterGrid (self-contained, reads project data from public/wp-content/)
All section components return null/empty gracefully when props are missing — no hard crashes.
SafeHtml Pages (Home, About, Services)
Home.jsx
└── SafeHtml
└── html-react-parser(require('home-content.html'))
└── useNavFix(ref) — intercepts <a> clicks for SPA navigation
About.jsx (same pattern)
Services.jsx (same pattern)
Central Configuration (config.js)
All magic values live in one file:
- Brand colors:
BRAND_PRIMARY,BRAND_DARK,BRAND_NAVY(from.envor defaults) - WhatsApp:
WHATSAPP_PHONE(from.env) - Routes:
SPA_ROUTES,PAGE_IDS(path → WordPress post ID) - Services:
POST_ID_TO_SLUG,SERVICE_SIDEBAR_ITEMS,MENU_ITEM_IDS
Key Fixes & Workarounds
| Issue | Solution |
|---|---|
| Elementor lazyload hides background images | Removed .e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded) CSS that applies background-image: none !important (was in theme.css — already cleaned) |
| Elfsight WhatsApp plugin crashes page | Replaced with custom WhatsAppButton.jsx component |
| Theme JS not found on route change | useLayoutInit.js polls with requestAnimationFrame until window.rakar_content_load_scripts is available |
| SPA links reload page | useNavFix.js intercepts same-site links, uses react-router-dom's useNavigate; Header nav uses <Link> components |
| Missing Elementor JS dependencies | Added webpack.runtime.min.js and frontend-modules.min.js to index.html |
| Waypoint plugin 404 | Created empty shim at public/wp-includes/js/waypoint.min.js |
Style Organization
CSS Import Order (in main.jsx)
import './css/variables.css' // 1. Brand color variables (first so everything else can use them)
import './css/kit.css' // 2. Elementor kit global styles
import './css/theme.css' // 3. Rakar theme overrides
import './css/layout.css' // 4. Header + Footer layout
import './css/pages.css' // 5. Main page styles
import './css/services.css' // 6. Service detail styles
import './css/dynamic.css' // 7. Dynamic overrides using var(--brand-*) with fallbacks
Order matters — variables.css is first so dynamic.css (which uses var(--brand-*)) resolves correctly.
Brand Color System
Colors are defined once in .env, flow through config.js, and land in CSS custom properties:
.env → config.js → variables.css
VITE_BRAND_PRIMARY → BRAND_PRIMARY → --brand-primary
VITE_BRAND_DARK → BRAND_DARK → --brand-dark
VITE_BRAND_NAVY → BRAND_NAVY → --brand-navy
dynamic.css uses these variables with fallbacks (e.g., var(--brand-primary, #FA2D39)) so it works even if variables.css is somehow absent.
Regenerating Combined CSS
After editing individual page files in src/css/pages/:
cat src/css/pages/page-*.css > src/css/pages.css
cat src/css/pages/service-*.css > src/css/services.css
Updating Content
HTML-based pages (Home, About, Services)
- Download updated HTML from WordPress
- Replace the corresponding file in
src/data/ - Update the CSS in
src/css/pages/if Elementor page CSS changed - Rebuild:
npm run build
Componentized pages (Contact, Project, ServiceDetail)
- Edit the relevant section component in
src/components/sections/ - For service detail content, edit
src/data/services.json - For contact data, edit
Contact.jsxdirectly - Rebuild:
npm run build
Brand Colors
Edit .env (or create from .env.example), then rebuild:
VITE_WHATSAPP_PHONE=971564148980
VITE_BRAND_PRIMARY=#FA2D39
VITE_BRAND_DARK=#101840
VITE_BRAND_NAVY=#0a004c
Setup & Development
cd react-app
npm install
npm run dev # Vite dev server (HMR enabled)
Build & Deploy
npm run build # Outputs to dist/ — 0 errors, 0 warnings
The site is served via a Python3 HTTP server on port 8011:
cd dist && python3 -m http.server 8011
No CI/CD pipeline is configured — deploy is manual.
Production File Structure
dist/
├── index.html 7.6 KB │ gzip: 2.1 KB
├── assets/index-*.css 74.0 KB │ gzip: 5.4 KB
├── assets/index-*.js 257.3 KB │ gzip: 80.9 KB (main bundle: router, components, CSS)
├── assets/Home-*.js 69.7 KB │ gzip: 10.8 KB
├── assets/SafeHtml-*.js 27.8 KB │ gzip: 10.1 KB (html-react-parser chunk)
├── assets/Services-*.js 29.8 KB │ gzip: 4.5 KB
├── assets/About-*.js 26.5 KB │ gzip: 3.9 KB
├── assets/ServiceDetail-*.js 25.1 KB │ gzip: 7.0 KB
├── assets/Contact-*.js 9.2 KB │ gzip: 2.3 KB
├── assets/Project-*.js 4.0 KB │ gzip: 1.5 KB
└── assets/Breadcrumb-*.js 0.6 KB │ gzip: 0.3 KB
All other assets (theme CSS/JS, Elementor assets, images) are served from public/wp-content/ and public/wp-includes/.
The build produces 0 errors, 0 warnings. If you see warnings, fix them before deploying.
Dependencies
| Package | Version | Purpose |
|---|---|---|
| react | ^19.2.6 | UI framework |
| react-dom | ^19.2.6 | DOM renderer |
| react-router-dom | ^7.17.0 | SPA routing |
| html-react-parser | ^6.1.4 | Safe HTML-to-React parsing |
| @fortawesome/fontawesome-free | ^7.3.0 | Icon library |
| vite | ^8.0.12 | Build tool |
| @vitejs/plugin-react | ^6.0.1 | Vite React plugin |