feat: add projects
1622
package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"ahooks": "^3.7.8",
|
||||
"animate.css": "^4.1.1",
|
||||
"antd": "^5.9.2",
|
||||
"axios": "^1.3.2",
|
||||
"hamburger-react": "^2.5.0",
|
||||
"nodemon": "^2.0.20",
|
||||
@ -23,6 +24,7 @@
|
||||
"sass": "^1.58.0",
|
||||
"unstated-next": "^1.1.0",
|
||||
"uuid": "^9.0.0",
|
||||
"waterfalljs-layout": "^0.1.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
75
src/assets/data/projects.js
Normal file
@ -0,0 +1,75 @@
|
||||
import fenrir from "../pics/fenrir.jpg";
|
||||
import roslund from "../pics/roslund.jpg";
|
||||
import goliath from "../pics/goliath.jpg";
|
||||
import icarus from "../pics/icarus.jpg";
|
||||
import mach2 from "../pics/mach2.jpg";
|
||||
import osiris from "../pics/osiris.jpg";
|
||||
|
||||
import Modulus from "../pics/modulus.jpg";
|
||||
import Roomba from "../pics/roomba.jpg";
|
||||
import MRK from "../pics/fancypants.jpg";
|
||||
import Mongol from "../pics/mongol.jpg";
|
||||
import ma from "../pics/matchlock.jpg";
|
||||
|
||||
const projects = {
|
||||
active: [
|
||||
{
|
||||
title: "Fenrir",
|
||||
content: `Fenrir is a two-wheeled gravity-drive robot. Fenrir's unique design makes it a very fast and agile robot, but also an interesting challenge to control. You can find the current controller code at our github repo: https://github.com/illinoistechrobotics/fenrir.`,
|
||||
img: fenrir,
|
||||
},
|
||||
{
|
||||
title: "Roslund",
|
||||
content: `Roslund is a simple frame robot with mechanum drive. Mechanum wheels have rollers at a 45° angle to the wheel plane, which allows the robot to have omnidirectional movement. Roslund was the winner of MRDC in 2010 and 2011. Roslund was also awarded the best design award by Grant Imahara (from the Mythbusters) a couple years back.`,
|
||||
img: roslund,
|
||||
},
|
||||
{
|
||||
title: "Goliath",
|
||||
content: `Golaith is designed to effectively compete in the annual MRDC. Goliath is based on a relatively simple drive base consisting of a basic square steel frame, 2 large drive wheels powered by 3HP Ampflow Magmotors and two steel ball casters. The simplicity of this design makes Goliath a very durable and reliable robot that is able to easily withstand encounters with other robots and course obstacles. In addition, this robot was designed such that the center of mass is low to the ground and as close as possible to the drive wheels which, when coupled with its powerful drivetrain, allows it to easily climb and descend relatively steep inclines as well as move very slowly and precisely when necessary. Overall, these design features enable Goliath to reliably perform many competition tasks with minimal impact from any obstacles encountered along the way.`,
|
||||
img: goliath,
|
||||
},
|
||||
{
|
||||
title: "Icarus",
|
||||
content: `Icarus is a quadcopter (a four-rotor helicopter) that is able to lift more weight and is more agile than a standard helicopter. But these benefits come at a cost of stability and require many electronic sensors to maintain stable flight. We are currently working on an RC car to tether to Icarus toact as a manipulator. This way it can pull Icarus close to balls without blowing them away and pick them up. The manipulator will be used during competitions such as MRDC.`,
|
||||
img: icarus,
|
||||
},
|
||||
{
|
||||
title: "C-Force",
|
||||
content: `C-Force is our award winning pumpkin launcher which uses centripetal force to launch pumpkins. A 5HP, 3 phase industrial motor and variable frequency drive is used to spin up the launching arm to approximately 200 RPM and then a second custom designed control system releases the pumpkin on command from a laptop connected via WiFi. There is an optical sensor on the launcher providing input to the controller to signal when the arm is at the proper angle to release the pumpkin. This allows the launcher to consistently throw the pumpkin at the ideal launch angle to acheive the maximum possible distance.`,
|
||||
img: mach2,
|
||||
},
|
||||
{
|
||||
title: "Osiris",
|
||||
content: `A full body spinner prototype design with a hexagonal pyramid shell. Used MPU-6050 Gyroscope/Accelerameter package, Yumo/Omron rotary encoders, and I2C communication between Arduino Unos for PID and translational drift control. Required a soldered interface board between Open Source Motor Controller (OSMC) and Arduino Mega consisting of a L7805 voltage regulator, line driver, and optocoupler packages to provide current and signal isolation. Each of 3 Ampflow magmotors has a peak of 4.6 horsepower and draws 150 Amps at full load. Future improvements include fabricating metal body and shell, and integrating the OSMC interface board into a PCB with motor controller components that can operate at 48 V.`,
|
||||
img: osiris,
|
||||
},
|
||||
],
|
||||
inactive: [
|
||||
{
|
||||
title: "Modulus",
|
||||
content: `Modulus uses a crab drive propulsion system and is primarily based off the FIRST robotics kit. A crab drive system consists of a 4 wheel base where each wheel can rotate independently without turning the entire body. This can be very useful for competitions where mobility is important such as MRDC.`,
|
||||
img: Modulus
|
||||
},
|
||||
{
|
||||
title: 'Roomba',
|
||||
content: `Our club received a donation of 30 Roombas. We have decided to build a robotic swarm using them. This swarm will be able to communicate with each other to work together to fulfill a task. Possible tasks that we have thought of include mapping, search and rescue, and playing sports. We are currently using Arduinos to control the Roombas through their on-board commands.`,
|
||||
img: Roomba
|
||||
},
|
||||
{
|
||||
title: 'MRK 1',
|
||||
content: `Fancy Pants is a lower extremity exoskeleton and is one of the older projects at ITR. It is currently undergoing a significant redesign with the goals of increasing precision, comfort and safety. Although this exo needs a lot of work before it will be hurling someone 15 feet into the air safely, it has potential for greatness.`,
|
||||
img: MRK
|
||||
},
|
||||
{
|
||||
title: 'Mongol',
|
||||
content: `Mongol is designed to compete in Mech-Warfare. In Mech-Warfare, all robots must walk on 2 or 4 legs as a means of propulsion. Each robot is also equipped with airsoft cannons and impact sensor plates. Each robot is given a certain number of 'hits' and is pitted against competitors in model cityscape to where the object of the competition is to reduce each opponents hits to zero before falling to zero themselves. Meanwhile, though able to be remote controlled, each robot's pilot cannot view the robot directly and must control the robot with visual information coming only from a wireless camera on the robot itself. Mongol was designed entirely in SolidWorks. For more information about Mech-Warfare visit mech-warfare.com`,
|
||||
img: Mongol
|
||||
},
|
||||
{
|
||||
title: 'Matchlock Ashigaru',
|
||||
content: `This project is a new approach to the Mech Warfare competition and legged robot design. Not only does this robot have a set of legs as opposed to wheels for travel over rough terrain, but it is also equipped with a pair of turret style airsoft guns as well as a camera to give an inside feel to the computer operated controls. During the Mech Warfare competition, it will also come prepared with a rotating turret style head which will allow for quick response which is necessity when competing against fast moving opponents. Additional objectives for this project will be planned after its first successful participation in the Mech Warfare competition later this year. For more information about Mech-Warfare visit mech-warfare.com`,
|
||||
img: ma
|
||||
}
|
||||
],
|
||||
};
|
||||
export default projects;
|
BIN
src/assets/pics/fancypants.jpg
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
src/assets/pics/fenrir.jpg
Normal file
After Width: | Height: | Size: 278 KiB |
BIN
src/assets/pics/goliath.jpg
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
src/assets/pics/icarus.jpg
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
src/assets/pics/mach2.jpg
Normal file
After Width: | Height: | Size: 173 KiB |
BIN
src/assets/pics/matchlock.jpg
Normal file
After Width: | Height: | Size: 147 KiB |
BIN
src/assets/pics/modulus.jpg
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
src/assets/pics/mongol.jpg
Normal file
After Width: | Height: | Size: 220 KiB |
BIN
src/assets/pics/osiris.jpg
Normal file
After Width: | Height: | Size: 2.6 MiB |
BIN
src/assets/pics/roomba.jpg
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
src/assets/pics/roslund.jpg
Normal file
After Width: | Height: | Size: 113 KiB |
158
src/assets/stylesheets/project.scss
Normal file
@ -0,0 +1,158 @@
|
||||
@font-face {
|
||||
font-family: itrFont;
|
||||
src: url(../font/LeagueSpartan-ExtraBold.ttf);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: itrFontMedium;
|
||||
src: url(../font/LeagueSpartan-Medium.ttf);
|
||||
}
|
||||
|
||||
.project {
|
||||
background-color: #232323;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
height: auto;
|
||||
opacity: 100%;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
-webkit-animation: fade 0.9s ease-in;
|
||||
-moz-animation: fade 0.9s ease-in;
|
||||
-ms-animation: fade 0.9s ease-in;
|
||||
-o-animation: fade 0.9s ease-in;
|
||||
animation: fade 0.9s ease-in;
|
||||
background: linear-gradient(#3b3b3b, #1a1a1a);
|
||||
}
|
||||
|
||||
.projects_wrap {
|
||||
flex-wrap: wrap;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
}
|
||||
.projects__title {
|
||||
margin: 20px 0 0 20px;
|
||||
font-family: itrFontMedium;
|
||||
color: #fff;
|
||||
}
|
||||
.project__content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.project__content__main {
|
||||
width: 100vw;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.project__content__main__container {
|
||||
padding: 10px 10px 20px 10px;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(66, 66, 66);
|
||||
box-sizing: border-box;
|
||||
height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: 0.3s;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
margin: 0 16px 16px 0;
|
||||
}
|
||||
.project_item_content {
|
||||
flex-grow: 1;
|
||||
overflow: scroll;
|
||||
}
|
||||
.project__content__main__container h1,
|
||||
.project__content__blocks_block h1 {
|
||||
color: white;
|
||||
opacity: 100%;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
font-family: "itrFont";
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.project__content__main__container span,
|
||||
.project__content__blocks_block span {
|
||||
color: rgb(238, 238, 238);
|
||||
opacity: 100%;
|
||||
font-size: 16px;
|
||||
font-family: "itrFontMedium";
|
||||
padding: 10px;
|
||||
line-height: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.project__content__blocks_block span {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.project__content__blocks {
|
||||
margin-top: 6vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
margin-right: calc(-1 * (100vw - 100%));
|
||||
}
|
||||
|
||||
.project__content__blocks_block {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(66, 66, 66);
|
||||
width: 25vw;
|
||||
margin-left: 3vw;
|
||||
margin-right: 3vw;
|
||||
min-height: 38vh;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.project__content__blocks_block p {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
color: rgb(136, 136, 136);
|
||||
opacity: 100%;
|
||||
font-size: 20px;
|
||||
font-family: "itrFontMedium";
|
||||
padding: 10px;
|
||||
line-height: 32px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.project__content__blocks_block:hover,
|
||||
.project__content__main__container:hover {
|
||||
background-color: #666666;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1550px) {
|
||||
.project__content__blocks {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.project__content__blocks_block {
|
||||
margin-bottom: 6vh;
|
||||
width: 80vw;
|
||||
}
|
||||
|
||||
.project__content__blocks_block h1 {
|
||||
font-size: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
20
src/assets/stylesheets/waterfall.scss
Normal file
@ -0,0 +1,20 @@
|
||||
#react-waterfall-comps li > div {
|
||||
transition: all 0.5s;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(66, 66, 66);
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
#react-waterfall-comps li > div:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 30px 50px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s;
|
||||
background-color: #666666;
|
||||
}
|
||||
#react-waterfall-comps li > div > img {
|
||||
width: 100%;
|
||||
}
|
21
src/components/AppOuter.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { useContainer } from "unstated-next";
|
||||
import DropdownContainer from "../components/DropdownContainer";
|
||||
import GlobalStore from "../store/global";
|
||||
import NavBar from "../components/NavBar";
|
||||
import Footer from "../components/Footer";
|
||||
|
||||
const AppOuter = ({ children }) => {
|
||||
const { isHamburger, setIsHamburger } = useContainer(GlobalStore);
|
||||
return (
|
||||
<div className="container">
|
||||
{isHamburger ? (
|
||||
<DropdownContainer hamburgerToggle={setIsHamburger} />
|
||||
) : null}
|
||||
<div style={{ marginTop: 70 }}>{children}</div>
|
||||
<NavBar hamburgerToggle={setIsHamburger}></NavBar>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppOuter;
|
@ -1,22 +1,96 @@
|
||||
import React, { Component } from "react";
|
||||
import backgroundVideo from "../assets/video.mp4";
|
||||
import "../assets/stylesheets/home.scss";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { AnimationOnScroll } from "react-animation-on-scroll";
|
||||
import "../assets/stylesheets/project.scss";
|
||||
import { useContainer } from "unstated-next";
|
||||
import GlobalStore from "../store/global";
|
||||
import { Image } from 'antd';
|
||||
import projects from '../assets/data/projects';
|
||||
|
||||
export default class Projects extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="home">
|
||||
<video autoPlay loop muted className="home__video">
|
||||
<source src={backgroundVideo} type="video/mp4" />
|
||||
</video>
|
||||
<div className="home__content">
|
||||
<label className="home__content__title">
|
||||
ILLINOIS TECH
|
||||
<br />
|
||||
ROBOTICS
|
||||
</label>
|
||||
const Project = () => {
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
const {bodySize} = useContainer(GlobalStore);
|
||||
const count = useMemo(() => {
|
||||
if (bodySize.width > 850) {
|
||||
return 3;
|
||||
}
|
||||
return 2;
|
||||
}, [bodySize]);
|
||||
return (
|
||||
<div className="project">
|
||||
<div className="project__content">
|
||||
<div className="project__content__main">
|
||||
<AnimationOnScroll
|
||||
animateIn="animate__fadeInDown"
|
||||
animateOnce="true"
|
||||
duration={1}
|
||||
delay={0}
|
||||
>
|
||||
<h1 className="projects__title">Active Projects</h1>
|
||||
<div className="projects_wrap" size={16}>
|
||||
{projects.active.map((item) => (
|
||||
<div
|
||||
className="project__content__main__container"
|
||||
style={{ width: `calc(${100 / count}% - 16px)` }}
|
||||
>
|
||||
<h3 style={{ color: "#fff", marginBottom: 12 }}>{item.title}</h3>
|
||||
<div className="project_item_content">
|
||||
<Image width={"100%"} src={item.img}></Image>
|
||||
<span>{item.content}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<h1 className="projects__title">Retired/Inactive Projects</h1>
|
||||
<div className="projects_wrap" size={16}>
|
||||
{projects.inactive.map((item) => (
|
||||
<div
|
||||
className="project__content__main__container"
|
||||
style={{ width: `calc(${100 / count}% - 16px)` }}
|
||||
>
|
||||
<h3 style={{ color: "#fff", marginBottom: 12 }}>{item.title}</h3>
|
||||
<div className="project_item_content">
|
||||
<Image width={"100%"} src={item.img}></Image>
|
||||
<span>{item.content}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* <div
|
||||
style={{
|
||||
height: "100vh",
|
||||
width: "100vw",
|
||||
border: "1px solid",
|
||||
marginTop: "30px",
|
||||
overflowY: "scroll",
|
||||
}}
|
||||
>
|
||||
<Waterfall
|
||||
columnWidth={236}
|
||||
columnCount={2}
|
||||
columnGap={24}
|
||||
rowGap={24}
|
||||
onChangeUlMaxH={(h) => (ulMaxHRef.current = h)}
|
||||
>
|
||||
{images.map((item, index) => {
|
||||
return (
|
||||
<li key={index} onClick={() => alert("图片地址为:" + item)}>
|
||||
<div>
|
||||
{index + 1}
|
||||
<img src={item} alt="" />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</Waterfall>
|
||||
</div> */}
|
||||
</AnimationOnScroll>
|
||||
</div>
|
||||
{/* <WaterfallPositionDemo /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Project;
|
||||
|
71
src/components/Waterfall.js
Normal file
@ -0,0 +1,71 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import Waterfall from "waterfalljs-layout/react";
|
||||
import "../assets/stylesheets/waterfall.scss";
|
||||
|
||||
const defimages = [
|
||||
"https://picsum.photos/640/200/?random",
|
||||
"https://picsum.photos/360/640/?random",
|
||||
"https://picsum.photos/480/720/?random",
|
||||
"https://picsum.photos/480/640/?random",
|
||||
"https://picsum.photos/360/?random",
|
||||
"https://picsum.photos/360/520/?random",
|
||||
"https://picsum.photos/520/360/?random",
|
||||
"https://picsum.photos/520/360/?random",
|
||||
"https://picsum.photos/520/360/?random",
|
||||
"https://picsum.photos/720/640/?random",
|
||||
];
|
||||
|
||||
export default function WaterfallPositionDemo() {
|
||||
const [images, setImages] = useState(defimages);
|
||||
const ulMaxHRef = useRef(0);
|
||||
|
||||
const handleSearchImage = async () => {
|
||||
function random(min, max) {
|
||||
return min + Math.floor(Math.random() * (max - min + 1));
|
||||
}
|
||||
const arr = [];
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const imgSrc = `${defimages[i]}=${random(1, 10000)}`;
|
||||
arr.push(imgSrc);
|
||||
}
|
||||
setImages((prev) => [...prev, ...arr]);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: "600px",
|
||||
width: "520px",
|
||||
border: "1px solid",
|
||||
marginTop: "30px",
|
||||
overflowY: "scroll",
|
||||
}}
|
||||
>
|
||||
<Waterfall
|
||||
columnWidth={236}
|
||||
columnCount={2}
|
||||
columnGap={24}
|
||||
rowGap={24}
|
||||
onChangeUlMaxH={(h) => (ulMaxHRef.current = h)}
|
||||
>
|
||||
{images.map((item, index) => {
|
||||
return (
|
||||
<li key={index} onClick={() => alert("图片地址为:" + item)}>
|
||||
<div>
|
||||
{index + 1}
|
||||
<img src={item} alt="" />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</Waterfall>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<button
|
||||
onClick={() => handleSearchImage()}
|
||||
style={{ margin: "30px auto" }}
|
||||
>
|
||||
LOAD MORE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client";
|
||||
import HomeRoute from "./routes/HomeRoute";
|
||||
import AboutRoute from "./routes/AboutRoute";
|
||||
import AwardsRoute from "./routes/AwardsRoute";
|
||||
import ProjectsRoute from "./routes/ProjectsRoute";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import ErrorRoute from "./routes/ErrorRoute";
|
||||
@ -25,6 +26,11 @@ const router = createBrowserRouter([
|
||||
path: "/awards",
|
||||
element: <AwardsRoute></AwardsRoute>,
|
||||
errorElement: <ErrorRoute></ErrorRoute>,
|
||||
},
|
||||
{
|
||||
path: "/projects",
|
||||
element: <ProjectsRoute></ProjectsRoute>,
|
||||
errorElement: <ErrorRoute></ErrorRoute>,
|
||||
}
|
||||
]);
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
import AppOuter from "../components/AppOuter";
|
||||
import Projects from "../components/Projects";
|
||||
|
||||
const ProjectsRoute = () => {
|
||||
return (
|
||||
<AppOuter>
|
||||
<Projects />
|
||||
</AppOuter>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectsRoute;
|