Initial commit
This commit is contained in:
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
||||
DISABLE_ESLINT_PLUGIN=true
|
||||
62
.gitea/workflows/build.yml
Normal file
62
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to build'
|
||||
required: true
|
||||
default: 'main'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ gitea.event.inputs.branch }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm install --no-audit --silent 2>&1 | tee install.log
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
|
||||
- name: Build (react-scripts build)
|
||||
env:
|
||||
CI: 'false'
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm run build 2>&1 | tee build.log
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Verify build folder exists
|
||||
run: test -d build || (echo "No build folder. Check build logs above."; exit 1)
|
||||
|
||||
- name: Upload logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-logs
|
||||
path: |
|
||||
install.log
|
||||
build.log
|
||||
npm-debug.log*
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Build completed
|
||||
if: success()
|
||||
run: echo "Build completed successfully"
|
||||
41
package.json
Normal file
41
package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "bulls-bikes-website",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"lucide-react": "^0.400.0",
|
||||
"framer-motion": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^3.4.0",
|
||||
"postcss": "^8.4.0",
|
||||
"autoprefixer": "^10.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/images/SourceSansPro-Black-1767013371329.otf
Normal file
1
public/images/SourceSansPro-Black-1767013371329.otf
Normal file
File diff suppressed because one or more lines are too long
1
public/images/SourceSansPro-Bold-1767013371350.otf
Normal file
1
public/images/SourceSansPro-Bold-1767013371350.otf
Normal file
File diff suppressed because one or more lines are too long
1
public/images/SourceSansPro-ExtraLight-1767013371395.otf
Normal file
1
public/images/SourceSansPro-ExtraLight-1767013371395.otf
Normal file
File diff suppressed because one or more lines are too long
1
public/images/SourceSansPro-Light-1767013371443.otf
Normal file
1
public/images/SourceSansPro-Light-1767013371443.otf
Normal file
File diff suppressed because one or more lines are too long
1
public/images/SourceSansPro-Regular-1767013371323.otf
Normal file
1
public/images/SourceSansPro-Regular-1767013371323.otf
Normal file
File diff suppressed because one or more lines are too long
1
public/images/SourceSansPro-Semibold-1767013371370.otf
Normal file
1
public/images/SourceSansPro-Semibold-1767013371370.otf
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525005009945-1767013370860.png
Normal file
1
public/images/ZEG_525005009945-1767013370860.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525005309941-1767013371107.png
Normal file
1
public/images/ZEG_525005309941-1767013371107.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525803760447_main-1767013370961.png
Normal file
1
public/images/ZEG_525803760447_main-1767013370961.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525803790447_main-1767013370861.png
Normal file
1
public/images/ZEG_525803790447_main-1767013370861.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525900040644-1767013370916.png
Normal file
1
public/images/ZEG_525900040644-1767013370916.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525900240841-1767013371270.png
Normal file
1
public/images/ZEG_525900240841-1767013371270.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525900250841-1767013371111.png
Normal file
1
public/images/ZEG_525900250841-1767013371111.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525907650741-1767013370913.png
Normal file
1
public/images/ZEG_525907650741-1767013370913.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_525907850741-1767013370902.png
Normal file
1
public/images/ZEG_525907850741-1767013370902.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_526502829945_main-1767013371028.png
Normal file
1
public/images/ZEG_526502829945_main-1767013371028.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_526502839945_main-1767013370952.png
Normal file
1
public/images/ZEG_526502839945_main-1767013370952.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_526502849945_main-1767013371016.png
Normal file
1
public/images/ZEG_526502849945_main-1767013371016.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_526801000737_main-1767013370961.png
Normal file
1
public/images/ZEG_526801000737_main-1767013370961.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_526801130636_main-1767013370953.png
Normal file
1
public/images/ZEG_526801130636_main-1767013370953.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ZEG_526801140636_main-1767013370955.png
Normal file
1
public/images/ZEG_526801140636_main-1767013370955.png
Normal file
File diff suppressed because one or more lines are too long
1
public/images/at-1767013371195.svg
Normal file
1
public/images/at-1767013371195.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWF0IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMTcwLjdoNTEydjE3MC42SDB6Ii8+CiAgPHBhdGggZmlsbD0iI2M4MTAyZSIgZD0iTTAgMGg1MTJ2MTcwLjdIMHptMCAzNDEuM2g1MTJWNTEySDB6Ii8+Cjwvc3ZnPgo=
|
||||
1
public/images/be-1767013371269.svg
Normal file
1
public/images/be-1767013371269.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWJlIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utd2lkdGg9IjFwdCI+CiAgICA8cGF0aCBmaWxsPSIjMDAwMDAxIiBkPSJNMCAwaDE3MC43djUxMkgweiIvPgogICAgPHBhdGggZmlsbD0iI2ZmZDkwYyIgZD0iTTE3MC43IDBoMTcwLjZ2NTEySDE3MC43eiIvPgogICAgPHBhdGggZmlsbD0iI2YzMTgzMCIgZD0iTTM0MS4zIDBINTEydjUxMkgzNDEuM3oiLz4KICA8L2c+Cjwvc3ZnPgo=
|
||||
1
public/images/bulls_typo-1767013371348.svg
Normal file
1
public/images/bulls_typo-1767013371348.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzUiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCAxMzUgMTAiPgogIDxwYXRoIGQ9Ik02Ny43ODIxMjUsMCBMNjQuMjc5OTY0OCw4LjAxMjcwMzEgTDc5LDguMDEyNzAzMSBMNzguMTIwMzU2MywxMCBMNTYsMTAgTDYwLjE0NjEzNzksMC40MzE5MDU0NjUgQzYwLjI1OTg2NzIsMC4xNjk1NzE2NCA2MC41MTY5MzA3LDAgNjAuODAwOTYwOSwwIEw2MC44MDA5NjA5LDAgTDY3Ljc4MjEyNSwwIFogTTQwLjc3ODIwNjQsMCBDNDAuNzc4MjA2NCwwIDM4LjA5MDEyNDQsNi4xODYwNDUxNCAzNy43MzQ1ODcxLDcuMDA3NTYyMzMgQzM3LjM3ODE4MTksNy44MjkwNzk1MiAzNy40Nzc5ODY5LDguMDE0MDAyMTMgMzguNzUzNzU1NSw4LjAxNDAwMjEzIEwzOC43NTM3NTU1LDguMDE0MDAyMTMgTDQ2LjY5NDE4NTMsOC4wMTIyMjk3MSBDNDcuOTIxMDYzOCw4LjAxNTE4Mzc0IDQ4LjIwODYxOCw3LjgwOTg3ODI5IDQ4LjU0Nzk1NTEsNy4wMzA2MDM4IEM0OC43MzcxNTA3LDYuNTk1NDc0NDIgNTAuNzYxODkwOSwxLjg4OTk5MTczIDUxLjM4OTkzOTMsMC40Mjk4MTIxMjMgQzUxLjUwMjQ3MzEsMC4xNjgzODAwMDcgNTEuNzU1NjAxOCwwLjAwMDI5NTQwMzUyMSA1Mi4wMzUzNDUxLDAuMDAwMjk1NDAzNTIxIEw1Mi4wMzUzNDUxLDAuMDAwMjk1NDAzNTIxIEw1OSwwIEM1OSwwIDU3Ljg2NzcxOTIsMi43MTM4NzIxNSA1Ni4zMzE1ODk4LDYuMzMwNDk3NDYgQzU0Ljc5NDg4MTcsOS45NDcxMjI3NyA1NC42NTM0MTg5LDEwIDQ5LjY1NjUxNDEsMTAgTDQ5LjY1NjUxNDEsMTAgTDMzLjk1Njc1LDEwIEMyOS45NDgwNTk2LDEwIDI5LjM2NjI5NzUsOS42NTY3NDExMSAzMC41MzMwMDM4LDYuODE0NjYzODMgTDMwLjUzMzAwMzgsNi44MTQ2NjM4MyBMMzMuMjY5Mzk3MiwwLjQzMTI4OTE0MSBDMzMuMzgxNjQxNiwwLjE2OTI2NjIxOCAzMy42MzUwNTk2LDAgMzMuOTE1MzgxNiwwIEwzMy45MTUzODE2LDAgWiBNMTM1LDAgTDEzNC4xNDY4MywxLjk4Nzg4Nzc0IEwxMTQuMzk5NDU4LDEuOTg3ODg3NzQgQzExNC4xMjQyMjMsMS45ODc4ODc3NCAxMTMuODYyNzIsMi4xMzQ3MTE5NiAxMTMuNzg0NDE2LDIuMzMzMjM0ODYgTDExMy43ODQ0MTYsMi4zMzMyMzQ4NiBMMTEzLjEwMzM0MSwzLjk3NjA3MDkgTDEzMS43NjYxMzYsMy45NzYwNzA5IEMxMzMuMTcyMTEzLDMuOTc2MDcwOSAxMzIuOTM1NzM4LDQuODczMjY0NCAxMzIuODQ2MDM5LDUuMDc0NDQ2MDkgTDEzMi44NDYwMzksNS4wNzQ0NDYwOSBMMTMxLjM5NTY1LDguNDM1NzQ1OTQgQzEzMS4xNTIyNjMsOC45MTM0NDE2NSAxMzAuNTMzNDIzLDkuOTk4MjI3NDcgMTI5LjA0NTA1MSw5Ljk5ODIyNzQ3IEwxMjkuMDQ1MDUxLDkuOTk4MjI3NDcgTDEwMywxMCBMMTAzLjg5NjQxMyw4LjAxMjcwMzEgTDEyMy41MjE2NTMsOC4wMTI3MDMxIEMxMjMuODE4MjE4LDguMDEyNzAzMSAxMjQuMTAwMTczLDcuODU0MzU3NDYgMTI0LjE4MzczNiw3LjY0MDQ3MjY3IEwxMjQuMTgzNzM2LDcuNjQwNDcyNjcgTDEyNC43Nzg5MSw2LjM3NzU0ODAxIEMxMjQuODY3MTQ5LDYuMTYwMTE4MTcgMTI0LjY3NDAxNyw1Ljk2MzM2NzggMTI0LjM3MjE5Myw1Ljk2MzM2NzggTDEyNC4zNzIxOTMsNS45NjMzNjc4IEwxMDYuMjc4ODYsNS45NjMzNjc4IEMxMDQuODk2ODQyLDUuOTYzMzY3OCAxMDUuMTE4MzE1LDUuMTE0OTE4NzYgMTA1LjM0MjcxMSw0LjYxODAyMDY4IEMxMDUuNTY1NjQ1LDQuMTIxMTIyNiAxMDYuNTM2NTY0LDEuNzA2OTQyMzkgMTA2Ljc3OTM2NywxLjIyODM2MDQxIEMxMDcuMDIyNDYxLDAuNzUwNjY0Njk3IDEwNy41NDQyOTcsMCAxMDkuMDMyNjcsMCBMMTA5LjAzMjY3LDAgTDEzNSwwIFogTTMyLDAgTDMwLjk2ODMwNzksMi4zODM2MTEwMSBDMzAuNzkzNDM1LDIuNzQ5MDI1MTcgMzAuMzk4OTQ3MywzLjEzNTQxMjk3IDI5Ljc2NDY2NzIsMy4yNzc1MDIwNyBMMjkuNzY0NjY3MiwzLjI3NzUwMjA3IEwyNi40MDY2OTY2LDMuOTc1ODM1OTkgTDI5LjgyMTEwNjEsNC45Njg5ODI2MyBDMjkuODU1OTA1Myw0Ljk4NzAwMjI1IDI5Ljg2ODE4NzMsNS4wMjk4MzU3NiAyOS44NTAzNDkxLDUuMDcxMTkyMjUgQzI5LjM4MDEyMjEsNi4xNjMyOTkwNyAyOC44NzA0MTcxLDcuMzQyODQ1MzMgMjguNzg2NzgyMiw3LjUyMTg1OTg2IEMyNy45NTQ4MTk2LDkuMzAzNDM4NSAyNy4zNzU4MDg4LDkuODAzODUyMDYgMjUuMzk5Mjc2Miw5Ljk0NDc1OTU0IEMyNC42NzE0MTg2LDkuOTk2NDU1MTYgMjMuNzU2MTEzNiwxMCAyMi41NjU5MjQ2LDEwIEMyMi41MDUwNjc1LDEwIDIyLjQ0MjQwMTIsOS45OTk5OTk5MSAyMi4zNzc5NzgxLDkuOTk5OTk5NzQgTDAsOS45OTk0MDkxOSBMMy41ODIyNjQxNCwxLjc1OTEyNzk3IEM0LjAzMTQzNjIsMC43MjU4MDY0NTIgNS4wODcxMDc1LDAgNi4xNDA3MzE4MSwwIEw2LjE0MDczMTgxLDAgTDMyLDAgWiBNOTEuNzgyMjY3OSwwIEw4OC4yODAxNTI0LDguMDEyNzAzMSBMMTAzLDguMDEyNzAzMSBMMTAyLjEyMDA3NCwxMCBMODAsMTAgTDg0LjE0NjM3ODEsMC40MzE5MDU0NjUgQzg0LjI1OTgxMjksMC4xNjk1NzE2NCA4NC41MTY4NzMxLDAgODQuODAwODk5NywwIEw4NC44MDA4OTk3LDAgTDkxLjc4MjI2NzksMCBaIE0yMS45MDAzNTQ2LDUuOTYzNjA2MjkgTDkuMjIyMDYzODIsNS45NjM2MDYyOSBMOC4zMjU0NzQyOCw4LjAxMjUyNTExIEwxOS41NTk3NDcsOC4wMTIyMjk3MSBDMjAuNzk5OTQxNSw4LjAxNTE4Mzc0IDIxLjA5MDkwOTEsNy44MDk1ODI4OSAyMS40MzM2MzY3LDcuMDMwMzA4NCBDMjEuNTMzMDYyOCw2LjgwNDYyMDExIDIxLjM1MzgwMzQsNy4yMTM0NTg1OCAyMS45MDAzNTQ2LDUuOTYzNjA2MjkgTDIxLjkwMDM1NDYsNS45NjM2MDYyOSBaIE0yMy4wMTEyOTUxLDEuOTg3NzcwMjkgTDExLjAxOTMzNjksMS45ODc3NzAyOSBDMTAuOTIyNTQyNywyLjIwODE0MTMyIDEwLjEyMDcwMDQsMy45NzU4MzU5OSAxMC4xMjA3MDA0LDMuOTc1ODM1OTkgTDEwLjEyMDcwMDQsMy45NzU4MzU5OSBMMjIuNzgwNTY4LDMuOTc1ODM1OTkgQzIyLjc4MDU2OCwzLjk3NTgzNTk5IDIzLjE3OTczNDYsMy4wOTExMDI0NSAyMy4zODM4NTA2LDIuNjM0NzA0MDEgQzIzLjUzMDM1NzksMi4zMDcxMDE1IDIzLjM0NjQxOTYsMS45ODgwNjU3IDIzLjAxMTI5NTEsMS45ODc3NzAyOSBMMjMuMDExMjk1MSwxLjk4Nzc3MDI5IFoiLz4KPC9zdmc+Cg==
|
||||
1
public/images/ch-1767013371194.svg
Normal file
1
public/images/ch-1767013371194.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWNoIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utd2lkdGg9IjFwdCI+CiAgICA8cGF0aCBmaWxsPSJyZWQiIGQ9Ik0wIDBoNTEydjUxMkgweiIvPgogICAgPGcgZmlsbD0iI2ZmZiI+CiAgICAgIDxwYXRoIGQ9Ik05NiAyMDhoMzIwdjk2SDk2eiIvPgogICAgICA8cGF0aCBkPSJNMjA4IDk2aDk2djMyMGgtOTZ6Ii8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K
|
||||
1
public/images/de-1767013371122.svg
Normal file
1
public/images/de-1767013371122.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWRlIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPHBhdGggZmlsbD0iI2ZjMCIgZD0iTTAgMzQxLjNoNTEyVjUxMkgweiIvPgogIDxwYXRoIGZpbGw9IiMwMDAwMDEiIGQ9Ik0wIDBoNTEydjE3MC43SDB6Ii8+CiAgPHBhdGggZmlsbD0icmVkIiBkPSJNMCAxNzAuN2g1MTJ2MTcwLjZIMHoiLz4KPC9zdmc+Cg==
|
||||
1
public/images/fr-1767013371270.svg
Normal file
1
public/images/fr-1767013371270.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWZyIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMGg1MTJ2NTEySDB6Ii8+CiAgPHBhdGggZmlsbD0iIzAwMDA5MSIgZD0iTTAgMGgxNzAuN3Y1MTJIMHoiLz4KICA8cGF0aCBmaWxsPSIjZTEwMDBmIiBkPSJNMzQxLjMgMEg1MTJ2NTEySDM0MS4zeiIvPgo8L3N2Zz4K
|
||||
1
public/images/gb-1767013371195.svg
Normal file
1
public/images/gb-1767013371195.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWdiIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPHBhdGggZmlsbD0iIzAxMjE2OSIgZD0iTTAgMGg1MTJ2NTEySDB6Ii8+CiAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUxMiAwdjY0TDMyMiAyNTZsMTkwIDE4N3Y2OWgtNjdMMjU0IDMyNCA2OCA1MTJIMHYtNjhsMTg2LTE4N0wwIDc0VjBoNjJsMTkyIDE4OEw0NDAgMHoiLz4KICA8cGF0aCBmaWxsPSIjQzgxMDJFIiBkPSJtMTg0IDMyNCAxMSAzNEw0MiA1MTJIMHYtM3ptMTI0LTEyIDU0IDggMTUwIDE0N3Y0NXpNNTEyIDAgMzIwIDE5NmwtNC00NEw0NjYgMHpNMCAxbDE5MyAxODktNTktOEwwIDQ5eiIvPgogIDxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik0xNzYgMHY1MTJoMTYwVjB6TTAgMTc2djE2MGg1MTJWMTc2eiIvPgogIDxwYXRoIGZpbGw9IiNDODEwMkUiIGQ9Ik0wIDIwOHY5Nmg1MTJ2LTk2ek0yMDggMHY1MTJoOTZWMHoiLz4KPC9zdmc+Cg==
|
||||
1
public/images/ico_facebook-1767013371320.svg
Normal file
1
public/images/ico_facebook-1767013371320.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDExIDIwIj4KICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMS4zNjYwOCw2LjQ1Mjg0IEwyMS4zNjYwOCw5LjQ5ODg0IEwxOS41NTQ0OCw5LjQ5ODg0IEMxOC44OTI4OCw5LjQ5ODg0IDE4LjQ0Njg4LDkuNjM3NjQgMTguMjE2MDgsOS45MTQ0NCBDMTcuOTg1MjgsMTAuMTkxMjQgMTcuODcwMDgsMTAuNjA2ODQgMTcuODcwMDgsMTEuMTYwNDQgTDE3Ljg3MDA4LDEzLjM0MTI0IEwyMS4yNTA4OCwxMy4zNDEyNCBMMjAuODAwODgsMTYuNzU2ODQgTDE3Ljg3MDA4LDE2Ljc1Njg0IEwxNy44NzAwOCwyNS41MTQ0NCBMMTQuMzM5MjgsMjUuNTE0NDQgTDE0LjMzOTI4LDE2Ljc1Njg0IEwxMS4zOTY4OCwxNi43NTY4NCBMMTEuMzk2ODgsMTMuMzQxMjQgTDE0LjMzOTI4LDEzLjM0MTI0IEwxNC4zMzkyOCwxMC44MjYwNCBDMTQuMzM5MjgsOS4zOTUyNCAxNC43MzkyOCw4LjI4NTY0IDE1LjUzOTI4LDcuNDk3MjQgQzE2LjMzOTI4LDYuNzA4NDQgMTcuNDA0NDgsNi4zMTQ0NCAxOC43MzUyOCw2LjMxNDQ0IEMxOS44NjYwOCw2LjMxNDQ0IDIwLjc0Mjg4LDYuMzYwNDQgMjEuMzY2MDgsNi40NTI4NCBaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTEgLTYpIi8+Cjwvc3ZnPgo=
|
||||
1
public/images/ico_instagram-1767013371338.svg
Normal file
1
public/images/ico_instagram-1767013371338.svg
Normal file
File diff suppressed because one or more lines are too long
1
public/images/ico_youtube-1767013371342.svg
Normal file
1
public/images/ico_youtube-1767013371342.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMCAwIDIwIDE0Ij4KICA8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMy44ODM4MzY3LDE4LjU1OTE4MzcgTDE5LjE3NTI2NTMsMTUuODI2MTIyNCBMMTMuODgzODM2NywxMy4wNjA0MDgyIEwxMy44ODM4MzY3LDE4LjU1OTE4MzcgWiBNMTUuOTA2Mjg1Nyw5LjEzNTUxMDIgQzE3LjEzMDc3NTUsOS4xMzU1MTAyIDE4LjMxMzIyNDUsOS4xNTE4MzY3MyAxOS40NTQwNDA4LDkuMTg0ODk3OTYgQzIwLjU5NDQ0OSw5LjIxNzU1MTAyIDIxLjQzMDc3NTUsOS4yNTIyNDQ5IDIxLjk2MzAyMDQsOS4yODg1NzE0MyBMMjIuNzYwOTc5Niw5LjMzMjI0NDkgQzIyLjc2ODMyNjUsOS4zMzIyNDQ5IDIyLjgyOTk1OTIsOS4zMzc5NTkxOCAyMi45NDY2OTM5LDkuMzQ4NTcxNDMgQzIzLjA2MzQyODYsOS4zNTk1OTE4NCAyMy4xNDcxMDIsOS4zNzA2MTIyNCAyMy4xOTgxMjI0LDkuMzgxNjMyNjUgQzIzLjI0OTE0MjksOS4zOTIyNDQ5IDIzLjMzNDg1NzEsOS40MDg5Nzk1OSAyMy40NTUyNjUzLDkuNDMwNjEyMjQgQzIzLjU3NTI2NTMsOS40NTI2NTMwNiAyMy42NzkzNDY5LDkuNDgxNjMyNjUgMjMuNzY2NjkzOSw5LjUxNzk1OTE4IEMyMy44NTQwNDA4LDkuNTU0NjkzODggMjMuOTU2MDgxNiw5LjYwMjA0MDgyIDI0LjA3MjgxNjMsOS42NjA0MDgxNiBDMjQuMTg5NTUxLDkuNzE4MzY3MzUgMjQuMzAyMjA0MSw5Ljc4OTc5NTkyIDI0LjQxMTU5MTgsOS44NzM0NjkzOSBDMjQuNTIwOTc5Niw5Ljk1NzE0Mjg2IDI0LjYyNjY5MzksMTAuMDUzODc3NiAyNC43Mjg3MzQ3LDEwLjE2MzI2NTMgQzI0Ljc3MjQwODIsMTAuMjA2OTM4OCAyNC44Mjg3MzQ3LDEwLjI3NDI4NTcgMjQuODk4MTIyNCwxMC4zNjUzMDYxIEMyNC45Njc1MTAyLDEwLjQ1NjMyNjUgMjUuMDczMjI0NSwxMC42Njk3OTU5IDI1LjIxNTI2NTMsMTEuMDA0ODk4IEMyNS4zNTczMDYxLDExLjM0IDI1LjQ1NDA0MDgsMTEuNzA4MTYzMyAyNS41MDUwNjEyLDEyLjEwODk3OTYgQzI1LjU2MzAyMDQsMTIuNTc1NTEwMiAyNS42MDg3MzQ3LDEzLjA3MzA2MTIgMjUuNjQxMzg3OCwxMy42MDEyMjQ1IEMyNS42NzQ0NDksMTQuMTI5Nzk1OSAyNS42OTQ0NDksMTQuNTQzMjY1MyAyNS43MDE3OTU5LDE0Ljg0MjA0MDggTDI1LjcwMTc5NTksMTYuNzY2MTIyNCBDMjUuNzA5MTQyOSwxNy44MjMyNjUzIDI1LjY0MzQyODYsMTguODggMjUuNTA1MDYxMiwxOS45MzY3MzQ3IEMyNS40NTQwNDA4LDIwLjMzNzU1MSAyNS4zNjI2MTIyLDIwLjcgMjUuMjMxNTkxOCwyMS4wMjQ0ODk4IEMyNS4xMDA1NzE0LDIxLjM0ODU3MTQgMjQuOTgzODM2NywyMS41NzMwNjEyIDI0Ljg4MTc5NTksMjEuNjk2NzM0NyBMMjQuNzI4NzM0NywyMS44ODI0NDkgQzI0LjYyNjY5MzksMjEuOTkxODM2NyAyNC41MjA5Nzk2LDIyLjA4ODU3MTQgMjQuNDExNTkxOCwyMi4xNzIyNDQ5IEMyNC4zMDIyMDQxLDIyLjI1NjMyNjUgMjQuMTg5NTUxLDIyLjMyNTMwNjEgMjQuMDcyODE2MywyMi4zOCBDMjMuOTU2MDgxNiwyMi40MzQ2OTM5IDIzLjg1NDA0MDgsMjIuNDgwNDA4MiAyMy43NjY2OTM5LDIyLjUxNjczNDcgQzIzLjY3OTM0NjksMjIuNTUzMDYxMiAyMy41NzUyNjUzLDIyLjU4MjQ0OSAyMy40NTUyNjUzLDIyLjYwNDA4MTYgQzIzLjMzNDg1NzEsMjIuNjI2MTIyNCAyMy4yNDc1MTAyLDIyLjY0MjQ0OSAyMy4xOTI4MTYzLDIyLjY1MzQ2OTQgQzIzLjEzODEyMjQsMjIuNjY0MDgxNiAyMy4wNTQwNDA4LDIyLjY3NTEwMiAyMi45NDEzODc4LDIyLjY4NjEyMjQgQzIyLjgyODMyNjUsMjIuNjk3MTQyOSAyMi43NjgzMjY1LDIyLjcwMjQ0OSAyMi43NjA5Nzk2LDIyLjcwMjQ0OSBDMjAuOTMxNTkxOCwyMi44NDEyMjQ1IDE4LjY0NjY5MzksMjIuOTEwMjA0MSAxNS45MDYyODU3LDIyLjkxMDIwNDEgQzE0LjM5NzcxNDMsMjIuODk1NTEwMiAxMy4wODc1MTAyLDIyLjg3MTgzNjcgMTEuOTc2MDgxNiwyMi44MzkxODM3IEMxMC44NjQ2NTMxLDIyLjgwNjUzMDYgMTAuMTM0MDQwOCwyMi43NzkxODM3IDkuNzg0MjQ0OSwyMi43NTcxNDI5IEw5LjI0ODMyNjUzLDIyLjcxMzQ2OTQgTDguODU0ODU3MTQsMjIuNjY5Nzk1OSBDOC41OTI0MDgxNiwyMi42MzM0Njk0IDguMzk0MDQwODIsMjIuNTk2NzM0NyA4LjI1OTM0Njk0LDIyLjU2MDQwODIgQzguMTI0MjQ0OSwyMi41MjQwODE2IDcuOTM4NTMwNjEsMjIuNDQ3MzQ2OSA3LjcwMTc5NTkyLDIyLjMzMTAyMDQgQzcuNDY0NjUzMDYsMjIuMjE0Mjg1NyA3LjI1ODkzODc4LDIyLjA2NDg5OCA3LjA4MzgzNjczLDIxLjg4MjQ0OSBDNy4wNDAxNjMyNywyMS44Mzg3NzU1IDYuOTgzODM2NzMsMjEuNzcxNDI4NiA2LjkxNDQ0ODk4LDIxLjY4MDQwODIgQzYuODQ1MDYxMjIsMjEuNTg5Mzg3OCA2LjczOTc1NTEsMjEuMzc1OTE4NCA2LjU5NzMwNjEyLDIxLjA0MDgxNjMgQzYuNDU1MjY1MzEsMjAuNzA1NzE0MyA2LjM1ODUzMDYxLDIwLjMzNzU1MSA2LjMwNzkxODM3LDE5LjkzNjczNDcgQzYuMjQ5NTUxMDIsMTkuNDcwMjA0MSA2LjIwMzgzNjczLDE4Ljk3MjY1MzEgNi4xNzExODM2NywxOC40NDQ0ODk4IEM2LjEzODEyMjQ1LDE3LjkxNTkxODQgNi4xMTgxMjI0NSwxNy41MDI0NDkgNi4xMTA3NzU1MSwxNy4yMDM2NzM1IEw2LjExMDc3NTUxLDE1LjI3OTU5MTggQzYuMTAzODM2NzMsMTQuMjIyODU3MSA2LjE2OTE0Mjg2LDEzLjE2NTcxNDMgNi4zMDc5MTgzNywxMi4xMDg5Nzk2IEM2LjM1ODUzMDYxLDExLjcwODE2MzMgNi40NDk5NTkxOCwxMS4zNDU3MTQzIDYuNTgwOTc5NTksMTEuMDIxMjI0NSBDNi43MTI0MDgxNiwxMC42OTcxNDI5IDYuODI4NzM0NjksMTAuNDczMDYxMiA2LjkzMDc3NTUxLDEwLjM0ODk3OTYgTDcuMDgzODM2NzMsMTAuMTYzMjY1MyBDNy4xODU4Nzc1NSwxMC4wNTM4Nzc2IDcuMjkxNTkxODQsOS45NTcxNDI4NiA3LjQwMDk3OTU5LDkuODczNDY5MzkgQzcuNTEwMzY3MzUsOS43ODk3OTU5MiA3LjYyMzQyODU3LDkuNzE4MzY3MzUgNy43Mzk3NTUxLDkuNjYwNDA4MTYgQzcuODU2NDg5OCw5LjYwMjA0MDgyIDcuOTU4NTMwNjEsOS41NTQ2OTM4OCA4LjA0NTg3NzU1LDkuNTE3OTU5MTggQzguMTMzMjI0NDksOS40ODE2MzI2NSA4LjIzNzMwNjEyLDkuNDUyNjUzMDYgOC4zNTc3MTQyOSw5LjQzMDYxMjI0IEM4LjQ3NzcxNDI5LDkuNDA4OTc5NTkgOC41NjM0Mjg1Nyw5LjM5MjI0NDkgOC42MTQ0NDg5OCw5LjM4MTYzMjY1IEM4LjY2NTQ2OTM5LDkuMzcwNjEyMjQgOC43NDkxNDI4Niw5LjM1OTU5MTg0IDguODY1ODc3NTUsOS4zNDg1NzE0MyBDOC45ODI2MTIyNCw5LjMzNzk1OTE4IDkuMDQ0MjQ0OSw5LjMzMjI0NDkgOS4wNTE1OTE4NCw5LjMzMjI0NDkgQzEwLjg4MDk3OTYsOS4yMDEyMjQ0OSAxMy4xNjU4Nzc2LDkuMTM1NTEwMiAxNS45MDYyODU3LDkuMTM1NTEwMiBaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNiAtOSkiLz4KPC9zdmc+Cg==
|
||||
1
public/images/it-1767013371270.svg
Normal file
1
public/images/it-1767013371270.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWl0IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utd2lkdGg9IjFwdCI+CiAgICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDUxMnY1MTJIMHoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDkyNDYiIGQ9Ik0wIDBoMTcwLjd2NTEySDB6Ii8+CiAgICA8cGF0aCBmaWxsPSIjY2UyYjM3IiBkPSJNMzQxLjMgMEg1MTJ2NTEySDM0MS4zeiIvPgogIDwvZz4KPC9zdmc+Cg==
|
||||
1
public/images/logo-desktop-1767013370838.svg
Normal file
1
public/images/logo-desktop-1767013370838.svg
Normal file
File diff suppressed because one or more lines are too long
1
public/images/logo-mobile-1767013370855.svg
Normal file
1
public/images/logo-mobile-1767013370855.svg
Normal file
File diff suppressed because one or more lines are too long
1
public/images/lt-1767013371271.svg
Normal file
1
public/images/lt-1767013371271.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWx0IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utd2lkdGg9IjFwdCIgdHJhbnNmb3JtPSJzY2FsZSguNTEzMTQgMS4wMzIyKSI+CiAgICA8cmVjdCB3aWR0aD0iMTA2MyIgaGVpZ2h0PSI3MDguNyIgZmlsbD0iIzAwNmE0NCIgcng9IjAiIHJ5PSIwIiB0cmFuc2Zvcm09InNjYWxlKC45Mzg2NSAuNjk2ODYpIi8+CiAgICA8cmVjdCB3aWR0aD0iMTA2MyIgaGVpZ2h0PSIyMzYuMiIgeT0iNDc1LjYiIGZpbGw9IiNjMTI3MmQiIHJ4PSIwIiByeT0iMCIgdHJhbnNmb3JtPSJzY2FsZSguOTM4NjUgLjY5Njg2KSIvPgogICAgPHBhdGggZmlsbD0iI2ZkYjkxMyIgZD0iTTAgMGg5OTcuOHYxNjQuNkgweiIvPgogIDwvZz4KPC9zdmc+Cg==
|
||||
1
public/images/nl-1767013371195.svg
Normal file
1
public/images/nl-1767013371195.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLW5sIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPHBhdGggZmlsbD0iI2FlMWMyOCIgZD0iTTAgMGg1MTJ2MTcwLjdIMHoiLz4KICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAxNzAuN2g1MTJ2MTcwLjZIMHoiLz4KICA8cGF0aCBmaWxsPSIjMjE0NjhiIiBkPSJNMCAzNDEuM2g1MTJWNTEySDB6Ii8+Cjwvc3ZnPgo=
|
||||
1
public/images/pl-1767013371269.svg
Normal file
1
public/images/pl-1767013371269.svg
Normal file
@@ -0,0 +1 @@
|
||||
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLXBsIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPGcgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01MTIgNTEySDBWMGg1MTJ6Ii8+CiAgICA8cGF0aCBmaWxsPSIjZGMxNDNjIiBkPSJNNTEyIDUxMkgwVjI1Nmg1MTJ6Ii8+CiAgPC9nPgo8L3N2Zz4K
|
||||
1232
public/index.html
Normal file
1232
public/index.html
Normal file
File diff suppressed because it is too large
Load Diff
24
src/App.js
Normal file
24
src/App.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import Header from './components/Header';
|
||||
import Hero from './components/Hero';
|
||||
import TopEBikes from './components/TopEBikes';
|
||||
import ProductSections from './components/ProductSections';
|
||||
import Footer from './components/Footer';
|
||||
import CookieConsent from './components/CookieConsent';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<Header />
|
||||
<main>
|
||||
<Hero />
|
||||
<TopEBikes />
|
||||
<ProductSections />
|
||||
</main>
|
||||
<Footer />
|
||||
<CookieConsent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
50
src/components/CookieConsent.js
Normal file
50
src/components/CookieConsent.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
const CookieConsent = () => {
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t border-gray-200 shadow-lg">
|
||||
<div className="container-max py-4">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0">
|
||||
<div className="flex-1 pr-4">
|
||||
<h3 className="font-bold text-bulls-gray mb-2">Privacy Settings</h3>
|
||||
<p className="text-sm text-gray-600 leading-relaxed">
|
||||
We use Cookies to offer a variety of services, to continuously improve them and to display advertisements based on your interests on our website, social media and partner websites. By clicking on "OK" you consent to the use of Cookies. You can view, change or revoke your Cookie settings at any time via the privacy policy page. You can find out more in the privacy policy.
|
||||
</p>
|
||||
<div className="flex space-x-2 mt-2">
|
||||
<a href="#" className="text-xs text-bulls-blue hover:underline">Privacy Policy</a>
|
||||
<a href="#" className="text-xs text-bulls-blue hover:underline">Imprint</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-3">
|
||||
<button
|
||||
onClick={() => setIsVisible(false)}
|
||||
className="px-6 py-2 text-sm border border-gray-300 rounded hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
More
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsVisible(false)}
|
||||
className="px-6 py-2 text-sm border border-gray-300 rounded hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
Deny
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsVisible(false)}
|
||||
className="px-6 py-2 text-sm bg-bulls-gray text-white rounded hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
Accept All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CookieConsent;
|
||||
84
src/components/Footer.js
Normal file
84
src/components/Footer.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer className="bg-white">
|
||||
{/* Newsletter section */}
|
||||
<div className="border-t border-gray-200 py-12">
|
||||
<div className="container-max text-center">
|
||||
<h3 className="text-2xl font-bold text-bulls-gray mb-4">
|
||||
STAY UPDATED WITH OUR LATEST NEWS
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-8">
|
||||
Subscribe to our newsletter and be the first to know about new products and updates
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<button className="btn-primary inline-flex items-center space-x-2">
|
||||
<span>FIND YOUR DEALER</span>
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main footer */}
|
||||
<div className="bg-bulls-gray text-white py-12">
|
||||
<div className="container-max">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
{/* Company info */}
|
||||
<div>
|
||||
<div className="text-2xl font-bold mb-4">
|
||||
<span className="bg-white text-black px-2 py-1 mr-1 transform -skew-x-12">BULLS</span>
|
||||
</div>
|
||||
<p className="text-gray-300 text-sm leading-relaxed">
|
||||
Premium E-Bikes and Mountain Bikes for every adventure. Experience the future of cycling with BULLS.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick links */}
|
||||
<div>
|
||||
<h4 className="font-bold mb-4">QUICK LINKS</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">E-Bikes</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Mountain Bikes</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Service</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Dealer Search</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Support */}
|
||||
<div>
|
||||
<h4 className="font-bold mb-4">SUPPORT</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Contact</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">FAQ</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Warranty</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Manual Downloads</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal */}
|
||||
<div>
|
||||
<h4 className="font-bold mb-4">LEGAL</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Imprint</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Data Privacy Notice</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Cookies</a></li>
|
||||
<li><a href="#" className="text-gray-300 hover:text-white transition-colors">Terms</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-600 mt-8 pt-8 text-center">
|
||||
<p className="text-gray-400 text-sm">
|
||||
© 2024 BULLS Bikes. All rights reserved. | Prices include VAT and are subject to change.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
83
src/components/Header.js
Normal file
83
src/components/Header.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Search, Menu, X, MapPin, User } from 'lucide-react';
|
||||
|
||||
const Header = () => {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-black bg-opacity-90 backdrop-blur-sm">
|
||||
{/* Top bar */}
|
||||
<div className="bg-gray-800 text-white text-sm py-2">
|
||||
<div className="container-max flex justify-between items-center">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>Dealer search</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<User className="w-4 h-4" />
|
||||
<span>About us</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<span>Premium E-Bikes & Mountain Bikes</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main navigation */}
|
||||
<nav className="py-4">
|
||||
<div className="container-max flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center">
|
||||
<div className="text-white text-2xl font-bold">
|
||||
<span className="bg-white text-black px-2 py-1 mr-1 transform -skew-x-12">BULLS</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden lg:flex items-center space-x-8">
|
||||
<div className="nav-link cursor-pointer">E-BIKES</div>
|
||||
<div className="nav-link cursor-pointer">BIKES</div>
|
||||
<div className="nav-link cursor-pointer">SERVICE</div>
|
||||
</div>
|
||||
|
||||
{/* Right side icons */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<Search className="w-6 h-6 text-white cursor-pointer hover:text-bulls-blue transition-colors" />
|
||||
<div className="w-6 h-6 text-white cursor-pointer hover:text-bulls-blue transition-colors">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
className="lg:hidden text-white"
|
||||
onClick={toggleMobileMenu}
|
||||
>
|
||||
{isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="lg:hidden bg-black bg-opacity-95 mt-4">
|
||||
<div className="container-max py-4 space-y-4">
|
||||
<div className="nav-link cursor-pointer block py-2">E-BIKES</div>
|
||||
<div className="nav-link cursor-pointer block py-2">BIKES</div>
|
||||
<div className="nav-link cursor-pointer block py-2">SERVICE</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
78
src/components/Hero.js
Normal file
78
src/components/Hero.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
|
||||
const fadeUpPreset = (delay = 0, duration = 1.2) => ({
|
||||
initial: { opacity: 0, y: 30 },
|
||||
whileInView: { opacity: 1, y: 0 },
|
||||
viewport: { once: true, amount: 0.2 },
|
||||
transition: { delay, duration, ease: "easeOut" }
|
||||
});
|
||||
|
||||
const Hero = () => {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
if (shouldReduceMotion) {
|
||||
return (
|
||||
<section className="relative h-screen flex items-center justify-start" style={{
|
||||
backgroundImage: "url('https://www.bulls-bikes.com/fileadmin/_processed_/2/8/csm_BULLS_Grinder_3_Lifestyle_2024_01_b5c8a5e5a7.jpg')",
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}}>
|
||||
<div className="absolute inset-0 bg-black bg-opacity-40"></div>
|
||||
<div className="relative z-10 container-max">
|
||||
<div className="max-w-2xl">
|
||||
<h1 className="hero-text text-hero-sm md:text-hero mb-4">
|
||||
READY FOR 2026
|
||||
</h1>
|
||||
<h2 className="hero-text text-2xl md:text-4xl mb-6">
|
||||
ONE OF OUR HIGHLIGHTS:
|
||||
</h2>
|
||||
<h3 className="hero-text text-4xl md:text-6xl font-black">
|
||||
THE GRINDER 3
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.section
|
||||
{...fadeUpPreset(0.1, 0.8)}
|
||||
className="relative h-screen flex items-center justify-start"
|
||||
style={{
|
||||
backgroundImage: "url('https://www.bulls-bikes.com/fileadmin/_processed_/2/8/csm_BULLS_Grinder_3_Lifestyle_2024_01_b5c8a5e5a7.jpg')",
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black bg-opacity-40"></div>
|
||||
<div className="relative z-10 container-max">
|
||||
<div className="max-w-2xl">
|
||||
<motion.h1
|
||||
{...fadeUpPreset(0.2, 0.8)}
|
||||
className="hero-text text-hero-sm md:text-hero mb-4"
|
||||
>
|
||||
READY FOR 2026
|
||||
</motion.h1>
|
||||
<motion.h2
|
||||
{...fadeUpPreset(0.4, 0.8)}
|
||||
className="hero-text text-2xl md:text-4xl mb-6"
|
||||
>
|
||||
ONE OF OUR HIGHLIGHTS:
|
||||
</motion.h2>
|
||||
<motion.h3
|
||||
{...fadeUpPreset(0.6, 0.8)}
|
||||
className="hero-text text-4xl md:text-6xl font-black"
|
||||
>
|
||||
THE GRINDER 3
|
||||
</motion.h3>
|
||||
</div>
|
||||
</div>
|
||||
</motion.section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero;
|
||||
85
src/components/ProductSections.js
Normal file
85
src/components/ProductSections.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
const fadeUpPreset = (delay = 0, duration = 1.2) => ({
|
||||
initial: { opacity: 0, y: 30 },
|
||||
whileInView: { opacity: 1, y: 0 },
|
||||
viewport: { once: true, amount: 0.2 },
|
||||
transition: { delay, duration, ease: "easeOut" }
|
||||
});
|
||||
|
||||
const ProductSections = () => {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: "VUCA EVO | pinion",
|
||||
subtitle: "MAXIMUM PERFORMANCE WITH A MINIMUM OF MAINTENANCE",
|
||||
buttonText: "SEE ALL MODELS",
|
||||
image: "https://www.bulls-bikes.com/fileadmin/_processed_/9/3/csm_BULLS_VUCA_EVO_Pinion_Lifestyle_2024_01_b5c8a5e5a7.jpg",
|
||||
textColor: "text-white",
|
||||
buttonStyle: "btn-secondary"
|
||||
},
|
||||
{
|
||||
title: "NO LIMITS!",
|
||||
subtitle: "",
|
||||
buttonText: "CHOOSE YOUR BIKE",
|
||||
image: "https://www.bulls-bikes.com/fileadmin/_processed_/7/4/csm_BULLS_Lifestyle_MTB_2024_02_c8f5e5a7b5.jpg",
|
||||
textColor: "text-white",
|
||||
buttonStyle: "btn-secondary"
|
||||
},
|
||||
{
|
||||
title: "MAXIMUM OFFROAD POWER",
|
||||
subtitle: "",
|
||||
buttonText: "CHOOSE YOUR E-BIKE",
|
||||
image: "https://www.bulls-bikes.com/fileadmin/_processed_/5/6/csm_BULLS_Lifestyle_E-MTB_2024_03_a8c8f5e5a7.jpg",
|
||||
textColor: "text-white",
|
||||
buttonStyle: "btn-secondary"
|
||||
}
|
||||
];
|
||||
|
||||
const ProductSection = ({ section, index }) => {
|
||||
const SectionComponent = shouldReduceMotion ? 'div' : motion.div;
|
||||
const sectionProps = shouldReduceMotion ? {} : fadeUpPreset(index * 0.1, 1.0);
|
||||
|
||||
return (
|
||||
<SectionComponent
|
||||
{...sectionProps}
|
||||
className="relative h-96 md:h-[500px] flex items-center justify-center overflow-hidden"
|
||||
style={{
|
||||
backgroundImage: `url('${section.image}')`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black bg-opacity-40"></div>
|
||||
<div className="relative z-10 text-center px-4">
|
||||
<h2 className={`text-4xl md:text-6xl font-black mb-4 ${section.textColor}`}>
|
||||
{section.title}
|
||||
</h2>
|
||||
{section.subtitle && (
|
||||
<p className={`text-lg md:text-xl mb-8 max-w-2xl ${section.textColor}`}>
|
||||
{section.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<button className={`${section.buttonStyle} inline-flex items-center space-x-2`}>
|
||||
<span>{section.buttonText}</span>
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</SectionComponent>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-0">
|
||||
{sections.map((section, index) => (
|
||||
<ProductSection key={index} section={section} index={index} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductSections;
|
||||
112
src/components/TopEBikes.js
Normal file
112
src/components/TopEBikes.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { Star } from 'lucide-react';
|
||||
|
||||
const fadeUpPreset = (delay = 0, duration = 1.2) => ({
|
||||
initial: { opacity: 0, y: 20 },
|
||||
whileInView: { opacity: 1, y: 0 },
|
||||
viewport: { once: true, amount: 0.2 },
|
||||
transition: { delay, duration, ease: "easeOut" }
|
||||
});
|
||||
|
||||
const TopEBikes = () => {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const bikes = [
|
||||
{
|
||||
name: "COPPERHEAD EVO 1",
|
||||
price: "from 3,199.00*",
|
||||
rating: 5,
|
||||
image: "https://www.bulls-bikes.com/fileadmin/_processed_/4/1/csm_BULLS_Copperhead_EVO_1_29_black_2024_01_a8c8f5e5a7.jpg",
|
||||
colors: ['#000', '#666', '#999']
|
||||
},
|
||||
{
|
||||
name: "EVO CX 1",
|
||||
price: "4,799.00*",
|
||||
rating: 5,
|
||||
image: "https://www.bulls-bikes.com/fileadmin/_processed_/8/2/csm_BULLS_EVO_CX_1_black_2024_01_b5c8a5e5a7.jpg",
|
||||
colors: ['#000', '#333']
|
||||
},
|
||||
{
|
||||
name: "GRINDER 1",
|
||||
price: "3,499.00*",
|
||||
rating: 5,
|
||||
image: "https://www.bulls-bikes.com/fileadmin/_processed_/1/5/csm_BULLS_Grinder_1_black_2024_01_c8f5e5a7b5.jpg",
|
||||
colors: ['#000', '#666', '#999', '#ccc']
|
||||
}
|
||||
];
|
||||
|
||||
const BikeCard = ({ bike, index }) => {
|
||||
const CardComponent = shouldReduceMotion ? 'div' : motion.div;
|
||||
const cardProps = shouldReduceMotion ? {} : fadeUpPreset(index * 0.1, 0.8);
|
||||
|
||||
return (
|
||||
<CardComponent {...cardProps} className="product-card">
|
||||
<div className="aspect-w-16 aspect-h-12 bg-gray-100">
|
||||
<img
|
||||
src={bike.image}
|
||||
alt={bike.name}
|
||||
className="w-full h-64 object-contain p-4"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-bold text-bulls-gray mb-2">{bike.name}</h3>
|
||||
<div className="flex items-center mb-3">
|
||||
{[...Array(bike.rating)].map((_, i) => (
|
||||
<Star key={i} className="w-4 h-4 text-yellow-400 fill-current" />
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xl font-bold text-bulls-gray mb-4">{bike.price}</p>
|
||||
<div className="flex space-x-2 mb-4">
|
||||
{bike.colors.map((color, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-4 h-4 rounded-full border border-gray-300"
|
||||
style={{ backgroundColor: color }}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardComponent>
|
||||
);
|
||||
};
|
||||
|
||||
const SectionComponent = shouldReduceMotion ? 'section' : motion.section;
|
||||
const sectionProps = shouldReduceMotion ? {} : fadeUpPreset(0.05, 1.0);
|
||||
|
||||
return (
|
||||
<SectionComponent {...sectionProps} className="section-padding bg-bulls-light-gray">
|
||||
<div className="container-max">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-bulls-gray mb-4">TOP E-BIKES</h2>
|
||||
<div className="flex justify-center space-x-2 mb-8">
|
||||
<div className="w-2 h-2 bg-bulls-blue rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-gray-300 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-gray-300 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{bikes.map((bike, index) => (
|
||||
<BikeCard key={index} bike={bike} index={index} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<div className="flex justify-center space-x-1">
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
i < 4 ? 'bg-bulls-blue' : 'bg-gray-300'
|
||||
}`}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopEBikes;
|
||||
47
src/index.css
Normal file
47
src/index.css
Normal file
@@ -0,0 +1,47 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.btn-primary {
|
||||
@apply bg-bulls-blue text-white px-6 py-3 rounded-md font-medium hover:bg-blue-600 transition-colors duration-200;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-white text-bulls-gray border border-gray-300 px-6 py-3 rounded-md font-medium hover:bg-gray-50 transition-colors duration-200;
|
||||
}
|
||||
|
||||
.section-padding {
|
||||
@apply py-16 px-4 sm:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
.container-max {
|
||||
@apply max-w-7xl mx-auto;
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
@apply text-white font-bold tracking-wide;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
@apply bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
@apply text-white hover:text-bulls-blue transition-colors duration-200 font-medium;
|
||||
}
|
||||
}
|
||||
13
src/index.js
Normal file
13
src/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
25
tailwind.config.js
Normal file
25
tailwind.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'bulls-gray': '#2a2a2a',
|
||||
'bulls-light-gray': '#f5f5f5',
|
||||
'bulls-blue': '#00a8e6',
|
||||
'bulls-dark': '#1a1a1a',
|
||||
'bulls-accent': '#ff6b35'
|
||||
},
|
||||
fontFamily: {
|
||||
'sans': ['Arial', 'Helvetica', 'sans-serif']
|
||||
},
|
||||
fontSize: {
|
||||
'hero': ['4rem', { lineHeight: '1.1' }],
|
||||
'hero-sm': ['2.5rem', { lineHeight: '1.2' }]
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
5
vercel.json
Normal file
5
vercel.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"installCommand": "npm install",
|
||||
"buildCommand": "CI=false npm run build",
|
||||
"outputDirectory": "build"
|
||||
}
|
||||
Reference in New Issue
Block a user