Merge branch 'chark1es-main'

This commit is contained in:
chark1es 2025-01-23 13:34:38 -08:00
commit 001d702729
6 changed files with 521 additions and 60 deletions

View file

@ -1,3 +1,166 @@
--- ---
const { filters, currentFilter } = Astro.props;
---
--- <div class="inline-flex border border-white/20 rounded-full p-1 relative">
<div
id="slider"
class="absolute h-[calc(100%-8px)] bg-[#FFB81C] rounded-full transition-none"
style="left: 4px;"
>
</div>
{
filters.map((filter) => (
<button
data-filter={filter}
class={`px-6 py-2 rounded-full transition-all relative z-10 ${
currentFilter === filter
? "text-black"
: "text-white hover:bg-white/10 hover:bg-opacity-50"
}`}
>
{filter}
</button>
))
}
</div>
<script>
const buttons = document.querySelectorAll("[data-filter]");
const officers = document.querySelectorAll("[data-officer]");
const container = officers[0]?.parentElement;
const slider = document.getElementById("slider");
// Define type order for consistent sorting
const typeOrder = ["Executives", "Internal", "Events", "Projects"];
function getTypeWeight(type) {
const index = typeOrder.indexOf(type);
return index === -1 ? typeOrder.length : index;
}
function sortOfficersByType() {
const officerArray = Array.from(officers);
officerArray.sort((a, b) => {
const aTypes = JSON.parse(a.getAttribute("data-types"));
const bTypes = JSON.parse(b.getAttribute("data-types"));
return getTypeWeight(aTypes[0]) - getTypeWeight(bTypes[0]);
});
officerArray.forEach((officer) => {
container.appendChild(officer);
});
}
function moveSlider(button) {
if (!slider) return;
const buttonRect = button.getBoundingClientRect();
const containerRect = button.parentElement.getBoundingClientRect();
slider.style.width = `${buttonRect.width}px`;
slider.style.left = `${buttonRect.left - containerRect.left}px`;
}
function updateFilter(selectedFilter, clickedButton) {
// Update button styles
buttons.forEach((btn) => {
const isSelected =
btn.getAttribute("data-filter") === selectedFilter;
btn.classList.toggle("text-black", isSelected);
btn.classList.toggle("text-white", !isSelected);
btn.classList.toggle("hover:bg-white/10", !isSelected);
btn.classList.toggle("hover:bg-opacity-50", !isSelected);
});
// move slider
moveSlider(clickedButton);
// fades out all officers
officers.forEach((officer) => {
officer.style.opacity = "0";
officer.style.transition = "opacity 300ms ease-out";
});
// waits, then removes and re-adds officers
setTimeout(() => {
// removes all officers from container
officers.forEach((officer) => {
officer.remove();
});
// filters officers and prepares them for re-insertion
const officersToShow = Array.from(officers).filter((officer) => {
const types = JSON.parse(
officer.getAttribute("data-types") || "[]"
);
return (
selectedFilter === "All" || types.includes(selectedFilter)
);
});
// sorts if needed
if (selectedFilter === "All") {
officersToShow.sort((a, b) => {
const aTypes = JSON.parse(a.getAttribute("data-types"));
const bTypes = JSON.parse(b.getAttribute("data-types"));
return getTypeWeight(aTypes[0]) - getTypeWeight(bTypes[0]);
});
}
// sets initial opacity to 0 for fade in
officersToShow.forEach((officer) => {
officer.style.opacity = "0";
officer.style.display = "";
container.appendChild(officer);
});
// triggers reflow and fades in
requestAnimationFrame(() => {
officersToShow.forEach((officer) => {
officer.style.opacity = "1";
});
});
}, 300); // matches fade-out duration
}
sortOfficersByType();
// init
const initialButton = Array.from(buttons).find(
(btn) => btn.getAttribute("data-filter") === "All"
);
// init
if (initialButton && slider) {
const buttonRect = initialButton.getBoundingClientRect();
slider.style.width = `${buttonRect.width}px`;
// turns on transitions
requestAnimationFrame(() => {
slider.classList.remove("transition-none");
slider.classList.add(
"transition-all",
"duration-300",
"ease-in-out"
);
});
}
// reveals officers after sorting with animation
requestAnimationFrame(() => {
officers.forEach((officer) => {
officer.style.transition = "opacity 300ms ease-out";
officer.style.visibility = "visible";
// triggers reflow
officer.offsetHeight;
officer.style.opacity = "1";
});
});
// addss click handlers
buttons.forEach((button) => {
button.addEventListener("click", () => {
const filterValue = button.getAttribute("data-filter");
updateFilter(filterValue, button);
});
});
</script>

