Added new header and product dropable

This commit is contained in:
Asgarsk 2024-11-28 18:39:11 +05:30
commit 1844fb8b45
207 changed files with 13638 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# Use official Node.js image as the base image
FROM node:20-alpine
# Set the working directory inside the container
WORKDIR /app
# Copy package.json and package-lock.json first to leverage Docker cache for npm install
COPY package*.json ./
# Install the application dependencies
RUN npm install
# Copy the rest of the application files into the container
COPY . .
# Expose port 5173, which Vite uses by default
EXPOSE 5173
# Run the application with `--host 0.0.0.0` to make it accessible externally
CMD ["npm", "run", "dev"]

112
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,112 @@
pipeline {
agent any
environment {
DOCKER_IMAGE = 'jassimsm/tech4biz-reactjs'
DOCKER_TAG = 'latest'
DOCKER_USERNAME = "jassimsm"
DOCKER_PASSWORD = "dckr_pat_c9HutePXaqreGfKi48H0WPWWfBs"
SSH_CREDENTIALS = 'tech4biz-demo'
REMOTE_SERVER = 'ubuntu@160.187.167.157'
REMOTE_WORKSPACE = '/home/ubuntu'
GIT_CREDENTIALS = 'git-cred'
}
stages {
stage('Add Host Key') {
steps {
script {
sshagent(credentials: [SSH_CREDENTIALS]) {
sh '''
mkdir -p ~/.ssh
ssh-keyscan -H 160.187.167.157 >> ~/.ssh/known_hosts
'''
}
}
}
}
stage('Checkout and Install Dependencies on Remote Server') {
steps {
script {
withCredentials([usernamePassword(credentialsId: GIT_CREDENTIALS, usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {
sshagent(credentials: [SSH_CREDENTIALS]) {
sh '''
ssh ${REMOTE_SERVER} "mkdir -p ${REMOTE_WORKSPACE} && cd ${REMOTE_WORKSPACE} && rm -rf Tech4Biz-Website && \
git clone https://${GIT_USERNAME}:${GIT_PASSWORD}@git.tech4biz.wiki/Tech4Biz-Services/Tech4Biz-Website.git && \
cd Tech4Biz-Website && git checkout main"
'''
}
}
}
}
}
stage('Build Docker Image on Remote Server') {
steps {
script {
try {
sshagent(credentials: [SSH_CREDENTIALS]) {
sh '''
ssh ${REMOTE_SERVER} "cd ${REMOTE_WORKSPACE}/Tech4Biz-Website && docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} ."
'''
}
} catch (Exception e) {
error "Failed to build Docker image on remote server: ${e.message}"
}
}
}
}
stage('Push Docker Image from Remote Server') {
steps {
script {
try {
sshagent(credentials: [SSH_CREDENTIALS]) {
sh '''
ssh ${REMOTE_SERVER} "echo ${DOCKER_PASSWORD} | docker login -u ${DOCKER_USERNAME} --password-stdin && docker push ${DOCKER_IMAGE}:${DOCKER_TAG}"
'''
}
} catch (Exception e) {
error "Failed to push Docker image from remote server: ${e.message}"
}
}
}
}
stage('Deploy') {
steps {
script {
sshagent(credentials: [SSH_CREDENTIALS]) {
sh '''
ssh ${REMOTE_SERVER} "cd ${REMOTE_WORKSPACE}/Tech4Biz-Website && \
docker pull ${DOCKER_IMAGE}:${DOCKER_TAG} && \
docker stop tech4biz-container || true && \
docker rm tech4biz-container || true && \
docker run -d -p 5173:5173 --name tech4biz-container ${DOCKER_IMAGE}:${DOCKER_TAG}"
'''
}
}
}
}
stage('Update Nginx Configuration') {
steps {
script {
sshagent(credentials: [SSH_CREDENTIALS]) {
sh '''
ssh ${REMOTE_SERVER} "sudo nginx -t && sudo systemctl reload nginx"
'''
}
}
}
}
}
post {
always {
cleanWs()
}
}
}

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0yxzB1wr+alBS
BI5uWzp/u4/V9jkt/QFPMC2MLWyanemD50hDBRs0lZxmiYoLHqUDWZNPg/jdSj8d
VV+bcjdpfIH8s95TewTovG/szuGLZpu33DFSi/WB/1mulS2qflW/5A4h/3XgrgU6
Zze1sEvGBHEzI/obBkt/pebmt0AUruYlUonQW23JWLaLCsjTd9kMVGpqG8mRreaH
N3sYCEMKa3flyucToEA32OYXLkuQgmgs5txcdGXQp/0ELstoxzvTNdJcCyBSZ2V8
5JzR/yt58MXjH6xcHq3VYEOqvGOmuGmbZxa6QfevGml7ivO0FWjHneSnnCFm5kKs
rehy+uZNAgMBAAECggEAQrABJYca6QlyIy7HEvHXvDIFNlkiclI232zYADLpMoc5
EYyIXSBPDrB5BmGJZ9yf5YJvEg+OBqJScZGCOKaCxghFMl9ujMB96RNI/cK581+f
5Mv17YruvkrgW8NvGsqK+sM2a1f9tSi7iPV12TD0YlBVKRYGNV23D5i0eTnzCy1x
JruG5088zPnLoDy8T9WRTfYEnqXFxQscsTyDF9+2rZ+Vyr2YHxIt55w7IIrHFiUR
OczY4UJhmMIF0grddHNaTrjxtjAhCR+ysKq+qLs8QLZf2uXz3RawIohe7Rbl47D0
E3R6fD3AeeLmc3wokz40s8RO8BV0ce6iWiH56i8uiQKBgQDq+NXy2vUXLM3CTYHB
onUmJbWybmyN4g7aJLHcoPzhVonr5e/cToh/uWqmDGcQlo7iNS9TZh23VdpaUbn0
ZEm2y0GhPA9K1WJl20F+NQT8/HPF4pdBdAr8fUSjDpqi61+AYTr0W/pGD1pFp60S
H+kxd/BjrbPEKZVR39Kn9pMJ7wKBgQDE+Q4T02IvmJjXdOqjz1SnC5CQfzpb1AqJ
3xFGZTlFuy9uXY+9I5KVlfssMD8MXv+UzjQruk0/W8dnDdxvf35xi3z8zIQSDJxU
frJQB/qH/xoVnPgBe9eoAxus68p16ymONqEnNfjwmGhjeADpcV22uqVn2lMP9gN7
sGiVhWU/gwKBgQDqs/9RA3DI41HrgWvYtKN7pTMmtbHszx6yuvCGRrARjVVasmSy
lCc4HUbv8XeJVDoIrcNF9Lw+Ap2Glhe+i+Ytlj1KBinoP9h3kViL7f27jZc+1CTt
ljHbHm1OyimgDqdoHra6mp0VGgS7is8PSZyucvVFO55SlI64J2/NojghNQKBgH8I
CFYs65O6nEfH9VNz8SpSQQePpfl3BNzp6eA2g/s+v1Y0LPFUMcMbGQPkkaTO15IG
cosI+ay06iLCQ7n8xXVA+ninBT4GuAOeOi13F9IBabcqpp9+WRTX/E9HOilWYlR/
UutQ2Z2BDUGpMR1cqY2hTe9uVEdk59YrbSeRAj01AoGAU34mB2IROZEytgHwpcde
upFIEizd80UVemoKU8pF2/9XgXDTHjSC9c4NF4WSmAWQsIcXpJaCkDyr+qNhO6zz
/acU3HYaCB6zL/TEUme2D6suF6r6jxL/Dplh3rSunODbWJnb7doyonp+vuYXRuvE
8K/7IqtqgiDXoTK8fqc5ArM=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEOTCCAqGgAwIBAgIQbxETXs1CjgYgD/b+KdTWeDANBgkqhkiG9w0BAQsFADB5
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExJzAlBgNVBAsMHmNvbXBA
Y29tcC1PcHRpUGxleC0zMDYwIChDT01QKTEuMCwGA1UEAwwlbWtjZXJ0IGNvbXBA
Y29tcC1PcHRpUGxleC0zMDYwIChDT01QKTAeFw0yNDExMTgxMTEzMjRaFw0yNzAy
MTgxMTEzMjRaMFIxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZp
Y2F0ZTEnMCUGA1UECwweY29tcEBjb21wLU9wdGlQbGV4LTMwNjAgKENPTVApMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtMscwdcK/mpQUgSObls6f7uP
1fY5Lf0BTzAtjC1smp3pg+dIQwUbNJWcZomKCx6lA1mTT4P43Uo/HVVfm3I3aXyB
/LPeU3sE6Lxv7M7hi2abt9wxUov1gf9ZrpUtqn5Vv+QOIf914K4FOmc3tbBLxgRx
MyP6GwZLf6Xm5rdAFK7mJVKJ0FttyVi2iwrI03fZDFRqahvJka3mhzd7GAhDCmt3
5crnE6BAN9jmFy5LkIJoLObcXHRl0Kf9BC7LaMc70zXSXAsgUmdlfOSc0f8refDF
4x+sXB6t1WBDqrxjprhpm2cWukH3rxppe4rztBVox53kp5whZuZCrK3ocvrmTQID
AQABo2QwYjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYD
VR0jBBgwFoAUESjQ1/iXrVC2pXmwsifWkOsW1JYwGgYDVR0RBBMwEYIJbG9jYWxo
b3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBgQCCXjtWpgqhWL6kxuHpc2Mi7/Qn
GDQFsgxJg5ydLJbvgglf2+IocGEro4IfU1xIbfT3gsoTbbl2SERq/jrhBzQzrFh6
rdrpRfuXAI2/bubPmO3gbPAx0P0yf0ADpwIrxQKqy+sWipMdVaFef644MK+C6jHx
9LOpo48WQWXkbd/wFPVyU0XIcofjTaB41ITPUuAk+/rMyBCGcUSZvDgsMo0Aguf6
8oO+bk6v2jBORkcel0FAWOaK9IIrG6vg1w1A5OWI0whCDsV01Zty7vqdRibTKb00
yswGas/nTZCIcaPnZ9pBLwUvzwsJrlsB9xEjASu4TlOGxbXOQI1d0kbvyZDLsR8A
/jmQT/UPXpUhIrxeYTQugco8w3K1r8SXPp5bHVgdj7aAt+vv68xlujkKN/FKlOii
rfc1IqH5YKQ0a9LO8wm4XAI9RxXubt6Of/hdlwPeJA2BTG2PmaDwYWZSpN0YnCul
sOkAe0EWYwFN3iga7JqsyhQVTzQH0ChHIYa+dBQ=
-----END CERTIFICATE-----

38
eslint.config.js Normal file
View File

@ -0,0 +1,38 @@
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tech4biz-Update</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

6440
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

56
package.json Normal file
View File

@ -0,0 +1,56 @@
{
"name": "tech4biz-update",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@react-spring/web": "^9.7.5",
"@tanstack/react-virtual": "^3.10.9",
"@types/react-window": "^1.8.8",
"embla-carousel-react": "^8.5.1",
"framer-motion": "^11.11.17",
"gsap": "^3.12.5",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lucide-react": "^0.454.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-feather": "^2.0.10",
"react-helmet": "^6.1.0",
"react-helmet-async": "^2.0.5",
"react-icons": "^5.3.0",
"react-intersection-observer": "^9.13.1",
"react-router-dom": "^6.28.0",
"react-swipeable": "^7.0.2",
"react-virtuoso": "^4.12.0",
"react-window": "^1.8.10",
"recharts": "^2.13.3",
"split-type": "^0.3.4"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-basic-ssl": "^1.1.0",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"vite": "^5.4.10",
"vite-plugin-compression": "^0.5.1"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

39
src/App.jsx Normal file
View File

@ -0,0 +1,39 @@
import React from 'react';
import {
createBrowserRouter,
RouterProvider,
createRoutesFromElements,
Route
} from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
import Home from './pages/Home';
import Services from './pages/ServicePage';
const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route path="/" element={<Home />} />
<Route path="/services" element={<Services />} />
</>
),
{
future: {
v7_startTransition: true,
v7_relativeSplatPath: true,
v7_fetcherPersist: true,
v7_normalizeFormMethod: true,
v7_partialHydration: true,
v7_skipActionErrorRevalidation: true
}
}
);
const App = () => {
return (
<HelmetProvider>
<RouterProvider router={router} />
</HelmetProvider>
);
};
export default App;

BIN
src/assets/AI-service.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/assets/Tech4biz.mp4 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/demo-card.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

BIN
src/assets/placeholder.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/png.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

View File

@ -0,0 +1,39 @@
import React from 'react';
import { motion } from 'framer-motion';
import { cloudConfig } from './config';
import { useInViewAnimation } from './hooks/useInViewAnimation';
import { containerVariants } from './utils/variants';
import CTAContent from './components/CTAContent';
import CTAButton from './components/CTAButton';
import styles from './styles/CloudCTA.module.css';
const CloudCTA = () => {
const { ref, controls } = useInViewAnimation();
return (
<section
className={styles.container}
aria-labelledby="cta-heading"
>
<div className={styles.wrapper}>
<motion.div
ref={ref}
variants={containerVariants}
initial="hidden"
animate={controls}
className={styles.content}
>
<CTAContent
heading={cloudConfig.heading}
description={cloudConfig.description}
image={cloudConfig.images.arrow}
/>
<CTAButton text={cloudConfig.button.text} />
</motion.div>
</div>
</section>
);
};
export default CloudCTA;

View File

@ -0,0 +1,42 @@
import React, { memo } from 'react';
import { motion } from 'framer-motion';
import { itemVariants } from '../utils/variants';
import styles from '../styles/CloudCTA.module.css';
const CTAButton = memo(({ text }) => {
return (
<motion.button
variants={itemVariants}
className={styles.button}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
aria-label={text}
>
{text}
<motion.svg
xmlns="http://www.w3.org/2000/svg"
className={styles.buttonIcon}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
animate={{ x: [0, 4, 0] }}
transition={{
duration: 1.5,
repeat: Infinity,
ease: "easeInOut"
}}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 7l5 5m0 0l-5 5m5-5H6"
/>
</motion.svg>
</motion.button>
);
});
CTAButton.displayName = 'CTAButton';
export default CTAButton;

View File

@ -0,0 +1,68 @@
import React, { memo, useEffect } from 'react';
import { motion } from 'framer-motion';
import { itemVariants } from '../utils/variants';
import OptimizedImage from '@/components/common/OptimizedImage';
import { preloadImage } from '../utils/performance';
import styles from '../styles/CloudCTA.module.css';
const CTAContent = memo(({ heading, description, image }) => {
useEffect(() => {
preloadImage(image.src);
}, [image.src]);
return (
<div className={styles.contentWrapper}>
<div className={styles.textContainer}>
<motion.h2
variants={itemVariants}
className={styles.contentHeading}
>
{heading}
</motion.h2>
<motion.div
variants={itemVariants}
className={styles.divider}
aria-hidden="true"
/>
<motion.p
variants={itemVariants}
className={styles.contentDescription}
>
{description}
</motion.p>
</div>
<div className={styles.imageContainer}>
<motion.img
src={image.src}
alt={image.alt}
width={image.width}
height={image.height}
className={styles.floatingImage}
initial={{
opacity: 0,
scale: 0.5,
y: 50,
rotate: -5
}}
animate={{
opacity: [0.2, 0.3, 0.2],
scale: [1, 1.05, 1],
y: [0, -15, 0],
rotate: [-5, 5, -5]
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "easeInOut"
}}
/>
</div>
</div>
);
});
CTAContent.displayName = 'CTAContent';
export default CTAContent;

View File

@ -0,0 +1,20 @@
import arrowShape from '@assets/CloudCTA/arrow-shape.png';
export const cloudConfig = {
heading: "MAXIMIZE YOUR CLOUD INVESTMENT",
description: "We help companies save thousands of dollars by optimizing their cloud server deployments across top platforms like AWS, Azure, and Google Cloud, while also cutting labor costs, so you can focus on driving business growth and performance.",
button: {
text: "Start Optimizing Today",
ariaLabel: "Start cloud optimization process"
},
images: {
arrow: {
src: arrowShape,
alt: "Decorative arrow shape",
srcSet: `${arrowShape} 1x, ${arrowShape.replace('.png', '@2x.png')} 2x`,
sizes: "(max-width: 768px) 100vw, 50vw",
width: 180,
height: 180
}
}
};

View File

@ -0,0 +1,2 @@
export { cloudConfig } from './content';
export { cloudCTASchema } from './schema';

View File

@ -0,0 +1,11 @@
export const ctaMeta = {
title: "Cloud Investment Optimization | Your Company",
description: "Optimize your cloud infrastructure costs across AWS, Azure, and Google Cloud platforms. Start saving today with our expert solutions.",
keywords: "cloud optimization, AWS optimization, Azure optimization, Google Cloud optimization, cloud cost savings",
openGraph: {
title: "Maximize Your Cloud Investment",
description: "Save thousands on cloud infrastructure with expert optimization",
image: "/og-cloud-optimization.jpg",
type: "website"
}
};

View File

@ -0,0 +1,34 @@
export const cloudCTASchema = {
"@context": "https://schema.org",
"@type": "Service",
"name": "Cloud Investment Optimization",
"description": "Cloud server deployment and optimization services for AWS, Azure, and Google Cloud platforms",
"provider": {
"@type": "Organization",
"name": "Your Company Name",
"url": "https://yourcompany.com"
},
"offers": {
"@type": "Offer",
"description": "Cloud optimization services to reduce infrastructure and labor costs",
"price": "0",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
},
"serviceType": "Cloud Optimization",
"areaServed": "Worldwide",
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Cloud Optimization Services",
"itemListElement": [
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Cloud Cost Optimization",
"description": "Reduce your cloud infrastructure costs"
}
}
]
}
};

