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 { MdEmail } from "react-icons/md";
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">
<Link 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]">
<div class="text-white">
<div class="text-ieee-yellow">
<Link
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}
</p>
</Link>
</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">
<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
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"
>
<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 class = "w-full flex justify-between px-[7%]">
<p 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 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}
</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}
</div>
</div>
</p>
</div>
</div>
</div>

View file

@ -4,16 +4,37 @@ import { Image } from "astro:assets";
import neko from "../../images/neko.png";
import { LiaDotCircle } from "react-icons/lia";
import Officer from "../board/Officer.astro";
import Filter from "../board/Filter.astro";
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 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={neko}
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:ww-[14vw] rounded-[2vw]"
>>>>>>> chark1es-main
/>
</div>
@ -22,19 +43,38 @@ import officers from "../../data/officers.json";
<p>MEET THE BOARD</p>
</div>
<<<<<<< HEAD
<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>
=======
<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]">
{
officers.map((officer) => (
<Officer
name={officer.name}
position={officer.position}
picture={officer.picture}
email={officer.email}
/>
<div
data-officer
data-types={JSON.stringify(officer.type)}
style="opacity: 0; visibility: hidden"
>
<Officer
name={officer.name}
position={officer.position}
picture={officer.picture}
email={officer.email}
/>
</div>
))
}
</div>

View file

@ -1,22 +1,16 @@
---
import { FaGear, FaMicrochip, FaCode } from "react-icons/fa6";
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%]">
{title}
</p>
<ul class="text-[1vw] font-light">
{list.map((item)=>(
<li>&#8226; {item}</li>
))}
{list.map((item: string) => <li>&#8226; {item}</li>)}
</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 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" />
<div class="grid grid-flow-col gap-[1.5vw] mt-[3%]">
{subteams.map((subteam)=>(
<Subteam title={subteam.title} list={subteam.list} />
))}
<div class="relative w-[75vw] h-[50vh] mt-[3%]">
<div id="carousel" class="absolute w-full h-full flex justify-center">
{
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>
<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",
"list": ["Design the Mechanisms and Structure of the Robot",
"Fabricate and assemble robot design",
"Integrate electronics and other hardware"]
},
{
"title": "Electrical",
"list": ["Design circuits to power and control robot subsystems",
"Work with mechanical team to assemble electronics in the robots"]
},
{
"title": "Embedded",
"list": ["Communicate between software and hardware",
"Provide low level software interface and API",
"Handle and sample data using math/physics knowledge"
]
}
{
"title": "Mechanical",
"list": [
"Design the Mechanisms and Structure of the Robot",
"Fabricate and assemble robot design",
"Integrate electronics and other hardware"
]
},
{
"title": "Electrical",
"list": [
"Design circuits to power and control robot subsystems",
"Work with mechanical team to assemble electronics in the robots"
]
},
{
"title": "Embedded",
"list": [
"Communicate between software and hardware",
"Provide low level software interface and API",
"Handle and sample data using math/physics knowledge"
]
},
{
"title": "AI",
"list": [
"Design decision making algorithms using behavioral trees",
"Conduct testing with simulation"
]
}
]