View file

@ -2,31 +2,53 @@
import { FaGear } from "react-icons/fa6"; import { FaGear } from "react-icons/fa6";
import { MdEmail } from "react-icons/md"; import { MdEmail } from "react-icons/md";
import Link from "next/link"; import Link from "next/link";
const {name, position, picture, email} = Astro.props; const { name, position, picture, email } = Astro.props;
--- ---
<div class = "text-white">
<div class = "text-ieee-yellow"> <div class="text-white">
<Link href={`mailto:${email}`} className = "flex items-center ml-[3%] py-[0.5vh] group"> <div class="text-ieee-yellow">
<MdEmail className = "md:text-[1.5vw] text-[2.5vw] mr-[0.5%] group-hover:scale-110 group-hover:opacity-70 duration-300"/> <Link
<p class = "md:text-[0.8vw] text-[2vw]"> href={`mailto:${email}`}
className="flex items-center ml-[3%] py-[0.5vh] group"
>
<MdEmail
className="md:text-[1.5vw] text-[2.5vw] mr-[0.5%] group-hover:scale-110 group-hover:opacity-70 duration-300"
/>
<p class="md:text-[0.8vw] text-[2vw]">
{email} {email}
</p> </p>
</Link> </Link>
</div> </div>
<div class = "md:w-[20vw] w-[35vw] aspect-[334/440] bg-gradient-to-t from-ieee-blue-100/5 to-ieee-blue-100/25 rounded-[10%] flex flex-col items-center relative"> <div
<img src ={picture} alt = "officer" class = "md:w-[18vw] w-[31vw] md:rounded-[1.5vw] rounded-[3vw] mt-[5%] mb-[3%]" > class="md:w-[20vw] w-[35vw] aspect-[334/440] bg-gradient-to-t from-ieee-blue-100/5 to-ieee-blue-100/25 rounded-[10%] flex flex-col items-center relative"
<div class = "bg-white w-fit rounded-full aspect-square md:p-[0.4vw] p-[0.8vw] text-ieee-black md:text-[1.8vw] text-[3.5vw] absolute md:right-[1.5vw] md:top-[1.5vw] right-[2.5vw] top-[2.5vw]"> >
<FaGear/> <img
src={picture}
alt="officer"
class="md:w-[18vw] w-[31vw] md:rounded-[1.5vw] rounded-[3vw] mt-[5%] mb-[3%]"
/>
<div
class="bg-white w-fit rounded-full aspect-square md:p-[0.4vw] p-[0.8vw] text-ieee-black md:text-[1.8vw] text-[3.5vw] absolute md:right-[1.5vw] md:top-[1.5vw] right-[2.5vw] top-[2.5vw]"
>
<FaGear />
</div> </div>
<div class = "w-full flex justify-between px-[7%]"> <p
<p data-inview class = " in-view:animate-fade-right md:text-[2vw] text-[3.5vw] font-light md:leading-[2.5vw] leading-[5vw]"> data-inview
class="in-view:animate-fade-right md:text-[2vw] text-[3.5vw] font-light md:leading-[2.5vw] leading-[5vw]"
>
<p
data-inview
class="in-view:animate-fade-right text-[2vw] font-light leading-[4.5vh]"
>
{name} {name}
</p> </p>
<div data-inview class = "md:mt-[0.5vw] mt-[1.5vw] in-view:animate-fade-up md:text-[0.8vw] text-[1.5vw] md:w-[8vw] w-[15vw] border-[0.11vw] border-white/90 rounded-full p-[0.5%] h-fit text-center"> <div
data-inview
class="md:mt-[0.5vw] mt-[1.5vw] in-view:animate-fade-up md:text-[0.8vw] text-[1.5vw] md:w-[8vw] w-[15vw] border-[0.11vw] border-white/90 rounded-full p-[0.5%] h-fit text-center"
>
{position} {position}
</div> </div>
</p>
</div> </div>
</div>
</div> </div>

View file

