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/AssignmentPage.jsx b/src/pages/AssignmentPage.jsx index 4601c70..5f768c2 100644 --- a/src/pages/AssignmentPage.jsx +++ b/src/pages/AssignmentPage.jsx @@ -20,6 +20,9 @@ const AssignmentPage = () => { const VITE_ASSIGNMENT_URL = import.meta.env.VITE_ASSIGNMENT_URL; const authUrl = import.meta.env.VITE_AUTH_URL; + const [searchTerm, setSearchTerm] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [loading, setLoading] = useState(false); useEffect(() => { document.title = "Assignment"; @@ -127,6 +130,7 @@ const AssignmentPage = () => { const handleSubmit = async (e) => { e.preventDefault(); + setLoading(true); if (!user.userId) return alert("Please login to submit an assignment."); @@ -139,6 +143,21 @@ const AssignmentPage = () => { formData.append("password", password); formData.append("description", description); + setTimeout(() => { + //replace with api + // if (editingIndex !== null) { + // const updatedProjects = [...projects]; + // updatedProjects[editingIndex] = newProject; + // setProjects(updatedProjects); + // } else { + // setProjects([...projects, newProject]); + // } + + alert(editingIndex !== null ? "Assignment updated!" : "Assignment submitted!"); + resetForm(); + setShowModal(false); + setLoading(false); + }, 2000); if (editingIndex !== null) { //edit mode await fetch(`${VITE_ASSIGNMENT_URL}/instructor/update/${assignmentId}`, { @@ -254,6 +273,15 @@ const AssignmentPage = () => { +
+ setSearchTerm(e.target.value)} + /> +
+ {showModal && (
@@ -303,24 +331,22 @@ const AssignmentPage = () => { />
-
- - setQrCodeNumber(e.target.value)} - required - /> -
- -
+
- setPassword(e.target.value)} - required - /> +
+ setPassword(e.target.value)} + required + /> + setShowPassword((prev) => !prev)} + > + {showPassword ? "Hide" : "Show"} + +
@@ -332,27 +358,23 @@ const AssignmentPage = () => { >
- {editingIndex === null && ( -
- - setFile(e.target.files[0])} - /> +
+ + setFile(e.target.files[0])} /> +
+ + {loading && ( +
+
+

Uploading...

)}
- -
@@ -363,23 +385,33 @@ const AssignmentPage = () => { {projects.length > 0 && (
- {projects.map((project, index) => ( -
-
- Student Name:{" "} - {project.studentname || project.studentName} |{" "} - CampID: {project.campid || project.campID} |{" "} - ProgramID:{" "} - {project.programid || project.programID} -
+ {projects + .filter((project) => { + if (!searchTerm.trim()) return true; + const regex = new RegExp(searchTerm, "i"); + return ( + regex.test(project.studentname || project.studentName || "") || + regex.test(project.campid || project.campID || "") || + regex.test(project.programid || project.programID || "") || + regex.test(project.title || "") || + regex.test(project.description || "") || + regex.test(project.fileName || "") || + regex.test(project.assignmenturl || "") || + regex.test(project.originalfile || "") || + regex.test(project.editablefile || "") + ); + }) + .map((project, index) => ( +
+
+

Student Name: {project.studentname || project.studentName}

+

CampID: {project.campid || project.campID}

+

ProgramID: {project.programid || project.programID}

+
{project.title &&

{project.title}

} {project.description &&

{project.description}

} - {project.fileName && ( -

- Uploaded File: {project.fileName} -

- )} + {project.fileName &&

Uploaded File: {project.fileName}

} {project.assignmenturl && (

@@ -412,19 +444,17 @@ const AssignmentPage = () => {

)} -
- - - +
+ + + +
-
- ))} + ))}
)}
); }; -export default AssignmentPage; +export default AssignmentPage; \ No newline at end of file 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/_assignment.scss b/src/scss/components/_assignment.scss index f83f856..6e5f81d 100644 --- a/src/scss/components/_assignment.scss +++ b/src/scss/components/_assignment.scss @@ -1,10 +1,10 @@ .assignment-page { max-width: 100%; padding: 20px; - margin-top: 70rem; + margin-top: 90rem; overflow-x: hidden; overflow-y: auto; - min-height: 100vh; + // min-height: 100vh; box-sizing: border-box; form { @@ -48,116 +48,55 @@ } } - // .project-list { - // display: grid; - // grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - // gap: 1.5rem; - // margin-top: 2rem; - // padding-bottom: 3rem; - // .project-item { - // background: #ffffff; - // border: 1px solid #e0e0e0; - // border-radius: 12px; - // padding: 1.5rem; - // box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); - // transition: transform 0.2s ease, box-shadow 0.2s ease; + .assignment-links a { + color: white !important; + } - // .project-meta { - // margin-bottom: 0.75rem; - - // strong { - // color: #34495e; - // } - // } - - // h4 { - // margin: 0.5rem 0; - // font-size: 1.2rem; - // color: #2d3436; - // } - - // p { - // margin: 0.25rem 0; - // color: #555; - // line-height: 1.4; - - // strong { - // color: #2d3436; - // } - // } - - // .action-buttons { - // display: flex; - // gap: 0.5rem; - // margin-top: 0.75rem; - - // button { - // background-color: #f4f4f4; - // border: 1px solid #ddd; - // border-radius: 6px; - // padding: 0.4rem 0.8rem; - // cursor: pointer; - // font-size: 0.9rem; - - // &:hover { - // background-color: #e9ecef; - // } - - // &:nth-child(1) { - // color: #2c3e50; - // } - - // &:nth-child(2) { - // color: #c0392b; - // } - - // &:nth-child(3) { - // color: #16a085; - // } - // } - // } - // } - // } .project-list { display: grid; - grid-template-columns: repeat(3, 1fr); // exactly 2 columns + grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-top: 2rem; - padding-bottom: 3rem; + padding-bottom: 7rem; .project-item { - background: #ffffff; - border: 1px solid #e0e0e0; + background: #0f0f1a; border-radius: 12px; padding: 1.5rem; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); transition: transform 0.2s ease, box-shadow 0.2s ease; overflow-wrap: break-word; - box-shadow: rgb(211, 0, 197) 0px 0px 15px, rgb(255, 42, 109) 0px 0px 25px; - + // box-shadow: rgb(211, 0, 197) 0px 0px 15px, rgb(255, 42, 109) 0px 0px 25px; + border: 2px solid rgb(75 187 236); + .project-meta { margin-bottom: 0.75rem; + color: lightgray; strong { - color: #34495e; + color: #00bfff; + font-weight: bold; } } h4 { margin: 0.5rem 0; font-size: 1.2rem; - color: #2d3436; + color: white; } p { - margin: 0.25rem 0; - color: #555; + margin: 1rem 0; + color: white; line-height: 1.4; + font-size: 15px; + // margin-bottom: 10px; strong { - color: #2d3436; + color: #00bfff; + font-weight: bold; } } @@ -167,19 +106,20 @@ margin-top: 0.75rem; button { - background-color: #f4f4f4; - border: 1px solid #ddd; + background-color: #000000; + border: 2px solid #00bfff; border-radius: 6px; padding: 0.4rem 0.8rem; cursor: pointer; font-size: 0.9rem; &:hover { - background-color: #e9ecef; + background-color: #E4E4E4; + border: 2px solid #E4E4E4; } &:nth-child(1) { - color: #2c3e50; + color: #007fff; } &:nth-child(2) { @@ -211,7 +151,7 @@ } .modal { - background: #fff; + background: #f3f3f3; padding: 2rem; border-radius: 12px; max-width: 500px; @@ -221,6 +161,10 @@ overflow: auto; } + .modal form { + background-color: #f3f3f3; + } + .modal h3 { margin-bottom: 1rem; font-size: 1.5rem; @@ -237,7 +181,7 @@ font-size: 0.9rem; font-weight: 500; margin-bottom: 0.4rem; - color: #555; + color: black } .modal input[type="text"], @@ -246,10 +190,11 @@ .modal textarea { width: 100%; padding: 0.6rem 0.8rem; - border: 1px solid #ccc; + border: 1px solid #BBBBBB; border-radius: 6px; font-size: 0.95rem; transition: border-color 0.2s ease; + background-color: white; &:focus { border-color: #007bff; @@ -288,7 +233,7 @@ } .modal-buttons button[type="button"] { - background-color: #e0e0e0; + background-color: #d3d3d3; color: #333; &:hover { @@ -312,7 +257,7 @@ background: #0f0f1a; border: 2px solid #00bfff; border-radius: 12px; - padding: 50px; + padding: 50px 100px; text-align: center; margin-bottom: 2rem; // box-shadow: 0 0 20px #00bfff; @@ -407,3 +352,70 @@ } } } + +// Search Bar Styling +.assignment-search-box { + margin: 1rem 0; + input { + width: 100%; + // padding: 0.5rem; + font-size: 1rem; + border-radius: 8px; + border: 1px solid #ccc; + background-color: #1a1a2b; + color: #fefefe; + box-shadow: rgb(211, 0, 197) 0px 0px 15px, rgb(255, 42, 109) 0px 0px 25px; + + &::placeholder { + color: #aaa; + } + + &:focus { + outline: none; + border-color: rgb(211, 0, 197); + } + } +} + +.password-field { + .input-wrapper { + position: relative; + + input { + width: 100%; + padding-right: 2.5rem; + } + + .eye-icon { + position: absolute; + top: 50%; + right: 0.75rem; + transform: translateY(-50%); + cursor: pointer; + color: #555; + user-select: none; + } + } +} + +.spinner-container { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 10px 0; +} + +.spinner { + width: 16px; + height: 16px; + border: 3px solid rgba(0, 0, 0, 0.2); + border-top: 3px solid #333; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} 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; + } + } +}