translation

This commit is contained in:
2026-01-13 03:58:08 +03:00
parent 6987f87a92
commit 3e82aab933
14 changed files with 2049 additions and 1326 deletions

View File

@ -1,9 +1,12 @@
import React, { useEffect, useState, useRef } from "react";
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
export default function EngineeringHeroFlowbite() {
const { t, i18n } = useTranslation();
const defaultConfig = {
main_title: "شريكك الهندسي لتنفيذ وإدارة المشاريع باحتراف",
main_title: "عندما تطلب الرؤية مستشارًا, ويحتاج المخطط منفذًا استراتيجيًا…نكون القرار بالقيادة من الرؤية حتى التسليم",
subtitle:
"حلول متكاملة تشمل التصميم، التنفيذ، التشغيل، والصيانة\nللمشاريع الصناعية والمدنية، وفق أحدث المعايير والتقنيات",
primary_color: "#e67e22",
@ -15,7 +18,11 @@ export default function EngineeringHeroFlowbite() {
font_size: 16,
};
const [config, setConfig] = useState(defaultConfig);
const [config, setConfig] = useState({
...defaultConfig,
main_title: t ? t('hero.main_title') : defaultConfig.main_title,
subtitle: t ? t('hero.subtitle') : defaultConfig.subtitle,
});
const [isMounted, setIsMounted] = useState(false);
const mainTitleRef = useRef(null);
@ -33,10 +40,32 @@ export default function EngineeringHeroFlowbite() {
}, []);
useEffect(() => {
const t = setTimeout(() => setIsMounted(true), 60);
return () => clearTimeout(t);
const tId = setTimeout(() => setIsMounted(true), 60);
return () => clearTimeout(tId);
}, []);
useEffect(() => {
const updateFromI18n = () => {
const lang = i18n.language || 'en';
let main = t('hero.main_title');
if (lang && lang.startsWith('en')) {
main = main.replace(/[—–]/g, ' - ');
}
setConfig((c) => ({
...c,
main_title: main,
subtitle: t('hero.subtitle'),
}));
document.documentElement.lang = lang;
document.documentElement.dir = lang.startsWith('ar') ? 'rtl' : 'ltr';
};
updateFromI18n();
i18n.on && i18n.on('languageChanged', updateFromI18n);
return () => {
i18n.off && i18n.off('languageChanged', updateFromI18n);
};
}, [i18n, t]);
useEffect(() => {
const main = mainTitleRef.current;
const sub = subtitleRef.current;
@ -45,17 +74,17 @@ export default function EngineeringHeroFlowbite() {
const baseSize = config.font_size || defaultConfig.font_size;
const applyStyles = () => {
const lang = i18n.language || 'en';
const isArabic = lang.startsWith('ar');
const width = typeof window !== "undefined" ? window.innerWidth : 1024;
// Make the *base font size* responsive: smaller on small screens
// fine-grained breakpoints so we avoid abrupt jumps
const responsiveBase = width <= 400 ? baseSize * 0.78 : width <= 640 ? baseSize * 0.88 : width <= 1024 ? baseSize * 0.96 : baseSize;
// multipliers: smaller on small screens
const headingMultiplier = width <= 640 ? 3.0 : 3.8;
const headingMultiplier = width <= 640 ? 2.6 : 3.2;
const subtitleMultiplier = width <= 640 ? 0.95 : 1.1;
// expose base to CSS (some rules reference --base)
const mainText = (config.main_title || defaultConfig.main_title).replace(/\s+/g, " ").trim();
const mainTextLength = mainText.length;
const lengthFactor = mainTextLength > 100 ? 0.78 : mainTextLength > 70 ? 0.86 : 1.0;
const maxFont = 68;
const minFont = Math.max( Math.round(responsiveBase * 1.6), 14 );
const root = document.documentElement;
root.style.setProperty('--base', `${responsiveBase}px`);
@ -71,13 +100,17 @@ export default function EngineeringHeroFlowbite() {
.slice(1)
.join("<br/>")}</span>`;
}
main.style.fontFamily = font;
// use responsiveBase so the font becomes smaller on small screens
main.style.fontSize = `${responsiveBase * headingMultiplier}px`;
const computedSize = Math.min(Math.max(Math.round(responsiveBase * headingMultiplier * lengthFactor), minFont), maxFont);
main.style.fontSize = `${computedSize}px`;
main.style.color = config.text_color || defaultConfig.text_color;
main.style.fontWeight = 800;
main.style.textAlign = "right";
main.style.textAlign = isArabic ? "right" : "left";
main.style.whiteSpace = "normal";
main.style.wordBreak = "break-word";
main.style.overflowWrap = "anywhere";
main.style.direction = isArabic ? "rtl" : "ltr";
main.style.display = "block";
}
if (sub) {
@ -86,107 +119,28 @@ export default function EngineeringHeroFlowbite() {
.map((s) => `<div>${s.trim()}</div>`)
.join("");
sub.style.fontFamily = font;
sub.style.fontSize = `${responsiveBase * subtitleMultiplier}px`;
const subSize = Math.round(responsiveBase * subtitleMultiplier);
sub.style.fontSize = `${subSize}px`;
sub.style.color = config.text_color || defaultConfig.text_color;
sub.style.textAlign = "right";
sub.style.textAlign = isArabic ? "right" : "left";
sub.style.maxWidth = "800px";
sub.style.whiteSpace = "normal";
sub.style.wordBreak = "break-word";
sub.style.overflowWrap = "anywhere";
sub.style.direction = isArabic ? "rtl" : "ltr";
sub.style.display = "block";
}
// keep CSS variables synced
root.style.setProperty("--ehb-primary", config.primary_color || defaultConfig.primary_color);
root.style.setProperty("--ehb-background", config.background_color || defaultConfig.background_color);
root.style.setProperty("--ehb-surface", config.secondary_surface || defaultConfig.secondary_surface);
root.style.setProperty("--ehb-action", config.secondary_action || defaultConfig.secondary_action);
};
// initial apply
applyStyles();
// update on resize so that small-screen changes reactively apply
window.addEventListener("resize", applyStyles);
return () => window.removeEventListener("resize", applyStyles);
}, [config]);
useEffect(() => {
const element = {
defaultConfig,
onConfigChange: async (newCfg) => {
setConfig((c) => ({ ...c, ...newCfg }));
},
mapToCapabilities: (cfg) => ({
recolorables: [
{
get: () => cfg.background_color || defaultConfig.background_color,
set: (value) => {
cfg.background_color = value;
window?.elementSdk?.setConfig?.({ background_color: value });
setConfig({ ...cfg });
},
},
{
get: () => cfg.secondary_surface || defaultConfig.secondary_surface,
set: (value) => {
cfg.secondary_surface = value;
window?.elementSdk?.setConfig?.({ secondary_surface: value });
setConfig({ ...cfg });
},
},
{
get: () => cfg.text_color || defaultConfig.text_color,
set: (value) => {
cfg.text_color = value;
window?.elementSdk?.setConfig?.({ text_color: value });
setConfig({ ...cfg });
},
},
{
get: () => cfg.primary_color || defaultConfig.primary_color,
set: (value) => {
cfg.primary_color = value;
window?.elementSdk?.setConfig?.({ primary_color: value });
setConfig({ ...cfg });
},
},
{
get: () => cfg.secondary_action || defaultConfig.secondary_action,
set: (value) => {
cfg.secondary_action = value;
window?.elementSdk?.setConfig?.({ secondary_action: value });
setConfig({ ...cfg });
},
},
],
borderables: [],
fontEditable: {
get: () => cfg.font_family || defaultConfig.font_family,
set: (value) => {
cfg.font_family = value;
window?.elementSdk?.setConfig?.({ font_family: value });
setConfig({ ...cfg });
},
},
fontSizeable: {
get: () => cfg.font_size || defaultConfig.font_size,
set: (value) => {
cfg.font_size = value;
window?.elementSdk?.setConfig?.({ font_size: value });
setConfig({ ...cfg });
},
},
}),
mapToEditPanelValues: (cfg) =>
new Map([
["main_title", cfg.main_title || defaultConfig.main_title],
["subtitle", cfg.subtitle || defaultConfig.subtitle],
]),
};
if (window?.elementSdk?.init) {
try {
window.elementSdk.init(element);
} catch (e) {}
}
}, []);
}, [config, i18n.language]);
const goToDepartments = (e) => {
e && e.preventDefault && e.preventDefault();
@ -198,11 +152,11 @@ export default function EngineeringHeroFlowbite() {
}
};
const OrangeActionButton = ({ onClick, children }) => {
const OrangeActionButton = ({ onClick }) => {
return (
<StyledWrapper>
<button className="button" onClick={onClick} aria-label="تعرف على أقسامنا">
{children}
<button className="button" onClick={onClick} aria-label={t('hero.action')}>
{t('hero.action')}
<svg className="icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
<path fillRule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm4.28 10.28a.75.75 0 000-1.06l-3-3a.75.75 0 10-1.06 1.06l1.72 1.72H8.25a.75.75 0 000 1.5h5.69l-1.72 1.72a.75.75 0 101.06 1.06l3-3z" clipRule="evenodd" />
</svg>
@ -211,18 +165,18 @@ export default function EngineeringHeroFlowbite() {
);
};
const isArabic = i18n.language && i18n.language.startsWith('ar');
return (
<div dir="rtl" className="h-screen w-full overflow-hidden content-container" style={{ background: "transparent" }}>
<div dir={isArabic ? "rtl" : "ltr"} className="h-screen w-full overflow-hidden content-container" style={{ background: "transparent" }}>
<style>{`
:root { --ehb-primary: #e67e22; --ehb-background: #000000; --ehb-surface: #95a5a6; --ehb-action: #34495e; --base: 16px }
.hero-section{position:relative;width:100%;height:100%;overflow:hidden}
.hero-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom, rgba(0,0,0,0.65), rgba(0,0,0,0.35));z-index:3}
/* use clamp for padding so layout breathes on small screens */
.hero-layout{position:relative;z-index:10;height:100%;display:flex;align-items:center;justify-content:space-between;padding:clamp(12px,4vw,40px);gap:2rem;direction:ltr;flex-direction:row}
.hero-layout.layout-ltr{flex-direction:row-reverse}
.hero-left{flex:1;display:flex;align-items:center;justify-content:flex-start;padding:20px;position:relative;flex-direction:column}
/* BIG IMAGE: slide-in slower, with delay */
.hero-left img{ max-width:100%; height:auto; display:block; transform: translateX(-20px) scale(0.995); opacity:1; will-change: transform, opacity; }
.hero-section.is-mounted .hero-left img{
transform: translateX(-120px) scale(0.96);
@ -234,8 +188,6 @@ export default function EngineeringHeroFlowbite() {
65% { transform: translateX(18px) scale(1.02); opacity: 1; filter: blur(0); }
100% { transform: translateX(0) scale(1); opacity: 1; }
}
/* partner bubbles - pop slower and float slower (made slightly larger and lighter background) */
.partner-logos{display:flex;gap:18px;margin-top:20px;align-items:flex-end;justify-content:flex-start}
.partner-bubble{
width:132px;height:132px;border-radius:9999px;background:linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.06));display:flex;align-items:center;justify-content:center;overflow:hidden;box-shadow:0 12px 36px rgba(0,0,0,0.45);backdrop-filter: blur(4px);transition:transform 420ms cubic-bezier(.2,.9,.2,1),box-shadow 420ms;
@ -251,16 +203,12 @@ export default function EngineeringHeroFlowbite() {
}
.partner-bubble img{max-width:78%;max-height:78%;object-fit:contain;filter:grayscale(0.06) saturate(0.95)}
.partner-bubble:hover{transform:translateY(-10px) scale(1.08);box-shadow:0 34px 72px rgba(0,0,0,0.55)}
@keyframes popIn {
0% { transform: translateY(30px) scale(0.6); opacity: 0; filter: blur(2px); }
65% { transform: translateY(-10px) scale(1.06); opacity: 1; filter: blur(0); }
100% { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes floatBubble{0%{transform:translateY(0) scale(1)}25%{transform:translateY(-10px) scale(1.03)}50%{transform:translateY(-24px) scale(1.07)}75%{transform:translateY(-10px) scale(1.03)}100%{transform:translateY(0) scale(1)}}
/* partner strip */
.partner-strip-wrap{position:fixed;left:0;bottom:14px;z-index:30;pointer-events:auto;width:100%;display:flex;justify-content:flex-start;padding-left:40px;padding-right:40px}
.partner-strip{backface-visibility:hidden;transform-style:preserve-3d;perspective:1000px;display:flex;align-items:center;gap:18px;padding:12px 20px;border-radius:14px;box-shadow:0 18px 40px rgba(0,0,0,0.55);
background:linear-gradient(90deg, rgba(6,23,58,0.98) 0%, rgba(30,50,90,0.9) 35%, rgba(230,126,34,0.98) 100%);
@ -273,11 +221,9 @@ export default function EngineeringHeroFlowbite() {
70%{transform:translateX(12px) translateZ(6px) rotateX(2deg) skewY(-1deg) scale(1.01);opacity:1}
100%{transform:translateX(0) translateZ(10px) rotateX(0deg) skewY(0deg) scale(1);opacity:1}
}
.partner-item{display:flex;align-items:center;justify-content:center;padding:6px;border-radius:10px;min-width:84px;min-height:48px;background:rgba(255,255,255,0.04);backdrop-filter:blur(4px);box-shadow:0 6px 18px rgba(0,0,0,0.45);transition:transform 400ms cubic-bezier(.2,.9,.2,1),box-shadow 400ms}
.partner-item img{max-height:36px;max-width:140px;object-fit:contain;display:block;filter:grayscale(0.06) saturate(0.95)}
.partner-item:hover{transform:translateY(-6px) translateZ(18px) rotateX(6deg);box-shadow:0 30px 50px rgba(0,0,0,0.55)}
@media (max-width: 1024px){
.partner-strip{padding:10px 14px;border-radius:12px}
.partner-item{min-width:72px;min-height:44px}
@ -288,19 +234,15 @@ export default function EngineeringHeroFlowbite() {
.partner-strip{gap:10px;padding:8px;border-radius:10px}
.partner-item img{max-height:24px}
.partner-logos{gap:12px}
/* make the partner bubbles slightly smaller (already present) */
.partner-bubble{width:100px;height:100px}
/* reduce the three bubble images a bit more on small screens only */
.partner-bubble img{max-width:64%;max-height:64%}
/* reduce spacing for smaller screens */
.hero-layout{gap:1rem}
}
/* HERO TEXT: slide from right (slower) */
.hero-content{flex:1;z-index:15;display:flex;flex-direction:column;align-items:flex-end;justify-content:center;padding:clamp(12px,3.5vw,40px)}
.hero-content{flex:1;z-index:15;display:flex;flex-direction:column;justify-content:center;padding:clamp(12px,3.5vw,40px)}
.hero-content.content-rtl{align-items:flex-end}
.hero-content.content-ltr{align-items:flex-start}
.hero-title{ opacity:1; transform: translateX(0) translateY(0) scale(1); will-change: transform, opacity; }
.hero-subtitle{ opacity:1; transform: translateX(0) translateY(0) scale(1); will-change: transform, opacity; }
.hero-section.is-mounted .hero-title{
opacity:0;
transform: translateX(120px) translateY(10px) scale(0.995);
@ -316,16 +258,15 @@ export default function EngineeringHeroFlowbite() {
65% { transform: translateX(-8px) translateY(-8px) scale(1.02); opacity:1; filter: blur(0); }
100% { opacity:1; transform: translateX(0) translateY(0) scale(1); }
}
@media (max-width: 768px){
.hero-layout{flex-direction:column;align-items:center;text-align:center}
.hero-content{align-items:center}
/* fallback if JS hasn't run yet: use --base variable */
.hero-title{font-size:calc(var(--base,16px) * 2.0) !important}
.hero-subtitle{font-size:calc(var(--base,16px) * 1.0) !important}
.hero-layout.layout-ltr{flex-direction:column;align-items:center;text-align:center}
.hero-content{align-items:center;padding-inline:16px}
.hero-content.content-rtl, .hero-content.content-ltr { align-items: center !important }
.hero-title{font-size:calc(var(--base,16px) * 2.0) !important; text-align:center !important}
.hero-subtitle{font-size:calc(var(--base,16px) * 1.0) !important; text-align:center !important}
.action-wrapper{align-self:center !important}
}
/* BUTTON: slower pop-in */
.button {
position: relative;
transition: all 0.3s ease-in-out;
@ -348,19 +289,16 @@ export default function EngineeringHeroFlowbite() {
transform: translateY(0) scale(1);
opacity: 1;
}
.hero-section.is-mounted .button{
transform: scale(0.6);
opacity: 0;
animation: popIn 900ms cubic-bezier(.2,.9,.2,1) 760ms both;
will-change: transform, opacity;
}
.icon { width: 24px; height: 24px; transition: all 0.3s ease-in-out; }
.button:hover { transform: scale(1.05); border-color: #fff9; }
.button:hover .icon { transform: translate(4px); }
.button:hover::before { animation: shine 1.5s ease-out infinite; }
.button::before {
content: "";
position: absolute;
@ -376,13 +314,11 @@ export default function EngineeringHeroFlowbite() {
left: -100px;
opacity: 0.6;
}
@keyframes popIn {
0% { transform: translateY(30px) scale(0.6); opacity: 0; filter: blur(2px); }
65% { transform: translateY(-10px) scale(1.06); opacity: 1; filter: blur(0); }
100% { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes shine {
0% { left: -100px; }
60% { left: 100%; }
@ -392,10 +328,9 @@ export default function EngineeringHeroFlowbite() {
<div className={`hero-section ${isMounted ? "is-mounted" : ""}`}>
<div className="hero-overlay" />
<div className="hero-layout">
<div className={`hero-layout ${isArabic ? '' : 'layout-ltr'}`}>
<div className="hero-left">
<img src="src/assets/REXNT.png" alt="REXNT" />
<div className="partner-logos" aria-hidden>
<div className="partner-bubble" style={{ animationDelay: "0ms" }} aria-label="شريك TPS">
<img src="src/assets/TPS-logo.png" alt="TPS" />
@ -409,17 +344,15 @@ export default function EngineeringHeroFlowbite() {
</div>
</div>
<div className="hero-content">
<h1 ref={mainTitleRef} className="hero-title text-white text-right" style={{ fontWeight: 800, marginBottom: 16, textShadow: "2px 2px 10px rgba(0,0,0,0.6)" }} />
<p ref={subtitleRef} className="hero-subtitle text-white text-right" style={{ maxWidth: 800, textShadow: "1px 1px 6px rgba(0,0,0,0.45)", lineHeight: 1.6 }} />
<div style={{ marginTop: 18 }}>
<OrangeActionButton onClick={goToDepartments}>تعرف على أقسامنا</OrangeActionButton>
<div className={`hero-content ${isArabic ? 'content-rtl' : 'content-ltr'}`}>
<h1 ref={mainTitleRef} className="hero-title text-white" style={{ fontWeight: 800, marginBottom: 16, textShadow: "2px 2px 10px rgba(0,0,0,0.6)" }} />
<p ref={subtitleRef} className="hero-subtitle text-white" style={{ maxWidth: 800, textShadow: "1px 1px 6px rgba(0,0,0,0.45)", lineHeight: 1.6 }} />
<div className="action-wrapper" style={{ marginTop: 18 }}>
<OrangeActionButton onClick={goToDepartments} />
</div>
</div>
</div>
</div>
</div>
);
}