Added new header and product dropable
24
.gitignore
vendored
Normal 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
@ -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
@ -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
@ -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
|
||||
28
certificates/localhost-key.pem
Normal 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-----
|
||||
25
certificates/localhost.pem
Normal 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
@ -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
@ -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
56
package.json
Normal 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
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
1
public/vite.svg
Normal 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
@ -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
|
After Width: | Height: | Size: 912 KiB |
BIN
src/assets/Case Studies Slider/3D-Visual-Img.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
src/assets/Case Studies Slider/automating.webp
Normal file
|
After Width: | Height: | Size: 596 KiB |
BIN
src/assets/Case Studies Slider/decentralized-banner.webp
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/Case Studies Slider/empower.webp
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/Case Studies Slider/fitness.webp
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
src/assets/Case Studies Slider/iotconnecctivity.webp
Normal file
|
After Width: | Height: | Size: 610 KiB |
BIN
src/assets/Case Studies Slider/transforming-blockchain.webp
Normal file
|
After Width: | Height: | Size: 167 KiB |
BIN
src/assets/Case Studies Slider/urban-connectivity.webp
Normal file
|
After Width: | Height: | Size: 853 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 52 KiB |
BIN
src/assets/Cloud-Service.webp
Normal file
|
After Width: | Height: | Size: 4.7 MiB |
BIN
src/assets/CloudCTA/arrow-shape.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src/assets/Header-slide/Img-1.webp
Normal file
|
After Width: | Height: | Size: 14 MiB |
BIN
src/assets/Header-slide/Img-2.webp
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
src/assets/Header-slide/Img-3.webp
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
src/assets/Header-slide/Img-4.webp
Normal file
|
After Width: | Height: | Size: 8.8 MiB |
BIN
src/assets/Header/Product-Card/cloud-drive-storage-platform.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 29 KiB |
BIN
src/assets/Header/Product-Card/learning-management-system.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/assets/Logo/AWS-Partner-Netwrok-tech4biz.webp
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src/assets/Logo/AZURE-Partner-Tech4biz.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/assets/Logo/Cloudtopiaa-Tech4biz.webp
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
src/assets/Logo/IBM-Tech4biz-partner.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/Logo/ISO-Partner-Tech4biz.webp
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/Logo/OPENAI-Partner-Tech4biz.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/Logo/Tech4biz-logo.webp
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/Product-Show-Case/ASIC-AI-Deployment.webp
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
src/assets/Product-Show-Case/Accelerated-Performance.webp
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
src/assets/Product-Show-Case/Adaptive-Solutions.webp
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
src/assets/Product-Show-Case/Advanced-FPGA-Architecture.webp
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src/assets/Service-Section/Cloud-Service.webp
Normal file
|
After Width: | Height: | Size: 4.7 MiB |
BIN
src/assets/Service-Section/Software Engineering Solutions.webp
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
src/assets/Service-Section/VLSI Design Services.webp
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/Service-Section/placeholder.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/Service-Section/service-main.webp
Normal file
|
After Width: | Height: | Size: 3.8 MiB |
BIN
src/assets/Tech4biz-logo.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/assets/Tech4biz.mp4
Normal file
BIN
src/assets/Videos/cloud-infra.mp4
Normal file
BIN
src/assets/Videos/deployment.mp4
Normal file
BIN
src/assets/Videos/solutions.mp4
Normal file
BIN
src/assets/demo-card.jpg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
src/assets/placeholder.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/png.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
src/assets/refference img/SlideLayout.png
Normal file
|
After Width: | Height: | Size: 766 KiB |
BIN
src/assets/service-main.webp
Normal file
|
After Width: | Height: | Size: 3.8 MiB |
BIN
src/assets/speech-bubble.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/tech4biz-hero-background.mp4
Normal file
39
src/components/CloudCTA/CloudCTA.jsx
Normal 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;
|
||||
42
src/components/CloudCTA/components/CTAButton.jsx
Normal 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;
|
||||
68
src/components/CloudCTA/components/CTAContent.jsx
Normal 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;
|
||||
20
src/components/CloudCTA/config/content.js
Normal 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
|
||||
}
|
||||
}
|
||||
};
|
||||
2
src/components/CloudCTA/config/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { cloudConfig } from './content';
|
||||
export { cloudCTASchema } from './schema';
|
||||
11
src/components/CloudCTA/config/meta.js
Normal 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"
|
||||
}
|
||||
};
|
||||
34
src/components/CloudCTA/config/schema.js
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
19
src/components/CloudCTA/hooks/useInViewAnimation.js
Normal 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 };
|
||||
};
|
||||
14
src/components/CloudCTA/index.jsx
Normal 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;
|
||||
112
src/components/CloudCTA/styles/CloudCTA.module.css
Normal 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;
|
||||
}
|
||||
}
|
||||
15
src/components/CloudCTA/utils/breakpoints.js
Normal 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']})`
|
||||
};
|
||||
24
src/components/CloudCTA/utils/performance.js
Normal 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;
|
||||
});
|
||||
};
|
||||
19
src/components/CloudCTA/utils/variants.js
Normal 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 }
|
||||
}
|
||||
};
|
||||
173
src/components/CloudSlider/CloudSlider.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
245
src/components/CloudSlider/CloudSlider.module.css
Normal 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;
|
||||
}
|
||||
}
|
||||
131
src/components/CloudSlider/sliderData.js
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
71
src/components/ExperienceSection/ExperienceService.jsx
Normal 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;
|
||||
@ -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);
|
||||
@ -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);
|
||||
28
src/components/ExperienceSection/config/animations.js
Normal 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"
|
||||
}
|
||||
}
|
||||
};
|
||||
65
src/components/ExperienceSection/config/constants.js
Normal 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"
|
||||
}
|
||||
}
|
||||
};
|
||||
12
src/components/ExperienceSection/config/schema.js
Normal 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"
|
||||
};
|
||||
26
src/components/ExperienceSection/hooks/useExperienceData.js
Normal 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 };
|
||||
};
|
||||
35
src/components/ExperienceSection/hooks/useScrollAnimation.js
Normal 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;
|
||||
};
|
||||
43
src/components/ExperienceSection/styles/ExperienceCard.css
Normal 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;
|
||||
}
|
||||
15
src/components/ExperienceSection/styles/ExperienceHeader.css
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
25
src/components/ExperienceSection/utils/experienceHelpers.js
Normal 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]);
|
||||
};
|
||||