@ -4,16 +4,37 @@ import { Image } from "astro:assets";
import neko from "../../images/neko.png"; import neko from "../../images/neko.png";
import { LiaDotCircle } from "react-icons/lia"; import { LiaDotCircle } from "react-icons/lia";
import Officer from "../board/Officer.astro"; import Officer from "../board/Officer.astro";
import Filter from "../board/Filter.astro";
import officers from "../../data/officers.json"; import officers from "../../data/officers.json";
// Get all unique types and add 'All' option
const typeOrder = ["Executives", "Internal", "Events", "Projects"];
const types = ["All", ...typeOrder];
const currentFilter = "All";
--- ---
<<<<<<< HEAD
<div class="text-white flex flex-col items-center md:mt-[10vw] mt-[20vw] mb-[10vh]"> <div class="text-white flex flex-col items-center md:mt-[10vw] mt-[20vw] mb-[10vh]">
<div data-inview class="relative w-[40vw] md:w-[21vw] in-view:animate-fade-down"> <div data-inview class="relative w-[40vw] md:w-[21vw] in-view:animate-fade-down">
=======
<div
class="text-white flex flex-col items-center md:mt-[10vw] mt-[20vw] mb-[10vh]"
>
<div
data-inview
class="relative w-[40vw] md:w-[21vw] in-view:animate-fade-down"
>
>>>>>>> chark1es-main
<Image src={about} alt="About background image" /> <Image src={about} alt="About background image" />
<Image <Image
src={neko} src={neko}
alt="About image" alt="About image"
<<<<<<< HEAD
class="absolute top-[10%] left-[16%] aspect-[399/491] object-cover w-[27vw] md:w-[14vw] rounded-[2vw]" class="absolute top-[10%] left-[16%] aspect-[399/491] object-cover w-[27vw] md:w-[14vw] rounded-[2vw]"
=======
class="absolute top-[10%] left-[16%] aspect-[399/491] object-cover w-[27vw] md:ww-[14vw] rounded-[2vw]"
>>>>>>> chark1es-main
/> />
</div> </div>
@ -22,19 +43,38 @@ import officers from "../../data/officers.json";
<p>MEET THE BOARD</p> <p>MEET THE BOARD</p>
</div> </div>
<<<<<<< HEAD
<p class="md:text-[1.3vw] text-[2.5vw] md:w-[56%] w-[70%] my-[3%] font-extralight text-center"> <p class="md:text-[1.3vw] text-[2.5vw] md:w-[56%] w-[70%] my-[3%] font-extralight text-center">
Our board comprises 31 students of varying majors, colleges, and interests! Feel free to reach out for any questions about our position or experiences. Our board comprises 31 students of varying majors, colleges, and interests! Feel free to reach out for any questions about our position or experiences.
</p> </p>
=======
<p
class="md:text-[1.3vw] text-[2.5vw] md:w-[56%] w-[70%] my-[3%] font-extralight text-center"
>
Our board comprises 31 students of varying majors, colleges, and
interests! Feel free to reach out for any questions about our position
or experiences.
</p>
<Filter filters={types} currentFilter={currentFilter} />
>>>>>>> chark1es-main
<div class="grid gap-[3vw] md:grid-cols-3 grid-cols-2 mt-[10vh]"> <div class="grid gap-[3vw] md:grid-cols-3 grid-cols-2 mt-[10vh]">
{ {
officers.map((officer) => ( officers.map((officer) => (
<div
data-officer
data-types={JSON.stringify(officer.type)}
style="opacity: 0; visibility: hidden"
>
<Officer <Officer
name={officer.name} name={officer.name}
position={officer.position} position={officer.position}
picture={officer.picture} picture={officer.picture}
email={officer.email} email={officer.email}
/> />
</div>
)) ))
} }
</div> </div>

View file

