diff --git a/.github/workflows/recruiting_ci.yml b/.github/workflows/recruiting_ci.yml index 09f6e58..beb54f1 100644 --- a/.github/workflows/recruiting_ci.yml +++ b/.github/workflows/recruiting_ci.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - name: 🏗 Setup repo - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: 🏗 Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v5 with: node-version: 18.x @@ -40,10 +40,10 @@ jobs: runs-on: ubuntu-latest steps: - name: 🏗 Setup repo - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: 🏗 Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v5 with: node-version: 20.x diff --git a/src/app/components/Departments.tsx b/src/app/components/Departments.tsx index 5d99691..5eae4f4 100644 --- a/src/app/components/Departments.tsx +++ b/src/app/components/Departments.tsx @@ -6,32 +6,64 @@ import { import { departments_data } from "@data/departments_data"; import DepartmentInfoCard from "@/app/genericComponents/DepartmentInfoCard"; import styled from "styled-components"; -import { SpacingM } from "@/app/genericComponents/tokens"; +import { useState } from "react"; +import { + BodyTextMedium, + MobileBodyTextMedium, + MobileBreakpoint, + SpacingM, + SpacingS, +} from "@/app/genericComponents/tokens"; + +const DepartmentsDescription = styled(SectionDescription)` + max-width: 70ch; + font-size: ${BodyTextMedium}; + white-space: pre-line; + + @media (max-width: ${MobileBreakpoint}) { + font-size: ${MobileBodyTextMedium}; + } +`; -const DepartmentsCards = styled.div` +const DepartmentsAccordion = styled.div` display: flex; - flex-wrap: wrap; - justify-content: space-around; - gap: ${SpacingM}; + flex-direction: column; margin-top: ${SpacingM}; + + @media (max-width: ${MobileBreakpoint}) { + margin-top: ${SpacingS}; + } `; export default function Departments() { + const [openDepartment, setOpenDepartment] = useState(null); + return ( {departments_data.title} - {departments_data.description} - - {departments_data.departments.map((department) => ( - - ))} - + + {departments_data.description} + + + {departments_data.departments.map((department) => { + const isOpen = openDepartment === department.name; + + return ( + + setOpenDepartment(isOpen ? null : department.name) + } + /> + ); + })} + ); } diff --git a/src/app/components/Footer.tsx b/src/app/components/Footer.tsx index fbdc585..5e3d609 100644 --- a/src/app/components/Footer.tsx +++ b/src/app/components/Footer.tsx @@ -39,18 +39,18 @@ export default function Footer() { href={"https://legal.hackersatupc.org/hackupc/legal_notice"} target={"_blank"} > - Legal notice + ⚖️ Legal notice {" "} |{" "} - Privacy and cookies + 🔐 Privacy and cookies - Made with ❤ by{" "} + ✨ Made with ❤ by{" "} Hackers@UPC diff --git a/src/app/components/Hackers.tsx b/src/app/components/Hackers.tsx index 62001cd..3623789 100644 --- a/src/app/components/Hackers.tsx +++ b/src/app/components/Hackers.tsx @@ -51,6 +51,11 @@ const CardsForHackers = styled(CardWithBackground)` const ButtonContainer = styled.div` display: flex; + + svg { + width: 0.875rem; + height: 0.875rem; + } `; export default function Hackers() { diff --git a/src/app/components/Hero.tsx b/src/app/components/Hero.tsx index 14fb61f..d7ffe7b 100644 --- a/src/app/components/Hero.tsx +++ b/src/app/components/Hero.tsx @@ -1,62 +1,129 @@ import styled from "styled-components"; +import { useEffect, useRef, useState } from "react"; +import Image from "next/image"; import { SmallMobileBreakpoint, MobileBreakpoint, - MobileTitleXL, - MobileTitleL, SpacingS, SpacingM, SpacingXXL, - TitleXL, - Primary300, + BodyTextMedium, + BodyTextSmall, + HeroTitleXL, + MobileTitleM, + TitleL, } from "@/app/genericComponents/tokens"; import { hero_data } from "@data/hero_data"; import { PrimaryButton } from "@/app/genericComponents/General"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowDown } from "@fortawesome/free-solid-svg-icons"; + +const HeroButton = styled(PrimaryButton)<{ $floating: boolean }>` + width: fit-content; + margin-top: 0; + padding: 0.9rem 1.75rem; + border: 1px solid rgba(255, 255, 255, 0.5); + border-radius: 999px; + background: linear-gradient(135deg, #e94662, #cb264b); + box-shadow: 0 0.35rem 1rem rgba(0, 0, 0, 0.3); + font-size: ${BodyTextMedium}; + font-weight: 700; + text-transform: none; + transition: + transform 0.2s ease, + box-shadow 0.2s ease, + background-color 0.2s ease; + + ${({ $floating }) => + $floating && + ` + position: fixed; + top: 1rem; + right: 1.5rem; + z-index: 10; + margin: 0; + `} + + &:hover { + background: linear-gradient(135deg, #ff627a, #df3c5a); + box-shadow: 0 0.75rem 1.75rem rgba(0, 0, 0, 0.3); + transform: translateY(-0.15rem); + } + + &:active { + transform: translateY(0); + } + + @media (max-width: ${MobileBreakpoint}) { + width: auto; + max-width: 100%; + padding: 0.8rem 1.35rem; + } +`; const Container = styled.div` position: relative; - margin-top: 5%; - margin-right: 10%; - margin-left: 10%; + width: 100%; + max-width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + min-height: 80vh; padding: ${SpacingXXL}; - border-radius: 20px; - border: 0.3333rem solid ${Primary300}; overflow: hidden; @media (max-width: ${MobileBreakpoint}) { - padding: ${SpacingS}; + min-height: clamp(28rem, 68svh, 40rem); + padding: 4rem ${SpacingS}; + justify-content: center; + align-items: center; } +`; - &::before { - content: ""; - position: absolute; - inset: 0; - background-image: url("/IMG_6219.jpeg"); - background-position: center 60%; - background-size: cover; - opacity: 0.3; - z-index: 0; - } +const HeroBackground = styled(Image)` + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + opacity: 0.3; + z-index: 0; +`; - > * { - position: relative; - z-index: 1; - } +const HeroContent = styled.div` + position: static; `; +const HeroImage = () => ( + +); + const Title = styled.h1` text-align: center; - font-size: ${TitleXL}; + font-size: ${HeroTitleXL}; background: transparent; color: #ffffff; text-shadow: 0 2px 6px rgba(0, 0, 0, 0); @media (max-width: ${MobileBreakpoint}) { - font-size: ${MobileTitleXL}; + max-width: 22ch; + font-size: clamp(2rem, 8vw, ${TitleL}); + line-height: 1.05; + letter-spacing: -0.03em; + margin: 0 auto; + text-align: center; } @media (max-width: ${SmallMobileBreakpoint}) { - font-size: ${MobileTitleL}; + font-size: ${MobileTitleM}; } `; @@ -64,23 +131,112 @@ const ButtonContainer = styled.div` display: flex; justify-content: center; margin-top: ${SpacingM}; + + @media (max-width: ${MobileBreakpoint}) { + width: 100%; + justify-content: center; + margin-top: ${SpacingS}; + } +`; + +const ClosedMessage = styled.p` + margin: 0; + font-style: italic; + text-align: center; + color: #ffffff; + font-size: ${BodyTextMedium}; + line-height: 1.45; + max-width: 24rem; +`; + +const DiscoverMoreButton = styled.button` + display: none; + position: absolute; + bottom: 1.5rem; + left: 50%; + border: none; + background: transparent; + color: #ffffff; + font-size: ${BodyTextSmall}; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + cursor: pointer; + padding: 0.5rem 0; + transform: translateX(-50%); + white-space: nowrap; + + svg { + display: inline-block; + width: 0.75rem; + height: 0.75rem; + margin-left: 0.5rem; + vertical-align: middle; + animation: discover-arrow 1.5s ease-in-out infinite; + } + + @keyframes discover-arrow { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(0.35rem); + } + } + + @media (max-width: ${MobileBreakpoint}) { + display: block; + } `; export default function Hero() { + const heroRef = useRef(null); + const [isHeroScrolledPast, setIsHeroScrolledPast] = useState(false); + + useEffect(() => { + const hero = heroRef.current; + if (!hero) return; + + const observer = new IntersectionObserver( + ([entry]) => setIsHeroScrolledPast(!entry.isIntersecting), + { threshold: 0.1 }, + ); + + observer.observe(hero); + return () => observer.disconnect(); + }, []); + + const handleScrollToNextSection = () => { + const nextSection = document.getElementById("presentation-cards"); + nextSection?.scrollIntoView({ behavior: "smooth", block: "start" }); + }; + return ( - - {hero_data.title} - - {hero_data.applicationsOpen ? ( - - {hero_data.applicationsOpenTextButton} - - ) : ( - - {hero_data.applicationsClosedTextButton} - - )} - + + + + {hero_data.title} + + {hero_data.applicationsOpen ? ( + + {hero_data.applicationsOpenTextButton} + + ) : ( + + {hero_data.applicationsClosedTextButton} + + )} + + + Discover more + + ); } diff --git a/src/app/components/PresentationCards.tsx b/src/app/components/PresentationCards.tsx index fb71274..bd4bca0 100644 --- a/src/app/components/PresentationCards.tsx +++ b/src/app/components/PresentationCards.tsx @@ -13,42 +13,153 @@ import { } from "@data/cards_data"; import EmblaCarousel from "@/app/genericComponents/EmblaCarousel"; import { EmblaOptionsType } from "embla-carousel"; +import styled from "styled-components"; +import { + MobileBodyTextMedium, + BodyTextMedium, + MobileBreakpoint, + SpacingS, + SpacingXS, +} from "@/app/genericComponents/tokens"; const OPTIONS: EmblaOptionsType = { loop: true }; +const QuestionsList = styled.ul` + width: 100%; + min-width: 0; + margin: 0 0 2rem; + padding: 0; + list-style: none; + text-align: left; + font-size: ${BodyTextMedium}; + + li { + display: flex; + align-items: flex-start; + gap: ${SpacingS}; + margin-bottom: ${SpacingXS}; + padding: ${SpacingXS} ${SpacingS}; + border-radius: ${SpacingXS}; + background: rgba(255, 255, 255, 0.06); + line-height: 1.55; + transition: + background-color 0.2s ease, + transform 0.2s ease; + } + + li:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateX(0.15rem); + } + + .question-emoji { + flex: 0 0 1.5rem; + display: grid; + place-items: center; + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + background: rgba(233, 70, 98, 0.16); + font-size: 1.25rem; + line-height: 1.4; + text-align: center; + } + + .question-text { + flex: 1; + min-width: 0; + } + + @media (max-width: ${MobileBreakpoint}) { + font-size: ${MobileBodyTextMedium}; + + li { + gap: ${SpacingXS}; + padding: ${SpacingXS}; + } + + .question-emoji { + flex-basis: 1.35rem; + width: 1.35rem; + height: 1.35rem; + font-size: 1.1rem; + } + } + + overflow-wrap: anywhere; +`; + +function renderIntroduction(text: string, boldPhrases: string[]) { + const phrasePattern = new RegExp(`(${boldPhrases.join("|")})`, "g"); + return text + .split(phrasePattern) + .map((part) => + boldPhrases.includes(part) ? {part} : part, + ); +} + export default function PresentationCards() { return ( - + - + {about_joining_data_card.title} - {about_joining_data_card.description} - + + {renderIntroduction( + about_joining_data_card.introduction, + about_joining_data_card.boldIntroductionPhrases, + )} + + + {about_joining_data_card.questions.map((question, index) => ( +
  • + + {question} +
  • + ))} +
    + + {renderIntroduction( + about_joining_data_card.conclusion, + about_joining_data_card.boldConclusionPhrases, + )} + +
    + +
    +
    + ); +} +export function VideoCards() { + return ( + - - {talk_data_card.title} - {talk_data_card.description} + + {trailer_data_card.title} + {trailer_data_card.description} - - {trailer_data_card.title} - {trailer_data_card.description} + + {talk_data_card.title} + {talk_data_card.description} diff --git a/src/app/components/Socials.tsx b/src/app/components/Socials.tsx index 44faace..32ddced 100644 --- a/src/app/components/Socials.tsx +++ b/src/app/components/Socials.tsx @@ -6,58 +6,112 @@ import { import { socials_data } from "@data/socials_data"; import styled from "styled-components"; import { + BodyTextMedium, Secondary100, Secondary500, + MobileBreakpoint, SpacingM, SpacingS, SpacingXS, - TitleM, Primary300, } from "@/app/genericComponents/tokens"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; const BorderedSection = styled(SectionContainer)` - border-radius: 10px; - border: 0.3333rem solid ${Primary300}; + border: 1px solid ${Primary300}; + border-radius: ${SpacingS}; padding: ${SpacingM}; + background: rgba(47, 67, 114, 0.18); + + @media (max-width: ${MobileBreakpoint}) { + padding: ${SpacingS}; + } +`; + +const SocialGroup = styled.div` + padding-top: ${SpacingM}; + + & + & { + margin-top: ${SpacingM}; + border-top: 1px solid rgba(255, 255, 255, 0.18); + } + + @media (max-width: ${MobileBreakpoint}) { + padding-top: ${SpacingS}; + + & + & { + margin-top: ${SpacingS}; + } + } `; const HeaderSocials = styled.a` - font-size: ${TitleM}; + font-size: ${BodyTextMedium}; font-weight: bold; text-decoration: none; color: ${Secondary100}; - margin-top: ${SpacingM}; - margin-bottom: ${SpacingXS}; display: flex; - justify-content: center; + align-items: center; + gap: ${SpacingXS}; cursor: pointer; + svg { + width: 0.625rem; + height: 0.625rem; + } + &:hover { color: ${Secondary500}; } `; const SocialsDescription = styled(SectionDescription)` - text-align: center; + text-align: left; margin-bottom: ${SpacingM}; `; const SocialsList = styled.div` - align-content: center; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr)); + gap: ${SpacingXS}; + margin-top: ${SpacingS}; + + @media (max-width: ${MobileBreakpoint}) { + grid-template-columns: repeat(4, 1fr); + } +`; + +const SocialItem = styled.a` display: flex; - flex-wrap: wrap; - gap: ${SpacingM}; + min-height: 4.25rem; + flex-direction: column; + align-items: center; justify-content: center; - padding: ${SpacingS}; -`; + gap: ${SpacingXS}; + border: 1px solid rgba(255, 255, 255, 0.16); + border-radius: ${SpacingXS}; + color: white; + text-decoration: none; + transition: + background-color 0.2s ease, + border-color 0.2s ease, + transform 0.2s ease; -const SocialItem = styled.div` - text-align: center; + svg { + width: 1.25rem; + height: 1.25rem; + } + + span { + font-size: 0.68rem; + opacity: 0.75; + } &:hover { - transform: scale(1.5); + background: ${Primary300}; + border-color: ${Secondary100}; + transform: translateY(-0.15rem); } `; @@ -67,53 +121,47 @@ export default function Socials() { {socials_data.title} {socials_data.description} - - HackUPC - - - - - {socials_data.socialsHackUPC.map((social) => ( - - + + HackUPC + + + + {socials_data.socialsHackUPC.map((social) => ( + - - - ))} - - - - Hackers@UPC{" "} - - - - - {socials_data.socialsHackersUPC.map((social) => ( - - {social.label} + + ))} + + + + + + Hackers@UPC + + + + {socials_data.socialsHackersUPC.map((social) => ( + - - - ))} - + {social.label} + + ))} + + ); } diff --git a/src/app/data/cards_data.ts b/src/app/data/cards_data.ts index 3dbf3a5..4657ab5 100644 --- a/src/app/data/cards_data.ts +++ b/src/app/data/cards_data.ts @@ -124,9 +124,23 @@ const images_for_joining: ImageInformation[] = [ export const about_joining_data_card: AboutJoiningInformation = { enabled: true, - title: "Per què hauria d’apuntar-me a Hackers@UPC?", - description: - "Ser part de l’equip de Hackers@UPC et donarà moltes experiències i et permetrà enfrontar-te a nous reptes. Tindràs l'oportunitat de conèixer i treballar amb un equip de gent apassionada, aprendràs noves habilitats i ajudaràs a fer créixer la comunitat a través de l'organització d’esdeveniments increïbles que apropen a la gent! Ser part d’una associació universitària com Hackers@UPC et farà viure la teva vida universitària d’una forma més divertida.", + title: "Què busquem?", + introduction: + "Busquem integrants que aportin passió i continuitat a HackUPC, un projecte que es va iniciar fa més d'una dècada. T'encantarà formar part de l'equip de HackUPC si vols...", + boldIntroductionPhrases: [ + "passió i continuitat a HackUPC", + "més d'una dècada", + ], + boldConclusionPhrases: ["et pot canviar la vida"], + questions: [ + "Entrar a un grup de persones treballadores i motivades", + "Compartir viatges, activitats, sopars, colònies...", + "Resoldre problemes i portar l'iniciativa", + "Aprendre coses que no t'ensenyaran a la universitat", + "Fer amics per a tota la vida", + ], + questionEmojis: ["💪", "✈️", "💡", "🎓", "🤝"], + conclusion: "Organitzar HackUPC et pot canviar la vida. Encara dubtes?", images: images_for_joining, }; @@ -142,6 +156,6 @@ export const trailer_data_card: VideoCardInformation = { enabled: true, title: "Què significa ser part de Hackers@UPC?", description: - "Ser part de Hackers@UPC significa treballar i cooperar amb altra gent, tenir reunions de forma regular, ser constants i ser capaç de treballar sota pressió. Sense passió i dedicació per part dels organitzadors, HackUPC no podria tirar endavant! Al final del dia, veure als hackers contents i veient com tenen un cap de setmana increïble, alguns cops amb experiències que els canvien la vida, fa que tot valgui la pena! També fem moltes activitats com a grup, i aporta una bona xarxa de contactes.", + "A HackUPC, la passió i la dedicació és molt important! Però això no és tot, també fem moltes activitats grupals, que construeixen un equip cohesionat.", video: "/videos/trailer.mp4", }; diff --git a/src/app/data/departments_data.ts b/src/app/data/departments_data.ts index 7e3166d..56d4d1d 100644 --- a/src/app/data/departments_data.ts +++ b/src/app/data/departments_data.ts @@ -11,59 +11,118 @@ import { const departments_list_data: DepartmentInformation[] = [ { - name: "Design", - question: - "Dibuixes o t’agrada dissenyar? T’agradaria participar dissenyant la temàtica dels esdeveniments?", - information: - "A HackUPC, el disseny és molt important perquè és la manera que tenim de comunicar i mostrar la nostra feina. Fa possible la connexió dels nostres esdeveniments amb Hackers, Sponsors i Partners. Com a membre de l’equip de disseny t'encarregaràs de treballar amb tot el relacionat als diferents de l’associació, però sobretot el més important HackUPC. Pàgines web, material per xarxes socials, stickers, samarretes, decoració per l’esdeveniment… això i molt més és del que s’encarrega el nostre equip!", + name: "Design 🎨", + question: "Dibuixes, dissenyes, o t’agradria aprendre dissenyar?", + information: [ + "El disseny és molt important perquè és la manera que tenim de comunicar i mostrar la nostra feina. Fa possible la connexió dels nostres esdeveniments amb Hackers, Sponsors i Partners.", + "Com a membre de l’equip de disseny t'encarregaràs de treballar amb tot el relacionat als diferents esdeveniments de l’associació, inclòs HackUPC. Dissenyaràs pàgines web, material per xarxes socials, stickers, samarretes, decoració per l’esdeveniment… i moltes coses més!", + "Si ets creatiu i vols aprendre a dissenyar, vine a design!", + ], + boldInformationPhrases: [ + "dissenyar", + "disseny", + "HackUPC", + "Pàgines web", + "xarxes socials", + ], icon: faPenNib, }, { - name: "Sponsorship", - question: - "Ets persuasiu? Pots comunicar-te còmodament en Català i en Anglès? Vine a aconseguir diners!", - information: - "Com qualsevol esdeveniment, HackUPC necessita un suport econòmic, aquest ve a través dels nostres increïbles sponsors que normalment venen a l'esdeveniment presentant un repte pels hackers. Normalment, aquests també porten coses increïbles durant l’esdeveniment, marxandatge, menjar i altres. Aconseguir els diners per poder cobrir totes les despeses que representa l’esdeveniment és la feina de l’equip de sponsorship. Per ser part d’aquest equip has d’estar disposat a parlar en anglès de forma regular i tenir reunions amb empreses on hauràs de negociar amb ells.", + name: "Sponsorship 🤝", + question: "Ets persuasiu? Pots comunicar-te bé en Català i en Anglès?", + information: [ + "Com qualsevol esdeveniment, HackUPC necessita un suport econòmic que prové dels nostres increïbles sponsors. Normalment, venen a l'esdeveniment per presentar un repte als hackers i també hi porten marxandatge, menjar i altres sorpreses.", + "Aconseguir els diners per cobrir totes les despeses de l’esdeveniment és la feina de l’equip de sponsorship.", + "Per formar part d’aquest equip, has d’estar disposat a parlar en anglès amb regularitat i a reunir-te amb empreses per negociar amb elles.", + ], + boldInformationPhrases: [ + "HackUPC", + "sponsorship", + "suport econòmic", + "sponsorship", + ], icon: faMoneyBill1Wave, }, { - name: "HackerXperience", + name: "HackerXperience 🎮", question: - "Vols millorar els records que s’emporten els hackers de HackUPC? Ajuda’ns a crear la millor experiència possible!", - information: - "A HackerXperience ens encarreguem que els participants gaudeixin de cada moment del cap de setmana. Planifiquem activitats perquè desconnectin, busquem mentors que els guiïn i ajudin, ens assegurem que els projectes es valorin de manera justa amb un bon equip de jutges, i molt més! Si ets creatiu, entusiasta i dinàmic, t'esperem a HackerXperience!", + "Vols millorar els records que s’emporten els hackers de HackUPC?", + information: [ + "A HackerXperience ens encarreguem que els participants gaudeixin de cada moment del cap de setmana.", + "Planifiquem activitats perquè desconnectin, busquem mentors que els guiïn i ajudin, i ens assegurem que els projectes es valorin de manera justa amb un bon equip de jutges.", + "Si ets creatiu, entusiasta i dinàmic, t'esperem a HackerXperience!", + ], + boldInformationPhrases: [ + "participants gaudeixin", + "activitats", + "mentors", + "equip de jutges", + ], icon: faVolleyball, }, { - name: "Logistics", - question: - "Treballes bé sota pressió? Ajuda’ns a fer que tot funcioni bé durant l’esdeveniment!", - information: - "Durant HackUPC, l'equip de logística s'encarrega de tot el que fa que l'esdeveniment sigui possible: des dels àpats per a més de 900 persones fins al marxandatge personalitzat que els hackers s'emporten a casa amb molt de carinyo. També transformem una aula en una cafeteria plena de snacks, cafès, begudes energètiques i alguna sorpresa extra, fins i tot espais per jugar a Jenga o als escacs! Tot això requereix una gran planificació i organització dels espais de la venue, i aquí és on entra l'equip de logística. Si ets una persona activa, t'agrada treballar en equip i vols viure aquesta bogeria amb nosaltres, uneix-te!", + name: "Logistics 🍕", + question: "Treballes bé sota pressió? T'agrada que tot funcioni bé?", + information: [ + "Durant HackUPC, l'equip de logística s'encarrega de tot el que fa que l'esdeveniment sigui possible: des dels àpats per a més de 900 persones fins al marxandatge personalitzat que els hackers s'emporten a casa.", + "També transformem una aula en una cafeteria plena de snacks, cafès, begudes energètiques i alguna sorpresa extra. Fins i tot preparem espais per jugar a Jenga o als escacs!", + "Tot això requereix una gran planificació i organització dels espais de la venue. Si ets una persona activa, t'agrada treballar en equip i vols viure aquesta bogeria amb nosaltres, uneix-te!", + ], + boldInformationPhrases: [ + "més de 900 persones", + "snacks", + "planificació i organització", + "treballar en equip", + ], icon: faPizzaSlice, }, { - name: "Marketing", - question: - "Tens facilitat per a les Xarxes Socials? Vols ajudar-nos a fer créixer la xarxa de hackers?", - information: - "HackUPC no seria possible sense els hackers, la nostra feina és promocionar tots els esdeveniments que fa l’associació perquè arribi a tothom! Ens encarreguem de mantenir actives les xarxes socials, creant de forma regular contingut i resolent preguntes i dubtes que poden sorgir. Necessitem ments creatives que estiguin al dia de les xarxes socials.", + name: "Marketing 📣", + question: "T'agradaria crear contingut per a les Xarxes Socials?", + information: [ + "HackUPC no seria possible sense els hackers. La nostra feina és promocionar tots els esdeveniments que fa l’associació perquè arribin a tothom.", + "Ens encarreguem de mantenir actives les xarxes socials, crear contingut de forma regular i resoldre les preguntes i els dubtes que puguin sorgir.", + "Necessitem ments creatives que estiguin al dia de les xarxes socials.", + ], + boldInformationPhrases: [ + "promocionar", + "xarxes socials", + "contingut", + "ments creatives", + ], icon: faBullhorn, }, { - name: "WebDev", - question: - "T’agrada programar i aprendre noves habilitats? Vine i ajuda’ns a millorar els nostres sistemes!", - information: - "HackUPC no seria possible sense les nostres pàgines web i sistemes informàtics. A WebDev, ens encarreguem de desenvolupar aquests sistemes web per millorar la feina dels organitzadors i hackers, i fer que la hackathon vagi millor. Utilitzem tecnologies modernes: Django (Python) i Next.js (Typescript) majoritàriament. Busquem a gent que tingui coneixements bàsics de programació i moltes ganes d’aprendre nous llenguatges i tecnologies.", + name: "WebDev 💻", + question: "T’agrada programar i aprendre noves habilitats?", + information: [ + "HackUPC no seria possible sense les nostres pàgines web i sistemes informàtics. A WebDev, desenvolupem eines per millorar la feina dels organitzadors i dels hackers.", + "Utilitzem tecnologies modernes, principalment Django (Python) i Next.js (Typescript).", + "Busquem gent amb coneixements bàsics de programació i moltes ganes d’aprendre nous llenguatges i tecnologies.", + ], + boldInformationPhrases: [ + "sistemes informàtics", + "Django (Python)", + "Next.js (Typescript)", + "ganes d’aprendre", + ], icon: faLaptopCode, }, { - name: "Staff", + name: "Staff 🙌", question: - "Ets capaç de liderar i assegurar que les coses passen? Estigues al capdavant de l’equip de voluntaris!", - information: - "Durant HackUPC, necessitem mans extres per tenir tot preparat pels hackers en tot moment, el nostre equip s’encarrega de reclutar un equip de voluntaris increïble que ens ajudarà. Coordinem i organitzem els voluntaris per treballar de forma eficient, així que si ets una persona organitzada que pot ser un bon líder, aquest és el teu equip!", + "Ets capaç de liderar un equip i assegurar-te que les coses passen?", + information: [ + "Durant HackUPC, necessitem mans extres per tenir-ho tot preparat per als hackers en tot moment.", + "L'equip de staff recluta, coordina i organitza un grup de voluntaris increïble perquè treballin de forma eficient.", + "Si ets una persona organitzada i pots ser un bon líder, aquest és el teu equip!", + ], + boldInformationPhrases: [ + "mans extres", + "voluntaris", + "treballar de forma eficient", + "bon líder", + ], icon: faPeopleGroup, }, ]; @@ -71,6 +130,6 @@ const departments_list_data: DepartmentInformation[] = [ export const departments_data: Departments = { title: "Els nostres departaments", description: - "A HackUPC, l'organització va per departaments. Cada departament té un objectiu específic al desenvolupament de l’esdeveniment, però ser part d’un departament no significa que només podràs fer feina d’aquell departament. Treballem també de forma molt horitzontal!", + "Treballem per departaments. Cada departament té unes responsabilitats concretes, però ens encanta donar-nos idees i col·laborar amb altres departaments. Tots sumem per fer que l’esdeveniment sigui un èxit.", departments: departments_list_data, }; diff --git a/src/app/data/hackers_data.ts b/src/app/data/hackers_data.ts index 495c5a1..6d1cb39 100644 --- a/src/app/data/hackers_data.ts +++ b/src/app/data/hackers_data.ts @@ -6,5 +6,5 @@ export const hackers_data: Hackers = { "Hackers@UPC és una organització sense ànim de lucre de la Universitat Politècnica de Catalunya.", description: "A part de HackUPC, a Hackers@UPC, organitzem molts esdeveniments durant tot l’any, nacionalment o internacionalment, per promoure la ciència i la tecnologia als estudiants, i crear una comunitat al món de la tecnologia. La nostra missió és fomentar l’aprenentatge, el disseny i la construcció d'una comunitat d’estudiants que puguin portar les seves idees a la realitat!", - buttonText: "Descobreix més a la nostra pàgina web", + buttonText: "🔎 Descobreix més a la nostra pàgina web", }; diff --git a/src/app/data/hero_data.ts b/src/app/data/hero_data.ts index 2745f0e..324cb7c 100644 --- a/src/app/data/hero_data.ts +++ b/src/app/data/hero_data.ts @@ -1,9 +1,9 @@ import { HeroInformation } from "@data/interfaces"; export const hero_data: HeroInformation = { - title: "Uneix-te a l'equip de HackUPC", - applicationsOpenTextButton: "Apunta’t ara!", - applicationsLink: "https://hackersatupc.typeform.com/to/ZBm8aTTg", + title: "UNEIX-TE A L'EQUIP DE HACKUPC", + applicationsOpenTextButton: "Apunta’t ara! ✨", + applicationsLink: "https://hackersatupc.typeform.com/to/yNU733vZ", applicationsClosedTextButton: "Les inscripcions estan tancades", applicationsOpen: false, }; diff --git a/src/app/data/interfaces.ts b/src/app/data/interfaces.ts index 61f56cb..dc708c7 100644 --- a/src/app/data/interfaces.ts +++ b/src/app/data/interfaces.ts @@ -17,7 +17,12 @@ export type ImageInformation = { export interface AboutJoiningInformation { enabled: boolean; title: string; - description: string; + introduction: string; + boldIntroductionPhrases: string[]; + boldConclusionPhrases: string[]; + questions: string[]; + questionEmojis: string[]; + conclusion: string; images: ImageInformation[]; } @@ -32,7 +37,8 @@ export interface DepartmentInformation { name: string; icon: IconDefinition; question: string; - information: string; + information: string[]; + boldInformationPhrases?: string[]; } export interface Departments { diff --git a/src/app/genericComponents/DepartmentInfoCard.tsx b/src/app/genericComponents/DepartmentInfoCard.tsx index 875f8ac..a2dc5c8 100644 --- a/src/app/genericComponents/DepartmentInfoCard.tsx +++ b/src/app/genericComponents/DepartmentInfoCard.tsx @@ -1,104 +1,174 @@ import styled, { css } from "styled-components"; -import { CardWithBackground } from "@/app/genericComponents/General"; import { DepartmentInformation } from "@data/interfaces"; import { - BodyText, BodyTextMedium, + MobileBodyTextMedium, MobileBreakpoint, - Primary100, - QuestionBoxBackgroundColor, SpacingS, - SpacingXS, - TitleS, } from "@/app/genericComponents/tokens"; -import { useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faChevronUp, faChevronDown } from "@fortawesome/free-solid-svg-icons"; - -const DepartmentCardTitle = styled.div` - font-size: ${TitleS}; - text-transform: uppercase; - margin-top: ${SpacingS}; - margin-bottom: ${SpacingS}; - text-align: center; -`; +import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons"; -const Question = styled.div` - font-size: ${BodyText}; - font-weight: bold; - gap: ${SpacingXS}; +const QuestionButton = styled.button` + width: 100%; + border: 0; + padding: ${SpacingS} 0; + background: transparent; + color: inherit; + cursor: pointer; + text-align: left; + font-family: inherit; + font-size: ${BodyTextMedium}; display: flex; - align-items: center; + align-items: flex-start; + justify-content: space-between; + gap: ${SpacingS}; + + > div { + margin: 0; + flex: 1; + min-width: 0; + } + + strong { + font-family: inherit; + font-weight: 700; + margin-right: ${SpacingS}; + + @media (max-width: ${MobileBreakpoint}) { + display: block; + margin-right: 0; + } + } + + svg { + flex: 0 0 auto; + width: 1rem; + height: 1rem; + margin-top: 0.25rem; + } + + &:focus-visible { + outline: 0.15rem solid #ffffff; + outline-offset: -0.15rem; + } +`; + +const QuestionText = styled.span` + font-family: inherit; + opacity: 0.5; + font-size: ${BodyTextMedium}; + + @media (max-width: ${MobileBreakpoint}) { + display: block; + max-width: 32rem; + margin-top: 0.35rem; + font-size: ${MobileBodyTextMedium}; + line-height: 1.45; + text-align: left; + text-wrap: pretty; + } `; -const Answer = styled.p<{ isVisible: boolean }>` +const Answer = styled.div<{ isVisible: boolean }>` font-size: ${BodyTextMedium}; - transform-origin: top; + font-family: inherit; + background: transparent; transition: - transform 0.5s ease, - opacity 0.5s ease; - transform: scaleY(0); + max-height 0.35s ease, + padding 0.35s ease, + opacity 0.35s ease; + max-height: 0; opacity: 0; line-height: 1.5rem; - height: 0; - visibility: hidden; + text-align: justify; + overflow: hidden; font-weight: normal; + margin: 0; + padding: 0; + + @media (max-width: ${MobileBreakpoint}) { + font-size: ${MobileBodyTextMedium}; + line-height: 1.5; + } + + p { + margin: 0 0 ${SpacingS}; + } + + p:last-child { + margin-bottom: 0; + } ${(props) => props.isVisible && css` - transform: scaleY(1); + max-height: 60rem; opacity: 1; - height: auto; - visibility: visible; - margin: ${SpacingS} 0; + padding: 0 0 ${SpacingS}; `} `; -const QuestionBox = styled.div` - border-radius: ${SpacingS}; - background-color: ${QuestionBoxBackgroundColor}; - padding: ${SpacingS}; - cursor: pointer; - - &:hover { - background-color: ${Primary100}; - } +const CardForDepartment = styled.div` + width: 100%; + border: 0; + border-bottom: 0.1rem solid rgba(255, 255, 255, 0.3); + border-radius: 0; + overflow: hidden; + background: transparent; `; -const CardForDepartment = styled(CardWithBackground)` - width: 45%; +type DepartmentInfoCardProps = DepartmentInformation & { + active: boolean; + onToggle: () => void; +}; - @media (max-width: ${MobileBreakpoint}) { - width: 100%; - } -`; +function renderBoldPhrases(text: string, phrases: string[]) { + if (phrases.length === 0) return text; + + const escapedPhrases = phrases.map((phrase) => + phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), + ); + const phrasePattern = new RegExp(`(${escapedPhrases.join("|")})`, "g"); + return text + .split(phrasePattern) + .map((part, index) => + phrases.includes(part) ? ( + {part} + ) : ( + part + ), + ); +} -export default function DepartmentInfoCard(props: DepartmentInformation) { - const { name, question, information, icon } = props; - const [active, setActive] = useState(false); +export default function DepartmentInfoCard(props: DepartmentInfoCardProps) { + const { name, question, information, boldInformationPhrases = [] } = props; + const { active, onToggle } = props; + const answerId = `${name.replace(/\W+/g, "-").toLowerCase()}-answer`; return ( - - - {" "} - {name} - - - setActive(!active)}> - -

    {question}

    - -
    - {information} -
    -
    + <> + + +
    + {name} + {question} +
    + +
    + + {information.map((paragraph) => ( +

    + {renderBoldPhrases(paragraph, boldInformationPhrases)} +

    + ))} +
    +
    + ); } diff --git a/src/app/genericComponents/EmblaCarousel.tsx b/src/app/genericComponents/EmblaCarousel.tsx index fcb9df3..97573f9 100644 --- a/src/app/genericComponents/EmblaCarousel.tsx +++ b/src/app/genericComponents/EmblaCarousel.tsx @@ -1,12 +1,6 @@ import React from "react"; -// eslint-disable-next-line import/no-extraneous-dependencies +import Image from "next/image"; import { EmblaOptionsType } from "embla-carousel"; -import { - NextButton, - PrevButton, - usePrevNextButtons, -} from "./EmblaCarouselArrowButtons"; -import useEmblaCarousel from "embla-carousel-react"; import { ImageInformation } from "@data/interfaces"; type PropType = { @@ -14,40 +8,24 @@ type PropType = { options?: EmblaOptionsType; }; -const EmblaCarousel: React.FC = (props) => { - const { slides, options } = props; - const [emblaRef, emblaApi] = useEmblaCarousel(options); - - const { - prevBtnDisabled, - nextBtnDisabled, - onPrevButtonClick, - onNextButtonClick, - } = usePrevNextButtons(emblaApi); +const EmblaCarousel: React.FC = ({ slides }) => { + const duplicatedSlides = [...slides, ...slides]; return ( -
    -
    -
    - {slides.map((index) => ( -
    -
    - {index.alt} -
    -
    - ))} -
    -
    - -
    -
    - - -
    +
    +
    + {duplicatedSlides.map((slide, index) => ( +
    + {slide.alt} +
    + ))}
    ); diff --git a/src/app/genericComponents/General.ts b/src/app/genericComponents/General.ts index ba8c12e..f65526b 100644 --- a/src/app/genericComponents/General.ts +++ b/src/app/genericComponents/General.ts @@ -4,10 +4,8 @@ import { BodyText, BodyTextMedium, MaxElement, - MobileBodyText, MobileBodyTextMedium, MobileBreakpoint, - MobileTitleL, MobileTitleM, Primary100, Primary300, @@ -18,20 +16,18 @@ import { SpacingL, SpacingM, SpacingS, - SpacingXL, SpacingXS, TitleL, - TitleXL, } from "@/app/genericComponents/tokens"; export const TransparentBackground = styled.div` margin: 0; - padding: ${SpacingXL} ${SpacingM}; + padding: ${SpacingL} ${SpacingM}; width: 100%; height: 100%; @media (max-width: ${MobileBreakpoint}) { - padding: ${SpacingM}; + padding: ${SpacingM} ${SpacingS}; } `; @@ -39,36 +35,53 @@ export const Background = styled(TransparentBackground)` background: ${BackgroundAccent}; `; -export const CardWithBorder = styled.div` - margin: 0 auto; - padding: ${SpacingL}; - text-align: center; - border: 0.3333rem solid ${Primary300}; +interface CardWithBorderProps { + $alignLeft?: boolean; +} + +export const CardWithBorder = styled.div` + width: 100%; + min-width: 0; + padding: ${SpacingM} 0; + text-align: ${({ $alignLeft }) => ($alignLeft ? "left" : "center")}; border-radius: ${SpacingS}; display: grid; max-width: ${MaxElement}; + > * { + min-width: 0; + max-width: 100%; + } + @media (max-width: ${MobileBreakpoint}) { - padding: ${SpacingM}; + padding: ${SpacingS} 0; + text-align: center; } `; export const CardTitle = styled.h2` font-size: ${TitleL}; - margin-bottom: ${SpacingS}; + margin-bottom: ${SpacingXS}; @media (max-width: ${MobileBreakpoint}) { font-size: ${MobileTitleM}; + text-align: center; } `; -export const CardBody = styled.p` +interface CardBodyProps { + $alignLeft?: boolean; +} + +export const CardBody = styled.p` font-size: ${BodyTextMedium}; - text-align: justify; + text-align: ${({ $alignLeft }) => ($alignLeft ? "left" : "justify")}; line-height: 1.6667; - margin-bottom: ${SpacingM}; + margin: 0 0 ${SpacingM}; @media (max-width: ${MobileBreakpoint}) { font-size: ${MobileBodyTextMedium}; + text-align: justify; + margin-bottom: ${SpacingS}; } `; @@ -81,27 +94,31 @@ export const VideoContainer = styled.div` `; export const SectionTitle = styled.h2` - font-size: ${TitleXL}; - text-transform: uppercase; - text-align: center; + font-size: ${TitleL}; + text-align: left; @media (max-width: ${MobileBreakpoint}) { - font-size: ${MobileTitleL}; + font-size: ${MobileTitleM}; + text-align: center; } `; export const SectionContainer = styled.div` + width: 100%; + min-width: 0; max-width: ${MaxElement}; margin: 0 auto; `; export const SectionDescription = styled.p` - font-size: ${BodyText}; + font-size: ${BodyTextMedium}; text-align: justify; - margin-top: ${SpacingS}; + margin-top: ${SpacingXS}; @media (max-width: ${MobileBreakpoint}) { - font-size: ${MobileBodyText}; + font-size: ${MobileBodyTextMedium}; + text-align: justify; + margin-top: ${SpacingXS}; } `; @@ -121,14 +138,14 @@ interface ButtonProps { } const ButtonBasic = styled.a` - padding: ${SpacingS} ${SpacingL}; + padding: ${SpacingS} ${SpacingM}; border-radius: ${SpacingXS}; text-align: center; text-decoration: none; text-transform: uppercase; font-size: ${BodyText}; width: ${(props) => props.width || "fit-content"}; - margin-top: ${SpacingS}; + margin-top: ${SpacingXS}; cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; @media (max-width: ${MobileBreakpoint}) { diff --git a/src/app/genericComponents/tokens.ts b/src/app/genericComponents/tokens.ts index 41e0939..51d2d51 100644 --- a/src/app/genericComponents/tokens.ts +++ b/src/app/genericComponents/tokens.ts @@ -1,6 +1,6 @@ // Font sizes export const TitleXXL: string = "78px"; -export const TitleXL: string = "50px"; +export const HeroTitleXL: string = "64px"; export const TitleL: string = "32px"; export const TitleM: string = "26px"; export const TitleS: string = "20px"; @@ -9,8 +9,6 @@ export const BodyTextMedium: string = "16px"; export const BodyTextSmall: string = "14px"; // Mobile font sizes -export const MobileTitleXL: string = "48px"; -export const MobileTitleL: string = "32px"; export const MobileTitleM: string = "26px"; export const MobileTitleS: string = "20px"; export const MobileBodyText: string = "17px"; @@ -40,7 +38,7 @@ export const BackgroundAccent: string = "#1d1e3b"; export const QuestionBoxBackgroundColor: string = "#304697"; // Responsive breakpoints -export const MobilePixels: number = 640; +export const MobilePixels: number = 768; export const MobileBreakpoint: string = MobilePixels + "px"; export const SmallMobilePixels: number = 370; export const SmallMobileBreakpoint: string = SmallMobilePixels + "px"; @@ -52,7 +50,6 @@ export const SpacingXS: string = "8px"; export const SpacingS: string = "16px"; export const SpacingM: string = "32px"; export const SpacingL: string = "64px"; -export const SpacingXL: string = "96px"; export const SpacingXXL: string = "160px"; export const MaxElement: string = "1000px"; diff --git a/src/app/globals.css b/src/app/globals.css index e5797a0..6fdb04d 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4,6 +4,15 @@ --embla-button-disabled-color: rgb(255, 255, 255); } +html, +body, +button, +input, +select, +textarea { + font-family: inherit; +} + * { box-sizing: border-box; padding: 0; @@ -13,81 +22,65 @@ html, body { + width: 100%; max-width: 100vw; overflow-x: hidden; /* Prevents horizontal scrollbar */ background-color: var(--body-color); } .embla { - max-width: 70rem; - margin: auto; - --slide-spacing: 0.5rem; - --slide-size: 100%; + width: 100%; + max-width: 100%; + min-width: 0; + margin-left: 0; + margin-right: 0; + overflow: hidden; } -.embla__viewport { +.marquee { + position: relative; + width: 100%; + max-width: 100%; + min-width: 0; overflow: hidden; } -.embla__container { +.marquee__track { display: flex; - touch-action: pan-y pinch-zoom; - margin-left: calc(var(--slide-spacing) * -1); + width: max-content; + animation: marquee-slide 26s linear infinite; } -.embla__slide { - transform: translate3d(0, 0, 0); - flex: 0 0 var(--slide-size); - min-width: 0; - padding-left: var(--slide-spacing); +.marquee__slide { + position: relative; + flex: 0 0 auto; + width: min(26vw, 20rem); + height: min(26vw, 20rem); + padding-right: 0; } -.embla__slide__image { - width: 80%; +.marquee__image { + display: block; object-fit: cover; + border-radius: 0; +} - @media (max-width: 640px) { - width: 100%; +@keyframes marquee-slide { + from { + transform: translateX(0); + } + to { + transform: translateX(-50%); } } -.embla__controls { - display: flex; - grid-template-columns: auto 1fr; - justify-content: center; - gap: 1.2rem; - margin-top: 1.8rem; -} -.embla__buttons { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.6rem; - align-items: center; -} -.embla__button { - -webkit-tap-highlight-color: var(--webkit-tap-highlight-color); - -webkit-appearance: none; - appearance: none; - background-color: transparent; - touch-action: manipulation; - text-decoration: none; - cursor: pointer; - border: 0; - padding: 0; - margin: 0; - box-shadow: inset 0 0 0 0.2rem rgb(234, 234, 234); - width: 2.6rem; - height: 2.6rem; - z-index: 1; - border-radius: 50%; - color: rgb(54, 49, 61); - display: flex; - align-items: center; - justify-content: center; -} -.embla__button:disabled { - color: var(--embla-button-disabled-color); + +.marquee__track { + animation-duration: 40s; } -.embla__button__svg { - width: 35%; - height: 35%; + +@media (max-width: 768px) { + .marquee__slide { + width: min(72vw, 20rem); + height: min(72vw, 20rem); + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e14303e..db0e90d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -25,7 +25,7 @@ export const metadata: Metadata = { }, }, openGraph: { - title: "Join HackUPC team", + title: "🚀 Join HackUPC team", type: "website", siteName: "recruiting HackersUPC", locale: "en_GB", diff --git a/src/app/page.tsx b/src/app/page.tsx index 36c598d..9bdf773 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,7 +8,7 @@ import { import Hackers from "@components/Hackers"; import Socials from "@components/Socials"; import Footer from "@components/Footer"; -import PresentationCards from "@components/PresentationCards"; +import PresentationCards, { VideoCards } from "@components/PresentationCards"; import styled from "styled-components"; import { MobileBreakpoint, @@ -17,11 +17,11 @@ import { } from "@/app/genericComponents/tokens"; const FirstBackground = styled(TransparentBackground)` - padding-top: ${SpacingS}; + min-width: 0; + padding: ${SpacingS} ${SpacingM} 0; @media (max-width: ${MobileBreakpoint}) { - padding-top: ${SpacingS}; - padding: ${SpacingM}; + padding: ${SpacingM} ${SpacingS} 0; } `; @@ -35,6 +35,9 @@ export default function HomePage() { + + +