View File

@ -0,0 +1,19 @@
import { useEffect, useRef } from 'react';
import { useAnimation, useInView } from 'framer-motion';
import { debouncedAnimation } from '../utils/performance';
export const useInViewAnimation = () => {
const ref = useRef(null);
const isInView = useInView(ref, { once: true });
const controls = useAnimation();
useEffect(() => {
if (isInView) {
debouncedAnimation(() => {
controls.start("visible");
});
}
}, [isInView, controls]);
return { ref, controls };
};

View File

@ -0,0 +1,14 @@
import React, { lazy, Suspense } from 'react';
import { ErrorBoundary } from '@/components/common/ErrorBoundary';
const CloudCTA = lazy(() => import('./CloudCTA'));
const CloudCTAWrapper = () => (
<ErrorBoundary>
<Suspense fallback={<div className="min-h-[400px] bg-white" />}>
<CloudCTA />
</Suspense>
</ErrorBoundary>
);
export default CloudCTAWrapper;

View File

@ -0,0 +1,112 @@
:root {
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1536px;
}
.container {
@apply w-full bg-white p-4 sm:p-6 md:p-8;
}
.wrapper {
@apply max-w-[80rem] mx-auto;
}
.content {
@apply bg-[#1F2937] rounded-[1.5rem] p-6 sm:p-8 md:p-12 flex flex-col gap-[1rem] relative overflow-hidden
border border-white/10 shadow-2xl;
}
.contentWrapper {
@apply flex flex-col md:flex-row md:items-center md:gap-16 gap-6;
}
.textContainer {
@apply w-full md:flex-[0.65];
}
.contentHeading {
@apply text-white text-[28px] sm:text-[30px] md:text-[32px] md:whitespace-nowrap font-[600] tracking-[0.03em] leading-[1.25] mb-4 font-syne uppercase;
}
.divider {
@apply w-[7rem] sm:w-[8rem] md:w-[9rem] h-[3px] bg-white/90 mb-6 md:mb-8;
}
.contentDescription {
@apply text-white/80 text-[14px] sm:text-[16px] md:text-[18px] font-[400] leading-[1.6] font-poppins;
}
.buttonContainer {
@apply mt-6 sm:mt-8;
}
.button {
@apply bg-[#f4791f] text-white px-6 sm:px-8 py-3 sm:py-3.5 rounded-full text-[15px] sm:text-[16px] md:text-[17px] font-medium
flex items-center gap-2 hover:bg-[#ff8827] transition-all duration-300 text-nowrap
shadow-lg hover:shadow-xl hover:translate-y-[-2px] justify-center w-full sm:w-auto;
}
.buttonText {
@apply whitespace-nowrap;
}
.buttonIcon {
@apply h-4 w-4 sm:h-5 sm:w-5;
}
.imageContainer {
@apply hidden md:flex md:flex-[0.35] items-center justify-end relative;
}
.floatingImage {
@apply absolute !important;
min-width: 400px !important;
max-width: 400px !important;
width: 400px !important;
height: 120px !important;
right: -45px !important;
bottom: -120px !important;
opacity: 100 !important;
object-fit: contain !important;
filter: brightness(1.2) !important;
will-change: transform, opacity !important;
}
@media (max-width: 480px) {
.content {
@apply p-5;
}
.contentHeading {
@apply text-[28px] leading-[1] mb-[0.5rem];
}
.contentDescription {
@apply text-[14px] leading-[1.5];
}
.divider {
@apply w-[6rem] mb-5;
}
.button {
@apply px-5 py-2.5 text-[14px];
}
.buttonIcon {
@apply h-4 w-4;
}
}
@media (min-width: 768px) {
.content {
@apply flex-row items-center justify-between;
}
.buttonContainer {
@apply mt-0;
}
}

View File

@ -0,0 +1,15 @@
export const breakpoints = {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px'
};
export const mediaQueries = {
sm: `@media (min-width: ${breakpoints.sm})`,
md: `@media (min-width: ${breakpoints.md})`,
lg: `@media (min-width: ${breakpoints.lg})`,
xl: `@media (min-width: ${breakpoints.xl})`,
'2xl': `@media (min-width: ${breakpoints['2xl']})`
};

View File

@ -0,0 +1,24 @@
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
export const debouncedAnimation = debounce((callback) => {
requestAnimationFrame(callback);
}, 16);
export const preloadImage = (src) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = resolve;
img.onerror = reject;
});
};