@ -1,22 +1,16 @@
--- ---
import { FaGear, FaMicrochip, FaCode } from "react-icons/fa6"; import { FaGear, FaMicrochip, FaCode } from "react-icons/fa6";
import { LuBrainCircuit } from "react-icons/lu"; import { LuBrainCircuit } from "react-icons/lu";
const {title, list}=Astro.props; const { title, list } = Astro.props;
--- ---
<div class="px-[10%] relative flex flex-col justify-center items-center w-[20vw] h-[43vh] bg-gradient-to-b from-ieee-blue-100/25 to-ieee-black backdrop-blur rounded-[2vw] border-white/40 border-[0.1vw]"> <div
class="px-[10%] flex flex-col justify-center items-center w-[20vw] h-[38vh] bg-gradient-to-b from-ieee-blue-100/25 to-ieee-black backdrop-blur rounded-[2vw] border-white/40 border-[0.1vw]"
>
<p class="text-[1.5vw] mb-[10%] font-semibold pt-[10%]"> <p class="text-[1.5vw] mb-[10%] font-semibold pt-[10%]">
{title} {title}
</p> </p>
<ul class="text-[1vw] font-light"> <ul class="text-[1vw] font-light">
{list.map((item)=>( {list.map((item: string) => <li>&#8226; {item}</li>)}
<li>&#8226; {item}</li>
))}
</ul> </ul>
<div class="-top-[10%] w-fit p-[5%] shadow-ieee-blue-300 text-[3.2vw] bg-gradient-to-b from-ieee-blue-100 to-ieee-blue-300 rounded-full absolute">
{title==="Mechanical" && <FaGear />}
{title==="Electrical" && <FaMicrochip />}
{title==="AI" && <LuBrainCircuit />}
{title==="Embedded" && <FaCode />}
</div>
</div> </div>

View file

@ -1,13 +1,243 @@
--- ---
import Subtitle from "../core/Subtitle.astro"; import Subtitle from "../core/Subtitle.astro";
import Subteam from "./Subteam.astro"; import Subteam from "./Subteam.astro";
import subteams from "../../data/subteams.json" import subteams from "../../data/subteams.json";
import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
import { FaGear, FaMicrochip, FaCode } from "react-icons/fa6";
import { LuBrainCircuit } from "react-icons/lu";
// duplicate the array 3 times for smoother looping
const duplicatedSubteams = [...subteams, ...subteams, ...subteams];
const centerIndex = Math.floor(subteams.length); // center in the middle
--- ---
<div class="flex flex-col items-center my-[10%]">
<div class="flex flex-col items-center my-[10%] relative">
<Subtitle title="Subteams" /> <Subtitle title="Subteams" />
<div class="grid grid-flow-col gap-[1.5vw] mt-[3%]"> <div class="relative w-[75vw] h-[50vh] mt-[3%]">
{subteams.map((subteam)=>( <div id="carousel" class="absolute w-full h-full flex justify-center">
<Subteam title={subteam.title} list={subteam.list} /> {
))} duplicatedSubteams.map((subteam, index) => {
let distance = index - centerIndex;
// wrap
if (distance > duplicatedSubteams.length / 2) {
distance -= duplicatedSubteams.length;
} else if (distance < -duplicatedSubteams.length / 2) {
distance += duplicatedSubteams.length;
}
return (
<div
class="carousel-item absolute transition-all duration-500"
style={{
transform: `translateX(${distance * 12}vw) scale(${
Math.abs(distance) === 0
? 1.2
: Math.abs(distance) === 1
? 1.0
: 0.8
})`,
opacity: Math.abs(distance) <= 2 ? 1 : 0,
zIndex:
Math.abs(distance) === 0
? 30
: Math.abs(distance) === 1
? 20
: 10,
width: "20vw",
}}
data-index={index}
>
<div class="relative">
<div class="relative w-full h-full backdrop-blur-md overflow-hidden rounded-[2vw]">
<div class="px-[10%] flex flex-col justify-center items-center w-[20vw] h-[38vh] bg-gradient-to-b from-ieee-blue-100/25 to-ieee-black backdrop-blur rounded-[2vw] border-white/40 border-[0.1vw]">
<p class="text-[1.5vw] mb-[10%] font-semibold pt-[10%]">
{subteam.title}
</p>
<ul class="text-[1vw] font-light">
{subteam.list.map(
(item: string) => (
<li>&#8226; {item}</li>
)
)}
</ul>
</div>
</div>
<div class="-top-[10%] left-1/2 -translate-x-1/2 w-fit p-[5%] shadow-ieee-blue-300 text-[3.2vw] bg-gradient-to-b from-ieee-blue-100 to-ieee-blue-300 rounded-full absolute">
{subteam.title === "Mechanical" && (
<FaGear />
)}
{subteam.title === "Electrical" && (
<FaMicrochip />
)}
{subteam.title === "AI" && (
<LuBrainCircuit />
)}
{subteam.title === "Embedded" && <FaCode />}
</div>
</div>
</div>
);
})
}
</div>
<button
id="prevBtn"
class="absolute left-[-3vw] top-[calc(50%-1.5vw)] transform -translate-y-1/2 text-[3vw] text-white hover:text-white/70 duration-300 z-20 bg-black/30 hover:bg-black/50 p-2 rounded-full backdrop-blur-sm"
aria-label="Previous card"
>
<IoIosArrowBack />
</button>
<button
id="nextBtn"
class="absolute right-[-3vw] top-[calc(50%-1.5vw)] transform -translate-y-1/2 text-[3vw] text-white hover:text-white/70 duration-300 z-20 bg-black/30 hover:bg-black/50 p-2 rounded-full backdrop-blur-sm"
aria-label="Next card"
>
<IoIosArrowForward />
</button>
</div> </div>
</div> </div>
<style>
.carousel-item {
transition: none;
}
.carousel-item[data-loaded="true"] {
transition:
transform 500ms,
opacity 200ms;
}
</style>
<script>
const items = document.querySelectorAll(".carousel-item");
const totalItems = items.length;
const singleSetLength = totalItems / 3; // length of one set of cards
let currentIndex = singleSetLength;
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
let autoAdvanceTimer: ReturnType<typeof setInterval>;
let isAnimating = false;
let isTransitioning = false;
function startAutoAdvance() {
if (autoAdvanceTimer) {
clearInterval(autoAdvanceTimer);
}
autoAdvanceTimer = setInterval(() => {
if (!isAnimating && !isTransitioning) {
updateCards(currentIndex + 1);
}
}, 10000);
}
setTimeout(() => {
items.forEach((item) => {
item.setAttribute("data-loaded", "true");
});
startAutoAdvance();
}, 100);
function updateCards(newIndex: number): void {
if (isAnimating || isTransitioning) return;
isAnimating = true;
isTransitioning = true;
// direction of movement
const direction = newIndex > currentIndex ? 1 : -1;
items.forEach((item, index) => {
const el = item as HTMLElement;
let distance = index - newIndex;
// wrap
if (distance > singleSetLength) {
distance -= totalItems;
} else if (distance < -singleSetLength) {
distance += totalItems;
}
const scale =
Math.abs(distance) === 0
? 1.2
: Math.abs(distance) === 1
? 1
: Math.abs(distance) === 2
? 0.8
: 0.6; // smaller scale for hidden cards (to transition)
// show only 5 cards at once (2 on each side)
el.style.transform = `translateX(${distance * 12}vw) scale(${scale})`;
el.style.opacity = Math.abs(distance) <= 2 ? "1" : "0";
el.style.zIndex =
Math.abs(distance) === 0
? "30"
: Math.abs(distance) === 1
? "20"
: "10";
});
// Update index with wrapping
if (newIndex >= totalItems) {
newIndex = 0;
} else if (newIndex < 0) {
newIndex = totalItems - 1;
}
currentIndex = newIndex;
setTimeout(() => {
isAnimating = false;
// small delay before allowing next transition
setTimeout(() => {
isTransitioning = false;
}, 50);
}, 500);
}
// button clicks
prevBtn?.addEventListener("click", () => {
if (!isAnimating && !isTransitioning) {
updateCards(currentIndex - 1);
startAutoAdvance();
}
});
nextBtn?.addEventListener("click", () => {
if (!isAnimating && !isTransitioning) {
updateCards(currentIndex + 1);
startAutoAdvance();
}
});
// click handler for cards
items.forEach((item, index) => {
item.addEventListener("click", () => {
if (isAnimating || isTransitioning) return;
const distance = index - currentIndex;
// visual position of the clicked card
let visualDistance = distance;
if (distance > totalItems / 2) {
visualDistance -= totalItems;
} else if (distance < -totalItems / 2) {
visualDistance += totalItems;
}
// only allow clicking on visible cards that aren't in the center
if (Math.abs(visualDistance) <= 2 && visualDistance !== 0) {
let targetIndex = currentIndex + visualDistance;
if (targetIndex >= totalItems) {
targetIndex = targetIndex % totalItems;
} else if (targetIndex < 0) {
targetIndex = totalItems + (targetIndex % totalItems);
}
updateCards(targetIndex);
startAutoAdvance();
}
});
});
</script>

View file

@ -1,20 +1,32 @@
[ [
{ {
"title": "Mechanical", "title": "Mechanical",
"list": ["Design the Mechanisms and Structure of the Robot", "list": [
"Design the Mechanisms and Structure of the Robot",
"Fabricate and assemble robot design", "Fabricate and assemble robot design",
"Integrate electronics and other hardware"] "Integrate electronics and other hardware"
]
}, },
{ {
"title": "Electrical", "title": "Electrical",
"list": ["Design circuits to power and control robot subsystems", "list": [
"Work with mechanical team to assemble electronics in the robots"] "Design circuits to power and control robot subsystems",
"Work with mechanical team to assemble electronics in the robots"
]
}, },
{ {
"title": "Embedded", "title": "Embedded",
"list": ["Communicate between software and hardware", "list": [
"Communicate between software and hardware",
"Provide low level software interface and API", "Provide low level software interface and API",
"Handle and sample data using math/physics knowledge" "Handle and sample data using math/physics knowledge"
] ]
},
{
"title": "AI",
"list": [
"Design decision making algorithms using behavioral trees",
"Conduct testing with simulation"
]
} }
] ]