feat: add CTAButton component with customizable variants, sizes, and arrows
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions

This commit is contained in:
Najjar\NajjarV02
2026-05-21 12:57:56 +04:00
parent 79f7f9e579
commit 7458818c62
31 changed files with 2852 additions and 1023 deletions
+240 -29
View File
@@ -266,53 +266,264 @@ html {
::-webkit-scrollbar-thumb { background: rgba(136, 145, 199, 0.28); border-radius: var(--radius-sm); }
::-webkit-scrollbar-thumb:hover { background: rgba(39, 63, 148, 0.7); }
/* === Buttons === */
.btn {
/* === CTA Button System === */
/* Reusable across all sections — primary | secondary | ghost | link, sizes sm | md | lg.
Arrow handled via .cta-arrow capsule; data-arrow="up-right" rotates on hover, "right" translates X. */
.cta-btn {
position: relative;
display: inline-flex;
align-items: center;
gap: 0.625rem;
padding: 0.875rem 1.75rem;
gap: 0.65rem;
border-radius: 999px;
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 0.08em;
font-family: inherit;
font-weight: 800;
letter-spacing: 0.22em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
transition: transform 0.25s cubic-bezier(0.16,1,0.3,1), box-shadow 0.25s ease, background 0.25s ease, color 0.25s ease;
border: 1px solid transparent;
white-space: nowrap;
isolation: isolate;
overflow: hidden;
border: 1px solid transparent;
transition: transform 0.3s cubic-bezier(0.16,1,0.3,1), box-shadow 0.3s, background 0.3s, border-color 0.3s, color 0.3s;
-webkit-user-select: none;
user-select: none;
}
.btn:hover { transform: translateY(-1px); }
.btn-primary {
background: linear-gradient(135deg, #3a55c4 0%, #273F94 55%, #1a2e6e 100%);
color: #FBFBFD;
box-shadow: 0 8px 28px rgba(39, 63, 148, 0.45), inset 0 1px 0 rgba(222, 224, 240, 0.25);
.cta-btn:focus-visible {
outline: 2px solid #DEE0F0;
outline-offset: 3px;
}
.btn-primary:hover {
box-shadow: 0 12px 38px rgba(58, 85, 196, 0.6), inset 0 1px 0 rgba(222, 224, 240, 0.35);
.cta-btn:disabled,
.cta-btn[aria-disabled='true'] {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
}
.btn-ghost {
background: rgba(222, 224, 240, 0.04);
color: #FBFBFD;
border-color: rgba(199, 207, 230, 0.28);
backdrop-filter: blur(12px);
/* sizes */
.cta-sm { padding: 0.62rem 1.05rem; font-size: 0.64rem; letter-spacing: 0.22em; }
.cta-md { padding: 0.95rem 1.45rem; font-size: 0.74rem; }
.cta-lg { padding: 1.05rem 1.7rem; font-size: 0.8rem; letter-spacing: 0.24em; }
/* shared arrow capsule */
.cta-arrow {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: rgba(222, 224, 240, 0.12);
border: 1px solid rgba(222, 224, 240, 0.22);
color: currentColor;
transition: transform 0.35s, background 0.35s, border-color 0.35s;
flex-shrink: 0;
}
.btn-ghost:hover {
background: rgba(39, 63, 148, 0.16);
border-color: rgba(136, 145, 199, 0.6);
color: #DEE0F0;
.cta-sm .cta-arrow { width: 20px; height: 20px; }
.cta-md .cta-arrow { width: 24px; height: 24px; }
.cta-lg .cta-arrow { width: 26px; height: 26px; }
/* PRIMARY — filled gradient, strongest emphasis */
.cta-primary {
background: linear-gradient(135deg, #5a76e8 0%, #4a66d8 35%, #2f4cb4 70%, #273F94 100%);
color: #FFFFFF;
box-shadow:
0 24px 56px rgba(39, 63, 148, 0.55),
0 0 0 1px rgba(222, 224, 240, 0.22),
0 0 32px rgba(74, 102, 216, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.24);
}
.cta-primary::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(120deg, transparent 30%, rgba(255, 255, 255, 0.2) 50%, transparent 70%);
transform: translateX(-100%);
transition: transform 0.7s ease;
pointer-events: none;
}
.cta-primary:hover {
transform: translateY(-2px);
box-shadow:
0 30px 66px rgba(39, 63, 148, 0.7),
0 0 0 1px rgba(222, 224, 240, 0.32),
0 0 44px rgba(74, 102, 216, 0.55),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.cta-primary:hover::before { transform: translateX(100%); }
.cta-primary .cta-arrow {
background: rgba(255, 255, 255, 0.18);
border-color: rgba(255, 255, 255, 0.24);
}
.cta-primary:hover .cta-arrow {
background: rgba(255, 255, 255, 0.32);
border-color: rgba(255, 255, 255, 0.4);
}
.btn-outline {
/* SECONDARY — glassmorphism, supporting actions */
.cta-secondary {
border-color: rgba(222, 224, 240, 0.55);
background:
linear-gradient(135deg, rgba(58, 85, 196, 0.16), rgba(14, 13, 18, 0.55) 60%),
rgba(14, 13, 18, 0.55);
color: #FFFFFF;
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
box-shadow:
0 14px 32px rgba(0, 0, 0, 0.55),
inset 0 1px 0 rgba(222, 224, 240, 0.22),
0 0 0 1px rgba(58, 85, 196, 0.18);
}
.cta-secondary::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(120deg, transparent 30%, rgba(222, 224, 240, 0.16) 50%, transparent 70%);
transform: translateX(-100%);
transition: transform 0.7s ease;
pointer-events: none;
}
.cta-secondary:hover {
border-color: rgba(222, 224, 240, 0.82);
background:
linear-gradient(135deg, rgba(74, 102, 216, 0.3), rgba(20, 22, 38, 0.62) 60%),
rgba(20, 22, 38, 0.62);
transform: translateY(-2px);
box-shadow:
0 22px 46px rgba(0, 0, 0, 0.62),
inset 0 1px 0 rgba(222, 224, 240, 0.3),
0 0 0 1px rgba(58, 85, 196, 0.5),
0 0 32px rgba(58, 85, 196, 0.4);
}
.cta-secondary:hover::before { transform: translateX(100%); }
/* GHOST — outline, low-emphasis action */
.cta-ghost {
border-color: rgba(222, 224, 240, 0.28);
background: transparent;
color: #ECEEF6;
}
.cta-ghost:hover {
border-color: rgba(222, 224, 240, 0.6);
background: rgba(58, 85, 196, 0.14);
color: #FFFFFF;
transform: translateY(-2px);
}
.cta-ghost .cta-arrow {
background: rgba(222, 224, 240, 0.08);
border-color: rgba(222, 224, 240, 0.2);
}
.cta-ghost:hover .cta-arrow {
background: rgba(58, 85, 196, 0.22);
border-color: rgba(222, 224, 240, 0.4);
}
/* LINK — text-only for minor inline links */
.cta-link {
padding: 0.45rem 0;
border: none;
background: transparent;
color: #DEE0F0;
border-color: rgba(136, 145, 199, 0.55);
font-size: 0.72rem;
letter-spacing: 0.2em;
border-bottom: 1px solid transparent;
border-radius: 0;
white-space: nowrap;
font-weight: 700;
}
.cta-link:hover {
color: #FFFFFF;
border-bottom-color: rgba(222, 224, 240, 0.55);
transform: translateX(2px);
}
.cta-link .cta-arrow {
width: auto;
height: auto;
padding: 0;
background: transparent;
border: none;
}
.cta-link:hover .cta-arrow { transform: translate(3px, -1px); }
/* arrow motion per direction */
.cta-btn[data-arrow='up-right']:hover .cta-arrow { transform: rotate(45deg); }
.cta-btn[data-arrow='right']:hover .cta-arrow { transform: translateX(3px); }
.cta-link[data-arrow='up-right']:hover .cta-arrow,
.cta-link[data-arrow='right']:hover .cta-arrow { transform: translate(3px, -1px); }
/* full-width on small screens for primary CTA wrappers */
.cta-btn.cta-full {
width: 100%;
justify-content: space-between;
}
/* === Legacy .btn aliases — kept for backward compat, route to new system === */
.btn {
display: inline-flex;
align-items: center;
gap: 0.65rem;
padding: 0.95rem 1.45rem;
border-radius: 999px;
font-size: 0.74rem;
font-weight: 800;
letter-spacing: 0.22em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
border: 1px solid transparent;
white-space: nowrap;
transition: transform 0.3s cubic-bezier(0.16,1,0.3,1), box-shadow 0.3s, background 0.3s, color 0.3s, border-color 0.3s;
}
.btn:hover { transform: translateY(-2px); }
.btn-primary {
background: linear-gradient(135deg, #5a76e8 0%, #4a66d8 35%, #2f4cb4 70%, #273F94 100%);
color: #FFFFFF;
box-shadow:
0 24px 56px rgba(39, 63, 148, 0.55),
0 0 0 1px rgba(222, 224, 240, 0.22),
0 0 32px rgba(74, 102, 216, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.24);
}
.btn-primary:hover {
box-shadow:
0 30px 66px rgba(39, 63, 148, 0.7),
0 0 0 1px rgba(222, 224, 240, 0.32),
0 0 44px rgba(74, 102, 216, 0.55),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.btn-ghost {
background:
linear-gradient(135deg, rgba(58, 85, 196, 0.16), rgba(14, 13, 18, 0.55) 60%),
rgba(14, 13, 18, 0.55);
color: #FFFFFF;
border-color: rgba(222, 224, 240, 0.55);
backdrop-filter: blur(14px);
box-shadow:
0 14px 32px rgba(0, 0, 0, 0.55),
inset 0 1px 0 rgba(222, 224, 240, 0.22),
0 0 0 1px rgba(58, 85, 196, 0.18);
}
.btn-ghost:hover {
border-color: rgba(222, 224, 240, 0.82);
background:
linear-gradient(135deg, rgba(74, 102, 216, 0.3), rgba(20, 22, 38, 0.62) 60%),
rgba(20, 22, 38, 0.62);
color: #FFFFFF;
box-shadow:
0 22px 46px rgba(0, 0, 0, 0.62),
inset 0 1px 0 rgba(222, 224, 240, 0.3),
0 0 0 1px rgba(58, 85, 196, 0.5),
0 0 32px rgba(58, 85, 196, 0.4);
}
.btn-outline {
background: transparent;
color: #ECEEF6;
border-color: rgba(222, 224, 240, 0.28);
}
.btn-outline:hover {
background: rgba(39, 63, 148, 0.14);
background: rgba(58, 85, 196, 0.14);
border-color: rgba(222, 224, 240, 0.6);
color: #FFFFFF;
}
/* === Layout helpers === */
+12 -22
View File
@@ -4,7 +4,7 @@ import { FooterAndContact } from '@/components/FooterAndContact';
import { Hero3DRobotics } from '@/components/robotics/Hero3DRobotics';
import { BrandShowcase } from '@/components/robotics/BrandShowcase';
import { CategoryShowcaseScroll } from '@/components/robotics/CategoryShowcaseScroll';
import { RobotProductCard } from '@/components/robotics/RobotProductCard';
import { FeaturedRobotsShowcase } from '@/components/robotics/FeaturedRobotsShowcase';
import { IndustryUseCases } from '@/components/robotics/IndustryUseCases';
import { DemoCTA } from '@/components/robotics/DemoCTA';
import { ConfigureCTA } from '@/components/robotics/ConfigureCTA';
@@ -83,7 +83,7 @@ export default function HomePage() {
</div>
</MotionSection>
<MotionSection style={{ padding: 'clamp(1.5rem, 3vw, 2.5rem) 0 clamp(3rem, 6vw, 5rem)' }} id="categories">
<MotionSection style={{ padding: 'clamp(3rem, 6vw, 5rem) 0' }} id="categories">
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
<SectionHeading
eyebrow="Robot categories"
@@ -97,21 +97,11 @@ export default function HomePage() {
<MotionSection style={{ padding: 'clamp(3rem, 6vw, 5rem) 0' }} id="featured">
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
<SectionHeading
eyebrow="Featured robots"
title="Available exclusively in the UAE through YS Lootah Robotics."
description="A live snapshot of robots ready to demo at our Dubai showroom or deploy at your venue. Request a UAE quotation or book a live demo."
eyebrow="Dubai showroom"
title="Robots you can see, test, and deploy now."
description="Four exhibits on our Dubai showroom floor — step in, request a live demo, or ask for a price."
/>
<div
style={{
display: 'grid',
gap: '1.25rem',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(280px, 100%), 1fr))',
}}
>
{FEATURED_ROBOTS.map((r, idx) => (
<RobotProductCard key={r.id} robot={r} priority={idx === 0} />
))}
</div>
<FeaturedRobotsShowcase robots={FEATURED_ROBOTS} />
</div>
</MotionSection>
@@ -124,9 +114,9 @@ export default function HomePage() {
<MotionSection style={{ padding: 'clamp(3rem, 6vw, 5rem) 0' }} id="services">
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
<SectionHeading
eyebrow="Our solutions"
title="Intelligent robotics across the full lifecycle."
description="From autonomous robotics and intelligent automation to motion programming, SDKs, and remote diagnostics a complete robotics partner for UAE businesses."
eyebrow="UAE deployment"
title="Real robots, deployed and supported across the UAE."
description="From Dubai showroom demo to live venue deployment — procurement, configuration, installation, training, and ongoing support, handled by one local team."
/>
<ServicesGrid />
</div>
@@ -147,9 +137,9 @@ export default function HomePage() {
<MotionSection style={{ padding: 'clamp(3rem, 6vw, 5rem) 0' }} id="how-it-works">
<div className="container-wide" style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
<SectionHeading
eyebrow="How we work"
title="From inquiry to live deployment."
description="Three steps from your first message to a robot running in your venue fully supported by our Dubai team."
eyebrow="Deployment timeline"
title="Five steps from procurement to support."
description="The robotics implementation journey — what changes at every stage, in order."
/>
<HowItWorks />
</div>
+1 -4
View File
@@ -177,10 +177,7 @@ export default async function RobotDetailPage({ params }: { params: Promise<Para
</MotionSection>
<MotionSection>
<ConfigureCTA
title={`Configure ${robot.name} for your business.`}
description="Choose persona, attire, color, and accessories visualize your deployment before requesting a quotation."
/>
<ConfigureCTA />
</MotionSection>
{related.length > 0 && (