View File

@ -0,0 +1,19 @@
export const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
duration: 0.6,
staggerChildren: 0.2
}
}
};
export const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5 }
}
};

View File

@ -0,0 +1,173 @@
import React, { useCallback } from 'react';
import useEmblaCarousel from 'embla-carousel-react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { slides } from './sliderData';
import styles from './CloudSlider.module.css';
import { motion } from 'framer-motion';
const DeploymentPoint = ({ point, index, isVisible, isMobile }) => (
<motion.div
initial={isMobile ? false : { opacity: 0, y: 20 }}
animate={isMobile ? { opacity: 1, y: 0 } : isVisible ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{
duration: isMobile ? 0.5 : 0.8,
delay: isMobile ? 0 : index * 0.1,
ease: [0.25, 0.1, 0.25, 1],
}}
className={`${styles.deploymentPoint} ${isMobile ? styles.mobilePoint : ''}`}
>
<div>
<div className="flex items-center gap-2">
<h3 className={styles.pointTitle}>{`${index + 1}. ${point.title}`}</h3>
{point.isNew && (
<span className={styles.newTag}>New</span>
)}
</div>
<p className={styles.pointDescription}>{point.description}</p>
</div>
</motion.div>
);
export default function CloudSlider() {
const [progress, setProgress] = React.useState(0);
const progressRef = React.useRef(null);
const [currentSlide, setCurrentSlide] = React.useState(0);
const [isMobile, setIsMobile] = React.useState(false);
const [emblaRef, emblaApi] = useEmblaCarousel({
loop: true,
align: 'center',
skipSnaps: false,
containScroll: false,
dragFree: false,
startIndex: 0,
slidesToScroll: 1,
speed: 15,
breakpoints: {
'(max-width: 768px)': {
align: 'center',
containScroll: 'keepSnaps',
dragFree: true
}
}
});
React.useEffect(() => {
if (!emblaApi) return;
const onSelect = () => {
setCurrentSlide(emblaApi.selectedScrollSnap());
};
emblaApi.on('select', onSelect);
onSelect(); // Initial call to set current slide
if (progressRef.current) {
clearInterval(progressRef.current);
}
const startProgress = () => {
let currentProgress = 0;
progressRef.current = setInterval(() => {
currentProgress += 1;
setProgress(currentProgress);
if (currentProgress >= 100) {
setProgress(0);
scrollNext();
currentProgress = 0;
}
}, 50); // 50ms * 100 steps = 5 seconds per slide
};
startProgress();
return () => {
emblaApi.off('select', onSelect);
if (progressRef.current) {
clearInterval(progressRef.current);
}
};
}, [emblaApi]);
React.useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth <= 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
const scrollPrev = useCallback(() => {
if (emblaApi) emblaApi.scrollPrev();
}, [emblaApi]);
const scrollNext = useCallback(() => {
if (emblaApi) emblaApi.scrollNext();
}, [emblaApi]);
return (
<div className={styles.container}>
<h1 className={styles.title}>
<span className={styles.titleWhite}>Cloud Infrastructure </span>
<span className={styles.titleBlue}>Deployments</span>
</h1>
<div className={styles.embla} ref={emblaRef}>
<div className={styles.emblaContainer}>
{slides.map((slide, index) => (
<div key={index} className={styles.emblaSlide}>
<div className={styles.slideContent}>
<div
className={styles.slideBackground}
style={{
backgroundImage: `url(${slide.backgroundImage})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
opacity: 0.15
}}
/>
<div className={styles.gradientOverlay} />
<div className={styles.contentWrapper}>
<h2 className={styles.slideTitle}>{slide.title}</h2>
<div className={styles.pointsContainer}>
{slide.points.map((point, idx) => (
<div key={idx} className={styles.pointWrapper}>
<DeploymentPoint
point={point}
index={idx}
isVisible={currentSlide === index}
isMobile={isMobile}
/>
</div>
))}
</div>
</div>
<div className={styles.progressBarContainer}>
{currentSlide === index && (
<div
className={styles.progressBar}
style={{ width: `${progress}%` }}
/>
)}
</div>
</div>
</div>
))}
</div>
</div>
<div className={styles.navigationButtons}>
<button onClick={scrollPrev} className={styles.navButton} aria-label="Previous slide">
<ChevronLeft className={styles.navIcon} />
</button>
<button onClick={scrollNext} className={styles.navButton} aria-label="Next slide">
<ChevronRight className={styles.navIcon} />
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,245 @@
.container {
@apply min-h-screen bg-[#1F2937] flex flex-col items-center justify-center p-4;
}
.title {
@apply text-5xl font-bold text-center mb-16 font-sans;
}
.titleWhite {
@apply text-white;
}
.titleBlue {
@apply text-blue-500;
}
.sliderContainer {
@apply relative w-full max-w-[1200px] mx-auto;
}
.slide {
@apply rounded-xl relative overflow-hidden w-full mx-auto;
min-height: 552px;
height: auto;
padding: 2rem;
@media (max-width: 768px) {
min-height: auto;
padding: 1rem;
}
}
.slideBackground {
@apply absolute inset-0 z-0;
}
.gradientOverlay {
@apply absolute inset-0 bg-gradient-to-br from-purple-500/10 to-emerald-500/5 pointer-events-none;
}
.contentWrapper {
@apply h-full flex flex-col relative z-10;
justify-content: space-between;
min-height: 552px;
@media (max-width: 768px) {
height: 100%;
justify-content: flex-start;
gap: 1.5rem;
}
}
.slideTitle {
@apply text-[24px] text-white font-syne font-semibold text-left;
@media (max-width: 768px) {
font-size: 20px;
position: relative;
top: 0;
left: 0;
margin-bottom: 1rem;
}
}
.pointsContainer {
@apply flex flex-col relative;
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: 24px;
padding-top: 32px;
overflow: hidden;
@media (max-width: 768px) {
display: flex;
flex-direction: column;
gap: 12px;
padding: 0;
margin: 0;
width: 100%;
height: calc(100% - 60px);
overflow-y: auto;
}
}
.pointWrapper {
@apply flex justify-center;
transform-style: preserve-3d;
perspective: 1000px;
@media (max-width: 768px) {
transform-style: flat;
perspective: none;
width: 100%;
}
}
.navigationButtons {
@apply flex justify-center gap-4 mt-8;
}
.navButton {
@apply p-2 rounded-full bg-gray-800/30 hover:bg-gray-700/30 transition-colors backdrop-blur-sm;
}
.navIcon {
@apply w-5 h-5 text-white/80;
}
.progressBarContainer {
@apply absolute bottom-0 left-0 right-0 h-1 bg-gray-800/50;
z-index: 20;
}
.progressBar {
@apply h-full bg-blue-500;
transition: width 50ms linear;
}
.deploymentPoint {
@apply flex items-start gap-3;
position: relative;
@media (max-width: 768px) {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 0.75rem;
width: 100%;
display: flex;
align-items: flex-start;
transition: all 0.2s ease;
/* Removed backdrop-filter: blur(8px); */
}
}
/* Removed the .starIcon class below
.starIcon {
@apply mt-1 text-blue-500;
}
@media (max-width: 768px) {
.starIcon {
color: rgb(96, 165, 250);
transform: scale(0.7);
margin-right: 0.5rem;
}
}
*/
.pointTitle {
@apply text-white font-medium tracking-wide font-poppins text-[18px] leading-[27px];
@media (max-width: 768px) {
font-size: 16px;
line-height: 1.3;
letter-spacing: 0.02em;
}
}
.newTag {
@apply px-1.5 py-0.5 text-[11px] bg-[#22c55e]/90 text-white rounded-[2px] uppercase tracking-wide font-medium;
}
.pointDescription {
@apply text-gray-400 font-poppins font-medium text-[14px] leading-[21px] mt-1.5 max-w-[280px];
@media (max-width: 768px) {
display: none; /* Hide descriptions only on mobile */
font-size: 13px;
line-height: 20px;
max-width: 100%;
}
}
.embla {
@apply relative w-full max-w-[1400px] mx-auto overflow-hidden;
padding: 1.6rem;
@media (max-width: 768px) {
padding: 1rem;
}
}
.emblaContainer {
@apply flex;
transform-style: preserve-3d;
backface-visibility: hidden;
will-change: transform;
transition: transform 200ms ease;
}
.emblaSlide {
flex: 0 0 80%;
margin: 0 2%;
position: relative;
overflow: hidden;
@media (max-width: 768px) {
flex: 0 0 calc(100% - 2rem);
margin: 0 1rem;
scroll-snap-align: center;
}
}
.slideContent {
@apply rounded-xl p-12 relative overflow-hidden;
transform: translateZ(0);
will-change: transform;
background: linear-gradient(to bottom right, rgba(31, 41, 55, 1), rgba(17, 24, 39, 1));
min-height: 552px;
@media (max-width: 768px) {
height: 480px;
padding: 1.25rem;
background: linear-gradient(200.96deg, rgba(31, 41, 55, 0.95), rgba(17, 24, 39, 0.98));
}
}
/* Additional adjustments to ensure proper spacing and sizing */
@media (max-width: 768px) {
.pointsContainer {
gap: 12px;
}
.deploymentPoint {
padding: 0.75rem;
/* backdrop-filter removed */
}
.slideContent {
padding: 1.25rem;
}
.pointWrapper {
width: 100%;
}
.pointTitle {
font-size: 16px;
}
.newTag {
font-size: 10px;
}
}

View File

@ -0,0 +1,131 @@
import SaaSImage from '@/assets/Cloud Infrastructure section/Software as a Service Solutions.webp';
import PaaSImage from '@/assets/Cloud Infrastructure section/Platform as a Service Architecture.webp';
import IaaSImage from '@/assets/Cloud Infrastructure section/Infrastructure as a Service Platform.webp';
import EnterpriseImage from '@/assets/Cloud Infrastructure section/Enterprise Cloud Infrastructure.webp';
export const slides = [
{
title: "Public Cloud Orchestration and Design",
backgroundImage: SaaSImage,
points: [
{
title: "Flexible Deployment",
description: "Choose from multiple deployment options that best suit your infrastructure needs",
isNew: false
},
{
title: "Auto-scaling",
description: "Automatically adjust resources based on demand and workload patterns",
isNew: false
},
{
title: "Multi-region Support",
description: "Deploy across multiple geographic regions for improved availability",
isNew: false
},
{
title: "Container Orchestration",
description: "Integrated container management with Kubernetes support",
isNew: true
},
{
title: "Infrastructure as Code",
description: "Define and manage infrastructure using modern IaC tools",
isNew: true
}
]
},
{
title: "Security and Compliance Features",
backgroundImage: PaaSImage,
points: [
{
title: "Advanced Encryption",
description: "Industry-standard encryption protocols to protect data at rest and in transit",
isNew: false
},
{
title: "Compliance Framework",
description: "Built-in compliance tools for HIPAA, GDPR, and other regulatory requirements",
isNew: false
},
{
title: "Identity Management",
description: "Robust IAM capabilities with role-based access control and SSO integration",
isNew: true
},
{
title: "Security Monitoring",
description: "24/7 threat detection and automated security incident response",
isNew: true
},
{
title: "Audit Logging",
description: "Comprehensive audit trails and logging for all system activities",
isNew: false
}
]
},
{
title: "Performance and Scalability",
backgroundImage: IaaSImage,
points: [
{
title: "Global CDN",
description: "Worldwide content delivery network for optimal performance",
isNew: true
},
{
title: "Load Balancing",
description: "Intelligent traffic distribution for high availability",
isNew: false
},
{
title: "Elastic Resources",
description: "Dynamic resource allocation based on real-time demands",
isNew: false
},
{
title: "Performance Monitoring",
description: "Real-time metrics and performance analytics",
isNew: true
},
{
title: "Disaster Recovery",
description: "Automated backup and recovery solutions",
isNew: false
}
]
},
{
title: "Enterprise Integration",
backgroundImage: EnterpriseImage,
points: [
{
title: "API Management",
description: "Comprehensive API gateway and management tools",
isNew: true
},
{
title: "Hybrid Cloud",
description: "Seamless integration between public and private clouds",
isNew: false
},
{
title: "Data Migration",
description: "Enterprise-grade tools for secure data transfer",
isNew: false
},
{
title: "DevOps Pipeline",
description: "Integrated CI/CD tools for automated deployments",
isNew: true
},
{
title: "Enterprise Support",
description: "24/7 dedicated support and consulting services",
isNew: false
}
]
}
];

View File

@ -0,0 +1,71 @@
import React, { Suspense } from 'react';
import ErrorBoundary from '../common/ErrorBoundary';
import { SECTION_CONFIG } from './config/constants';
import { experienceSchema } from './config/schema';
import './styles/ExperienceSection.css';
import { Helmet } from 'react-helmet-async';
import { useExperienceData } from './hooks/useExperienceData';
const ExperienceHeader = React.lazy(() => import('./components/ExperienceHeader'));
const ExperienceCard = React.lazy(() => import('./components/ExperienceCard'));
const ExperienceService = () => {
const { experiences, isLoading, error } = useExperienceData();
if (error) {
return (
<div className="error-message text-center text-red-600">
<h2>Something went wrong while loading experiences.</h2>
<p>Please try again later.</p>
</div>
);
}
return (
<ErrorBoundary>
<section className="experience-section max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-24">
<script type="application/ld+json">
{JSON.stringify(experienceSchema)}
</script>
<Helmet>
<title>{SECTION_CONFIG.title}</title>
<meta name="description" content={SECTION_CONFIG.metaDescription} />
<meta name="keywords" content="AI, Quantum Computing, 3D Design, EV Technology, Business Growth" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{/* Open Graph Meta Tags */}
<meta property="og:title" content={SECTION_CONFIG.title} />
<meta property="og:description" content={SECTION_CONFIG.metaDescription} />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://yourwebsite.com/experience" />
<meta property="og:image" content="https://yourwebsite.com/images/experience-og-image.jpg" />
{/* Twitter Card Meta Tags */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={SECTION_CONFIG.title} />
<meta name="twitter:description" content={SECTION_CONFIG.metaDescription} />
<meta name="twitter:image" content="https://yourwebsite.com/images/experience-twitter-image.jpg" />
</Helmet>
<Suspense fallback={<div className="header-placeholder animate-pulse bg-gray-100 rounded-lg h-24" />}>
<ExperienceHeader />
</Suspense>
<div className="experience-grid grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
{isLoading ? (
<div className="loading-message">Loading experiences...</div>
) : (
experiences.map((experience, index) => (
<Suspense key={experience.id} fallback={<div className="card-placeholder animate-pulse bg-gray-100 rounded-lg h-64" />}>
<ExperienceCard {...experience} index={index} />
</Suspense>
))
)}
</div>
</section>
</ErrorBoundary>
);
};
export default ExperienceService;

View File

@ -0,0 +1,56 @@
import React, { useRef, useEffect } from 'react';
import { motion } from 'framer-motion';
import { ArrowRight } from 'lucide-react';
import gsap from 'gsap';
import { ANIMATION_CONFIG } from '../config/constants';
import '../styles/ExperienceCard.css';
const ExperienceCard = ({ icon: Icon, title, description, stats }) => {
const cardRef = useRef(null);
useEffect(() => {
const card = cardRef.current;
gsap.fromTo(
card,
ANIMATION_CONFIG.card.initial,
{
...ANIMATION_CONFIG.card.animate,
scrollTrigger: {
trigger: card,
...ANIMATION_CONFIG.card.trigger,
},
}
);
}, []);
return (
<div ref={cardRef} className="experience-card group">
<div className="card-pattern" aria-hidden="true" />
<div className="experience-card-content">
<div className="flex items-start gap-4 mb-6">
<div className="card-icon-wrapper">
<Icon className="experience-icon w-6 h-6 text-white" aria-hidden="true" />
</div>
<div>
<h3 className="card-title">{title}</h3>
{stats && <span className="card-stats">{stats}</span>}
</div>
</div>
<p className="experience-description">{description}</p>
<motion.button
className="experience-button inline-flex items-center focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
whileHover={{ x: 10 }}
transition={{ type: "spring", stiffness: 400 }}
aria-label={`Learn more about ${title}`}
>
<span className="mr-2">Learn More</span>
<ArrowRight className="experience-button-icon w-5 h-5" />
</motion.button>
</div>
</div>
);
};
export default React.memo(ExperienceCard);

View File

@ -0,0 +1,57 @@
import React, { useRef, useEffect } from 'react';
import gsap from 'gsap';
import { ANIMATION_CONFIG, SECTION_CONFIG } from '../config/constants';
import '../styles/ExperienceHeader.css';
const ExperienceHeader = () => {
const headerRef = useRef(null);
useEffect(() => {
const ctx = gsap.context(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: headerRef.current,
...ANIMATION_CONFIG.header.trigger,
},
});
tl.from(".header-subtitle", {
opacity: 0,
y: -20,
duration: ANIMATION_CONFIG.header.duration,
ease: ANIMATION_CONFIG.header.ease,
})
.from(
".header-title",
{
opacity: 0,
y: 30,
duration: ANIMATION_CONFIG.header.duration,
ease: ANIMATION_CONFIG.header.ease,
},
"-=0.3"
)
.from(
".header-line",
{
scaleX: 0,
duration: ANIMATION_CONFIG.header.duration,
ease: "power3.inOut",
},
"-=0.5"
);
}, headerRef);
return () => ctx.revert();
}, []);
return (
<div ref={headerRef} className="section-header">
<h2 className="header-subtitle">{SECTION_CONFIG.subtitle}</h2>
<h1 className="header-title">{SECTION_CONFIG.title}</h1>
<div className="header-line" />
</div>
);
};
export default React.memo(ExperienceHeader);

View File

@ -0,0 +1,28 @@
export const ANIMATION_CONFIG = {
card: {
initial: {
opacity: 0,
y: 50,
scale: 0.95,
},
animate: {
opacity: 1,
y: 0,
scale: 1,
duration: 0.8,
ease: "power3.out"
},
trigger: {
start: "top bottom-=100",
toggleActions: "play none none reverse"
}
},
header: {
duration: 0.8,
ease: "power3.out",
trigger: {
start: "top center+=100",
toggleActions: "play none none reverse"
}
}
};

View File

@ -0,0 +1,65 @@
import { Monitor, Cpu, Box, BatteryCharging } from 'lucide-react';
export const SECTION_CONFIG = {
title: "How Our Services Will Help You to Grow Your Business",
subtitle: "Our Working Process",
metaDescription: "Explore our innovative services in AI, Quantum Computing, 3D Design, and EV Technology to accelerate your business growth.",
experiences: [
{
id: "exp-0",
icon: Monitor,
title: "AI in Medical Industry",
description: "Using efficient AI infrastructure, our primary focus is to disrupt and revolutionize the healthcare business by innovatively redefining the quality of medical services.",
stats: "100% AI-Driven"
},
{
id: "exp-1",
icon: Cpu,
title: "Quantum Computing Acceleration",
description: "With faster hardware's accelerating the efficiency of quantum computing setups, complex computing problems and demands can be handled in a limited timeframe.",
stats: "100x Faster"
},
{
id: "exp-2",
icon: Box,
title: "3D Design Engineering",
description: "Modern design and engineering approaches for 3D project or product outcome is a reliable approach to achieving a more efficient, budget-friendly, and solution-providing design and infrastructure outcome.",
stats: "$200k Saved"
},
{
id: "exp-3",
icon: BatteryCharging,
title: "EV PCB Board Designs",
description: "Use advanced technological infrastructure to curb emissions, driving your business and the consumers associated with it towards a safer and better future.",
stats: "Zero Emission"
}
]
};
export const ANIMATION_CONFIG = {
card: {
initial: {
opacity: 0,
y: 50,
scale: 0.95,
},
animate: {
opacity: 1,
y: 0,
scale: 1,
duration: 0.8,
ease: "power3.out"
},
trigger: {
start: "top bottom-=100",
toggleActions: "play none none reverse"
}
},
header: {
duration: 0.8,
ease: "power3.out",
trigger: {
start: "top center+=100"
}
}
};

View File

@ -0,0 +1,12 @@
export const experienceSchema = {
"@context": "https://schema.org",
"@type": "Service",
"name": "Business Growth Services",
"provider": {
"@type": "Organization",
"name": "Your Company Name"
},
"serviceType": ["AI Services", "Quantum Computing", "3D Design", "EV Technology"],
"description": "Comprehensive business growth services including AI in healthcare, quantum computing acceleration, 3D design engineering, and EV PCB board designs.",
"areaServed": "Worldwide"
};

View File

@ -0,0 +1,26 @@
import { useState, useEffect, useMemo } from 'react';
import { SECTION_CONFIG } from '../config/constants';
import { processExperienceData } from '../utils/experienceHelpers';
export const useExperienceData = () => {
const [experiences, setExperiences] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const memoizedProcessedData = useMemo(() => {
return processExperienceData(SECTION_CONFIG.experiences);
}, []);
useEffect(() => {
try {
setExperiences(memoizedProcessedData);
setIsLoading(false);
} catch (err) {
console.error("Error processing experience data:", err);
setError(err);
setIsLoading(false);
}
}, [memoizedProcessedData]);
return { experiences, isLoading, error };
};

View File

@ -0,0 +1,35 @@
import { useEffect, useRef } from 'react';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
export const useScrollAnimation = (options = {}) => {
const elementRef = useRef(null);
useEffect(() => {
const element = elementRef.current;
const animation = gsap.fromTo(
element,
{ opacity: 0, y: 50 },
{
opacity: 1,
y: 0,
duration: 0.8,
scrollTrigger: {
trigger: element,
start: "top bottom-=100",
toggleActions: "play none none reverse",
...options
}
}
);
return () => {
if (animation) animation.kill();
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
};
}, [options]);
return elementRef;
};

View File

@ -0,0 +1,43 @@
.experience-card {
@apply relative bg-white rounded-2xl p-6 lg:p-8 shadow-lg hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1;
}
.card-pattern {
@apply absolute top-0 right-0 w-32 h-32 bg-blue-50 rounded-bl-full opacity-20 transition-all duration-300;
}
.group:hover .card-pattern {
@apply bg-blue-100;
}
.experience-card-content {
@apply relative;
}
.card-icon-wrapper {
@apply p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl shadow-lg;
}
.card-title {
@apply text-xl lg:text-2xl font-bold text-gray-800 mb-2 line-clamp-2;
}
.card-stats {
@apply inline-block px-3 py-1 bg-blue-50 text-blue-600 text-sm font-semibold rounded-full;
}
.experience-description {
@apply text-gray-600 leading-relaxed mb-6 line-clamp-4 lg:line-clamp-none;
}
.experience-button {
@apply inline-flex items-center text-blue-600 font-semibold cursor-pointer;
}
.experience-button-icon {
@apply w-5 h-5 transition-transform duration-300;
}
.group:hover .experience-button-icon {
@apply translate-x-1;
}

View File

@ -0,0 +1,15 @@
.section-header {
@apply text-center mb-16 lg:mb-24;
}
.header-subtitle {
@apply text-sm lg:text-base font-bold text-blue-600 tracking-wider uppercase mb-4;
}
.header-title {
@apply text-3xl lg:text-5xl font-bold text-gray-900 mb-6 max-w-3xl mx-auto leading-tight;
}
.header-line {
@apply w-24 h-1 bg-gradient-to-r from-blue-500 to-blue-600 mx-auto rounded-full;
}

View File

@ -0,0 +1,44 @@
.card-pattern {
@apply absolute top-0 right-0 w-32 h-32 bg-blue-50 rounded-bl-full opacity-20 transition-all duration-300 group-hover:bg-blue-100;
}
.card-icon-wrapper {
@apply p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl shadow-lg;
}
.card-title {
@apply text-xl lg:text-2xl font-bold text-gray-800 mb-2 line-clamp-2;
}
.card-stats {
@apply inline-block px-3 py-1 bg-blue-50 text-blue-600 text-sm font-semibold rounded-full;
}
.section-header {
@apply text-center mb-16 lg:mb-24;
}
.header-subtitle {
@apply text-sm lg:text-base font-bold text-blue-600 tracking-wider uppercase mb-4;
}
.header-title {
@apply text-3xl lg:text-5xl font-bold text-gray-900 mb-6 max-w-3xl mx-auto leading-tight;
}
.header-line {
@apply w-24 h-1 bg-gradient-to-r from-blue-500 to-blue-600 mx-auto rounded-full;
}
.experience-section {
/* Additional styles if needed */
}
.experience-grid {
/* Additional styles if needed */
}
.header-placeholder,
.card-placeholder {
border-radius: 0.5rem;
}

View File

@ -0,0 +1,25 @@
export const processExperienceData = (experiences) => {
return experiences.map((experience, index) => ({
...experience,
id: `exp-${index}`,
animationDelay: index * 0.2,
className: `experience-card-${index}`
}));
};
export const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
export const validateExperienceData = (experience) => {
const requiredFields = ['title', 'description', 'icon'];
return requiredFields.every(field => !!experience[field]);
};

Some files were not shown because too many files have changed in this diff Show More