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 = () => { >
+ ByteCamp Logo - 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; + } + } +}