diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a7e8006 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 +charset = utf-8 + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..ec5f006 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,34 @@ +name: Docker +on: + push: + schedule: + - cron: "42 2 2 * *" + workflow_dispatch: +jobs: + buildDockerImage: + name: Build Docker image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: ghcr.io/wisvch/hackdelft-2022 + tags: type=sha, prefix={{date 'YYYYMMDD'}}- + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + push: ${{ github.ref == 'refs/heads/main' }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a986e31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +yarn.lock +dist +.parcel-cache +node_modules +yarn-error.log +*.report.html diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8671b25 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +# Start the builder container. +FROM buildkite/puppeteer AS builder +WORKDIR /src + +# Instal Dependencies. +COPY ./package.json . +RUN yarn + +# Copy source files and build. +COPY ./static ./static +COPY ./data ./data +COPY ./src ./src +RUN yarn build + +# Start the NGINX container. +FROM wisvch/nginx +COPY --from=builder /src/dist /srv diff --git a/data/faq.json b/data/faq.json new file mode 100644 index 0000000..93ca311 --- /dev/null +++ b/data/faq.json @@ -0,0 +1,50 @@ +[ + { + "question": "Who can attend?", + "answer": "Any current university student can attend HackDelft." + }, + { + "question": "How can I sign up for HackDelft?", + "answer": "Unfortunately we are completely sold out! Use the button on top of the page to sign up for the waiting list." + }, + { + "question": "What languages can I use during the event?", + "answer": "You can use any language or tool you want during the event!" + }, + { + "question": "What is the maximum group size?", + "answer": "There is a limit of four participants per group. Do you have more friends? You can join in multiple groups! Don't know who joins you yet? You can form your groups later, or even at the event itself." + }, + { + "question": "What if I've never been to a hackathon before?", + "answer": "HackDelft is a very beginner friendly hackathon, so that is no issue! At the start of the hackathon we will explain how the weekend works, after which you can start hacking with your team." + }, + { + "question": "Can I still participate if I am not a programmer?", + "answer": "Yes! We have some challenges specifically designed for you. These challenges don't require a lot of programming experience, and are more focused on mathematics." + }, + { + "question": "Can I start working on my submission before the event begins?", + "answer": "No, this is not allowed. The challenges will also not be announced before the event starts." + }, + { + "question": "How much will it cost to attend HackDelft?", + "answer": "The price has been set at 1,- for the entire weekend. In return you can hack away all weekend, get three meals, and much more!" + }, + { + "question": "What do I need to bring to the event?", + "answer": "You will have to bring your laptop, some chargers, and an air mattress with sleeping bag if you intent to sleep. Some hygiene items will also be appreciated by other hackers." + }, + { + "question": "What will the Covid-19 measures look like at the event?", + "answer": "We fill follow the recommendations of the Dutch government with regards to Covid-19. Currently there are no restrictions in place, but we will keep a close eye on any changes as the event approaches." + }, + { + "question": "Do you provide any travel reimbursement?", + "answer": "Unfortunately, we do not provide any travel reimbursement. We hope to see you at the event regardless!" + }, + { + "question": "My question is not in here!", + "answer": "Send us an email at `hackdelft@ch.tudelft.nl`, and we will be happy to help you out!" + } +] diff --git a/data/photos.ts b/data/photos.ts new file mode 100644 index 0000000..b439989 --- /dev/null +++ b/data/photos.ts @@ -0,0 +1,24 @@ +// Create an array with all photos. +// This is required as the bundler (currently) cannot currently read the contents of the photo folder +export const photosMap: URL[] = [ + new URL("/static/photos/YAW01116.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01127.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01268.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01272.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01275.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01280.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01287.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01289.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01296.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01298.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01349.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01399.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01429.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01437.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01448.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01449.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01454.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01640.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01727.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01760.jpg?as=webp&width=1200", import.meta.url), +]; diff --git a/data/sponsors.json b/data/sponsors.json new file mode 100644 index 0000000..0b90a34 --- /dev/null +++ b/data/sponsors.json @@ -0,0 +1,70 @@ +[ + { + "type": "organisation", + "title": "Organisation", + "items": [ + { + "logoKey": "ch", + "url": "https://wisv.ch/", + "title": "W.I.S.V. Christiaan Huygens" + }, + { + "logoKey": "xtudelft", + "url": "https://www.tudelft.nl/en/x/", + "title": "X TU Delft" + } + ] + }, + { + "type": "primary", + "title": "Primary", + "items": [ + { + "logoKey": "cgi", + "url": "https://www.cgi.com/en", + "title": "CGI" + }, + { + "logoKey": "ind", + "url": "https://ind.nl/over-ind/werken-bij-de-IND/Paginas/default.aspx", + "title": "Immigratie- en Naturalisatiedienst" + } + ] + }, + { + "type": "secondary", + "title": "Supporting", + "items": [ + { + "logoKey": "ded", + "url": "https://www.dutchenergydrink.nl/", + "title": "Dutch Energy Drink" + }, + { + "logoKey": "elastic", + "url": "https://www.elastic.co/", + "title": "Elastic" + }, + { + "logoKey": "koro", + "url": "https://koro-handels-gmbh.jobs.personio.de/", + "title": "KoRo" + }, + { + "logoKey": "bit", + "url": "https://wearebit.com/join-us/", + "title": "Bit" + }, + { + "logoKey": "bwl", + "url": "https://www.biteswelove.nl/", + "title": "Bites We Love" + }, + { + "logoKey": "kleine", + "url": "https://www.dekleineconsultant.nl/", + "title": "De Kleine Consultant" + } + ] + } +] diff --git a/data/sponsors.ts b/data/sponsors.ts new file mode 100644 index 0000000..ebed32e --- /dev/null +++ b/data/sponsors.ts @@ -0,0 +1,26 @@ +// Create an object, mapping the logo key to the URL out of the bundler. +// This is required as the bundler (currently) cannot currently read the images from the JSON URL's directly. +export const sponsorLogoMap: { [key: string]: URL } = { + ch: new URL("/static/sponsors/ch.png?as=webp&width=200", import.meta.url), + xtudelft: new URL( + "/static/sponsors/xtudelft.png?as=webp&width=200", + import.meta.url + ), + cgi: new URL("/static/sponsors/cgi.png?as=webp&width=200", import.meta.url), + elastic: new URL( + "/static/sponsors/elastic.png?as=webp&width=200", + import.meta.url + ), + ded: new URL("/static/sponsors/ded.png?as=webp&width=200", import.meta.url), + koro: new URL( + "/static/sponsors/koro.png?as=webp&width=200", + import.meta.url + ), + bit: new URL("/static/sponsors/bit.png?as=webp&width=200", import.meta.url), + bwl: new URL("/static/sponsors/bwl.png?as=webp&width=200", import.meta.url), + ind: new URL("/static/sponsors/ind.png?as=webp&width=200", import.meta.url), + kleine: new URL( + "/static/sponsors/kleine.png?as=webp&width=200", + import.meta.url + ), +}; diff --git a/data/timeline.json b/data/timeline.json new file mode 100644 index 0000000..03d7b72 --- /dev/null +++ b/data/timeline.json @@ -0,0 +1,71 @@ +{ + "saturday": [ + { + "time": "12:00", + "name": "Walk-in", + "description": "We will welcome you in hall X1 of TU Delft X! You then have a bit of time to get settled in before the hacking starts." + }, + { + "time": "13:00", + "name": "Opening Presentation", + "description": "Here you will finally be introduced to the challenges, and get other important information about the event. If you don't have a team yet, you can form it here." + }, + { + "time": "14:00", + "name": "Start Hacking!", + "description": "After the challenges have been divided you can now get started on your project. We are excited to see what you will come up with!" + }, + { + "time": "± 17:00", + "name": "Workshop - Elastic", + "description": "In this workshop Elastic will give an introduction to their platform, and how you can use it for your project." + }, + { + "time": "18:30 - 20:00", + "name": "Dinner", + "description": "" + } + ], + "sunday": [ + { + "time": "7:00", + "name": "Morning Gymnastics", + "description": "Get a fresh start of the day with Cas and Berend" + }, + { + "time": "8:00 - 10:00", + "name": "Breakfast", + "description": "" + }, + { + "time": "± 10:00", + "name": "Workshop - Bit", + "description": "Our supporting partner Bit has a talk prepared!" + }, + { + "time": "± 12:00", + "name": "Pitch Workshop", + "description": "De Kleine Consultant will come and prepare you for your pitch at 14:00!" + }, + { + "time": "12:30 - 14:30", + "name": "Lunch", + "description": "" + }, + { + "time": "14:00", + "name": "Hacking Ends", + "description": "It has been 24 hours since we started! Now it's time to pitch your project to the jury." + }, + { + "time": "14:30", + "name": "Networking Drinks", + "description": "Talk to your fellow hackers at the drinks after the hacking has ended." + }, + { + "time": "± 16:00", + "name": "Award Ceremony", + "description": "We have a lot of prizes to award here, maybe to you?" + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a79b0da --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "hackdelft-2022", + "version": "0.1.0", + "description": "HackDelft informational website for the 21/22 edition", + "repository": "git@github.com:WISVCH/hackdelft-2022.git", + "author": "Dany Sluijk ", + "browserslist": "> 0.5%, last 2 versions, not dead", + "license": "MIT", + "private": true, + "type": "module", + "scripts": { + "start": "parcel -p 8000 ./src/index.html", + "build": "yarn clear-cache && parcel build ./src/index.html --reporter parcel-reporter-prerender && yarn fix-prerender", + "serve": "yarn build && http-server ./dist -p 8000 -g -b", + "clear-cache": "rm -rf dist .parcel-cache parcel-bundle-reports", + "fix-prerender": "sed -i 's/http:\\/\\/localhost:8000\\//\\//g' dist/index.html", + "lighthouse": "lighthouse http://localhost:8000 --view" + }, + "dependencies": { + "@fortawesome/free-brands-svg-icons": "^6.0.0", + "@fortawesome/free-solid-svg-icons": "^6.0.0", + "preact": "^10.6.6" + }, + "devDependencies": { + "@parcel/packager-raw-url": "2.5.0", + "@parcel/transformer-sass": "^2.5.0", + "@parcel/transformer-webmanifest": "^2.5.0", + "http-server": "^14.1.0", + "lighthouse": "^9.4.0", + "parcel": "^2.5.0", + "parcel-reporter-prerender": "^2.0.0", + "sharp": "^0.29.1" + }, + "@parcel/transformer-css": { + "cssModules": true + } +} diff --git a/src/.github/workflows/docker.yml b/src/.github/workflows/docker.yml new file mode 100644 index 0000000..ec5f006 --- /dev/null +++ b/src/.github/workflows/docker.yml @@ -0,0 +1,34 @@ +name: Docker +on: + push: + schedule: + - cron: "42 2 2 * *" + workflow_dispatch: +jobs: + buildDockerImage: + name: Build Docker image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: ghcr.io/wisvch/hackdelft-2022 + tags: type=sha, prefix={{date 'YYYYMMDD'}}- + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + push: ${{ github.ref == 'refs/heads/main' }} diff --git a/src/_mixins.scss b/src/_mixins.scss new file mode 100644 index 0000000..68368f8 --- /dev/null +++ b/src/_mixins.scss @@ -0,0 +1,15 @@ +// Taken from https://getbootstrap.com/docs/5.0/layout/breakpoints/#available-breakpoints. +$breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 992px, + xl: 1200px, + xxl: 1400px, +); + +@mixin breakpoint($breakpoint: xs) { + @media (min-width: map-get($breakpoints, $breakpoint)) { + @content; + } +} diff --git a/src/_variables.scss b/src/_variables.scss new file mode 100644 index 0000000..8bba036 --- /dev/null +++ b/src/_variables.scss @@ -0,0 +1,8 @@ +$background-color: #181226; +$primary-color: #f7931e; +$secondary-color: #662d91; +$tu-color: #00a6d6; +$discord-color: #7289da; + +$primary-text: #fff; +$sponsor-background: #fff; diff --git a/src/components/about/index.tsx b/src/components/about/index.tsx new file mode 100644 index 0000000..24545be --- /dev/null +++ b/src/components/about/index.tsx @@ -0,0 +1,43 @@ +import { h, FunctionalComponent } from "preact"; +import { faTicket } from "@fortawesome/free-solid-svg-icons"; +import { faDiscord } from "@fortawesome/free-brands-svg-icons"; + +import { Icon } from "/src/components"; + +import * as style from "./style.scss"; + +/** + * Renders the about section. + */ +export const About: FunctionalComponent = () => ( +
+

About

+

+ Join us on April 30th and May 1st in this all-inclusive 24 hour + hackathon where you solve fun challenges provided by our partner + companies, win prizes, and have a good time! +

+

+ We are completely sold out, but you can still sign up for the + waiting list. +

+
+ + + Sign up for the waiting list! + + + + Join Our Discord! + +
+
+); diff --git a/src/components/about/style.scss b/src/components/about/style.scss new file mode 100644 index 0000000..935b5a6 --- /dev/null +++ b/src/components/about/style.scss @@ -0,0 +1,60 @@ +@import "/src/mixins"; +@import "/src/variables"; + +.about { + h2 { + color: $secondary-color; + margin: 0; + padding-left: 2px; + } + + p { + margin: 8px; + + a { + color: $primary-color; + } + } + + div { + display: flex; + flex-direction: column; + justify-content: center; + + a { + margin: 8px 16px; + padding: 0.6em 1.6em; + border-radius: 6px; + white-space: nowrap; + text-decoration: none; + cursor: pointer; + + svg { + display: inline-block; + vertical-align: middle; + } + + &:hover { + text-decoration: underline; + } + } + } +} + +.tickets { + color: $primary-text; + background-color: $primary-color; +} + +.discord { + color: $primary-text; + background-color: $discord-color; +} + +@include breakpoint(sm) { + .about { + div { + flex-direction: row; + } + } +} diff --git a/src/components/committee/index.tsx b/src/components/committee/index.tsx new file mode 100644 index 0000000..e87dbbc --- /dev/null +++ b/src/components/committee/index.tsx @@ -0,0 +1,53 @@ +import { h, FunctionalComponent } from "preact"; + +import * as style from "./style.scss"; + +const picture = new URL( + "/static/committee.png?as=webp&width=1200", + import.meta.url +); + +/** + * Renders the committee section. + */ +export const Committee: FunctionalComponent = () => ( +
+
+ HackDelft Committee +
+
+

Meet The Committee

+

From left to right

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
The Head of AcquisitionBerend Krouwels
The Head of LogisticsAnnerieke Ohm
The SecretaryNina Immig
The ChairmanDany Sluijk
The TreasurerNiels van den Dool
The Commissioner of Promotional AffairsRicardo Vogel
The Qualitate QuaCas Wever
+
+
+); diff --git a/src/components/committee/style.scss b/src/components/committee/style.scss new file mode 100644 index 0000000..b78ba90 --- /dev/null +++ b/src/components/committee/style.scss @@ -0,0 +1,77 @@ +@import "/src/variables"; +@import "/src/mixins"; + +.container { + width: calc(100% - 16px); + display: flex; + flex-direction: column; + margin: 0 8px; +} + +.image { + flex: 2; + align-self: center; + + img { + width: 100%; + border: 4px solid $secondary-color; + border-radius: 12px; + box-sizing: border-box; + object-fit: cover; + } +} + +.committee { + flex: 3; + + h2 { + color: $primary-color; + margin-bottom: 0; + } + + h3 { + color: $secondary-color; + margin-top: 0; + padding: 0; + } + + table { + border-collapse: collapse; + width: 100%; + line-height: 1.4; + } + + tr { + border-bottom: 1px dotted $secondary-color; + + &:last-child { + border-bottom: none; + } + } + + th { + float: left; + display: contents; + color: mix($primary-color, $primary-text, 80%); + } + + td { + white-space: nowrap; + } +} + +@include breakpoint(lg) { + .container { + flex-direction: row; + width: 100%; + margin: 0; + } + + .committee { + margin-left: 16px; + + tr { + border-bottom: none; + } + } +} diff --git a/src/components/faq/index.tsx b/src/components/faq/index.tsx new file mode 100644 index 0000000..097bb81 --- /dev/null +++ b/src/components/faq/index.tsx @@ -0,0 +1,32 @@ +import { h, FunctionalComponent } from "preact"; + +import faqData from "/data/faq.json"; + +import * as style from "./style.scss"; + +/** + * Renders the FAQ. + * @returns The FAQ section of the page. + */ +export const FAQ: FunctionalComponent = () => ( +
+

Frequently Asked Questions

+ +
+); + +interface Question { + question: string; + answer: string; +} diff --git a/src/components/faq/style.scss b/src/components/faq/style.scss new file mode 100644 index 0000000..579cc54 --- /dev/null +++ b/src/components/faq/style.scss @@ -0,0 +1,45 @@ +@import "/src/variables"; +@import "/src/mixins"; + +.faq { + margin: 12px 0; + padding: 16px; + background: $primary-color; + + h2 { + margin: 0; + padding: 8px; + color: $primary-text; + } + + ul { + padding: 0; + list-style-type: none; + } + + li { + border-left: 3px solid mix($primary-color, $primary-text, 75%); + padding-left: 16px; + margin: 0 0 16px 8px; + } + + summary { + cursor: pointer; + font-size: 1.1em; + font-weight: 600; + color: mix($primary-color, $primary-text, 40%); + } + + p { + margin: 8px 0; + font-size: 1.1em; + line-height: 20px; + color: $primary-text; + } +} + +@include breakpoint(sm) { + .faq { + border-radius: 6px; + } +} diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx new file mode 100644 index 0000000..7e1123e --- /dev/null +++ b/src/components/footer/index.tsx @@ -0,0 +1,30 @@ +import { h, FunctionalComponent } from "preact"; +import { faEnvelope } from "@fortawesome/free-solid-svg-icons"; +import { faGithub } from "@fortawesome/free-brands-svg-icons"; + +import { Icon } from "/src/components"; + +import * as style from "./style.scss"; + +/** + * Renders the footer of the site. + */ +export const Footer: FunctionalComponent = () => ( + +); diff --git a/src/components/footer/style.scss b/src/components/footer/style.scss new file mode 100644 index 0000000..4c4c45f --- /dev/null +++ b/src/components/footer/style.scss @@ -0,0 +1,16 @@ +@import "/src/variables"; + +.footer { + display: flex; + padding-bottom: 8px; + width: 100%; + justify-content: end; + + * { + color: $secondary-color; + + &:hover { + color: $primary-color; + } + } +} diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx new file mode 100644 index 0000000..987d4b9 --- /dev/null +++ b/src/components/header/index.tsx @@ -0,0 +1,18 @@ +import { h, FunctionalComponent } from "preact"; + +import * as style from "./style.scss"; + +import icon from "url:/static/logo_alt.png?as=webp&width=500"; + +/** + * Renders the header of the site. + */ +export const Header: FunctionalComponent = () => ( +
+ HackDelft + +

Date: April 30th & May 1st

+

Location: TU Delft X

+
+
+); diff --git a/src/components/header/style.scss b/src/components/header/style.scss new file mode 100644 index 0000000..8fc42d3 --- /dev/null +++ b/src/components/header/style.scss @@ -0,0 +1,44 @@ +@import "/src/mixins"; +@import "/src/variables"; + +.header { + display: flex; + flex-direction: column; + margin-top: 32px; + + img { + height: 4em; + margin: 0 auto; + } + + span { + margin: 16px 2vw; + + h2 { + margin: 0; + white-space: nowrap; + color: $primary-color; + } + + h3 { + margin: 0; + white-space: nowrap; + color: $secondary-color; + } + } +} + +@include breakpoint(sm) { + .header { + flex-direction: row; + margin-bottom: 24px; + + img { + margin: 0; + } + + span { + margin: 0 2vw; + } + } +} diff --git a/src/components/icon/index.tsx b/src/components/icon/index.tsx new file mode 100644 index 0000000..b42946e --- /dev/null +++ b/src/components/icon/index.tsx @@ -0,0 +1,41 @@ +import { h, FunctionalComponent, RenderableProps } from "preact"; +import { IconDefinition } from "@fortawesome/free-solid-svg-icons"; + +import * as style from "./style.scss"; + +/** + * Font Awesome icon component. + * @param props Props to render with. + * @returns JSX of the icon. + */ +export const Icon: FunctionalComponent> = (props) => { + const styles = `${style.icon} ${props.pad ? style.pad : ""}`; + const definition = props.icon.icon; + const path = + typeof definition[4] === "string" + ? definition[4] + : definition[4].reduce((c: string, v: string) => `${c} ${v}`, ""); + + return ( + + + + ); +}; + +interface Props { + // The icon from FontAwesome to show. + icon: IconDefinition; + // Height of the icon. + height?: string; + // Color of the icon. + // This is infered from the text color if not set. + color?: string; + // Whether or not to add padding to the icon. + pad?: boolean; +} diff --git a/src/components/icon/style.scss b/src/components/icon/style.scss new file mode 100644 index 0000000..96d45ea --- /dev/null +++ b/src/components/icon/style.scss @@ -0,0 +1,8 @@ +.icon { + align-items: inherit; + fill: currentColor; +} + +.pad { + margin: 0 0.5em; +} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..1cec7ad --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,9 @@ +export { About } from "./about"; +export { Committee } from "./committee"; +export { FAQ } from "./faq"; +export { Footer } from "./footer"; +export { Header } from "./header"; +export { Icon } from "./icon"; +export { Photos } from "./photos"; +export { Sponsors } from "./sponsors"; +export { Timeline } from "./timeline"; diff --git a/src/components/photos/index.tsx b/src/components/photos/index.tsx new file mode 100644 index 0000000..45b1057 --- /dev/null +++ b/src/components/photos/index.tsx @@ -0,0 +1,94 @@ +import { h, FunctionalComponent } from "preact"; +import { useEffect, useRef, useState } from "preact/hooks"; + +import { photosMap } from "/data/photos"; + +import * as style from "./style.scss"; + +/** + * Renders the photos section. + */ +export const Photos: FunctionalComponent = () => { + const gallery = useRef(null); + const requestRef = useRef(null); + const shouldAnimate = useRef(true); + const lastScrolled = useRef(0); + + const animate = (time: DOMHighResTimeStamp) => { + if (!shouldAnimate.current) { + requestRef.current = null; + return; + } + + requestRef.current = requestAnimationFrame(animate); + + if (gallery.current === null) { + return; + } + + const scaledTime = time * 0.06; + const max = gallery.current.scrollWidth - gallery.current.clientWidth; + gallery.current.scrollLeft = scaledTime % max; + lastScrolled.current = scaledTime % max; + }; + + useEffect(() => { + requestRef.current = requestAnimationFrame(animate); + gallery.current.onscroll = (e) => { + if (!shouldAnimate.current) return; + + const diff = lastScrolled.current - gallery.current.scrollLeft; + if (Math.abs(diff) < 4) return; + + shouldAnimate.current = false; + }; + + () => { + if (requestRef.current === null) return; + + cancelAnimationFrame(requestRef.current); + }; + }, []); + + return ( +
+

Photos

+

+ Take a look at last edition to get an impression of the event. +

+
(shouldAnimate.current = false)} + > + {photosMap.map((url) => ( + + ))} +
+
+ ); +}; + +const Photo: FunctionalComponent = (props) => { + const [opened, setOpened] = useState(false); + + const onClick = () => { + setOpened(!opened); + }; + + const classes = `${style.image} ${opened ? style.fullimage : ""}`; + + return ( + + HackDelft 2019 photo + + ); +}; + +interface PhotoProps { + url: URL; +} diff --git a/src/components/photos/style.scss b/src/components/photos/style.scss new file mode 100644 index 0000000..5df51e1 --- /dev/null +++ b/src/components/photos/style.scss @@ -0,0 +1,68 @@ +@import "/src/variables"; + +.photos { + margin-bottom: 8px; + + h2 { + color: $secondary-color; + margin: 0; + padding-left: 2px; + } + + p { + margin: 0 8px; + } +} + +.gallery { + display: flex; + overflow-x: scroll; + gap: 12px; + scrollbar-width: auto; + scrollbar-color: $primary-color $background-color; + + &::-webkit-scrollbar { + height: 8px; + } + + &::-webkit-scrollbar-track { + background: $background-color; + } + + &::-webkit-scrollbar-thumb { + background-color: $primary-color; + } +} + +.image { + cursor: pointer; + + img { + height: 260px; + width: 400px; + object-fit: cover; + border-radius: 6px; + } +} + +.fullimage { + position: fixed; + display: flex; + + top: 0; + left: 0; + right: 0; + bottom: 0; + justify-content: center; + align-items: center; + + background-color: #181226bb; + backdrop-filter: blur(12px); + + img { + width: auto; + height: auto; + max-height: 90vh; + max-width: 90vw; + } +} diff --git a/src/components/sponsors/index.tsx b/src/components/sponsors/index.tsx new file mode 100644 index 0000000..cacf996 --- /dev/null +++ b/src/components/sponsors/index.tsx @@ -0,0 +1,79 @@ +import { h, FunctionalComponent, Fragment } from "preact"; +import { useState, useEffect } from "preact/hooks"; + +import { sponsorLogoMap } from "/data/sponsors"; + +import sponsorData from "/data/sponsors.json"; + +import * as style from "./style.scss"; + +/** + * Renders the sponsors section. + */ +export const Sponsors: FunctionalComponent = () => ( +
+

Sponsors

+ {sponsorData.map((sponsor: SponsorCategory) => sponsorItem(sponsor))} +
+); + +export const sponsorItem = (category: SponsorCategory) => { + const [sponsors, setSponsors] = useState([]); + useEffect(() => { + shuffleArray(category.items); + setSponsors(category.items); + }, []); + + return ( + +

{category.title}

+ +
+ ); +}; + +/** + * Randomize the order of an array. + * @param array The array to randomize. + */ +const shuffleArray = (array: any[]) => { + for (var i = array.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +}; + +interface SponsorCategory { + type: "organisation" | "primary" | "secondary"; + title: string; + items: Sponsor[]; +} + +interface Sponsor { + logoKey: string; + url: string; + title: string; +} diff --git a/src/components/sponsors/style.scss b/src/components/sponsors/style.scss new file mode 100644 index 0000000..0d63119 --- /dev/null +++ b/src/components/sponsors/style.scss @@ -0,0 +1,82 @@ +@import "/src/variables"; +@import "/src/mixins"; + +.sponsors { + h2 { + color: $secondary-color; + margin: 0; + padding-left: 2px; + } + + h3 { + margin: 4px 16px; + color: $primary-color; + } +} + +.sponsorlist { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + gap: 16px 32px; + padding: 0; + + li { + width: 66%; + list-style: none; + justify-content: center; + } + + .img { + display: flex; + padding: 8px; + border: 3px solid currentColor; + border-radius: 12px; + background-color: $sponsor-background; + + img { + width: 100%; + height: 120px; + object-fit: contain; + } + } + + a { + text-decoration: none; + } + + p { + margin: 8px 0 0 0; + text-align: center; + } + + [data-type="organisation"] > * { + color: $tu-color; + } + + [data-type="primary"] > * { + color: $primary-color; + } + + [data-type="secondary"] > * { + color: $secondary-color; + } +} + +@include breakpoint(sm) { + .sponsorlist li { + width: 50%; + } +} + +@include breakpoint(md) { + .sponsorlist li { + width: 33%; + } +} + +@include breakpoint(xl) { + .sponsorlist li { + width: 20%; + } +} diff --git a/src/components/timeline/index.tsx b/src/components/timeline/index.tsx new file mode 100644 index 0000000..b787623 --- /dev/null +++ b/src/components/timeline/index.tsx @@ -0,0 +1,56 @@ +import { faCalendar, faClock } from "@fortawesome/free-solid-svg-icons"; +import { h, FunctionalComponent } from "preact"; + +import { Icon } from "/src/components/icon"; + +import { saturday, sunday } from "/data/timeline.json"; + +import * as style from "./style.scss"; + +/** + * Renders the timeline section. + */ +export const Timeline: FunctionalComponent = () => ( +
+

Schedule

+

+ This is an indication of the schedule of the event! It is an + indication, and therefore subject to change. +

+
+
+

Saturday

+ {(saturday as TimelineEntry[]).map((entry) => + renderEntry(entry) + )} +
+
+

Sunday

+ {(sunday as TimelineEntry[]).map((entry) => renderEntry(entry))} +
+
+
+); + +const renderEntry = (entry: TimelineEntry) => ( +
+ + +
+

+ + {entry.name} +

+ {entry.description} +
+
+); + +interface TimelineEntry { + time: string; + name: string; + description: string; +} diff --git a/src/components/timeline/style.scss b/src/components/timeline/style.scss new file mode 100644 index 0000000..530101e --- /dev/null +++ b/src/components/timeline/style.scss @@ -0,0 +1,70 @@ +@import "/src/mixins"; +@import "/src/variables"; + +.timeline { + h2 { + color: $primary-color; + margin: 0; + } + + p { + margin: 0; + } +} + +.container { + display: flex; + flex-direction: column; + + h3 { + color: $secondary-color; + margin: 4px 16px; + } + + div { + display: flex; + flex-direction: column; + } +} + +.timelineEntry { + margin: 0 8px; + + time { + font-style: italic; + + svg { + padding-right: 4px; + } + } + + div { + margin-bottom: 8px; + padding: 8px 16px; + border: 2px solid $secondary-color; + border-radius: 4px; + } + + h4 { + margin: 0; + color: $primary-color; + + svg { + padding-right: 4px; + } + } +} + +@include breakpoint(md) { + .container { + flex-direction: row; + + div { + width: 92%; + } + } + + .timelineEntry { + align-self: center; + } +} diff --git a/src/data/faq.json b/src/data/faq.json new file mode 100644 index 0000000..93ca311 --- /dev/null +++ b/src/data/faq.json @@ -0,0 +1,50 @@ +[ + { + "question": "Who can attend?", + "answer": "Any current university student can attend HackDelft." + }, + { + "question": "How can I sign up for HackDelft?", + "answer": "Unfortunately we are completely sold out! Use the button on top of the page to sign up for the waiting list." + }, + { + "question": "What languages can I use during the event?", + "answer": "You can use any language or tool you want during the event!" + }, + { + "question": "What is the maximum group size?", + "answer": "There is a limit of four participants per group. Do you have more friends? You can join in multiple groups! Don't know who joins you yet? You can form your groups later, or even at the event itself." + }, + { + "question": "What if I've never been to a hackathon before?", + "answer": "HackDelft is a very beginner friendly hackathon, so that is no issue! At the start of the hackathon we will explain how the weekend works, after which you can start hacking with your team." + }, + { + "question": "Can I still participate if I am not a programmer?", + "answer": "Yes! We have some challenges specifically designed for you. These challenges don't require a lot of programming experience, and are more focused on mathematics." + }, + { + "question": "Can I start working on my submission before the event begins?", + "answer": "No, this is not allowed. The challenges will also not be announced before the event starts." + }, + { + "question": "How much will it cost to attend HackDelft?", + "answer": "The price has been set at 1,- for the entire weekend. In return you can hack away all weekend, get three meals, and much more!" + }, + { + "question": "What do I need to bring to the event?", + "answer": "You will have to bring your laptop, some chargers, and an air mattress with sleeping bag if you intent to sleep. Some hygiene items will also be appreciated by other hackers." + }, + { + "question": "What will the Covid-19 measures look like at the event?", + "answer": "We fill follow the recommendations of the Dutch government with regards to Covid-19. Currently there are no restrictions in place, but we will keep a close eye on any changes as the event approaches." + }, + { + "question": "Do you provide any travel reimbursement?", + "answer": "Unfortunately, we do not provide any travel reimbursement. We hope to see you at the event regardless!" + }, + { + "question": "My question is not in here!", + "answer": "Send us an email at `hackdelft@ch.tudelft.nl`, and we will be happy to help you out!" + } +] diff --git a/src/data/photos.ts b/src/data/photos.ts new file mode 100644 index 0000000..b439989 --- /dev/null +++ b/src/data/photos.ts @@ -0,0 +1,24 @@ +// Create an array with all photos. +// This is required as the bundler (currently) cannot currently read the contents of the photo folder +export const photosMap: URL[] = [ + new URL("/static/photos/YAW01116.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01127.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01268.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01272.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01275.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01280.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01287.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01289.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01296.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01298.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01349.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01399.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01429.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01437.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01448.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01449.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01454.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01640.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01727.jpg?as=webp&width=1200", import.meta.url), + new URL("/static/photos/YAW01760.jpg?as=webp&width=1200", import.meta.url), +]; diff --git a/src/data/sponsors.json b/src/data/sponsors.json new file mode 100644 index 0000000..0b90a34 --- /dev/null +++ b/src/data/sponsors.json @@ -0,0 +1,70 @@ +[ + { + "type": "organisation", + "title": "Organisation", + "items": [ + { + "logoKey": "ch", + "url": "https://wisv.ch/", + "title": "W.I.S.V. Christiaan Huygens" + }, + { + "logoKey": "xtudelft", + "url": "https://www.tudelft.nl/en/x/", + "title": "X TU Delft" + } + ] + }, + { + "type": "primary", + "title": "Primary", + "items": [ + { + "logoKey": "cgi", + "url": "https://www.cgi.com/en", + "title": "CGI" + }, + { + "logoKey": "ind", + "url": "https://ind.nl/over-ind/werken-bij-de-IND/Paginas/default.aspx", + "title": "Immigratie- en Naturalisatiedienst" + } + ] + }, + { + "type": "secondary", + "title": "Supporting", + "items": [ + { + "logoKey": "ded", + "url": "https://www.dutchenergydrink.nl/", + "title": "Dutch Energy Drink" + }, + { + "logoKey": "elastic", + "url": "https://www.elastic.co/", + "title": "Elastic" + }, + { + "logoKey": "koro", + "url": "https://koro-handels-gmbh.jobs.personio.de/", + "title": "KoRo" + }, + { + "logoKey": "bit", + "url": "https://wearebit.com/join-us/", + "title": "Bit" + }, + { + "logoKey": "bwl", + "url": "https://www.biteswelove.nl/", + "title": "Bites We Love" + }, + { + "logoKey": "kleine", + "url": "https://www.dekleineconsultant.nl/", + "title": "De Kleine Consultant" + } + ] + } +] diff --git a/src/data/sponsors.ts b/src/data/sponsors.ts new file mode 100644 index 0000000..ebed32e --- /dev/null +++ b/src/data/sponsors.ts @@ -0,0 +1,26 @@ +// Create an object, mapping the logo key to the URL out of the bundler. +// This is required as the bundler (currently) cannot currently read the images from the JSON URL's directly. +export const sponsorLogoMap: { [key: string]: URL } = { + ch: new URL("/static/sponsors/ch.png?as=webp&width=200", import.meta.url), + xtudelft: new URL( + "/static/sponsors/xtudelft.png?as=webp&width=200", + import.meta.url + ), + cgi: new URL("/static/sponsors/cgi.png?as=webp&width=200", import.meta.url), + elastic: new URL( + "/static/sponsors/elastic.png?as=webp&width=200", + import.meta.url + ), + ded: new URL("/static/sponsors/ded.png?as=webp&width=200", import.meta.url), + koro: new URL( + "/static/sponsors/koro.png?as=webp&width=200", + import.meta.url + ), + bit: new URL("/static/sponsors/bit.png?as=webp&width=200", import.meta.url), + bwl: new URL("/static/sponsors/bwl.png?as=webp&width=200", import.meta.url), + ind: new URL("/static/sponsors/ind.png?as=webp&width=200", import.meta.url), + kleine: new URL( + "/static/sponsors/kleine.png?as=webp&width=200", + import.meta.url + ), +}; diff --git a/src/data/timeline.json b/src/data/timeline.json new file mode 100644 index 0000000..03d7b72 --- /dev/null +++ b/src/data/timeline.json @@ -0,0 +1,71 @@ +{ + "saturday": [ + { + "time": "12:00", + "name": "Walk-in", + "description": "We will welcome you in hall X1 of TU Delft X! You then have a bit of time to get settled in before the hacking starts." + }, + { + "time": "13:00", + "name": "Opening Presentation", + "description": "Here you will finally be introduced to the challenges, and get other important information about the event. If you don't have a team yet, you can form it here." + }, + { + "time": "14:00", + "name": "Start Hacking!", + "description": "After the challenges have been divided you can now get started on your project. We are excited to see what you will come up with!" + }, + { + "time": "± 17:00", + "name": "Workshop - Elastic", + "description": "In this workshop Elastic will give an introduction to their platform, and how you can use it for your project." + }, + { + "time": "18:30 - 20:00", + "name": "Dinner", + "description": "" + } + ], + "sunday": [ + { + "time": "7:00", + "name": "Morning Gymnastics", + "description": "Get a fresh start of the day with Cas and Berend" + }, + { + "time": "8:00 - 10:00", + "name": "Breakfast", + "description": "" + }, + { + "time": "± 10:00", + "name": "Workshop - Bit", + "description": "Our supporting partner Bit has a talk prepared!" + }, + { + "time": "± 12:00", + "name": "Pitch Workshop", + "description": "De Kleine Consultant will come and prepare you for your pitch at 14:00!" + }, + { + "time": "12:30 - 14:30", + "name": "Lunch", + "description": "" + }, + { + "time": "14:00", + "name": "Hacking Ends", + "description": "It has been 24 hours since we started! Now it's time to pitch your project to the jury." + }, + { + "time": "14:30", + "name": "Networking Drinks", + "description": "Talk to your fellow hackers at the drinks after the hacking has ended." + }, + { + "time": "± 16:00", + "name": "Award Ceremony", + "description": "We have a lot of prizes to award here, maybe to you?" + } + ] +} diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..e875f59 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,3 @@ +declare module "*.scss"; +declare module "*.json"; +declare module "url:*"; diff --git a/src/global.scss b/src/global.scss new file mode 100644 index 0000000..c616889 --- /dev/null +++ b/src/global.scss @@ -0,0 +1,42 @@ +@import "/src/variables"; +@import "/src/mixins"; + +html, +body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + scroll-behavior: smooth; +} + +body { + display: flex; + flex-direction: column; + color: $primary-text; + background: $background-color; + font-family: Helvetica, sans-serif; + line-height: 1.5; + font-size: 1.1em; +} + +@include breakpoint(sm) { + body { + width: 80%; + margin: 0 10%; + } +} + +@include breakpoint(md) { + body { + width: 70%; + margin: 0 15%; + } +} + +@include breakpoint(lg) { + body { + width: 60%; + margin: 0 20%; + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..4012baf --- /dev/null +++ b/src/index.html @@ -0,0 +1,39 @@ + + + + HackDelft: It's Hacktastic! + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..d226041 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,38 @@ +import { Fragment, h, render } from "preact"; + +import { + About, + Committee, + FAQ, + Footer, + Header, + Photos, + Sponsors, + Timeline, +} from "/src/components"; + +import "./global.scss"; + +/** + * Start the application. + */ +const start = (): void => { + render( + +
+ + + + + + +