diff --git a/package-lock.json b/package-lock.json
index 889d019..748313d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,9 +15,11 @@
"@monaco-editor/react": "^4.7.0",
"@uiw/codemirror-theme-vscode": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9",
+ "framer-motion": "^12.9.7",
"monaco-editor": "^0.52.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^6.27.0",
"sass": "^1.86.3"
},
@@ -2676,6 +2678,33 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/framer-motion": {
+ "version": "12.9.7",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.9.7.tgz",
+ "integrity": "sha512-Eo5TYU6sEPPy82GDx32PJm++G+AkBCrzxtEQOWLnpQX896Q3LFrsYhMZ5YO5ct4wL7wyHU6hqlrpYXeexKAevg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.9.6",
+ "motion-utils": "^12.9.4",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2977,6 +3006,21 @@
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
"license": "MIT"
},
+ "node_modules/motion-dom": {
+ "version": "12.9.6",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.9.6.tgz",
+ "integrity": "sha512-IK9pm5zU8BIp3FCoUGF3T7AHVLVOlXxlwco/bIbcnpBtyYb2gDQhdOzUh2KSDJVjYl1MZ9vdq8tnFTTahX2lfg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.9.4"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.9.4",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.9.4.tgz",
+ "integrity": "sha512-BW3I65zeM76CMsfh3kHid9ansEJk9Qvl+K5cu4DVHKGsI52n76OJ4z2CUJUV+Mn3uEP9k1JJA3tClG0ggSrRcg==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -3197,6 +3241,15 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -3427,6 +3480,12 @@
"node": ">=8.0"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index a8ec0eb..1108c1d 100644
--- a/package.json
+++ b/package.json
@@ -17,9 +17,11 @@
"@monaco-editor/react": "^4.7.0",
"@uiw/codemirror-theme-vscode": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9",
+ "framer-motion": "^12.9.7",
"monaco-editor": "^0.52.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^6.27.0",
"sass": "^1.86.3"
},
diff --git a/public/images/battlesnake.png b/public/images/battlesnake.png
new file mode 100644
index 0000000..1f3206a
Binary files /dev/null and b/public/images/battlesnake.png differ
diff --git a/public/images/bytecamp.png b/public/images/bytecamp.png
new file mode 100644
index 0000000..304a862
Binary files /dev/null and b/public/images/bytecamp.png differ
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 3d39611..1796d90 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -5,6 +5,9 @@ import { Link } from "react-router-dom";
const authUrl = import.meta.env.VITE_AUTH_URL;
+// Using URL reference for ByteCamp logo
+const bytecampLogo = "/images/bytecamp.png";
+
const Navbar = () => {
const [glitchEffect, setGlitchEffect] = useState(false);
const [activeLink, setActiveLink] = useState("/");
@@ -74,8 +77,13 @@ const Navbar = () => {
>
+
- BATTLESNAKE
+ BYTECAMP
diff --git a/src/components/Services.jsx b/src/components/Services.jsx
index c4e59ce..c52f748 100644
--- a/src/components/Services.jsx
+++ b/src/components/Services.jsx
@@ -1,29 +1,55 @@
-import React from 'react';
-import '../scss/components/Services.scss';
-import ServiceCard from '../components/ServicesCard.jsx';
+import React from "react";
+import "../scss/components/_services.scss";
+import ServiceCard from "../components/ServicesCard.jsx";
+import { FaYoutube } from "react-icons/fa";
+import battlesnakeLogo from "../../public/images/battlesnake.png";
+import bytecampLogo from "../../public/images/bytecamp.png";
+
+// // Using URL references for public assets instead of direct imports
+// const battlesnakeLogo = "/images/battlesnake.png";
+// const bytecampLogo = "/images/bytecamp.png";
function Services() {
return (
- Develop your own algorithm to find food, stay alive, and eliminate others. Battlesnakes are controlled by a web server you deploy, running the code you write.
+
+ Develop your own algorithm to find food, stay alive, and eliminate
+ others. Battlesnakes are controlled by a web server you deploy, running
+ the code you write.
+
+ }
title="What is Battlesnake?"
desc="Battlesnake is a competitive game where your code is the controller. All you need is a web server that responds to the Battlesnake API."
- cta="What how it works"
+ cta="How it works?"
+ link="https://docs.battlesnake.com/"
/>
}
+ title="Tutorial"
+ desc="Learn how to create your first Battlesnake with an easy-to-follow video guide to get you started quickly."
+ cta="Watch tutorial"
+ link="https://www.youtube.com/watch?v=IA7CeGRfuNE"
/>
+ }
+ title="Are you ready to start?"
+ desc="Join ByteCamp coding bootcamp and start learning Battlesnake today!"
+ cta="Register here"
+ link="https://www.bytecamp.ca/#r"
/>
diff --git a/src/components/ServicesCard.jsx b/src/components/ServicesCard.jsx
index 473dee7..fd4ef7d 100644
--- a/src/components/ServicesCard.jsx
+++ b/src/components/ServicesCard.jsx
@@ -1,14 +1,95 @@
-import React from 'react';
-import '../scss/components/ServicesCard.scss';
+// src/components/ServicesCard.jsx
+import React from "react";
+import { motion, useInView, useAnimation } from "framer-motion";
+import { useRef, useEffect } from "react";
+import "../scss/components/_servicesCard.scss";
+
+function ServiceCard({ icon, title, desc, cta, link = "#", index = 0 }) {
+ const ref = useRef(null);
+ const isInView = useInView(ref, { once: true, amount: 0.8 });
+ const controls = useAnimation();
+
+ useEffect(() => {
+ if (isInView) {
+ controls.start("visible");
+ }
+ }, [isInView, controls]);
+
+ // Different animation variants based on the card index for a staggered effect
+ const getVariants = () => {
+ // Alternate between slide-in directions based on index
+ const direction = index % 2 === 0 ? 1 : -1;
+
+ return {
+ hidden: {
+ opacity: 0,
+ x: 60 * direction,
+ y: 20,
+ },
+ visible: {
+ opacity: 1,
+ x: 0,
+ y: 0,
+ transition: {
+ type: "spring",
+ duration: 0.8,
+ delay: index * 0.2, // Stagger effect based on index
+ damping: 15,
+ stiffness: 100,
+ },
+ },
+ };
+ };
-function ServiceCard({ icon, title, desc, cta }) {
return (
-
-
{icon}
-
{title}
+
+
+
+
+
+ {icon}
+
+
+
+ {title}
+
+
{desc}
- {cta} →
-
+
+
+ {cta} →
+
+
);
}
diff --git a/src/pages/SignUp.jsx b/src/pages/SignUp.jsx
index 9f6804b..655e4eb 100644
--- a/src/pages/SignUp.jsx
+++ b/src/pages/SignUp.jsx
@@ -2,7 +2,6 @@ import React from "react";
function SignUpForm() {
const authUrl = import.meta.env.VITE_AUTH_URL;
-
const [state, setState] = React.useState({
name: "",
email: "",
@@ -33,7 +32,6 @@ function SignUpForm() {
};
const googleAuth = () => {
-
window.open(`${authUrl}/auth/google`, "_self");
};
diff --git a/src/scss/components/Services.scss b/src/scss/components/Services.scss
deleted file mode 100644
index 563a6aa..0000000
--- a/src/scss/components/Services.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-.services {
- padding: 5rem 3rem;
- // background-image: url("../../../public/images/grid-background-3.png");
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
- background-attachment: fixed;
-
- h2 {
- font-size: 2.5rem;
- font-weight: 900;
- text-align: center;
- margin-bottom: 3rem;
- color: #fff;
- text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.7);
- background-color: rgba(0, 0, 0, 0.5);
- padding: 1.5rem;
- border-radius: 10px;
- }
-
- .cards {
- display: flex;
- justify-content: space-between;
- gap: 2rem;
- }
-}
diff --git a/src/scss/components/ServicesCard.scss b/src/scss/components/ServicesCard.scss
deleted file mode 100644
index f6a5848..0000000
--- a/src/scss/components/ServicesCard.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-.service-card {
- background-color: #fff;
- border: 2px solid #000;
- border-radius: 1rem;
- padding: 2rem;
- box-shadow: 6px 6px 0 #000;
- flex: 1;
-
- .icon {
- font-size: 2.5rem;
- margin-bottom: 1rem;
- }
-
- h3 {
- font-size: 1.5rem;
- font-weight: 700;
- margin-bottom: 1rem;
- }
-
- p {
- font-size: 1rem;
- line-height: 1.6;
- margin-bottom: 1.5rem;
- }
-
- .link {
- font-weight: bold;
- text-decoration: none;
- color: #202020;
- }
- }
-
\ No newline at end of file
diff --git a/src/scss/components/_hero.scss b/src/scss/components/_hero.scss
index b069433..56ed53b 100644
--- a/src/scss/components/_hero.scss
+++ b/src/scss/components/_hero.scss
@@ -93,7 +93,7 @@ $cyber-black: #1a1a1a;
margin-top: 0;
background-color: $dark-bg;
overflow: hidden;
-
+
// Matrix digital rain canvas
.matrix-rain {
position: absolute;
@@ -154,7 +154,7 @@ $cyber-black: #1a1a1a;
padding: 2rem;
// Add a subtle backdrop filter to make content more readable against the matrix rain
backdrop-filter: blur(2px);
-
+
@media (max-width: 768px) {
flex-direction: column-reverse;
gap: 2rem;
@@ -169,34 +169,37 @@ $cyber-black: #1a1a1a;
align-items: flex-start;
justify-content: center;
padding-right: 2rem;
-
+
@media (max-width: 768px) {
align-items: center;
text-align: center;
padding-right: 0;
}
}
-
+
// Title styling
&-title {
- font-size: 3.5rem;
+ font-size: 5rem;
font-weight: bold;
color: white;
margin-bottom: 1rem;
letter-spacing: 4px;
- text-shadow: 0 0 10px rgba($neon-blue, 0.7);
+ text-shadow: 0 0 10px rgba($neon-blue, 0.7), 0 0 20px rgba($neon-blue, 0.7),
+ 0 0 30px rgba($neon-blue, 0.7);
animation: flicker 3s infinite alternate;
-
+
&-highlight {
+ font-size: 5rem;
color: $neon-pink;
- text-shadow: 0 0 10px rgba($neon-pink, 0.7);
+ text-shadow: 0 0 10px rgba($neon-pink, 0.7),
+ 0 0 20px rgba($neon-pink, 0.7), 0 0 30px rgba($neon-pink, 0.7);
}
-
+
@media (max-width: 768px) {
- font-size: 2.5rem;
+ font-size: 5rem;
}
}
-
+
// Subtitle styling
&-subtitle {
font-size: 1.2rem;
@@ -205,12 +208,12 @@ $cyber-black: #1a1a1a;
max-width: 500px;
line-height: 1.6;
letter-spacing: 1px;
-
+
@media (max-width: 768px) {
font-size: 1rem;
}
}
-
+
// Logo styling
&-logo-container {
position: relative;
@@ -219,7 +222,7 @@ $cyber-black: #1a1a1a;
justify-content: center;
align-items: center;
max-width: 50%;
-
+
@media (max-width: 768px) {
max-width: 80%;
margin-bottom: 1rem;
@@ -231,7 +234,7 @@ $cyber-black: #1a1a1a;
height: auto;
border-radius: 8px;
filter: drop-shadow(0 0 10px rgba($neon-blue, 0.7))
- drop-shadow(0 0 20px rgba($neon-purple, 0.5));
+ drop-shadow(0 0 20px rgba($neon-purple, 1));
animation: pulse 4s ease-in-out infinite;
z-index: 2;
}
@@ -317,7 +320,7 @@ $cyber-black: #1a1a1a;
left: 0;
width: 100%;
height: 100%;
- background-color: rgba($neon-pink, 0.2);
+ background-color: rgba($neon-pink, 0.5);
opacity: 0;
z-index: 1;
diff --git a/src/scss/components/_navbar.scss b/src/scss/components/_navbar.scss
index 47fee7c..6c687a6 100644
--- a/src/scss/components/_navbar.scss
+++ b/src/scss/components/_navbar.scss
@@ -7,7 +7,7 @@ $dark-bg: #0d0221;
$cyber-black: #1a1a1a;
// Breakpoints
-$mobile-breakpoint: 768px;
+$mobile-breakpoint: 1160px;
@keyframes scanline {
0% {
@@ -202,6 +202,7 @@ $mobile-breakpoint: 768px;
background-color: rgba($dark-bg, 0.7);
&-text {
+ font-size: 1.2rem;
color: white;
letter-spacing: 2px;
animation: neon-flicker 5s infinite alternate;
diff --git a/src/scss/components/_services.scss b/src/scss/components/_services.scss
new file mode 100644
index 0000000..84e78f8
--- /dev/null
+++ b/src/scss/components/_services.scss
@@ -0,0 +1,77 @@
+// Services.scss
+$neon-pink: #ff2a6d;
+$neon-blue: #05d9e8;
+$neon-purple: #d300c5;
+$neon-yellow: #f7f500;
+$dark-bg: #0d0221;
+$cyber-black: #1a1a1a;
+
+@keyframes gridMovement {
+ 0% {
+ background-position: 0 0;
+ }
+ 100% {
+ background-position: 0 25px;
+ }
+}
+
+.services {
+ padding: 5rem 3rem;
+ background-color: $dark-bg;
+ position: relative;
+ overflow: hidden;
+
+ // Grid background effect
+ .grid-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image: linear-gradient(
+ rgba($neon-blue, 0.1) 1px,
+ transparent 1px
+ ),
+ linear-gradient(90deg, rgba($neon-blue, 0.1) 1px, transparent 1px);
+ background-size: 25px 25px;
+ z-index: 0;
+ opacity: 0.3;
+ pointer-events: none;
+ animation: gridMovement 15s linear infinite;
+ }
+
+ .services-heading {
+ font-size: 2rem;
+ font-weight: 700;
+ text-align: center;
+ margin-bottom: 4rem;
+ color: white;
+ text-shadow: 0 0 10px rgba($neon-pink, 0.5);
+ position: relative;
+ z-index: 1;
+ max-width: 900px;
+ margin-left: auto;
+ margin-right: auto;
+
+ // Responsive font size
+ @media (max-width: 768px) {
+ font-size: 1.5rem;
+ margin-bottom: 2.5rem;
+ }
+ }
+
+ .cards-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 2rem;
+ position: relative;
+ z-index: 1;
+ max-width: 1200px;
+ margin: 0 auto;
+
+ // Stack cards on small screens
+ @media (max-width: 768px) {
+ grid-template-columns: 1fr;
+ }
+ }
+}
diff --git a/src/scss/components/_servicesCard.scss b/src/scss/components/_servicesCard.scss
new file mode 100644
index 0000000..8bb5ed5
--- /dev/null
+++ b/src/scss/components/_servicesCard.scss
@@ -0,0 +1,115 @@
+// ServicesCard.scss
+$neon-pink: #ff2a6d;
+$neon-blue: #05d9e8;
+$neon-purple: #d300c5;
+$neon-yellow: #f7f500;
+$dark-bg: #0d0221;
+$cyber-black: #1a1a1a;
+
+@keyframes scanline {
+ 0% {
+ transform: translateY(-100%);
+ }
+ 100% {
+ transform: translateY(100%);
+ }
+}
+
+@keyframes borderGlow {
+ 0% {
+ box-shadow: 0 0 5px $neon-pink;
+ }
+ 50% {
+ box-shadow: 0 0 15px $neon-pink, 0 0 25px $neon-pink;
+ }
+ 100% {
+ box-shadow: 0 0 5px $neon-pink;
+ }
+}
+
+.service-card {
+ position: relative;
+ background-color: $cyber-black;
+ border-radius: 4px;
+ padding: 2rem;
+ color: white;
+ margin-bottom: 20px;
+ overflow: hidden;
+ border: 1px solid rgba($neon-pink, 0.5);
+ box-shadow: 0 0 10px rgba($neon-pink, 0.3);
+ width: 100%;
+
+ // The card border effect
+ .card-border {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: -1;
+ opacity: 0;
+ transition: opacity 0.5s ease;
+ }
+
+ // Scanline effect
+ .card-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, $neon-pink, transparent);
+ animation: scanline 2s linear infinite;
+ opacity: 0;
+ transition: opacity 0.5s ease;
+ }
+
+ &:hover {
+ animation: borderGlow 1.5s infinite;
+ border-color: $neon-pink;
+
+ .card-border {
+ opacity: 1;
+ }
+
+ .card-glow {
+ opacity: 0.7;
+ }
+ }
+
+ .icon {
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ transition: color 0.3s ease, text-shadow 0.3s ease;
+ }
+
+ h3 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin-bottom: 1rem;
+ letter-spacing: 1px;
+ color: white;
+ transition: color 0.3s ease, text-shadow 0.3s ease;
+ }
+
+ p {
+ font-size: 1rem;
+ line-height: 1.6;
+ margin-bottom: 1.5rem;
+ color: #aaa;
+ }
+
+ .link {
+ font-weight: bold;
+ text-decoration: none;
+ color: white;
+ transition: color 0.3s ease;
+ display: inline-block;
+ letter-spacing: 0.5px;
+ position: relative;
+
+ &:hover {
+ color: $neon-pink;
+ }
+ }
+}