Merge branch 'main' into andy/reduce-flicker
BIN
bun.lockb
4
nixpacks.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
[phases.setup]
|
||||
nixPkgs = ["nodejs_18", "bun"]
|
||||
aptPkgs = ["curl", "wget"]
|
||||
|
BIN
public/404.png
Before Width: | Height: | Size: 2 MiB |
BIN
public/404.webp
Normal file
After Width: | Height: | Size: 431 KiB |
Before Width: | Height: | Size: 112 KiB |
BIN
public/calendar.webp
Normal file
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 203 KiB |
BIN
public/halloween.webp
Normal file
After Width: | Height: | Size: 186 KiB |
Before Width: | Height: | Size: 308 KiB |
BIN
public/hardhack.webp
Normal file
After Width: | Height: | Size: 183 KiB |
BIN
public/map.png
Before Width: | Height: | Size: 74 KiB |
BIN
public/map.webp
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.3 MiB |
BIN
public/officers/akhil.webp
Normal file
After Width: | Height: | Size: 1,020 KiB |
Before Width: | Height: | Size: 1.1 MiB |
BIN
public/officers/allie.webp
Normal file
After Width: | Height: | Size: 987 KiB |
Before Width: | Height: | Size: 949 KiB |
BIN
public/officers/andy.webp
Normal file
After Width: | Height: | Size: 741 KiB |
Before Width: | Height: | Size: 1.4 MiB |
BIN
public/officers/anika.webp
Normal file
After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.5 MiB |
BIN
public/officers/anu.webp
Normal file
After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.4 MiB |
BIN
public/officers/ashlee.webp
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 699 KiB |
BIN
public/officers/charles.webp
Normal file
After Width: | Height: | Size: 540 KiB |
Before Width: | Height: | Size: 1.5 MiB |
BIN
public/officers/christine.webp
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.8 MiB |
BIN
public/officers/dhruv.webp
Normal file
After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 1.4 MiB |
BIN
public/officers/dihan.webp
Normal file
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.2 MiB |
BIN
public/officers/emma.webp
Normal file
After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.3 MiB |
BIN
public/officers/erik.webp
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.2 MiB |
BIN
public/officers/jonathan.webp
Normal file
After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.3 MiB |
BIN
public/officers/lauren.webp
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.5 MiB |
BIN
public/officers/lisa.webp
Normal file
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.5 MiB |
BIN
public/officers/philip.webp
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.2 MiB |
BIN
public/officers/pranav.webp
Normal file
After Width: | Height: | Size: 943 KiB |
Before Width: | Height: | Size: 1.1 MiB |
BIN
public/officers/rafaella.webp
Normal file
After Width: | Height: | Size: 987 KiB |
Before Width: | Height: | Size: 1.7 MiB |
BIN
public/officers/raymond.webp
Normal file
After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1 MiB |
BIN
public/officers/ridhi.webp
Normal file
After Width: | Height: | Size: 973 KiB |
Before Width: | Height: | Size: 2.1 MiB |
BIN
public/officers/rohil.webp
Normal file
After Width: | Height: | Size: 2.1 MiB |
Before Width: | Height: | Size: 1.4 MiB |
BIN
public/officers/shing.webp
Normal file
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.4 MiB |
BIN
public/officers/shipra.webp
Normal file
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 235 KiB |
BIN
public/officers/stella.webp
Normal file
After Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 82 KiB |
BIN
public/officers/steph.webp
Normal file
After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 1.7 MiB |
BIN
public/officers/terri.webp
Normal file
After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 205 KiB |
BIN
public/officers/zarif.webp
Normal file
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 1.5 MiB |
BIN
public/project.webp
Normal file
After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 4.2 MiB |
BIN
public/robocup.webp
Normal file
After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 216 KiB |
BIN
public/signal.webp
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 60 KiB |
BIN
public/supercomp.webp
Normal file
After Width: | Height: | Size: 42 KiB |
|
@ -1,3 +1,166 @@
|
|||
---
|
||||
const { filters, currentFilter } = Astro.props;
|
||||
---
|
||||
|
||||
---
|
||||
<div class="inline-flex border border-white/20 rounded-full md:p-[0.2vw] p-[0.4vw] relative my-[3vw]">
|
||||
<div
|
||||
id="slider"
|
||||
class="absolute h-[calc(100%-15%)] bg-[#FFB81C] rounded-full transition-none"
|
||||
style="left: 1%;"
|
||||
>
|
||||
</div>
|
||||
{
|
||||
filters.map((filter) => (
|
||||
<button
|
||||
data-filter={filter}
|
||||
class={`md:text-[1.3vw] text-[2.5vw] md:px-[1.8vw] px-[3vw] md:py-[0.2vw] py-[0.4vw] 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>
|
||||
|
|
|
@ -2,32 +2,55 @@
|
|||
import { FaGear } from "react-icons/fa6";
|
||||
import { MdEmail } from "react-icons/md";
|
||||
import Link from "next/link";
|
||||
const {name, position, picture, email} = Astro.props;
|
||||
import { Image } from "astro:assets";
|
||||
const { name, position, picture, email } = Astro.props;
|
||||
---
|
||||
<div class = "text-white">
|
||||
|
||||
<div class = "text-ieee-yellow">
|
||||
<Link href="s1hung@ucsd.edu" className = "flex items-center ml-[3%] py-[0.5vh]">
|
||||
<MdEmail className = "text-[1.5vw] mr-[0.5%]"/>
|
||||
<p class = "text-[0.8vw]">
|
||||
<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 = "w-[20vw] aspect-[334/440] bg-gradient-to-t from-ieee-blue-100/5 to-ieee-blue-100/25 rounded-[10%] flex flex-col items-center">
|
||||
<img src ={picture} alt = "officer" class = "w-[18vw] rounded-[10%] pt-[5%] pb-[3%] relative" >
|
||||
<div class = "bg-white w-fit rounded-full aspect-square p-[0.4vw] text-ieee-black text-[1.8vw] absolute ml-[13.5vw] mt-[3vh]">
|
||||
<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"
|
||||
>
|
||||
<Image
|
||||
src={picture}
|
||||
alt="officer"
|
||||
class="md:w-[18vw] w-[31vw] md:rounded-[1.5vw] rounded-[3vw] mt-[5%] mb-[3%]"
|
||||
width={334}
|
||||
height={440}
|
||||
/>
|
||||
<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 = "opacity-0 in-view:animate-fade-right text-[2vw] font-light leading-[4.5vh]">
|
||||
<div
|
||||
class="flex w-[85%] justify-between"
|
||||
>
|
||||
<p
|
||||
data-inview
|
||||
class="in-view:animate-fade-right md:text-[2vw] text-[3.5vw] font-light md:leading-[2.5vw] leading-[5vw] w-[10vw]"
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
<div data-inview class = "opacity-0 in-view:animate-fade-up text-[0.8vw] w-[8vw] 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] w-fit border-[0.11vw] border-white/90 rounded-full px-[1vw] py-[0.1vw] h-fit text-center"
|
||||
>
|
||||
{position}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -1,43 +1,64 @@
|
|||
---
|
||||
import about from "../../images/about.png";
|
||||
import about from "../../images/about.webp";
|
||||
import { Image } from "astro:assets";
|
||||
import neko from "../../images/neko.png";
|
||||
import neko from "../../images/neko.webp";
|
||||
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";
|
||||
---
|
||||
|
||||
<div class="text-white flex flex-col items-center mt-[15vh] mb-[10vh]">
|
||||
<div data-inview class="relative w-[21vw] in-view:animate-fade-down">
|
||||
<Image src={about} alt="About background image" />
|
||||
<Image
|
||||
src={neko}
|
||||
alt="About image"
|
||||
class="absolute top-[10%] left-[16%] aspect-[399/491] object-cover w-[14vw] rounded-[2vw]"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="text-white flex flex-col items-center md:mt-[5vw] mt-[10vw] mb-[10vh]"
|
||||
>
|
||||
<div
|
||||
data-inview
|
||||
class="relative w-[40vw] md:w-[21vw] mb-[10vh] in-view:animate-fade-down"
|
||||
>
|
||||
<Image src={about} alt="About background image" />
|
||||
<Image
|
||||
src={neko}
|
||||
alt="About image"
|
||||
class="absolute top-[10%] left-[16%] aspect-[399/491] object-cover w-[27vw] md:w-[14vw] rounded-[2vw]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-[2.5vw] flex items-center mt-[2vh]">
|
||||
<LiaDotCircle className="mr-[1vw] pt-[0.5%]" />
|
||||
<p>MEET THE BOARD</p>
|
||||
</div>
|
||||
<div class="text-[5vw] md:text-[2.5vw] flex items-center mt-[1vw]">
|
||||
<LiaDotCircle className="mr-[1vw] pt-[0.5%]" />
|
||||
<p>MEET THE BOARD</p>
|
||||
</div>
|
||||
|
||||
<p class="text-[1.3vw] w-[56%] my-[3%] font-extralight text-center">
|
||||
Erat hendrerit tristique erat; parturient cursus fringilla feugiat. Eget
|
||||
faucibus fames ridiculus nec egestas convallis cubilia malesuada. Tellus
|
||||
nibh vivamus tempus molestie tristique quis
|
||||
</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>
|
||||
|
||||
<div class="grid gap-[3vw] grid-cols-3 mt-[10vh]">
|
||||
{
|
||||
officers.map((officer) => (
|
||||
<Officer
|
||||
name={officer.name}
|
||||
position={officer.position}
|
||||
picture={officer.picture}
|
||||
email={officer.email}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Filter filters={types} currentFilter={currentFilter} />
|
||||
|
||||
<div class="grid gap-[3vw] md:grid-cols-3 grid-cols-2 mt-[2vh]">
|
||||
{
|
||||
officers.map((officer) => (
|
||||
<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>
|
||||
</div>
|
||||
|
|
|
@ -4,14 +4,14 @@ const {title, text} = Astro.props;
|
|||
---
|
||||
|
||||
<div class="flex flex-col items-center text-white my-[10%]">
|
||||
<div class="flex items-center text-[2.5vw] mb-[3%]">
|
||||
<div class="flex items-center text-[4.5vw] md:text-[2.5vw] mb-[3%]">
|
||||
<LiaDotCircle className=" mr-[1vw] pt-[0.5%]"/>
|
||||
<p class="text-transparent bg-clip-text bg-gradient-to-b from-white via-white to-ieee-black">
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="w-[70%] text-[1.4vw] font-light ">
|
||||
<p class="w-[70%] md:text-[1.4vw] text-[2vw] font-light ">
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
|
@ -5,22 +5,22 @@ import whiteLogoHorizontal from "../../images/logos/white_logo_horizontal.svg";
|
|||
import { IoHeart } from "react-icons/io5";
|
||||
---
|
||||
|
||||
<div class="w-full py-[2%] flex justify-center">
|
||||
<div class="w-full md:py-[2%] py-[3vw] flex justify-center">
|
||||
<div
|
||||
class="w-[95%] bg-black/40 py-[1%] px-[1.5%] rounded-[2vw] border-[0.1vw] flex justify-between"
|
||||
class="w-[95%] bg-black/40 md:py-[1%] py-[2vw] px-[1.5%] rounded-[2vw] border-[0.1vw] flex justify-between"
|
||||
>
|
||||
<Link href="/" className="hover:opacity-70 duration-300">
|
||||
<Image
|
||||
class="w-[15vw]"
|
||||
class="md:w-[15vw] w-[35vw]"
|
||||
src={whiteLogoHorizontal}
|
||||
alt="IEEE UCSD Logo"
|
||||
/>
|
||||
</Link>
|
||||
<div class="flex items-center">
|
||||
<p class="text-white text-[1.2vw]">
|
||||
<p class="text-white md:text-[1.2vw] text-[2.7vw]">
|
||||
made by IEEE UCSD webmasters with
|
||||
</p>
|
||||
<IoHeart className=" text-ieee-blue-100 ml-[1vw] text-[1.5vw]" />
|
||||
<IoHeart className=" text-ieee-blue-100 ml-[1vw] md:text-[1.5vw] text-[4vw]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,146 +4,151 @@ import whiteLogoHorizontal from "../../images/logos/white_logo_horizontal.svg";
|
|||
import pages from "../../data/pages.json";
|
||||
---
|
||||
|
||||
<div class="md:w-full w-fit fixed z-10">
|
||||
<div
|
||||
class="flex justify-between items-center bg-black my-[1%] mx-[2.5%] py-[0.5%] px-[1%] md:rounded-full md:border-[0.1vw]"
|
||||
>
|
||||
<a href="/" class="hover:opacity-60 duration-300 hidden md:flex">
|
||||
<Image
|
||||
class="w-[15vw]"
|
||||
src={whiteLogoHorizontal}
|
||||
alt="IEEE UCSD Logo"
|
||||
/>
|
||||
</a>
|
||||
<div class="w-full">
|
||||
<div
|
||||
class="flex justify-between items-center bg-ieee-black my-[1%] mx-[2.5%] py-[0.5%] px-[1%] rounded-full md:border-[0.1vw]"
|
||||
>
|
||||
<a href="/" class="hover:opacity-60 duration-300">
|
||||
<Image
|
||||
class="w-[15vw] md:block hidden"
|
||||
src={whiteLogoHorizontal}
|
||||
alt="IEEE UCSD Logo"
|
||||
/>
|
||||
<Image
|
||||
class="w-[40vw] md:hidden block"
|
||||
src={whiteLogoHorizontal}
|
||||
alt="IEEE UCSD Logo"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden md:flex md:w-[55%] md:justify-between">
|
||||
{
|
||||
pages.map((page) => (
|
||||
<a
|
||||
href={page.path}
|
||||
class={`uppercase rounded-full duration-300 px-[1.5vw] py-[0.2vw] text-[1.2vw] text-nowrap
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden md:flex md:w-[55%] md:justify-between">
|
||||
{
|
||||
pages.map((page) => (
|
||||
<a
|
||||
href={page.path}
|
||||
class={`uppercase rounded-full duration-300 px-[1.5vw] py-[0.2vw] text-[1.2vw] text-nowrap
|
||||
${
|
||||
page.name === "Online Store"
|
||||
? "bg-ieee-yellow text-black hover:opacity-70"
|
||||
: "text-white border-white hover:opacity-50 border-[0.1vw] font-light"
|
||||
page.name === "Online Store"
|
||||
? "bg-ieee-yellow text-black hover:opacity-70"
|
||||
: "text-white border-white hover:opacity-50 border-[0.1vw] font-light"
|
||||
}`}
|
||||
>
|
||||
{page.name}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Mobile Hamburger/Close Button -->
|
||||
<button
|
||||
id="menu-btn"
|
||||
class="md:hidden text-white p-2 flex justify-center items-center focus:outline-none relative z-[60] scale-150"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<!-- Hamburger Icon -->
|
||||
<svg
|
||||
class="w-6 h-6 menu-icon"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
<!-- Close Icon -->
|
||||
<svg
|
||||
class="w-6 h-6 close-icon hidden"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
>
|
||||
{page.name}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<div
|
||||
id="mobile-menu"
|
||||
class="fixed inset-0 z-[51] hidden xl:hidden motion-safe:transition-transform motion-safe:duration-300 translate-x-full"
|
||||
<!-- Mobile Hamburger/Close Button -->
|
||||
<button
|
||||
id="menu-btn"
|
||||
class="md:hidden text-white p-2 flex justify-center items-center focus:outline-none relative z-[60] scale-150"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center h-[70vh] justify-evenly bg-black"
|
||||
>
|
||||
{
|
||||
pages.map((page) => (
|
||||
<a
|
||||
href={page.path}
|
||||
class={`block py-2 px-8 text-center rounded-[3rem] motion-safe:transition-colors motion-safe:duration-200 uppercase font-bold text-xl
|
||||
<!-- Hamburger Icon -->
|
||||
<svg
|
||||
class="w-6 h-6 menu-icon"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
<!-- Close Icon -->
|
||||
<svg
|
||||
class="w-6 h-6 close-icon hidden"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<div
|
||||
id="mobile-menu"
|
||||
class="fixed inset-0 z-[51] hidden xl:hidden motion-safe:transition-transform motion-safe:duration-300 translate-x-full"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center min-h-screen justify-center bg-black py-20 px-4 space-y-8"
|
||||
>
|
||||
{
|
||||
pages.map((page) => (
|
||||
<a
|
||||
href={page.path}
|
||||
class={`block py-4 px-12 text-center rounded-[3rem] motion-safe:transition-colors motion-safe:duration-200 uppercase font-bold text-2xl w-full max-w-md
|
||||
${
|
||||
page.name === "Online Store"
|
||||
? "bg-[#f3c135] text-black border-[#f3c135] hover:bg-[#dba923] hover:border-[#dba923]"
|
||||
: "text-white hover:text-gray-300 border-white"
|
||||
page.name === "Online Store"
|
||||
? "bg-[#f3c135] text-black border-[#f3c135] hover:bg-[#dba923] hover:border-[#dba923]"
|
||||
: "text-white hover:text-gray-300 border-white border-2"
|
||||
}`}
|
||||
>
|
||||
{page.name}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
>
|
||||
{page.name}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#mobile-menu.show {
|
||||
@apply translate-x-0;
|
||||
}
|
||||
#mobile-menu.show {
|
||||
@apply translate-x-0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const menuBtn = document.getElementById("menu-btn");
|
||||
const mobileMenu = document.getElementById("mobile-menu");
|
||||
const menuIcon = document.querySelector(".menu-icon");
|
||||
const closeIcon = document.querySelector(".close-icon");
|
||||
const menuBtn = document.getElementById("menu-btn");
|
||||
const mobileMenu = document.getElementById("mobile-menu");
|
||||
const menuIcon = document.querySelector(".menu-icon");
|
||||
const closeIcon = document.querySelector(".close-icon");
|
||||
|
||||
function toggleMenu(show: boolean) {
|
||||
if (show) {
|
||||
mobileMenu?.classList.remove("hidden");
|
||||
menuIcon?.classList.add("hidden");
|
||||
closeIcon?.classList.remove("hidden");
|
||||
document.body.style.overflow = "hidden";
|
||||
function toggleMenu(show: boolean) {
|
||||
if (show) {
|
||||
mobileMenu?.classList.remove("hidden");
|
||||
menuIcon?.classList.add("hidden");
|
||||
closeIcon?.classList.remove("hidden");
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
setTimeout(() => {
|
||||
mobileMenu?.classList.add("show");
|
||||
}, 10);
|
||||
} else {
|
||||
mobileMenu?.classList.remove("show");
|
||||
menuIcon?.classList.remove("hidden");
|
||||
closeIcon?.classList.add("hidden");
|
||||
document.body.style.overflow = "";
|
||||
setTimeout(() => {
|
||||
mobileMenu?.classList.add("show");
|
||||
}, 10);
|
||||
} else {
|
||||
mobileMenu?.classList.remove("show");
|
||||
menuIcon?.classList.remove("hidden");
|
||||
closeIcon?.classList.add("hidden");
|
||||
document.body.style.overflow = "";
|
||||
|
||||
setTimeout(() => {
|
||||
mobileMenu?.classList.add("hidden");
|
||||
}, 100);
|
||||
}
|
||||
setTimeout(() => {
|
||||
mobileMenu?.classList.add("hidden");
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
menuBtn?.addEventListener("click", () => {
|
||||
const isMenuHidden = mobileMenu?.classList.contains("hidden") ?? true;
|
||||
toggleMenu(isMenuHidden);
|
||||
});
|
||||
menuBtn?.addEventListener("click", () => {
|
||||
const isMenuHidden = mobileMenu?.classList.contains("hidden") ?? true;
|
||||
toggleMenu(isMenuHidden);
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener("click", (e) => {
|
||||
if (
|
||||
!mobileMenu?.contains(e.target as Node) &&
|
||||
!menuBtn?.contains(e.target as Node) &&
|
||||
!mobileMenu?.classList.contains("hidden")
|
||||
) {
|
||||
toggleMenu(false);
|
||||
}
|
||||
});
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener("click", (e) => {
|
||||
if (
|
||||
!mobileMenu?.contains(e.target as Node) &&
|
||||
!menuBtn?.contains(e.target as Node) &&
|
||||
!mobileMenu?.classList.contains("hidden")
|
||||
) {
|
||||
toggleMenu(false);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
const {question, answer} = Astro.props;
|
||||
---
|
||||
<div class = "text-white w-full flex flex-col mb-[2vh]">
|
||||
<p data-inview class = "in-view:animate-fade-right text-ieee-yellow text-[1.4vw] mb-[2vh] font-semibold pl-[1vw]">
|
||||
<div class = "text-white w-full flex flex-col mb-[2vh] animate-ease-in-out">
|
||||
<p data-inview class = "in-view:animate-fade-right text-ieee-yellow text-[2.5vw] md:text-[1.4vw] mb-[2vh] font-semibold pl-[1vw]">
|
||||
{question}
|
||||
</p>
|
||||
<p data-inview class = "w-[60%] mb-[2vh] pl-[1vw] in-view:animate-fade-left">
|
||||
<p data-inview class = "md:w-[70%] w-[80%] mb-[2vh] pl-[1vw] text-[2vw] md:text-[1.2vw] in-view:animate-fade-left animate-ease-in-out">
|
||||
{answer}
|
||||
</p>
|
||||
<dev>
|
||||
|
|
|
@ -4,8 +4,8 @@ import { LiaDotCircle } from "react-icons/lia";
|
|||
const {faq} = Astro.props;
|
||||
|
||||
---
|
||||
<div class = "text-white w-full h-[90vh] ml-[15vw] mb-[20%] mt-[10%]">
|
||||
<div class = "text-[2.5vw] flex items-center my-[7vh]">
|
||||
<div class = "text-white md:ml-[15vw] ml-[10vw] mb-[20%] mt-[10%]">
|
||||
<div class = "text-[4.5vw] md:text-[2.5vw] flex items-center my-[7vh]">
|
||||
<LiaDotCircle className = "mr-[1vw]" />
|
||||
<p>
|
||||
Frequently Asked Questions
|
||||
|
|
|
@ -1,71 +1,79 @@
|
|||
---
|
||||
import { LiaDotCircle } from "react-icons/lia";
|
||||
import { Image } from "astro:assets";
|
||||
import evan from "../../images/evan.png";
|
||||
import { FaDiscord, FaFacebook } from "react-icons/fa";
|
||||
import jellyfish from "../../images/jellyfish.webp";
|
||||
import { FaDiscord } from "react-icons/fa";
|
||||
import { RiInstagramFill } from "react-icons/ri";
|
||||
import { MdEmail } from "react-icons/md";
|
||||
import Link from "next/link";
|
||||
---
|
||||
|
||||
<div class="text-white flex flex-col items-center h-[65vh] justify-between">
|
||||
<div class="flex items-center text-[2.5vw]">
|
||||
<div
|
||||
class="text-white flex flex-col items-center md:h-[35vw] h-[60vw] justify-between"
|
||||
>
|
||||
<div class="flex items-center text-[4.5vw] md:text-[2.5vw]">
|
||||
<LiaDotCircle className=" mr-[1vw] pt-[0.5%]" />
|
||||
<p>Social Media</p>
|
||||
</div>
|
||||
|
||||
<p class="w-1/3 text-center text-[1.2vw] pb-[5%]">
|
||||
Lorem ipsum is placeholder text commonly used in the graphic, print, and
|
||||
publishing industries f
|
||||
<p class="md:w-1/3 w-3/5 text-center text-[2vw] md:text-[1.2vw] pb-[5%]">
|
||||
Stay connected with us on Discord, Facebook, and Instagram! We regularly
|
||||
post information on upcoming events and competitions.
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="w-[85%] rounded-[3vw] bg-gradient-to-r from-ieee-blue-300 to-ieee-blue-100 relative h-[30vh] flex items-center text-white/90"
|
||||
class="md:w-[85%] w-full rounded-[3vw] bg-gradient-to-r from-ieee-blue-300 to-ieee-blue-100 relative md:h-[15vw] h-[30vw] flex items-center text-white/90"
|
||||
>
|
||||
<div data-inview class="w-2/5 flex justify-evenly ml-[5%] in-view:animate-flip-up animate-duration-1000">
|
||||
<Link
|
||||
href="https://discord.gg/XxfjqZSjca"
|
||||
target="_blank"
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div
|
||||
class="border-[0.15vw] rounded-full shadow-glow hover:scale-110 duration-300 p-[1vw] text-[2.2vw] text-ieee-black/80 border-ieee-blue-100 bg-gradient-radial from-white to-ieee-blue-100"
|
||||
>
|
||||
<FaDiscord />
|
||||
</div>
|
||||
<p class="text-[1.3vw] font-semibold">Discord</p>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="https://www.facebook.com/ieeeucsd"
|
||||
target="_blank"
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div
|
||||
class="border-[0.15vw] rounded-full shadow-glow hover:scale-110 duration-300 p-[1vw] text-[2.2vw] text-ieee-black/80 border-ieee-blue-100 bg-gradient-radial from-white to-ieee-blue-100"
|
||||
>
|
||||
<FaFacebook />
|
||||
</div>
|
||||
<p class="text-[1.3vw] font-semibold">Facebook</p>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
data-inview
|
||||
class="md:w-2/5 w-1/2 flex justify-evenly ml-[5%] animate-ease-in-out in-view:animate-flip-up animate-duration-1000"
|
||||
>
|
||||
<Link
|
||||
href="https://www.instagram.com/ieee.ucsd"
|
||||
target="_blank"
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div
|
||||
class="border-[0.15vw] rounded-full shadow-glow hover:scale-110 duration-300 p-[1vw] text-[2.2vw] text-ieee-black/80 border-ieee-blue-100 bg-gradient-radial from-white to-ieee-blue-100"
|
||||
class="border-[0.15vw] rounded-full shadow-glow hover:scale-110 duration-300 p-[1vw] text-[5vw] md:text-[2.2vw] text-ieee-black/80 border-ieee-blue-100 bg-gradient-radial from-white to-ieee-blue-100"
|
||||
>
|
||||
<RiInstagramFill />
|
||||
</div>
|
||||
<p class="text-[1.3vw] font-semibold">Instagram</p>
|
||||
<p class="text-[2vw] md:text-[1.3vw] font-semibold">
|
||||
Instagram
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="https://discord.gg/XxfjqZSjca"
|
||||
target="_blank"
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div
|
||||
class="border-[0.15vw] rounded-full shadow-glow hover:scale-110 duration-300 p-[1vw] text-[5vw] md:text-[2.2vw] text-ieee-black/80 border-ieee-blue-100 bg-gradient-radial from-white to-ieee-blue-100"
|
||||
>
|
||||
<FaDiscord />
|
||||
</div>
|
||||
<p class="text-[2vw] md:text-[1.3vw] font-semibold">Discord</p>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="mailto:ieee@ucsd.edu"
|
||||
target="_blank"
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div
|
||||
class="border-[0.15vw] rounded-full shadow-glow hover:scale-110 duration-300 p-[1vw] text-[5vw] md:text-[2.2vw] text-ieee-black/80 border-ieee-blue-100 bg-gradient-radial from-white to-ieee-blue-100"
|
||||
>
|
||||
<MdEmail />
|
||||
</div>
|
||||
<p class="text-[2vw] md:text-[1.3vw] font-semibold">Email</p>
|
||||
</Link>
|
||||
</div>
|
||||
<Image
|
||||
data-inview
|
||||
src={evan}
|
||||
src={jellyfish}
|
||||
alt="cat placeholder"
|
||||
class="absolute -bottom-[10vh] w-[25vw] right-0 in-view:animate-wiggle"
|
||||
class="absolute bottom-0 md:w-[25vw] w-[35vw] right-[4vw] animate-wiggle animate-infinite"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ const {title} = Astro.props;
|
|||
import { LiaDotCircle } from "react-icons/lia";
|
||||
---
|
||||
|
||||
<div class="flex items-center text-[2.5vw] mb-[5%]">
|
||||
<div class="flex items-center md:text-[2.5vw] text-[4vw] mb-[5%]">
|
||||
<LiaDotCircle className=" mr-[1vw] pt-[0.5%]"/>
|
||||
<p>
|
||||
{title}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
---
|
||||
const {title} = Astro.props;
|
||||
const { title } = Astro.props;
|
||||
import { LiaDotCircle } from "react-icons/lia";
|
||||
---
|
||||
|
||||
<div class="flex items-center text-[3vw] ml-[10%] pt-[10%] text-white font-semibold">
|
||||
<LiaDotCircle className=" mr-[1vw] text-[2.7vw]"/>
|
||||
<p>
|
||||
{title}
|
||||
</p>
|
||||
<div
|
||||
class="flex items-center md:text-[3vw] text-[4.5vw] ml-[10%] md:pt-[5%] pt-[10%] text-white font-semibold"
|
||||
>
|
||||
<LiaDotCircle className=" mr-[1vw] text-[2.7vw]" />
|
||||
<p>
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
358
src/components/events/Calendar.jsx
Normal file
|
@ -0,0 +1,358 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
|
||||
const Calendar = ({ CALENDAR_API_KEY, EVENT_CALENDAR_ID }) => {
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const [events, setEvents] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [hoveredEvent, setHoveredEvent] = useState(null);
|
||||
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
|
||||
|
||||
const getDaysInMonth = (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastDay.getDate();
|
||||
const startingDay = firstDay.getDay();
|
||||
const endingDay = lastDay.getDay();
|
||||
|
||||
const days = [];
|
||||
// Add empty slots for days before the first of the month
|
||||
for (let i = 0; i < startingDay; i++) {
|
||||
days.push(null);
|
||||
}
|
||||
// Add all days of the month
|
||||
for (let i = 1; i <= daysInMonth; i++) {
|
||||
days.push(new Date(year, month, i));
|
||||
}
|
||||
// Add empty slots for remaining days in the last week
|
||||
const remainingDays = 6 - endingDay;
|
||||
for (let i = 0; i < remainingDays; i++) {
|
||||
days.push(null);
|
||||
}
|
||||
return days;
|
||||
};
|
||||
|
||||
// Format date to match event dates
|
||||
const formatDate = (date) => {
|
||||
if (!date) return "";
|
||||
return date.toISOString().split("T")[0];
|
||||
};
|
||||
|
||||
// Get events for a specific day
|
||||
const getEventsForDay = (day) => {
|
||||
if (!day) return [];
|
||||
const dayStr = formatDate(day);
|
||||
return events.filter((event) => {
|
||||
const eventDate = event.start.dateTime
|
||||
? new Date(event.start.dateTime).toISOString().split("T")[0]
|
||||
: event.start.date;
|
||||
return eventDate === dayStr;
|
||||
});
|
||||
};
|
||||
|
||||
// Format time for display
|
||||
const formatEventTime = (dateTimeStr) => {
|
||||
if (!dateTimeStr) return "";
|
||||
const date = new Date(dateTimeStr);
|
||||
return date.toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const calendarId = EVENT_CALENDAR_ID;
|
||||
const userTimeZone = "America/Los_Angeles";
|
||||
|
||||
const loadGapiAndListEvents = async () => {
|
||||
try {
|
||||
console.log("Starting to load events...");
|
||||
|
||||
if (typeof window.gapi === "undefined") {
|
||||
console.log("Loading GAPI script...");
|
||||
await new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://apis.google.com/js/api.js";
|
||||
document.body.appendChild(script);
|
||||
script.onload = () => {
|
||||
console.log("GAPI script loaded");
|
||||
window.gapi.load("client", resolve);
|
||||
};
|
||||
script.onerror = () => {
|
||||
console.error("Failed to load GAPI script");
|
||||
reject(new Error("Failed to load the Google API script."));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Initializing GAPI client...");
|
||||
await window.gapi.client.init({
|
||||
apiKey: CALENDAR_API_KEY,
|
||||
discoveryDocs: [
|
||||
"https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest",
|
||||
],
|
||||
});
|
||||
|
||||
// Get first and last day of current month
|
||||
const firstDay = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth(),
|
||||
1,
|
||||
);
|
||||
const lastDay = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth() + 1,
|
||||
0,
|
||||
);
|
||||
|
||||
console.log("Fetching events...");
|
||||
const response = await window.gapi.client.calendar.events.list({
|
||||
calendarId: calendarId,
|
||||
timeZone: userTimeZone,
|
||||
singleEvents: true,
|
||||
timeMin: firstDay.toISOString(),
|
||||
timeMax: lastDay.toISOString(),
|
||||
orderBy: "startTime",
|
||||
});
|
||||
|
||||
console.log("Response received:", response);
|
||||
|
||||
if (response.result.items) {
|
||||
setEvents(response.result.items);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Detailed Error: ", error);
|
||||
setError(error.message || "Failed to load events");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!CALENDAR_API_KEY) {
|
||||
setError("API key is missing");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
loadGapiAndListEvents();
|
||||
}, [CALENDAR_API_KEY, currentDate]);
|
||||
|
||||
const monthNames = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
|
||||
const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
|
||||
const changeMonth = (increment) => {
|
||||
setCurrentDate(
|
||||
new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth() + increment,
|
||||
1,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleEventMouseEnter = (event, e) => {
|
||||
const target = e.target;
|
||||
setTooltipPosition({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
});
|
||||
setHoveredEvent({ event, target });
|
||||
};
|
||||
|
||||
const handleEventMouseLeave = () => {
|
||||
setHoveredEvent(null);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
if (hoveredEvent) {
|
||||
// Check if the mouse is still over the event element
|
||||
const rect = hoveredEvent.target.getBoundingClientRect();
|
||||
const isStillHovering =
|
||||
e.clientX >= rect.left &&
|
||||
e.clientX <= rect.right &&
|
||||
e.clientY >= rect.top &&
|
||||
e.clientY <= rect.bottom;
|
||||
|
||||
if (!isStillHovering) {
|
||||
setHoveredEvent(null);
|
||||
} else {
|
||||
setTooltipPosition({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (hoveredEvent) {
|
||||
const rect = hoveredEvent.target.getBoundingClientRect();
|
||||
const mouseX = tooltipPosition.x - 15; // Subtract the offset added to the tooltip
|
||||
const mouseY = tooltipPosition.y - 15;
|
||||
|
||||
const isStillHovering =
|
||||
mouseX >= rect.left &&
|
||||
mouseX <= rect.right &&
|
||||
mouseY >= rect.top &&
|
||||
mouseY <= rect.bottom;
|
||||
|
||||
if (!isStillHovering) {
|
||||
setHoveredEvent(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll, true);
|
||||
return () => window.removeEventListener("scroll", handleScroll, true);
|
||||
}, [hoveredEvent, tooltipPosition]);
|
||||
|
||||
if (!CALENDAR_API_KEY) {
|
||||
return (
|
||||
<div className="text-white">
|
||||
Error: Calendar API key is not configured
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="md:w-[90vw] w-[95vw] mx-auto p-[3vw] relative z-10"
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
{/* Hovering Calendar Header */}
|
||||
<div className="flex justify-center mb-[2vw]">
|
||||
<div className="bg-gradient-to-t from-ieee-blue-100/5 to-ieee-blue-100/25 rounded-[1.5vw] p-[1vw] backdrop-blur-sm w-[30vw] px-[2vw]">
|
||||
<div className="flex items-center gap-[3vw]">
|
||||
<button
|
||||
onClick={() => changeMonth(-1)}
|
||||
className="text-white hover:text-ieee-yellow transition-colors text-[2vw] bg-ieee-black/40 w-[4vw] h-[4vw] rounded-[1vw] flex items-center justify-center"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<h2 className="text-white text-[2.5vw] font-bold whitespace-nowrap">
|
||||
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => changeMonth(1)}
|
||||
className="text-white hover:text-gray transition-colors text-[2vw] bg-ieee-black/40 w-[4vw] h-[4vw] rounded-[1vw] flex items-center justify-center"
|
||||
>
|
||||
→
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Calendar Body */}
|
||||
<div className="bg-gradient-to-t from-ieee-blue-100/5 to-ieee-blue-100/25 rounded-[1.5vw] p-[1vw] relative">
|
||||
{/* Week Days Header */}
|
||||
<div className="grid grid-cols-7 gap-[0.5vw] mb-[1vw]">
|
||||
{weekDays.map((day, index) => (
|
||||
<div key={day} className="flex justify-center w-full">
|
||||
<div
|
||||
className={`text-white text-center font-semibold p-[0.5vw] text-[1.2vw] bg-ieee-black/60 w-full h-[4vw] flex items-center justify-center
|
||||
${
|
||||
index === 0
|
||||
? "rounded-tl-[2vw] rounded-[0.5vw]"
|
||||
: index === 6
|
||||
? "rounded-tr-[2vw] rounded-[0.5vw]"
|
||||
: "rounded-[0.5vw]"
|
||||
}`}
|
||||
>
|
||||
{day}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Calendar Grid */}
|
||||
<div className="grid grid-cols-7 gap-[0.5vw] relative">
|
||||
{getDaysInMonth(currentDate).map((day, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`min-h-[10vw] p-[0.5vw] rounded relative ${
|
||||
day ? "bg-white/5" : "bg-transparent"
|
||||
} border border-white/10`}
|
||||
>
|
||||
{day && (
|
||||
<>
|
||||
<div className="text-white mb-[0.5vw] text-[1vw]">
|
||||
{day.getDate()}
|
||||
</div>
|
||||
<div className="space-y-[0.5vw]">
|
||||
{getEventsForDay(day).map((event, eventIndex) => (
|
||||
<div
|
||||
key={eventIndex}
|
||||
className="text-[0.8vw] border border-gray-300 text-white p-[0.5vw] rounded truncate cursor-pointer hover:bg-white/10 transition-colors relative"
|
||||
onMouseEnter={(e) => handleEventMouseEnter(event, e)}
|
||||
onMouseLeave={handleEventMouseLeave}
|
||||
>
|
||||
{event.summary}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tooltip */}
|
||||
{hoveredEvent && (
|
||||
<div
|
||||
className="fixed z-[9999] bg-ieee-blue-100 text-white p-[1vw] rounded-[0.5vw] shadow-xl border border-white/20 min-w-[15vw]"
|
||||
style={{
|
||||
left: `${tooltipPosition.x + 15}px`,
|
||||
top: `${tooltipPosition.y + 15}px`,
|
||||
}}
|
||||
>
|
||||
<h3 className="text-[1vw] font-semibold mb-[0.5vw]">
|
||||
{hoveredEvent.event.summary}
|
||||
</h3>
|
||||
{hoveredEvent.event.description && (
|
||||
<p className="text-[0.8vw] mb-[0.5vw] text-white/80">
|
||||
{hoveredEvent.event.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="text-[0.8vw] text-white/90">
|
||||
{hoveredEvent.event.start.dateTime ? (
|
||||
<>
|
||||
<p>
|
||||
Start: {formatEventTime(hoveredEvent.event.start.dateTime)}
|
||||
</p>
|
||||
<p>End: {formatEventTime(hoveredEvent.event.end.dateTime)}</p>
|
||||
</>
|
||||
) : (
|
||||
<p>All day event</p>
|
||||
)}
|
||||
{hoveredEvent.event.location && (
|
||||
<p className="mt-[0.3vw]">
|
||||
Location: {hoveredEvent.event.location}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Calendar;
|
170
src/components/events/EventList.jsx
Normal file
|
@ -0,0 +1,170 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const UpcomingEvent = ({ name, location, date, time, delay, description }) => (
|
||||
<div className="text-white w-[40vw] pl-[8%] md:border-l-[0.3vw] border-l-[0.5vw] border-white/70 pb-[5%] relative">
|
||||
<p
|
||||
data-inview
|
||||
className={`animate-duration-500 animate-delay-${delay * 200} in-view:animate-fade-left py-[0.2%] px-[2%] w-fit border-[0.1vw] font-light rounded-full md:text-[1.3vw] text-[2.3vw]`}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
<div
|
||||
data-inview
|
||||
className={`animate-duration-500 animate-delay-${delay * 200 + 100} in-view:animate-fade-left flex justify-between items-center min-w-[70%] w-fit md:text-[1.2vw] text-[2vw] my-[2%]`}
|
||||
>
|
||||
Location: {location}
|
||||
{date && (
|
||||
<>
|
||||
<div className="bg-white h-[0.5vw] w-[0.5vw] rounded-full mx-[0.5vw]" />
|
||||
<p>{date}</p>
|
||||
</>
|
||||
)}
|
||||
{time && (
|
||||
<>
|
||||
<div className="bg-white h-[0.5vw] w-[0.5vw] rounded-full mx-[0.5vw]" />
|
||||
<p>{time}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
data-inview
|
||||
className={`animate-duration-500 animate-delay-${delay * 200 + 200} in-view:animate-fade-left md:text-[1vw] text-[1.8vw] text-white/60`}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
<div className="bg-ieee-yellow md:h-[1.2vw] h-[1.5vw] md:w-[1.2vw] w-[1.5vw] rounded-full absolute -top-[1.5%] -left-[2%]" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const EventList = ({ CALENDAR_API_KEY, EVENT_CALENDAR_ID }) => {
|
||||
const [events, setEvents] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const apiKey = CALENDAR_API_KEY;
|
||||
const calendarId = EVENT_CALENDAR_ID;
|
||||
const userTimeZone = "America/Los_Angeles";
|
||||
|
||||
const loadGapiAndListEvents = async () => {
|
||||
try {
|
||||
console.log("Starting to load events...");
|
||||
|
||||
if (typeof window.gapi === "undefined") {
|
||||
console.log("Loading GAPI script...");
|
||||
await new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://apis.google.com/js/api.js";
|
||||
document.body.appendChild(script);
|
||||
script.onload = () => {
|
||||
console.log("GAPI script loaded");
|
||||
window.gapi.load("client", resolve);
|
||||
};
|
||||
script.onerror = () => {
|
||||
console.error("Failed to load GAPI script");
|
||||
reject(new Error("Failed to load the Google API script."));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Initializing GAPI client...");
|
||||
await window.gapi.client.init({
|
||||
apiKey: apiKey,
|
||||
discoveryDocs: [
|
||||
"https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest",
|
||||
],
|
||||
});
|
||||
|
||||
console.log("Fetching events...");
|
||||
const response = await window.gapi.client.calendar.events.list({
|
||||
calendarId: calendarId,
|
||||
timeZone: userTimeZone,
|
||||
singleEvents: true,
|
||||
timeMin: new Date().toISOString(),
|
||||
maxResults: 3,
|
||||
orderBy: "startTime",
|
||||
});
|
||||
|
||||
console.log("Response received:", response);
|
||||
|
||||
if (response.result.items) {
|
||||
setEvents(response.result.items);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Detailed Error: ", error);
|
||||
setError(error.message || "Failed to load events");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!CALENDAR_API_KEY) {
|
||||
setError("API key is missing");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
loadGapiAndListEvents();
|
||||
}, [CALENDAR_API_KEY]);
|
||||
|
||||
if (!CALENDAR_API_KEY) {
|
||||
return (
|
||||
<div className="text-white">
|
||||
Error: Calendar API key is not configured
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{error && <p className="text-white">Error: {error}</p>}
|
||||
{!loading && !error && events.length === 0 && (
|
||||
<UpcomingEvent
|
||||
name="No Upcoming Events!"
|
||||
location="¯\_(ツ)_/¯"
|
||||
date=""
|
||||
time=""
|
||||
delay={0}
|
||||
description="There are no upcoming events! Check back again soon :)
|
||||
...or just wait for the entire page to load. This is here by default LOL"
|
||||
/>
|
||||
)}
|
||||
{!loading && !error && events.length > 0 && (
|
||||
<div>
|
||||
{events.map((event, index) => {
|
||||
const startDate = new Date(
|
||||
event.start.dateTime || event.start.date,
|
||||
);
|
||||
const day = startDate.toLocaleDateString("en-US", {
|
||||
weekday: "short",
|
||||
});
|
||||
const date = startDate.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
const time = startDate.toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
return (
|
||||
<UpcomingEvent
|
||||
key={index}
|
||||
name={event.summary || "No Title"}
|
||||
location={event.location || "No location provided"}
|
||||
date={`${day} ${date}`}
|
||||
time={time}
|
||||
delay={index}
|
||||
description={event.description || "No description available."}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventList;
|
|
@ -1,20 +1,19 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import eventbg from "../../images/eventbg.png";
|
||||
import eventbg from "../../images/eventbg.webp";
|
||||
import { LiaDotCircle } from "react-icons/lia";
|
||||
---
|
||||
|
||||
<div class="w-full pt-[25vh] flex justify-center relative">
|
||||
<Image
|
||||
src={eventbg}
|
||||
alt="Event Page Background"
|
||||
class="w-[45%] rounded-[2vw] aspect-[2/1] object-cover relative in-view:animate-fade-down opacity-0"
|
||||
data-inview
|
||||
/>
|
||||
<div
|
||||
class="absolute -bottom-[6%] left-[20%] flex items-center text-[3vw] py-[1.5%] px-[3%] text-white bg-ieee-black rounded-[2vw]"
|
||||
>
|
||||
<LiaDotCircle className=" mr-[2vw] pt-[0.5%]" />
|
||||
<p>EVENTS</p>
|
||||
</div>
|
||||
<div class="w-full md:pt-[5vw] pt-[10vw] flex justify-center relative">
|
||||
<Image
|
||||
src={eventbg}
|
||||
alt="Event Page Background"
|
||||
class="md:w-[45%] w-[80%] rounded-[2vw] aspect-[2/1] object-cover"
|
||||
/>
|
||||
<div
|
||||
class="absolute -bottom-[6%] md:left-[20%] left-[10%] flex items-center md:text-[3vw] text-[6vw] py-[1.5%] px-[3%] text-white bg-ieee-black rounded-[2vw]"
|
||||
>
|
||||
<LiaDotCircle className=" mr-[2vw] pt-[0.5%]" />
|
||||
<p>EVENTS</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,45 +1,28 @@
|
|||
---
|
||||
import UpcomingEvent from "./UpcomingEvent.astro";
|
||||
import { LiaDotCircle } from "react-icons/lia";
|
||||
import EventList from "./EventList.jsx";
|
||||
const CALENDAR_API_KEY = import.meta.env.CALENDAR_API_KEY;
|
||||
const EVENT_CALENDAR_ID = import.meta.env.EVENT_CALENDAR_ID;
|
||||
---
|
||||
<div class="flex ml-[15%] my-[10%]">
|
||||
<div class="w-1/4 text-white pr-[5%] mr-[10%]">
|
||||
<div class="w-[6vw] h-[0.3vw] bg-ieee-yellow rounded-full"/>
|
||||
<div class="flex items-center text-[2vw] font-bold my-[10%]">
|
||||
<LiaDotCircle className=" mr-[1vw] text-[2.5vw]"/>
|
||||
<p>
|
||||
Upcoming <br/> Events
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-[1.3vw] font-light">
|
||||
SCROLL DOWN TO SEE THE UPCOMING EVENTS FOR IEEE!
|
||||
|
||||
<div class="flex ml-[15%] md:my-[10%] my-[20%]">
|
||||
<div class="md:w-1/4 w-[30%] text-white pr-[5%] mr-[10%]">
|
||||
<div class="w-[6vw] h-[0.3vw] bg-ieee-yellow rounded-full"></div>
|
||||
<div class="flex items-center md:text-[2vw] text-[4vw] font-bold my-[10%]">
|
||||
<LiaDotCircle className=" mr-[1vw] text-[2.5vw]" />
|
||||
<p>
|
||||
Upcoming <br /> Events
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<UpcomingEvent
|
||||
name="Beach Social"
|
||||
location="La Jolla Cove"
|
||||
date="Tue 9 Jun 2024"
|
||||
time="2 pm"
|
||||
delay="0"
|
||||
description="Cursus nec orci pulvinar convallis mollis a diam. Nostra aptent praesent suscipit nisl dignissim consequat. Semper malesuada pharetra fusce; elementum maximus"
|
||||
/>
|
||||
<UpcomingEvent
|
||||
name="Beach Social"
|
||||
location="La Jolla Cove"
|
||||
date="Tue 9 Jun 2024"
|
||||
time="2 pm"
|
||||
delay="300"
|
||||
description="Cursus nec orci pulvinar convallis mollis a diam. Nostra aptent praesent suscipit nisl dignissim consequat. Semper malesuada pharetra fusce; elementum maximus"
|
||||
/>
|
||||
<UpcomingEvent
|
||||
name="Beach Social"
|
||||
location="La Jolla Cove"
|
||||
date="Tue 9 Jun 2024"
|
||||
time="2 pm"
|
||||
delay="700"
|
||||
description="Cursus nec orci pulvinar convallis mollis a diam. Nostra aptent praesent suscipit nisl dignissim consequat. Semper malesuada pharetra fusce; elementum maximus"
|
||||
/>
|
||||
</div>
|
||||
<p class="md:text-[1.3vw] text-[2vw] font-light">
|
||||
SCROLL DOWN TO SEE THE UPCOMING EVENTS FOR IEEE!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EventList
|
||||
client:load
|
||||
CALENDAR_API_KEY={CALENDAR_API_KEY}
|
||||
EVENT_CALENDAR_ID={EVENT_CALENDAR_ID}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,22 +3,32 @@ import Link from "next/link";
|
|||
import { LiaDotCircle } from "react-icons/lia";
|
||||
---
|
||||
|
||||
<div class="pt-[14vh] text-white w-full flex justify-center mb-[10vh]">
|
||||
<div class="w-3/4 h-[40vh] flex justify-between flex-col">
|
||||
<div class="flex items-center text-[3vw] pl-[5%] pt-[5%]">
|
||||
<LiaDotCircle className=" mr-[1vw] pt-[0.5%]"/>
|
||||
<p>
|
||||
Contact Us
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-[1.25vw]">
|
||||
The <a class=" text-ieee-yellow underline" href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6" target="_blank">IEEE Project Space</a> is an open-access, collaborative space located at EBU1-4710. Students can do homework or get access to basic electronic tools such as soldering stations, breadboard components, and Arduino and Raspberry PI parts!
|
||||
</p>
|
||||
<div class="flex justify-end">
|
||||
<Link data-inview href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6" target="_blank" className="in-view:animate-jump-in border-white/70 border-[0.1vw] py-[1%] px-[8%] rounded-[0.7vw] hover:text-ieee-yellow hover:border-ieee-yellow duration-300 text-[1.2vw]">
|
||||
DISCORD
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="md:pt-[5vw] pt-[10vw] text-white w-full flex justify-center md:mb-[8vw] mb-[15vw]"
|
||||
>
|
||||
<div class="w-3/4 md:h-[25vw] h-[35vw] flex justify-between flex-col">
|
||||
<div class="flex items-center md:text-[3vw] text-[4.5vw] pl-[5%] pt-[5%]">
|
||||
<LiaDotCircle className=" mr-[1vw] pt-[0.5%]" />
|
||||
<p>Contact Us</p>
|
||||
</div>
|
||||
<p class="md:text-[1.25vw] text-[2vw]">
|
||||
The <a
|
||||
class="text-ieee-yellow underline"
|
||||
href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6"
|
||||
target="_blank">IEEE Project Space</a
|
||||
> is an open-access, collaborative space located at EBU1-4710. Students can
|
||||
do homework or get access to basic electronic tools such as soldering stations,
|
||||
breadboard components, and Arduino and Raspberry PI parts!
|
||||
</p>
|
||||
<div class="flex justify-end">
|
||||
<Link
|
||||
data-inview
|
||||
href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6"
|
||||
target="_blank"
|
||||
className="in-view:animate-jump-in border-white/70 border-[0.1vw] py-[1%] px-[8%] rounded-[0.7vw] hover:text-ieee-yellow hover:border-ieee-yellow duration-300 md:text-[1.2vw] text-[2vw]"
|
||||
>
|
||||
DISCORD
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
20
src/components/join/BuildingLocation.astro
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<div
|
||||
data-inview
|
||||
class="flex flex-col items-center md:w-[40vw] w-[100vw] h-[100vw] md:h-[40vw] my-[10%] in-view:animate-fade-down"
|
||||
>
|
||||
<p class="text-ieee-yellow md:text-[1.2vw] text-[2.5vw]">Each officer has their own OAH</p>
|
||||
<p class="text-white md:text-[2.5vw] text-[4.5vw] my-[2%]">Check out our calendar!</p>
|
||||
<iframe
|
||||
class="w-[90%] rounded-[1.5vw]"
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3350.627565860723!2d-117.23806952372503!3d32.881572078634505!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x80dc06c3689b4f99%3A0xdf55f97f07f34d4f!2sIrwin%20%26%20Joan%20Jacobs%20School%20of%20Engineering!5e0!3m2!1sen!2sus!4v1736755644414!5m2!1sen!2sus"
|
||||
width="600"
|
||||
height="450"
|
||||
style="border:0;"
|
||||
allowfullscreen=""
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||
</div>
|
|
@ -1,8 +1,9 @@
|
|||
---
|
||||
import OH from "./OH.astro"
|
||||
import OAH from "./OAH.astro";
|
||||
import BuildingLocation from "./BuildingLocation.astro";
|
||||
---
|
||||
|
||||
<div class="w-full flex justify-evenly px-[10%]">
|
||||
<OH title="Check out our calendar!" subtitle="Each officer has their own OAH" image="/calendar.png" />
|
||||
<OH title="Check out our location!" subtitle="We are located in EBU1-4710" image="/map.png" />
|
||||
<div class="w-full flex flex-col md:flex-row items-center md:justify-evenly px-[10%]">
|
||||
<OAH />
|
||||
<BuildingLocation />
|
||||
</div>
|
|
@ -6,16 +6,16 @@ import { IoMdCalendar } from "react-icons/io";
|
|||
import { RiRobot2Fill } from "react-icons/ri";
|
||||
const {image, text, link, delay} = Astro.props;
|
||||
---
|
||||
<div data-inview class={`w-[15vw] relative group in-view:animate-fade-up animate-delay-${delay} animate-duration-1000`}>
|
||||
<div data-inview class={` animate-ease-in-out md:w-[15vw] w-[24vw] relative group in-view:animate-fade-up animate-delay-${delay} animate-duration-1000`}>
|
||||
|
||||
<img src={image} alt="involvement background" class="opacity-70 aspect-[230/425] object-cover rounded-[2vw] group-hover:opacity-50 duration-300"/>
|
||||
<Link
|
||||
href={link}
|
||||
target={text==="H.A.R.D. HACK"? "_blank":"_self"}
|
||||
className="absolute top-0 w-[15vw] pt-[5%] aspect-[230/425] flex flex-col justify-between"
|
||||
className="absolute top-0 md:w-[15vw] w-[25vw] pt-[5%] aspect-[230/425] flex flex-col justify-between"
|
||||
>
|
||||
<div class="w-full flex justify-end pr-[5%]">
|
||||
<div class="bg-white w-fit rounded-full aspect-square p-[0.5vw] text-ieee-black text-[2vw]">
|
||||
<div class="w-full flex justify-end md:pr-[5%] pr-[2vw]">
|
||||
<div class="bg-white w-fit rounded-full aspect-square p-[0.5vw] text-ieee-black text-[4.5vw] md:text-[2vw]">
|
||||
{
|
||||
text === "PROJECTS"? <RiRobot2Fill/>:
|
||||
text === "EVENTS"? <IoMdCalendar/>:
|
||||
|
@ -24,16 +24,16 @@ const {image, text, link, delay} = Astro.props;
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-[3%] text-white w-full bg-gradient-to-t from-black via-black to-transparent rounded-b-[2vw] pt-[30%] pb-[5%]">
|
||||
<div class="text-[1.1vw] duration-300 flex w-full px-[3%] justify-between items-end">
|
||||
<div class="px-[3%] text-white w-full bg-gradient-to-t from-black via-black to-transparent rounded-b-[2vw] pt-[20vw] pb-[3vw] md:pt-[30%] md:pb-[5%]">
|
||||
<div class="text-[2vw] md:text-[1.1vw] duration-300 flex w-full px-[3%] justify-between items-end">
|
||||
<p class="pt-[3%] pb-[2%] px-[10%] border-[0.1vw] border-white rounded-full group-hover:text-ieee-yellow group-hover:border-ieee-yellow duration-300 font-light">
|
||||
{text}
|
||||
</p>
|
||||
<GoArrowDownRight className="text-[3vw] leading-none group-hover:text-ieee-yellow"/>
|
||||
<GoArrowDownRight className="text-[5vw] md:text-[3vw] leading-none group-hover:text-ieee-yellow"/>
|
||||
</div>
|
||||
|
||||
{text === "H.A.R.D. HACK" &&
|
||||
<p class="text-[1vw] text-center pt-[10%] group-hover:text-ieee-yellow duration-300">
|
||||
<p class="text-[1.8vw] md:text-[1vw] text-center pt-[10%] group-hover:text-ieee-yellow duration-300">
|
||||
UC San Diego’s largest
|
||||
hardware focused hackathon
|
||||
hold by IEEE UCSD, HKN, and TNT
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import Involvement from "./Involvement.astro";
|
||||
import involve from "../../data/involve.json";
|
||||
---
|
||||
<div class="pl-[10%] pr-[6%] flex items-center mb-[10%]">
|
||||
<div class="flex w-3/5 justify-between">
|
||||
<div class="pl-[10%] pr-[6%] flex md:items-center md:mb-[10%] mb-[30vw] mt-[20vw] md:mt-0 flex-col-reverse md:flex-row">
|
||||
<div class="flex md:w-3/5 w-[95%] justify-between">
|
||||
{involve.map((item)=>(
|
||||
<Involvement
|
||||
text = {item.text}
|
||||
|
@ -14,11 +14,11 @@ import involve from "../../data/involve.json";
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div class="w-2/5 flex flex-col px-[5%]">
|
||||
<p class="text-ieee-yellow text-[1.2vw] mb-[5%]">
|
||||
Get involve in international IEEE
|
||||
<div class="md:w-2/5 w-4/5 flex flex-col px-[5%] pb-[10vw] md:pb-0">
|
||||
<p class="text-ieee-yellow md:text-[1.2vw] text-[2.5vw] mb-[5%]">
|
||||
Get involve in IEEE @ UCSD
|
||||
</p>
|
||||
<p class="text-white text-[2vw] font-bold">
|
||||
<p class="text-white md:text-[2vw] text-[3vw] font-bold">
|
||||
How To Keep Up With And Get Engaged With IEEE at UCSD
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -3,22 +3,35 @@ import Link from "next/link";
|
|||
import { LiaDotCircle } from "react-icons/lia";
|
||||
---
|
||||
|
||||
<div class="pt-[14vh] text-white w-full flex justify-center mb-[20vh]">
|
||||
<div class="w-3/4 h-[40vh] flex justify-between flex-col">
|
||||
<div data-inview class="flex items-center text-[3vw] pl-[5%] pt-[5%] in-view:animate-fade-right">
|
||||
<LiaDotCircle className=" mr-[1vw] pt-[0.5%]"/>
|
||||
<p>
|
||||
Join Us
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-[1.25vw]">
|
||||
The <a class=" text-ieee-yellow underline" href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6" target="_blank">IEEE Project Space</a> is an open-access, collaborative space located at EBU1-4710. Students can do homework or get access to basic electronic tools such as soldering stations, breadboard components, and Arduino and Raspberry PI parts!
|
||||
</p>
|
||||
<div data-inview class="flex justify-end in-view:animate-fade-up">
|
||||
<Link href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6" target="_blank" className="text-[1.2vw] border-white/70 border-[0.1vw] py-[1%] px-[8%] rounded-[0.7vw] hover:text-ieee-yellow hover:border-ieee-yellow duration-300">
|
||||
JOIN
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div class="text-white w-full flex justify-center mb-[10vw]">
|
||||
<div class="w-3/4 md:h-[20vw] h-[40vw] flex justify-between flex-col">
|
||||
<div
|
||||
data-inview
|
||||
class="animate-ease-in-out flex items-center text-[6vw] md:text-[3vw] pl-[5%] pt-[5%] in-view:animate-fade-right"
|
||||
>
|
||||
<LiaDotCircle className=" mr-[1vw] pt-[0.5%]" />
|
||||
<p>Join Us</p>
|
||||
</div>
|
||||
<p class="text-[2.2vw] md:text-[1.25vw]">
|
||||
The <a
|
||||
class="text-ieee-yellow underline"
|
||||
href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6"
|
||||
target="_blank">IEEE Project Space</a
|
||||
> is an open-access, collaborative space located at EBU1-4710. Students can
|
||||
do homework or get access to basic electronic tools such as soldering stations,
|
||||
breadboard components, and Arduino and Raspberry PI parts!
|
||||
</p>
|
||||
<div
|
||||
data-inview
|
||||
class="animate-ease-in-out flex justify-end in-view:animate-fade-up"
|
||||
>
|
||||
<Link
|
||||
href="https://maps.app.goo.gl/y4RwNCkoKBEHGHsv6"
|
||||
target="_blank"
|
||||
className="text-[2.5vw] md:text-[1.2vw] border-white/70 border-[0.1vw] py-[1%] px-[8%] rounded-[0.7vw] hover:text-ieee-yellow hover:border-ieee-yellow duration-300"
|
||||
>
|
||||
JOIN
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
19
src/components/join/OAH.astro
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<div
|
||||
data-inview
|
||||
class="flex flex-col items-center md:w-[40vw] w-[100vw] h-[100vw] md:h-[40vw] my-[10%] in-view:animate-fade-down"
|
||||
>
|
||||
<p class="text-ieee-yellow md:text-[1.2vw] text-[2.5vw]">Each officer has their own OAH</p>
|
||||
<p class="text-white md:text-[2.5vw] text-[4.5vw] my-[2%]">Check out our calendar!</p>
|
||||
<iframe
|
||||
class="w-[90%] rounded-[1.5vw]"
|
||||
src="https://calendar.google.com/calendar/embed?src=c_62493071bab19c7c60d103460604dc7b3b569ffc1e58a42617978f626dff02ac%40group.calendar.google.com&ctz=America%2FLos_Angeles"
|
||||
style="border: 0"
|
||||
width="600"
|
||||
height="450"
|
||||
frameborder="0"
|
||||
scrolling="no"></iframe>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
const {title, subtitle, image} = Astro.props;
|
||||
---
|
||||
|
||||
<div data-inview class="flex flex-col items-center w-[40vw] my-[10%] in-view:animate-fade-down">
|
||||
<p class="text-ieee-yellow text-[1.2vw]">
|
||||
{subtitle}
|
||||
</p>
|
||||
<p class="text-white text-[2.5vw] my-[2%]">
|
||||
{title}
|
||||
</p>
|
||||
<img src={image} alt="Office hours" class="object-cover aspect-[620/408] w-[90%] rounded-[2vw]" />
|
||||
</div>
|
|
@ -2,19 +2,19 @@ import Link from "next/link";
|
|||
|
||||
const Resource = ({icon, title, text, link}) => {
|
||||
return (
|
||||
<div class="text-white flex w-[30vw] items-center">
|
||||
<div class = "mr-[1vw] bg-gradient-radial from-ieee-blue-300 via-ieee-black to-ieee-black rounded-full text-[6.5vw] aspect-square w-[12vw] flex justify-center items-center">
|
||||
<div class="text-white flex md:w-[30vw] w-[40vw] items-center">
|
||||
<div class = "mr-[1vw] bg-gradient-radial from-ieee-blue-300 via-ieee-black to-ieee-black rounded-full text-[8vw] md:text-[6.5vw] aspect-square w-[12vw] flex justify-center items-center">
|
||||
{icon}
|
||||
</div>
|
||||
<div class="w-[24vw]">
|
||||
<p class = "text-[1.8vw] mb-[2vh] font-extralight">
|
||||
<div class="md:w-[24vw] w-[27vw] ">
|
||||
<p class = "md:text-[1.8vw] text-[2.5vw] mb-[2vh] font-extralight">
|
||||
{title}
|
||||
</p>
|
||||
<p class = "text-[1vw] mb-[1vh] font-light">
|
||||
<p class = "md:text-[1vw] text-[1.8vw] mb-[1vh] font-light">
|
||||
{text}
|
||||
</p>
|
||||
<div class="flex justify-end mt-[5%]">
|
||||
<Link href={link} target="_blank" className=" text-[1.1vw] font-extralight border-white/70 border-[0.1vw] py-[1%] px-[11%] rounded-[0.5vw] cursor-pointer hover:text-ieee-yellow hover:border-ieee-yellow duration-300">
|
||||
<Link href={link} target="_blank" className=" md:text-[1.1vw] text-[2vw] font-extralight border-white/70 border-[0.1vw] py-[1%] px-[11%] rounded-[0.5vw] cursor-pointer hover:text-ieee-yellow hover:border-ieee-yellow duration-300">
|
||||
VIEW
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import Resource from "./Resource.jsx"
|
||||
import { LiaDotCircle } from "react-icons/lia";
|
||||
import { RiSlideshowLine } from "react-icons/ri";
|
||||
import { BiMoviePlay } from "react-icons/bi";
|
||||
import { IoMdGlobe } from "react-icons/io";
|
||||
|
||||
const Resources = () => {
|
||||
return (
|
||||
<div class = "text-white w-full flex flex-col items-center h-[55vh] justify-center">
|
||||
<div class = "text-[2.5vw] flex items-center">
|
||||
<div class = "text-white w-full flex flex-col items-center justify-center mb-[20vw] md:mb-[5vw]">
|
||||
<div class = "text-[4.5vw] md:text-[2.5vw] flex items-center">
|
||||
<LiaDotCircle className = "mr-[1vw] pt-[0.5%]" />
|
||||
<p>
|
||||
Member Resources
|
||||
</p>
|
||||
</div>
|
||||
<div class = " w-[90%] flex justify-evenly mt-[9vh]">
|
||||
<div class = "md:w-[90%] w-full flex justify-evenly mt-[5vw]">
|
||||
<Resource
|
||||
icon = <RiSlideshowLine/>
|
||||
title = "Workshop Slides"
|
||||
text = "Hac at maecenas maximus faucibus venenatis blandit. Netus elit fusce a tortor"
|
||||
text = "Find our database of workshop slides here."
|
||||
link = "http://www.google.com"
|
||||
/>
|
||||
<Resource
|
||||
icon = <BiMoviePlay/>
|
||||
title = "Workshop Videos"
|
||||
text = "Hac at maecenas maximus faucibus venenatis blandit. Netus elit fusce a tortor"
|
||||
link = "http://www.google.com"
|
||||
icon = <IoMdGlobe/>
|
||||
title = "International IEEE"
|
||||
text = "Our parent organization provides variety of events including project sponsorship, IEEE DataPort dataset database, and renowned student contests."
|
||||
link = "https://www.ieee.org/"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|