├── .devcontainer
└── devcontainer.json
├── .github
└── workflows
│ └── caniuse-update.yml
├── .gitignore
├── .prettierignore
├── LICENSE
├── README.md
├── components
├── Card.js
├── FancyCard.js
├── Map.js
├── Plausible.js
└── mdx
│ ├── Alert.js
│ ├── Code.js
│ └── MDXContent.js
├── content
├── brilliant.mdx
├── codeday.mdx
├── figma.mdx
├── orpheus.mdx
├── stickers.mdx
└── zoom.mdx
├── lib
├── content.js
├── github.js
└── utils.js
├── manifest.js
├── next.config.js
├── package.json
├── pages
├── 404.js
├── [slug].js
├── _app.js
└── api
│ ├── [key].js
│ └── auth
│ └── [...nextauth].js
├── prettier.config.js
├── public
├── adafruit-banner.jpg
├── banner.png
├── cards
│ ├── bakebuild.png
│ ├── boba.png
│ ├── cider.png
│ ├── hackaccino.png
│ ├── hackcraft.png
│ ├── hacklet.png
│ ├── jams.png
│ ├── nest.png
│ ├── onboard.png
│ ├── puttingtheyouincpu.png
│ ├── raspapi.webp
│ ├── shipwrecked.png
│ ├── sidequests.png
│ ├── sinerider.png
│ ├── solder.png
│ ├── someassemblyrequired.png
│ ├── spaces.png
│ ├── sprig.png
│ ├── swirl.svg
│ ├── tonic.png
│ ├── toppings.png
│ └── visioneer.png
├── club-stats.png
├── codeday.png
├── dino_handing_docs-2.png
├── figma.png
├── leaflet.css
├── robots.txt
├── scrollbar.css
├── sparkle.svg
├── stickers.png
├── style.css
└── zoom.png
├── styles
└── globals.css
└── yarn.lock
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "tasks": {
3 | "test": "echo \"No test script defined in package.json\"",
4 | "build": "yarn install && yarn build",
5 | "launch": "yarn install && yarn dev"
6 | }
7 | }
--------------------------------------------------------------------------------
/.github/workflows/caniuse-update.yml:
--------------------------------------------------------------------------------
1 | name: Update Browserslist database
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 2 1,15 * *'
7 |
8 | permissions:
9 | contents: write
10 | pull-requests: write
11 |
12 | jobs:
13 | update-browserslist-database:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout repository
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 | - name: Configure git
21 | run: |
22 | git config --global user.email "action@github.com"
23 | git config --global user.name "GitHub Action"
24 | - name: Update Browserslist database and create PR if applies
25 | uses: c2corg/browserslist-update-action@v2
26 | with:
27 | github_token: ${{ secrets.GITHUB_TOKEN }}
28 | branch: browserslist-update
29 | base_branch: main
30 | commit_message: 'build: update Browserslist db'
31 | title: 'build: update Browserslist db'
32 | body: Auto-generated by [browserslist-update-action](https://github.com/c2corg/browserslist-update-action/). Caniuse database has been updated. Review changes, merge this PR, and be merry.
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.yarn
6 | /.pnp
7 | .pnp.js
8 | .pnp.cjs
9 | .pnp.loader.mjs
10 | package-lock.json
11 |
12 | # testing
13 | /coverage
14 |
15 | # next.js
16 | /.next/
17 | /out/
18 |
19 | # production
20 | /build
21 |
22 | # misc
23 | .DS_Store
24 | *.pem
25 |
26 | # debug
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 | .pnpm-debug.log*
31 |
32 | # local env files
33 | .env*.local
34 |
35 | # vercel
36 | .vercel
37 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.yarn
6 | /.pnp
7 | .pnp.js
8 | .pnp.cjs
9 | .pnp.loader.mjs
10 | package-lock.json
11 |
12 | # testing
13 | /coverage
14 |
15 | # next.js
16 | /.next/
17 | /out/
18 |
19 | # production
20 | /build
21 |
22 | # misc
23 | .DS_Store
24 | *.pem
25 |
26 | # debug
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 | .pnpm-debug.log*
31 |
32 | # local env files
33 | .env*.local
34 |
35 | # vercel
36 | .vercel
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 The Hack Foundation
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | Deals for Hack Clubbers—powered by [Next.js], [Theme UI] & [MDX].
9 |
10 | [next.js]: https://nextjs.org
11 | [mdx]: https://mdxjs.com
12 | [theme ui]: http://theme-ui.com
13 |
14 | ## How do I add a card?
15 |
16 | All the cards except for the current ones are stored in `manifest.js`. If you take a look at it, you'll notice it's in JSON:
17 |
18 | ```js
19 | export default [
20 | {
21 | category: 'Perks',
22 | color: 'primary',
23 | icon: 'bag',
24 | items: [
25 | {
26 | name: 'Zoom Pro Meetings',
27 | description: 'Run Zoom Pro meetings for free in your Hack Club',
28 | icon: 'welcome',
29 | external: false,
30 | url: '/zoom.mdx',
31 | forUseBy: 'clubbers'
32 | },
33 | ...
34 | ]
35 | },
36 | ...
37 | ]
38 | ```
39 |
40 | To add a card to an existing category, you simply add to the cards in the category, like so:
41 |
42 | ```diff
43 | export default [
44 | {
45 | category: 'Perks',
46 | color: 'primary',
47 | icon: 'bag',
48 | items: [
49 | {
50 | name: 'Zoom Pro Meetings',
51 | description: 'Run Zoom Pro meetings for free in your Hack Club',
52 | icon: 'welcome',
53 | external: false,
54 | url: '/zoom.mdx',
55 | forUseBy: 'clubbers'
56 | },
57 | + {
58 | + name: 'Theme',
59 | + description: "Hack Club's theme, using Theme UI",
60 | + icon: 'grid',
61 | + external: true,
62 | + url: 'https://theme.hackclub.com',
63 | + forUseBy: 'everyone'
64 | + }
65 | ]
66 | },
67 | ```
68 |
69 | To add a new category, you'll follow a similar pattern at the bottom of the file:
70 |
71 | ```diff
72 | export default [
73 | {
74 | category: 'Perks',
75 | color: 'primary',
76 | icon: 'bag',
77 | items: [
78 | {
79 | name: 'Zoom Pro Meetings',
80 | description: 'Run Zoom Pro meetings for free in your Hack Club',
81 | icon: 'welcome',
82 | external: false,
83 | url: '/zoom.mdx',
84 | forUseBy: 'clubbers'
85 | },
86 | ]
87 | },
88 | + {
89 | + category: '',
90 | + color: '',
91 | + icon: ''
92 | + items: [
93 | + ...
94 | + ]
95 | + }
96 | ]
97 | ```
98 |
99 | _For `color` and `icon`, you can use [Hack Club's theme](https://theme.hackclub.com)._
100 |
101 | When you edit the file to make these changes, you'll be asked to make a pull request.
102 |
103 | Let us know if you have any questions in [Slack](https://hackclub.slack.com)!
104 |
--------------------------------------------------------------------------------
/components/Card.js:
--------------------------------------------------------------------------------
1 | import { Box, Card, Flex, Heading, Text, Link, Badge } from 'theme-ui'
2 | import Icon from '@hackclub/icons'
3 | import { useRouter } from 'next/router'
4 | import { buildRoute } from '../lib/utils'
5 |
6 | export function InternalCard({
7 | item,
8 | onMobile,
9 | onHover,
10 | onHoverLeave,
11 | categories,
12 | query,
13 | sx = {}
14 | }) {
15 | const router = useRouter()
16 | return (
17 |
20 | router.push(
21 | buildRoute({
22 | route: item.url,
23 | categories,
24 | query
25 | }),
26 | undefined,
27 | {
28 | scroll: false
29 | }
30 | )
31 | }
32 | target={!item.external ? '' : '_blank'}
33 | className={`plausible-event-name=${item.name.replace(' ', '-')}`}
34 | sx={{
35 | textDecoration: 'none',
36 | alignSelf: 'flex-start',
37 | zIndex: 999,
38 | cursor: 'pointer',
39 | transition: 'transform .125s ease-in-out, box-shadow .125s ease-in-out',
40 | ':hover,:focus': {
41 | transform: 'scale(1.0225)'
42 | },
43 | p: '0 !important',
44 | ...(!onMobile && {
45 | '@media screen and (min-width: 992px)': {
46 | maxHeight: '175px',
47 | position: 'relative',
48 | '::after': {
49 | content: '""',
50 | position: 'absolute',
51 | zIndex: 1,
52 | bottom: 0,
53 | left: 0,
54 | pointerEvents: 'none',
55 | backgroundImage:
56 | 'linear-gradient(to bottom, rgba(255,255,255, 0), rgba(255,255,255, 1) 90%)',
57 | width: '100%',
58 | height: '75px'
59 | },
60 | ':hover': {
61 | overflow: 'visible',
62 | zIndex: 1000,
63 | '::after': {
64 | display: 'none'
65 | },
66 | '.more': {
67 | display: 'none'
68 | }
69 | }
70 | }
71 | }),
72 | ...sx
73 | }}
74 | onMouseEnter={onHover}
75 | onMouseLeave={onHoverLeave}
76 | >
77 |
93 |
94 |
95 |
104 |
114 | {item.icon && }
115 |
116 |
117 | {item.name}
118 |
119 |
120 | {item.description}
121 |
122 |
123 | {item.current && (
124 |
133 | #current
134 |
135 | )}
136 | {item.category && (
137 |
146 | #{item.category.toLowerCase()}
147 |
148 | )}
149 |
150 |
151 |
152 | )
153 | }
154 |
155 | export function ExternalCard({
156 | item,
157 | onMobile,
158 | onHover,
159 | onHoverLeave,
160 | sx = {}
161 | }) {
162 | return (
163 |
211 |
227 |
228 |
229 |
239 |
249 | {item.icon && }
250 |
251 |
252 | {item.name}
253 |
254 |
255 | {item.description}
256 |
257 |
258 | {item.current && (
259 |
268 | #current
269 |
270 | )}
271 | {item.category && (
272 |
281 | #{item.category.toLowerCase()}
282 |
283 | )}
284 |
285 |
286 |
287 | )
288 | }
289 |
290 | export default function InfoCard(props) {
291 | if (props.item.external) return
292 | else return
293 | }
294 |
--------------------------------------------------------------------------------
/components/FancyCard.js:
--------------------------------------------------------------------------------
1 | import { Box, Card, Image, Link, Text } from 'theme-ui'
2 | import Icon from '@hackclub/icons'
3 | import { useState } from 'react'
4 |
5 | export default function FancyCard({
6 | item,
7 | sx = {},
8 | onMobile,
9 | onHover,
10 | onHoverLeave
11 | }) {
12 | const {
13 | background,
14 | titleColor,
15 | descriptionColor,
16 | arrowColor,
17 | name,
18 | description,
19 | img,
20 | url
21 | } = item
22 | return (
23 |
41 |
55 | onHover()}
58 | onMouseLeave={() => onHoverLeave()}
59 | sx={{
60 | mr: 3,
61 | background: background.includes('linear-gradient')
62 | ? ''
63 | : background,
64 | position: 'relative',
65 | p: '0 !important',
66 | width: '100%',
67 | '@media screen and (max-width: 992px)': {
68 | height: '100%'
69 | },
70 | height: onMobile ? '100%' : 'initial',
71 | ...(!onMobile && {
72 | '@media screen and (min-width: 992px)': {
73 | maxHeight: '175px',
74 | position: 'relative',
75 | '::after': {
76 | content: '""',
77 | position: 'absolute',
78 | zIndex: 1,
79 | bottom: 0,
80 | left: 0,
81 | pointerEvents: 'none',
82 | backgroundColor: background.includes('linear-gradient')
83 | ? 'black'
84 | : background,
85 | width: '100%',
86 | height: '25px'
87 | },
88 | ':hover': {
89 | overflow: 'visible',
90 | zIndex: 1000,
91 | '::after': {
92 | display: 'none'
93 | },
94 | '.more': {
95 | display: 'none'
96 | }
97 | }
98 | }
99 | }),
100 | overflow: "hidden",
101 | }}
102 | >
103 |
112 |
118 |
119 |
135 |
136 |
137 |
150 |
158 |
159 | {name}
160 |
161 |
162 | {description}
163 |
164 |
165 |
166 |
167 |
168 | )
169 | }
170 |
--------------------------------------------------------------------------------
/components/Map.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled'
2 | import { useState, useEffect } from 'react'
3 | import { Box, Card, Embed } from 'theme-ui'
4 | import {
5 | MapContainer,
6 | TileLayer,
7 | Marker,
8 | Popup,
9 | Tooltip,
10 | useMapEvents
11 | } from 'react-leaflet'
12 | import 'leaflet/dist/leaflet.css'
13 | import L from 'leaflet'
14 |
15 | const mapIcon = new L.Icon({
16 | iconUrl: 'https://assets.hackclub.com/icon-rounded.png',
17 | iconRetinaUrl: 'https://assets.hackclub.com/icon-rounded.png',
18 | iconAnchor: null,
19 | popupAnchor: null,
20 | shadowUrl: null,
21 | shadowSize: null,
22 | shadowAnchor: null,
23 | iconSize: new L.Point(20, 20),
24 | popupAnchor: [0, 0]
25 | })
26 |
27 | const StyledMapContainer = styled(MapContainer)`
28 | position: absolute;
29 | top: 0;
30 | left: 0;
31 | `
32 |
33 | function MapEvents({ showFrame }) {
34 | const map = useMapEvents({
35 | dblclick() {
36 | window.open('https://hackclub.github.io/map/', '_blank')
37 | }
38 | })
39 | }
40 |
41 | export default function Map() {
42 | const [clubs, setClubs] = useState([])
43 | const [center, setCenter] = useState(
44 | window.innerWidth > 767.98 ? [35.683, -25.099] : [55, -100]
45 | )
46 | const [frame, setFrame] = useState(false) // Originally used to overlay a map over entire page. Not in use right now
47 |
48 | useEffect(() => {
49 | fetch(
50 | 'https://api2.hackclub.com/v0.1/Club%20Applications/Clubs%20Dashboard'
51 | )
52 | .then(res => res.json())
53 | .then(json => {
54 | setClubs(
55 | json
56 | .filter(
57 | club =>
58 | club.fields.Status &&
59 | club.fields.Status != 'inactive' &&
60 | club.fields.Status != 'unknown' &&
61 | club.fields.Latitude &&
62 | club.fields.Longitude
63 | )
64 | .map(club => ({
65 | latitude: club.fields.Latitude,
66 | longitude: club.fields.Longitude,
67 | venue: club.fields.Venue
68 | }))
69 | )
70 | })
71 | .catch(err => console.log(err))
72 | }, [])
73 |
74 | return (
75 | <>
76 | {/*
77 |
85 |
89 | {clubs.map((club, idx) => (
90 |
95 | {club.venue}
96 |
97 | ))}
98 | setFrame(true)} />
99 |
100 | */}
101 |
112 | {frame === true && (
113 | setFrame(false)}
115 | sx={{
116 | background: 'rgba(0, 0, 0, 0.1)',
117 | width: '100%',
118 | height: '100vh',
119 | display: 'flex',
120 | alignItems: 'center',
121 | justifyContent: 'center',
122 | position: 'fixed',
123 | zIndex: 9999,
124 | top: 0,
125 | left: 0
126 | }}
127 | >
128 |
134 | event.stopPropagation()}
142 | >
143 |
144 |
145 |
146 | )}
147 | >
148 | )
149 | }
150 |
--------------------------------------------------------------------------------
/components/Plausible.js:
--------------------------------------------------------------------------------
1 | import Script from 'next/script'
2 |
3 | export default function Plausible() {
4 | return (
5 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/components/mdx/Alert.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Alert({ children }) {
4 | return (
5 |
6 |
16 | {children}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/components/mdx/Code.js:
--------------------------------------------------------------------------------
1 | import { useSession, signIn } from 'next-auth/react'
2 | import { useState } from 'react'
3 | import { useThemeUI, Text } from 'theme-ui'
4 | import useSWR from 'swr'
5 |
6 | export default function Code(props) {
7 | const { data: session, status } = useSession()
8 | const { theme } = useThemeUI()
9 | let [open, setOpen] = useState(false)
10 | const fetcher = (...args) => fetch(...args).then(res => res.json())
11 | const { data, error, isLoading } = useSWR(`/api/${props.children}`, fetcher, {
12 | refreshInterval: 2000
13 | })
14 | if (status === 'authenticated' && session.user && !isLoading) {
15 | return {data.key}
16 | } else if (isLoading)
17 | return Loading...
18 |
19 | return (
20 | <>
21 | signIn('slack')}
25 | >
26 | 👁 Reveal Code 👁
27 |
28 |
35 | >
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/components/mdx/MDXContent.js:
--------------------------------------------------------------------------------
1 | import Code from './Code'
2 | import Alert from './Alert'
3 | import styled from '@emotion/styled'
4 | import { MDXRemote } from 'next-mdx-remote'
5 | import { useRouter } from 'next/router'
6 | import theme from '@hackclub/theme'
7 | import { Box, Link, BaseStyles } from 'theme-ui'
8 | import { buildRoute } from '../../lib/utils'
9 |
10 | const components = {
11 | Code,
12 | Alert,
13 | a: children =>
14 | }
15 |
16 | export const Styled = styled(BaseStyles)`
17 | font-size: 1.25rem;
18 | font-family: inherit;
19 |
20 | a {
21 | word-break: break-word;
22 | }
23 |
24 | .heading a {
25 | color: inherit;
26 | text-decoration: none;
27 | }
28 |
29 | @media print {
30 | font-size: 1rem;
31 | color: black;
32 |
33 | pre,
34 | code,
35 | pre code span {
36 | background-color: ${theme.colors.snow};
37 | color: black;
38 | font-size: 1rem !important;
39 | }
40 |
41 | a {
42 | color: ${theme.colors.blue};
43 | }
44 | a::after {
45 | content: ' (' attr(href) ') ';
46 | }
47 | }
48 |
49 | .details-video summary {
50 | list-style: none;
51 | }
52 |
53 | .details-video summary::-webkit-details-marker {
54 | // I hate safari
55 | display: none !important;
56 | }
57 |
58 | .details-video-summary {
59 | cursor: pointer;
60 | display: flex;
61 | gap: 6px;
62 | align-items: center;
63 | font-weight: bold;
64 | padding: 5px 0;
65 | }
66 |
67 | .details-video-caret {
68 | width: 0;
69 | height: 0;
70 | border-top: 6px solid transparent;
71 | border-bottom: 6px solid transparent;
72 | border-left: 8px solid currentColor; // Create a right-facing triangle
73 | }
74 |
75 | details[open] .details-video-caret {
76 | transform: rotate(90deg);
77 | }
78 |
79 | .video-summary-camera-icon {
80 | fill: currentColor;
81 | flex: 0 0 auto;
82 | }
83 |
84 | .details-video video {
85 | max-width: 100%;
86 | }
87 | `
88 |
89 | export default function MDXContent({ source, categories, query }) {
90 | const router = useRouter()
91 |
92 | return (
93 |
94 |
97 | router.push(
98 | buildRoute({ route: '/', categories, query }),
99 | undefined,
100 | {
101 | scroll: false
102 | }
103 | )
104 | }
105 | sx={{ cursor: 'pointer', fontSize: 2 }}
106 | >
107 | ← Back
108 |
109 |
110 |
111 |
112 |
113 | )
114 | }
115 |
--------------------------------------------------------------------------------
/content/brilliant.mdx:
--------------------------------------------------------------------------------
1 | # Brilliant Premium for Hack Clubbers
2 |
3 |
4 | 💬 To redeem the promotion you'll need to sign in with the{' '}
5 | Hack Club Slack!
6 |
7 |
8 | Brilliant is a online platform for learning STEM topics using interactive learning. Brilliant Premium unlocks all courses on Brilliant.
9 |
10 | Brilliant is offering all Hack Clubbers free Brilliant Premium. Fill out this form recYV0odLlxdaBpgV
to get Brilliant Premium!
11 |
12 | Note: It may look like you don't have premium, but you'll still be able to access all the courses.
13 |
--------------------------------------------------------------------------------
/content/codeday.mdx:
--------------------------------------------------------------------------------
1 | # CodeDay for Hack Clubbers
2 |
3 |
4 | 💬 To redeem the promotion you'll need to sign in with the{' '}
5 | Hack Club Slack!
6 |
7 |
8 | CodeDay is a worldwide event where student programmers, artists, musicians, actors, and everyone else can get together and build apps & games for 24 hours. Students of all skill levels are welcome — there will be workshops and mentors who can help if you're new!
9 |
10 | This season, CodeDay is offering a 20% discount for Hack Clubbers to attend in-person CodeDay events. Use the code rec1ROxdH7LNkFVoz
when you register for any CodeDay event listed on their website ([codeday.org](https://www.codeday.org)).
11 |
--------------------------------------------------------------------------------
/content/figma.mdx:
--------------------------------------------------------------------------------
1 | # Figma for Hack Clubbers
2 |
3 |
4 | 💬 To redeem the deal you'll need to sign in with the{' '}
5 | Hack Club Slack!
6 |
7 |
8 | Figma is the design software we use at Hack Club HQ for designing branding, icons, website assets, social media posts, and so much more. It’s real-time collaborative, all-online, and free to get started with.
9 |
10 | ## How to redeem
11 |
12 | 1. Head to [figma.com/education/apply](https://figma.com/education/apply).
13 | 2. Select 'Student' as your role.
14 | 3. Use 'K12' as the institution type.
15 | 4. Agree to the terms.
16 | 5. When verification fails, select 'provide more information'.
17 | 6. For school name and school website, enter recRid6yzQ06BUoku
.
18 | 7. Have fun!!
19 |
--------------------------------------------------------------------------------
/content/orpheus.mdx:
--------------------------------------------------------------------------------
1 | # Orpheus
2 |
3 | Orpheus is your best friend on the Hack Club Slack, whilst they may be a dinosaur they are very helpful.
4 |
5 | ### General Commands
6 |
7 | These are commands relevant to all Slack members, whether or not they are leaders.
8 |
9 | Sometimes HQ may wish to ship you things, if you move you can update your address by running:
10 |
11 | `/address`
12 |
13 | If you believe someone has broken the CoC, you can report them by running:
14 |
15 | `/report`
16 |
17 | HQ offers a set of freebies to Hack Clubbers, you can redeem them by running:
18 |
19 | `/get`
20 |
21 | If you need a breakout channel on Slack you can run:
22 |
23 | `@orpheus breakout`
24 |
--------------------------------------------------------------------------------
/content/stickers.mdx:
--------------------------------------------------------------------------------
1 | # Stickers for Hack Clubbers
2 |
3 | This is only available to club leaders. If you’re looking to start a club, [apply here](https://apply.hackclub.com).
4 |
5 | ## How to request sticker shipments
6 |
7 | 1. Sign into the Hack Club Slack
8 | 2. Fill out this form: recowYSV2wClqHBIo
9 | 3. Make sure to include your full address.
10 |
11 | With great power comes great responsibility
12 |
13 | This is a system built on trust. Every shipment request is manually sent by someone at HQ, and every sticker shipment is personally prepared, packed, and mailed.
14 |
15 | We are relying on you to be a responsible user of it. Stickers are funded by donations to Hack Club and are not cheap - every package costs real donor money.
16 |
17 | It's OK to use this system for good faith, creative purposes. Meet someone cool and want to send them some Hack Club stickers? Great! That's awesome. Exactly what this is for.
18 |
--------------------------------------------------------------------------------
/content/zoom.mdx:
--------------------------------------------------------------------------------
1 | # Zoom Pro for Hack Clubbers
2 |
3 | We offer Zoom Pro meetings to every Hack Clubber via /z!
4 |
5 | To start a Zoom Pro meeting, simply type `/z` anywhere in the Hack Club Slack.
6 |
7 | 
8 |
9 | ## Google Calendar Integration
10 |
11 | If you want to schedule meetings ahead of time (& give people a Zoom link), we also built a Google Calendar integration. Here's how to install it:
12 |
13 | 1. Visit this Google Marketplace URL: rectIOiBkDHFNIukk
.
14 | 2. Install the app
15 | 
16 | 3. Once it's installed, create a calendar event. Select `Zoom Pro Meeting` from the dropdown (if it doesn't appear, try refreshing—it will eventually appear).
17 | 
18 | 4. You should see this "login required" thing. Click on `Log in`.
19 | 
20 | 5. A Slack OAuth window should pop up. Follow the instructions to sign in with your Slack account.
21 | 
22 | 6. Now it will work!
23 | 
24 |
25 | ---
26 |
27 | We think /z is at feature parity with having your own paid Zoom Pro account. We even cancelled our own HQ Zoom Pro accounts and have been entirely using /z since February. That said, if you find something you need that isn't in /z, or you discover any bugs, please let us know in the `#development-of-slash-z` channel on Slack, or DM `@msw`.
28 |
29 | ---
30 |
31 |
32 |
33 | For posterity, you can see the out of date instructions that we used to show on this page:
34 |
35 |
36 | **HQ is buying a Zoom Pro account for every Hack Club this semester.**
37 |
38 | _Note: We're only paying for Zoom Pro accounts through the end of this semester. We're developing `/z` and once it reaches the point where we're using it instead of our Zoom Pro accounts at HQ, we intend to switch all clubs to it._
39 |
40 | _`/z` in beta is currently available to use by anyone - both leaders and members - but for now full Zoom Pro accounts still have more features._
41 |
42 | This is only available to your club’s point of contact; the point of contact then can create meetings for the club. If you’re not sure if you’re the point of contact, running the command will let you know who that this. If the point of contact is the wrong leader, reach out to @matthew or @msw.
43 |
44 | ## How to redeem
45 |
46 | 1. Sign into the Hack Club Slack
47 | 2. Type `/get zoom pro`
48 | 3. Accept your email invite & sign up
49 | 4. Have fun!
50 |
51 |
52 |
--------------------------------------------------------------------------------
/lib/content.js:
--------------------------------------------------------------------------------
1 | import { getRawFileFromRepo } from './github'
2 | import { shuffle } from './utils'
3 | import manifest from '../manifest'
4 |
5 | export const everything = async () => {
6 | // for now, just disable fetching the carousel from the main site-- those
7 | // cards will likely just be moved to stay inside manifest.json
8 | // const everything = await getRawFileFromRepo('lib/carousel.json')
9 | return [
10 | /*
11 | ...JSON.parse(everything).map(item => ({
12 | ...item,
13 | name: item.title,
14 | url: item.link.startsWith('/')
15 | ? `https://hackclub.com${item.link}`
16 | : item.link,
17 | category: 'Current',
18 | external: item.external ? item.external : true
19 | })),
20 | */
21 | ...manifest
22 | .flatMap(category => {
23 | return category.items.map(item => ({
24 | ...item,
25 | color: category.color,
26 | category: category.category,
27 | url: item.external ? item.url : item.url.replace('.mdx', '')
28 | }))
29 | })
30 | .sort((a, b) => (a.current === true ? -1 : 1))
31 | ]
32 | }
33 |
34 | export const categories = async () => {
35 | return [
36 | ...manifest.map(category => ({
37 | category: category.category,
38 | color: category.color,
39 | icon: category.icon
40 | }))
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/lib/github.js:
--------------------------------------------------------------------------------
1 | export const GITHUB_URL = "https://github.com";
2 | export const RAW_GITHUB_URL = "https://raw.githubusercontent.com";
3 | export const REPO_NAME = "hackclub/site";
4 | export const REPO_BRANCH = "main";
5 |
6 | const getErrorText = (res) => {
7 | try {
8 | return res.text();
9 | } catch (res) {
10 | return res.statusText;
11 | }
12 | };
13 |
14 | const getError = async (res) => {
15 | const errorText = await getErrorText(res);
16 | const error = new Error(
17 | `GitHub raw download error (${res.status}): ${errorText}`
18 | );
19 | error.headers = res.headers.raw();
20 | return error;
21 | };
22 |
23 | export const getRawFileFromGitHub = async (path) => {
24 | const res = await fetch(RAW_GITHUB_URL + path);
25 | let text = res.text();
26 | if (res.ok) return text;
27 | console.error(RAW_GITHUB_URL + path);
28 | throw await getError(res);
29 | };
30 |
31 | export const getRawFileFromRepo = (path, branch, repo) =>
32 | getRawFileFromGitHub(
33 | `/${repo || REPO_NAME}/${branch || REPO_BRANCH}/${path}`
34 | );
35 |
36 | export const getEditUrl = (path) =>
37 | `${GITHUB_URL}/${REPO_NAME}/edit/${REPO_BRANCH}/${path}`;
38 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | export const random = (min, max) =>
2 | Math.floor(Math.random() * (max - min + 1) + min)
3 |
4 | export const shuffle = arr => {
5 | let res = []
6 | while (arr.length > 0) {
7 | const pos = random(0, arr.length - 1)
8 | res.push(arr[pos])
9 | arr.splice(pos, 1)
10 | }
11 | return res
12 | }
13 |
14 | export const buildRoute = ({ route = '/', query = '', categories }) => {
15 | return `${route}?category=${categories.join(',')}&query=${encodeURI(query)}`
16 | }
17 |
--------------------------------------------------------------------------------
/manifest.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | category: "YSWS",
4 | color: "green",
5 | icon: "bolt",
6 | items: [
7 | {
8 | name: "Boba Drops",
9 | description: "Build an HTML + CSS static site, get free boba",
10 | img: "/cards/boba.png",
11 | background: "#C76B0F",
12 | titleColor: "#F6D193",
13 | descriptionColor: "#F6D193",
14 | external: true,
15 | url: "https://hack.club/boba",
16 | fancy: true,
17 | },
18 | {
19 | name: "Swirl",
20 | description: "Build a cool website, get free ice cream",
21 | img: "/cards/swirl.svg",
22 | background: "#fde09d",
23 | titleColor: "#b875ff",
24 | descriptionColor: "#5d5031",
25 | external: true,
26 | url: "https://swirl.hackclub.com/",
27 | fancy: true,
28 | },
29 | {
30 | name: "Toppings",
31 | description: "Add some flavor to your site with CSS and earn toppings for your Ice Cream & Boba!",
32 | img: "/cards/toppings.png",
33 | background: "#ffcc00",
34 | titleColor: "#000",
35 | descriptionColor: "#000",
36 | external: true,
37 | url: "https://toppings.hackclub.com",
38 | fancy: true,
39 | },
40 | {
41 | name: "Hackaccino",
42 | description: "Make a 3D website, get a free Frappuccino",
43 | img: "/cards/hackaccino.png",
44 | background: "#FFF1DE",
45 | titleColor: "#4F2A0E",
46 | descriptionColor: "#4F2A0E",
47 | arrowColor: "#ba9d75",
48 | external: true,
49 | url: "https://hack.club/fraps",
50 | fancy: true,
51 | },
52 | {
53 | name: "Shipwrecked",
54 | description: "On August 8-11, you and 130 other students will gather on Cathleen Stone Island in the Boston Harbor for a once in a lifetime, 4-day story-based hackathon!",
55 | img: "/cards/shipwrecked.png",
56 | background: "#45cff6",
57 | titleColor: "black",
58 | descriptionColor: "white",
59 | external: true,
60 | url: "https://shipwrecked.hackclub.com",
61 | fancy: true,
62 | },
63 | {
64 | name: "Sprig",
65 | description: "Join hundreds of teenagers making tile-based JavaScript games",
66 | img: "/cards/sprig.png",
67 | background: "#000",
68 | titleColor: "green",
69 | descriptionColor: "white",
70 | external: true,
71 | url: "https://sprig.hackclub.com",
72 | fancy: true,
73 | },
74 | {
75 | name: "Cider",
76 | description: "Design and code an iOS app and get it shipped to the App Store for free",
77 | img: "/cards/cider.png",
78 | background: "#b93c3c",
79 | titleColor: "#fffffb",
80 | descriptionColor: "#ffffff",
81 | external: true,
82 | url: "https://cider.hackclub.com/",
83 | fancy: true,
84 | },
85 | {
86 | name: "BakeBuild",
87 | description: "Design a cookie cutter and get it shipped to you",
88 | img: "/cards/bakebuild.png",
89 | background: "#ffffff",
90 | titleColor: "#83583c",
91 | descriptionColor: "#b19065",
92 | arrowColor: "#9a9a9a",
93 | external: true,
94 | url: "https://bakebuild.hackclub.com/",
95 | fancy: true,
96 | },
97 | // Limited-time YSWSs start here
98 | {
99 | name: "Tonic",
100 | description: "Make a Jekyll theme, show it to the world, and get a Hack Club hat",
101 | img: "/cards/tonic.png",
102 | background: "#faebd7",
103 | titleColor: "#706044",
104 | descriptionColor: "#706044",
105 | external: true,
106 | url: "https://tonic.hackclub.com/",
107 | fancy: true,
108 | },
109 | {
110 | name: "Solder",
111 | description: "Make your first circuit board, get a kit of electronics to assemble it for free (Ends May 30th)",
112 | img: "/cards/solder.png",
113 | background: "#faf2fb",
114 | titleColor: "#ff60a3",
115 | descriptionColor: "#000000",
116 | external: true,
117 | url: "https://solder.hackclub.com/",
118 | fancy: true,
119 | },
120 | {
121 | name: "Hacklet",
122 | description: "Spend two hours building a javascript bookmarklet, get $10 to buy a domain (Ends May 10th)",
123 | img: "/cards/hacklet.png",
124 | background: "#000000",
125 | titleColor: "#ffffff",
126 | descriptionColor: "#ffffff",
127 | external: true,
128 | url: "https://hacklet.hackclub.com/",
129 | fancy: true,
130 | },
131 | {
132 | name: "Hackcraft",
133 | description: "Make a Minecraft mod, get Minecraft (Ends June 1st)",
134 | img: "/cards/hackcraft.png",
135 | background: "#30AE1F",
136 | titleColor: "#FFFFFF",
137 | descriptionColor: "#FFFFFF",
138 | external: true,
139 | url: "https://hackcraft.hackclub.com/",
140 | fancy: true,
141 | },
142 | {
143 | name: "Sidequests",
144 | description: "A short, low-volume series of miscellaneous YSWSs",
145 | img: "/cards/sidequests.png",
146 | background: "#000000",
147 | titleColor: "#ffffff",
148 | descriptionColor: "#ffffff",
149 | external: true,
150 | url: "https://sidequests.hackclub.com/",
151 | fancy: true,
152 | },
153 | ],
154 | },
155 | {
156 | category: "Activities",
157 | color: "orange",
158 | icon: "idea",
159 | items: [
160 | {
161 | name: "Jams",
162 | description: "Collaborative coding workshops where sparks ignite, fears dissolve, and inventions come to life",
163 | img: "/cards/jams.png",
164 | background: "#1f2d3d",
165 | titleColor: "#ffffff",
166 | descriptionColor: "#ffffff",
167 | external: true,
168 | url: "https://jams.hackclub.com/",
169 | fancy: true,
170 | },
171 | {
172 | name: "Some Assembly Required",
173 | description: "An approachable introduction to assembly",
174 | img: "/cards/someassemblyrequired.png",
175 | background: "#ffffff",
176 | titleColor: "#17171d",
177 | descriptionColor: "#17171d",
178 | arrowColor: "#9a9a9a",
179 | external: true,
180 | url: "https://github.com/hackclub/some-assembly-required",
181 | fancy: true,
182 | },
183 | {
184 | name: "Putting The \"You\" In CPU",
185 | description: "Curious exactly what happens when you run a program on your computer? Read this.",
186 | img: "/cards/puttingtheyouincpu.png",
187 | background: "#ffffff",
188 | titleColor: "#17171d",
189 | descriptionColor: "#17171d",
190 | arrowColor: "#9a9a9a",
191 | external: true,
192 | url: "https://cpu.land/",
193 | fancy: true,
194 | },
195 | {
196 | name: "SineRider",
197 | description: "Help build a game about love, math, and graphing 💖",
198 | img: "/cards/sinerider.png",
199 | background: "#271932",
200 | titleColor: "#CAB4D4",
201 | descriptionColor: "#ffffff",
202 | external: true,
203 | url: "https://sinerider.com",
204 | fancy: true,
205 | },
206 | {
207 | name: "Workshops",
208 | description:
209 | "Learn programming and create fun projects with short, in-depth workshops",
210 | icon: "idea",
211 | external: true,
212 | url: "https://workshops.hackclub.com",
213 | forUseBy: "everyone",
214 | },
215 | {
216 | name: "Muse",
217 | description: "Music programming language",
218 | icon: "list",
219 | external: true,
220 | url: "https://muse.hackclub.com",
221 | forUseBy: "everyone",
222 | },
223 | ],
224 | },
225 | {
226 | category: "Resources",
227 | color: "blue",
228 | icon: "explore",
229 | items: [
230 | {
231 | name: "Nest",
232 | description: "Free Linux server for all Hack Clubbers to host anything they need",
233 | img: "/cards/nest.png",
234 | background: "#e9e9e9",
235 | titleColor: "#1f2d3d",
236 | descriptionColor: "#1f2d3d",
237 | arrowColor: "#7f7f7f",
238 | external: true,
239 | url: "https://hackclub.app/",
240 | fancy: true,
241 | },
242 | {
243 | name: "Spaces",
244 | description:
245 | "Create, share, and deploy websites or other apps instantly with GIT integration for clubs.",
246 | img: "/cards/spaces.png",
247 | background: "#dfcdd1",
248 | titleColor: "#1f2d3d",
249 | descriptionColor: "#1f2d3d",
250 | arrowColor: "#7f7f7f",
251 | external: true,
252 | url: "https://spaces.hackclub.com",
253 | fancy: true
254 | },
255 | {
256 | name: "Pizza",
257 | description: "Earn $5 per shipped project from your club to buy pizza!",
258 | icon: "briefcase",
259 | external: true,
260 | url: "https://airtable.com/appSUAc40CDu6bDAp/pagvu2xGhfsMC8AOL/form",
261 | forUseBy: "leaders",
262 | },
263 | {
264 | name: "Posters",
265 | description: "Get large Hack Club posters to promote your Hack Club",
266 | icon: "docs-fill",
267 | external: true,
268 | url: "https://forms.hackclub.com/t/uzgyhTqvsFus",
269 | forUseBy: "leaders",
270 | },
271 | {
272 | name: "International Posters",
273 | description: "If you're outside of US/Canada, get large Hack Club posters to promote your Hack Club",
274 | icon: "docs-fill",
275 | external: true,
276 | url: "https://hack.club/intl-posters",
277 | forUseBy: "leaders",
278 | },
279 | {
280 | name: "Branding & Logos",
281 | description: "Make anything Hack Club branded",
282 | icon: "like",
283 | external: true,
284 | url: "https://hackclub.com/brand",
285 | forUseBy: "everyone",
286 | },
287 | {
288 | name: "Stickers",
289 | description: "Get a box of stickers for your next meeting or event",
290 | icon: "sticker",
291 | external: false,
292 | url: "/stickers.mdx",
293 | forUseBy: "leaders",
294 | },
295 | {
296 | name: "School Toolbox",
297 | description: "Resources to help with school admin or IT challenges",
298 | icon: "briefcase",
299 | external: true,
300 | url: "https://school-toolbox.hackclub.com",
301 | forUseBy: "everyone",
302 | },
303 | {
304 | name: "Slack Community",
305 | description:
306 | "Be part of a fun, technically-diverse and supportive community on Slack",
307 | icon: "slack",
308 | external: true,
309 | url: "https://hackclub.com/slack",
310 | forUseBy: "everyone",
311 | },
312 | {
313 | name: "Leadership Guide",
314 | description: "Advice on how to lead a club",
315 | icon: "docs-fill",
316 | external: true,
317 | url: "https://archived.guide.hackclub.com/#/",
318 | forUseBy: "leaders",
319 | },
320 | {
321 | name: "Leader Newsletter",
322 | description: "The bi-weekly run-down for Hack Club leaders",
323 | icon: "docs",
324 | external: true,
325 | url: "https://workshops.hackclub.com/leader-newsletters",
326 | forUseBy: "leaders",
327 | },
328 | {
329 | name: "DNS",
330 | description: "Redeem a free hackclub.com subdomain",
331 | icon: "web",
332 | external: true,
333 | url: "https://github.com/hackclub/dns",
334 | forUseBy: "clubbers",
335 | },
336 | {
337 | name: "Theme Starter",
338 | description:
339 | "A sample Next.js project for getting started with MDX, Theme UI, & Hack Club Theme",
340 | icon: "help",
341 | external: true,
342 | url: "https://github.com/hackclub/theme-starter",
343 | forUseBy: "everyone",
344 | },
345 | {
346 | name: "Theme",
347 | description: "Hack Club's theme, using Theme UI",
348 | icon: "grid",
349 | external: true,
350 | url: "https://theme.hackclub.com",
351 | forUseBy: "everyone",
352 | },
353 | {
354 | name: "Gas Fund",
355 | description: "Drive to any high school hackathon and get the cost of gas reimbursed",
356 | icon: "briefcase",
357 | external: true,
358 | url: "https://gas.hackclub.com/",
359 | forUseBy: "everyone",
360 | },
361 | {
362 | name: "Hackathons Page",
363 | description:
364 | "Everything you need to know to run an amazing event, all in one place",
365 | icon: "event-check",
366 | external: true,
367 | url: "https://hackclub.com/hackathons/",
368 | forUseBy: "everyone",
369 | },
370 | {
371 | name: "HCB",
372 | description: "The full stack financial toolkit to run your club or hackathon, built by Hack Club",
373 | icon: "bank-account",
374 | external: true,
375 | url: "https://hackclub.com/hcb",
376 | forUseBy: "everyone",
377 | },
378 | ],
379 | },
380 | {
381 | category: "Perks",
382 | color: "primary",
383 | icon: "bag",
384 | items: [
385 | {
386 | name: "Events",
387 | description: "Join an event with other Hack Clubbers",
388 | icon: "event-code",
389 | external: true,
390 | url: "https://events.hackclub.com",
391 | forUseBy: "clubbers",
392 | },
393 | {
394 | name: "AMAs",
395 | description:
396 | "Call someone who we’ve always wanted to talk to—and the entire Hack Club Slack community is invited to ask questions & chat with the guest live.",
397 | icon: "person",
398 | external: true,
399 | url: "https://hackclub.com/amas/",
400 | forUseBy: "everyone",
401 | },
402 | {
403 | name: "Brilliant Premium",
404 | description: "Free Brilliant Premium student access",
405 | icon: "code",
406 | external: false,
407 | url: "/brilliant.mdx",
408 | forUseBy: "clubbers",
409 | },
410 | {
411 | name: "Zoom Pro Meetings",
412 | description: "Run Zoom Pro meetings for free in your Hack Club",
413 | icon: "welcome",
414 | external: false,
415 | url: "/zoom.mdx",
416 | forUseBy: "clubbers",
417 | },
418 | {
419 | name: "Figma",
420 | description: "Free access to Figma's team plan",
421 | icon: "photo",
422 | external: false,
423 | url: "/figma.mdx",
424 | forUseBy: "clubbers",
425 | },
426 | {
427 | name: "CodeDay",
428 | description: "Discount for in-person CodeDay events",
429 | icon: "event-code",
430 | external: false,
431 | url: "/codeday.mdx",
432 | forUseBy: "clubbers",
433 | },
434 | {
435 | name: "Code Crafters",
436 | description: "Code crafters free 2 years free membership",
437 | icon: "code",
438 | external: true,
439 | url: "https://codecrafters.io/event/hackclub",
440 | forUseBy: "clubbers",
441 | },
442 | {
443 | name: "Hackathons",
444 | description: "Find hackathons to attend",
445 | icon: "crosshairs",
446 | external: true,
447 | url: "https://hackathons.hackclub.com",
448 | forUseBy: "everyone",
449 | },
450 | ],
451 | },
452 | ];
453 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | module.exports = {
3 | experiments: {
4 | topLevelAwait: true
5 | },
6 | async rewrites() {
7 | return [
8 | {
9 | source: '/',
10 | destination: '/home'
11 | }
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "build": "next build",
4 | "dev": "next dev",
5 | "format": "prettier --ignore-path ./.prettierignore --write ."
6 | },
7 | "dependencies": {
8 | "@emotion/react": "^11.10.5",
9 | "@emotion/styled": "^11.10.5",
10 | "@hackclub/icons": "^0.0.12",
11 | "@hackclub/meta": "^1.1.32",
12 | "@hackclub/theme": "^0.3.3",
13 | "@next/font": "^13.1.6",
14 | "@theme-ui/mdx": "^0.15.5",
15 | "airtable-plus": "^1.0.4",
16 | "fuzzaldrin": "^2.1.0",
17 | "leaflet": "^1.9.3",
18 | "next": "^13.1.6",
19 | "next-auth": "^4.19.2",
20 | "next-mdx-remote": "^4.3.0",
21 | "nprogress": "^0.2.0",
22 | "prop-type": "^0.0.1",
23 | "prop-types": "^15.8.1",
24 | "react": "^18.2.0",
25 | "react-dom": "^18.2.0",
26 | "react-leaflet": "^4.2.0",
27 | "swr": "^2.0.3",
28 | "theme-ui": "^0.15.5"
29 | },
30 | "devDependencies": {
31 | "prettier": "^2.8.4"
32 | },
33 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
34 | }
35 |
--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { useRouter } from 'next/router'
3 |
4 | export default function Custom404() {
5 | const router = useRouter()
6 | useEffect(() => {
7 | router.replace('/')
8 | })
9 | return null
10 | }
11 |
--------------------------------------------------------------------------------
/pages/[slug].js:
--------------------------------------------------------------------------------
1 | import '@hackclub/theme/fonts/reg-bold.css'
2 | import { useState, useEffect, useRef } from 'react'
3 | import { serialize } from 'next-mdx-remote/serialize'
4 | import {
5 | Box,
6 | Container,
7 | Grid,
8 | Flex,
9 | Heading,
10 | Button,
11 | Card,
12 | Input,
13 | Badge
14 | } from 'theme-ui'
15 | import Icon from '@hackclub/icons'
16 | import dynamic from 'next/dynamic'
17 | import { useRouter } from 'next/router'
18 | import InfoCard from '../components/Card'
19 | import FancyCard from '../components/FancyCard'
20 | import { filter } from 'fuzzaldrin'
21 | import { everything, categories } from '../lib/content'
22 | import MDXContent from '../components/mdx/MDXContent'
23 | import { buildRoute } from '../lib/utils'
24 |
25 | let Map
26 | if (!Map) {
27 | try {
28 | Map = dynamic(() => import('../components/Map'), {
29 | ssr: false
30 | })
31 | } catch (err) {}
32 | }
33 |
34 | const isMobile = () => {
35 | let check = false
36 | ;(function (a) {
37 | if (
38 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(
39 | a
40 | ) ||
41 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
42 | a.substr(0, 4)
43 | )
44 | )
45 | check = true
46 | })(navigator.userAgent || navigator.vendor || window.opera)
47 | return check
48 | }
49 |
50 | export default function Index({ everything = [], menu, source }) {
51 | const router = useRouter()
52 |
53 | if (!Map) {
54 | try {
55 | Map = dynamic(() => import('../components/Map'), {
56 | ssr: false
57 | })
58 | } catch (err) {}
59 | }
60 |
61 | const [onMobile, setMobile] = useState(null)
62 | useEffect(() => {
63 | if (onMobile === null) setMobile(isMobile())
64 | }, [onMobile])
65 |
66 | const [hover, setHover] = useState('')
67 |
68 | const [items, setItems] = useState([])
69 | const updateItems = (categories, query) => {
70 | let newItems = categories.length
71 | ? everything.filter(item => categories.includes(item.category))
72 | : everything
73 | if (query)
74 | newItems = Array.from(
75 | new Set([
76 | ...filter(newItems, query, { key: 'category' }),
77 | ...filter(newItems, query, { key: 'name' }),
78 | ...filter(newItems, query, { key: 'description' }),
79 | ...filter(newItems, query, { key: 'link' })
80 | ])
81 | )
82 | setItems(newItems)
83 | }
84 |
85 | const color = category => {
86 | if (
87 | hover === category ||
88 | (categories.includes(category) && router.query.category)
89 | )
90 | return 'primary'
91 | return 'secondary'
92 | }
93 |
94 | const [categories, setCategories] = useState([])
95 | const selectCategory = category => {
96 | // this code is still written for multiple categories to be selected at once, but only one is selectable at a time
97 | setCategories([category])
98 | console.log("selecting", category, categories)
99 | router.replace(
100 | buildRoute({ categories: [category], query }),
101 | undefined,
102 | {
103 | ...(router.query.slug === 'home' && { shallow: true }),
104 | scroll: false
105 | }
106 | )
107 | }
108 | const removeCategory = category => {
109 | setCategories(categories.filter(x => x !== category))
110 | router.replace(
111 | buildRoute({ categories: categories.filter(x => x !== category), query }),
112 | undefined,
113 | {
114 | ...(router.query.slug === 'home' && { shallow: true }),
115 | scroll: false
116 | }
117 | )
118 | }
119 |
120 | const [query, setQuery] = useState('')
121 | const updateQuery = newQuery => {
122 | setQuery(newQuery)
123 | router.replace(buildRoute({ categories, query: newQuery }), undefined, {
124 | shallow: true,
125 | scroll: false
126 | })
127 | }
128 |
129 | useEffect(() => {
130 | const queryCategories = router.query.category
131 | ? router.query.category.split(',')
132 | : []
133 | const queryQuery = router.query.query ? router.query.query : ''
134 | setCategories(queryCategories)
135 | setQuery(queryQuery)
136 | setHover('')
137 | updateItems(queryCategories, queryQuery)
138 | }, [router.query])
139 |
140 | return (
141 |
142 |
143 |
155 |
162 |
173 | Hack Club Toolbox
174 |
175 |
185 | Tools for hacking, learning, and leading.
186 |
187 |
188 |
189 |
205 |
206 |
223 |
224 |
225 |
226 |
227 |
228 |
237 |
248 |
251 | router.push(buildRoute({ categories: [], query }), undefined, {
252 | scroll: false
253 | })
254 | }
255 | sx={{
256 | cursor: 'pointer',
257 | fontSize: 1,
258 | bg: categories.length ? 'secondary' : 'primary',
259 | px: 2,
260 | py: 1,
261 | borderRadius: 'default',
262 | transition: 'color 0.2s'
263 | }}
264 | >
265 | All
266 |
267 | {menu.map((category, idx) => (
268 |
271 | categories.includes(category.category)
272 | ? removeCategory(category.category)
273 | : selectCategory(category.category)
274 | }
275 | key={idx}
276 | sx={{
277 | display: 'flex',
278 | justifyContent: 'space-between',
279 | gap: 2,
280 | cursor: 'pointer',
281 | fontSize: 1,
282 | bg: color(category.category),
283 | px: 2,
284 | py: 1,
285 | borderRadius: 'default',
286 | transition: 'color 0.2s'
287 | }}
288 | >
289 | {category.category}
290 | {categories.includes(category.category) && ×}
291 |
292 | ))}
293 |
294 |
308 |
316 | Categories
317 |
318 |
321 | router.push(buildRoute({ categories: [], query }), undefined, {
322 | scroll: false
323 | })
324 | }
325 | sx={{
326 | alignItems: 'center',
327 | cursor: 'pointer',
328 | gap: 2,
329 | mt: 3,
330 | color: categories.length ? 'secondary' : 'primary',
331 | textDecoration: 'none',
332 | transition: 'color 0.2s',
333 | ':hover': {
334 | color: 'primary'
335 | },
336 | width: 'fit-content'
337 | }}
338 | >
339 |
347 |
348 |
349 |
350 | All
351 |
352 |
353 | {menu.map((category, idx) => (
354 |
357 | categories.includes(category.category)
358 | ? removeCategory(category.category)
359 | : selectCategory(category.category)
360 | }
361 | key={idx}
362 | sx={{
363 | alignItems: 'center',
364 | cursor: 'pointer',
365 | gap: 2,
366 | mt: 3,
367 | color: color(category.category),
368 | textDecoration: 'none',
369 | transition: 'color 0.2s',
370 | ':hover': {
371 | color: 'primary'
372 | },
373 | width: 'fit-content'
374 | }}
375 | >
376 |
384 |
385 |
386 |
387 | {category.category}
388 |
389 |
390 | ))}
391 |
408 |
416 |
417 |
418 |
419 | Suggest
420 |
421 |
422 |
423 |
424 | {everything.length === 0 ? (
425 |
430 | ) : (
431 | <>
432 | updateQuery(event.target.value)}
435 | sx={{
436 | border: '1px dashed',
437 | mb: 3,
438 | textAlign: ['left', 'left', 'left'],
439 | width: '100%',
440 | bg: 'sheet',
441 | fontSize: 2,
442 | px: [null, 3]
443 | }}
444 | value={query}
445 | />
446 | {items.length > 0 ? (
447 |
448 | {items.map((item, idx) => {
449 | if (item.fancy)
450 | return (
451 | setHover(item.category)}
454 | onHoverLeave={() => setHover('')}
455 | key={idx}
456 | item={item}
457 | sx={{
458 | height: '100%',
459 | '@media screen and (min-width: 64em)': {
460 | mb:
461 | items[items.length - 1] === item
462 | ? 6
463 | : 'default'
464 | }
465 | }}
466 | />
467 | )
468 | return (
469 | setHover(item.category)}
472 | onHoverLeave={() => setHover('')}
473 | key={idx}
474 | item={item}
475 | categories={categories}
476 | query={query}
477 | sx={{
478 | height: '100%',
479 | '@media screen and (min-width: 64em)': {
480 | mb:
481 | items[items.length - 1] === item ? 6 : 'default'
482 | }
483 | }}
484 | />
485 | )
486 | })}
487 |
488 | ) : (
489 |
490 | Couldn't find what you were looking for. Try making a
491 | suggestion!
492 |
493 | )}
494 | >
495 | )}
496 |
497 |
498 |
499 |
500 | )
501 | }
502 |
503 | export const getStaticPaths = async () => {
504 | const paths = (await everything())
505 | .filter(item => !item.external)
506 | .map(item => ({
507 | params: {
508 | slug: item.url.replace('/', '').replace('.mdx', '')
509 | }
510 | }))
511 | return {
512 | paths: [...paths, { params: { slug: 'home' } }],
513 | fallback: false
514 | }
515 | }
516 |
517 | export const getStaticProps = async ({ params }) => {
518 | const { slug } = params
519 | if (slug && slug !== 'home') {
520 | const fs = require('fs')
521 | const path = require('path')
522 | const source = fs.readFileSync(
523 | path.join(process.cwd(), 'content', `${slug}.mdx`)
524 | )
525 | return {
526 | props: {
527 | source: await serialize(source),
528 | menu: (await categories()).filter(cat => cat.category !== "Highlighted") // don't show highlighted on the sidebar
529 | }
530 | }
531 | }
532 | return {
533 | props: {
534 | everything: await everything(),
535 | menu: (await categories()).filter(cat => cat.category !== "Highlighted") // don't show highlighted on the sidebar
536 | },
537 | revalidate: 60 * 60 * 24
538 | }
539 | }
540 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import theme from '@hackclub/theme'
2 | import Meta from '@hackclub/meta'
3 | import Head from 'next/head'
4 | import { ThemeProvider } from 'theme-ui'
5 | import { SessionProvider } from 'next-auth/react'
6 | import Plausible from '../components/Plausible'
7 | import '../public/scrollbar.css'
8 |
9 | export default function App({
10 | Component,
11 | pageProps: { session, ...pageProps }
12 | }) {
13 | return (
14 |
32 |
33 |
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/pages/api/[key].js:
--------------------------------------------------------------------------------
1 | import { getSession } from 'next-auth/react'
2 | const AirtablePlus = require('airtable-plus')
3 |
4 | export default async function handler(req, res) {
5 | const session = await getSession({ req })
6 | if (session != null) {
7 | const airtable = new AirtablePlus({
8 | baseID: 'appvp3mpqIrosrqdP',
9 | apiKey: process.env.AIRTABLE,
10 | tableName: 'Codes'
11 | })
12 | const record = await airtable.find(req.query.key)
13 | res.json({ key: record.fields.key })
14 | } else res.json({ key: '👁' })
15 | }
16 |
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].js:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth'
2 | import SlackProvider from 'next-auth/providers/slack'
3 |
4 | export default NextAuth({
5 | providers: [
6 | SlackProvider({
7 | clientId: process.env.SLACK_CLIENT_ID,
8 | clientSecret: process.env.SLACK_CLIENT_SECRET
9 | })
10 | ]
11 | })
12 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'none',
4 | arrowParens: 'avoid',
5 | printWidth: 80,
6 | semi: false
7 | }
8 |
--------------------------------------------------------------------------------
/public/adafruit-banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/adafruit-banner.jpg
--------------------------------------------------------------------------------
/public/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/banner.png
--------------------------------------------------------------------------------
/public/cards/bakebuild.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/bakebuild.png
--------------------------------------------------------------------------------
/public/cards/boba.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/boba.png
--------------------------------------------------------------------------------
/public/cards/cider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/cider.png
--------------------------------------------------------------------------------
/public/cards/hackaccino.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/hackaccino.png
--------------------------------------------------------------------------------
/public/cards/hackcraft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/hackcraft.png
--------------------------------------------------------------------------------
/public/cards/hacklet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/hacklet.png
--------------------------------------------------------------------------------
/public/cards/jams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/jams.png
--------------------------------------------------------------------------------
/public/cards/nest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/nest.png
--------------------------------------------------------------------------------
/public/cards/onboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/onboard.png
--------------------------------------------------------------------------------
/public/cards/puttingtheyouincpu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/puttingtheyouincpu.png
--------------------------------------------------------------------------------
/public/cards/raspapi.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/raspapi.webp
--------------------------------------------------------------------------------
/public/cards/shipwrecked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/shipwrecked.png
--------------------------------------------------------------------------------
/public/cards/sidequests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/sidequests.png
--------------------------------------------------------------------------------
/public/cards/sinerider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/sinerider.png
--------------------------------------------------------------------------------
/public/cards/solder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/solder.png
--------------------------------------------------------------------------------
/public/cards/someassemblyrequired.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/someassemblyrequired.png
--------------------------------------------------------------------------------
/public/cards/spaces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/spaces.png
--------------------------------------------------------------------------------
/public/cards/sprig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/sprig.png
--------------------------------------------------------------------------------
/public/cards/swirl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/cards/tonic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/tonic.png
--------------------------------------------------------------------------------
/public/cards/toppings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/toppings.png
--------------------------------------------------------------------------------
/public/cards/visioneer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/cards/visioneer.png
--------------------------------------------------------------------------------
/public/club-stats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/club-stats.png
--------------------------------------------------------------------------------
/public/codeday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/codeday.png
--------------------------------------------------------------------------------
/public/dino_handing_docs-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/dino_handing_docs-2.png
--------------------------------------------------------------------------------
/public/figma.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/figma.png
--------------------------------------------------------------------------------
/public/leaflet.css:
--------------------------------------------------------------------------------
1 | /* required styles */
2 |
3 | .leaflet-pane,
4 | .leaflet-tile,
5 | .leaflet-marker-icon,
6 | .leaflet-marker-shadow,
7 | .leaflet-tile-container,
8 | .leaflet-pane > svg,
9 | .leaflet-pane > canvas,
10 | .leaflet-zoom-box,
11 | .leaflet-image-layer,
12 | .leaflet-layer {
13 | position: absolute;
14 | left: 0;
15 | top: 0;
16 | }
17 | .leaflet-container {
18 | overflow: hidden;
19 | }
20 | .leaflet-tile,
21 | .leaflet-marker-icon,
22 | .leaflet-marker-shadow {
23 | -webkit-user-select: none;
24 | -moz-user-select: none;
25 | user-select: none;
26 | -webkit-user-drag: none;
27 | }
28 | /* Prevents IE11 from highlighting tiles in blue */
29 | .leaflet-tile::selection {
30 | background: transparent;
31 | }
32 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */
33 | .leaflet-safari .leaflet-tile {
34 | image-rendering: -webkit-optimize-contrast;
35 | }
36 | /* hack that prevents hw layers "stretching" when loading new tiles */
37 | .leaflet-safari .leaflet-tile-container {
38 | width: 1600px;
39 | height: 1600px;
40 | -webkit-transform-origin: 0 0;
41 | }
42 | .leaflet-marker-icon,
43 | .leaflet-marker-shadow {
44 | display: block;
45 | }
46 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
47 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
48 | .leaflet-container .leaflet-overlay-pane svg,
49 | .leaflet-container .leaflet-marker-pane img,
50 | .leaflet-container .leaflet-shadow-pane img,
51 | .leaflet-container .leaflet-tile-pane img,
52 | .leaflet-container img.leaflet-image-layer,
53 | .leaflet-container .leaflet-tile {
54 | max-width: none !important;
55 | max-height: none !important;
56 | }
57 |
58 | .leaflet-container.leaflet-touch-zoom {
59 | -ms-touch-action: pan-x pan-y;
60 | touch-action: pan-x pan-y;
61 | }
62 | .leaflet-container.leaflet-touch-drag {
63 | -ms-touch-action: pinch-zoom;
64 | /* Fallback for FF which doesn't support pinch-zoom */
65 | touch-action: none;
66 | touch-action: pinch-zoom;
67 | }
68 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
69 | -ms-touch-action: none;
70 | touch-action: none;
71 | }
72 | .leaflet-container {
73 | -webkit-tap-highlight-color: transparent;
74 | }
75 | .leaflet-container a {
76 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
77 | }
78 | .leaflet-tile {
79 | filter: inherit;
80 | visibility: hidden;
81 | }
82 | .leaflet-tile-loaded {
83 | visibility: inherit;
84 | }
85 | .leaflet-zoom-box {
86 | width: 0;
87 | height: 0;
88 | -moz-box-sizing: border-box;
89 | box-sizing: border-box;
90 | z-index: 800;
91 | }
92 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
93 | .leaflet-overlay-pane svg {
94 | -moz-user-select: none;
95 | }
96 |
97 | .leaflet-pane {
98 | z-index: 400;
99 | }
100 |
101 | .leaflet-tile-pane {
102 | z-index: 200;
103 | }
104 | .leaflet-overlay-pane {
105 | z-index: 400;
106 | }
107 | .leaflet-shadow-pane {
108 | z-index: 500;
109 | }
110 | .leaflet-marker-pane {
111 | z-index: 600;
112 | }
113 | .leaflet-tooltip-pane {
114 | z-index: 650;
115 | }
116 | .leaflet-popup-pane {
117 | z-index: 700;
118 | }
119 |
120 | .leaflet-map-pane canvas {
121 | z-index: 100;
122 | }
123 | .leaflet-map-pane svg {
124 | z-index: 200;
125 | }
126 |
127 | .leaflet-vml-shape {
128 | width: 1px;
129 | height: 1px;
130 | }
131 | .lvml {
132 | behavior: url(#default#VML);
133 | display: inline-block;
134 | position: absolute;
135 | }
136 |
137 | /* control positioning */
138 |
139 | .leaflet-control {
140 | position: relative;
141 | z-index: 800;
142 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
143 | pointer-events: auto;
144 | }
145 | .leaflet-top,
146 | .leaflet-bottom {
147 | position: absolute;
148 | z-index: 1000;
149 | pointer-events: none;
150 | }
151 | .leaflet-top {
152 | top: 0;
153 | }
154 | .leaflet-right {
155 | right: 0;
156 | }
157 | .leaflet-bottom {
158 | bottom: 0;
159 | }
160 | .leaflet-left {
161 | left: 0;
162 | }
163 | .leaflet-control {
164 | float: left;
165 | clear: both;
166 | }
167 | .leaflet-right .leaflet-control {
168 | float: right;
169 | }
170 | .leaflet-top .leaflet-control {
171 | margin-top: 10px;
172 | }
173 | .leaflet-bottom .leaflet-control {
174 | margin-bottom: 10px;
175 | }
176 | .leaflet-left .leaflet-control {
177 | margin-left: 10px;
178 | }
179 | .leaflet-right .leaflet-control {
180 | margin-right: 10px;
181 | }
182 |
183 | /* zoom and fade animations */
184 |
185 | .leaflet-fade-anim .leaflet-tile {
186 | will-change: opacity;
187 | }
188 | .leaflet-fade-anim .leaflet-popup {
189 | opacity: 0;
190 | -webkit-transition: opacity 0.2s linear;
191 | -moz-transition: opacity 0.2s linear;
192 | transition: opacity 0.2s linear;
193 | }
194 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
195 | opacity: 1;
196 | }
197 | .leaflet-zoom-animated {
198 | -webkit-transform-origin: 0 0;
199 | -ms-transform-origin: 0 0;
200 | transform-origin: 0 0;
201 | }
202 | .leaflet-zoom-anim .leaflet-zoom-animated {
203 | will-change: transform;
204 | }
205 | .leaflet-zoom-anim .leaflet-zoom-animated {
206 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1);
207 | -moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1);
208 | transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1);
209 | }
210 | .leaflet-zoom-anim .leaflet-tile,
211 | .leaflet-pan-anim .leaflet-tile {
212 | -webkit-transition: none;
213 | -moz-transition: none;
214 | transition: none;
215 | }
216 |
217 | .leaflet-zoom-anim .leaflet-zoom-hide {
218 | visibility: hidden;
219 | }
220 |
221 | /* cursors */
222 |
223 | .leaflet-interactive {
224 | cursor: pointer;
225 | }
226 | .leaflet-grab {
227 | cursor: -webkit-grab;
228 | cursor: -moz-grab;
229 | cursor: grab;
230 | }
231 | .leaflet-crosshair,
232 | .leaflet-crosshair .leaflet-interactive {
233 | cursor: crosshair;
234 | }
235 | .leaflet-popup-pane,
236 | .leaflet-control {
237 | cursor: auto;
238 | }
239 | .leaflet-dragging .leaflet-grab,
240 | .leaflet-dragging .leaflet-grab .leaflet-interactive,
241 | .leaflet-dragging .leaflet-marker-draggable {
242 | cursor: move;
243 | cursor: -webkit-grabbing;
244 | cursor: -moz-grabbing;
245 | cursor: grabbing;
246 | }
247 |
248 | /* marker & overlays interactivity */
249 | .leaflet-marker-icon,
250 | .leaflet-marker-shadow,
251 | .leaflet-image-layer,
252 | .leaflet-pane > svg path,
253 | .leaflet-tile-container {
254 | pointer-events: none;
255 | }
256 |
257 | .leaflet-marker-icon.leaflet-interactive,
258 | .leaflet-image-layer.leaflet-interactive,
259 | .leaflet-pane > svg path.leaflet-interactive,
260 | svg.leaflet-image-layer.leaflet-interactive path {
261 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
262 | pointer-events: auto;
263 | }
264 |
265 | /* visual tweaks */
266 |
267 | .leaflet-container {
268 | background: #ddd;
269 | outline: 0;
270 | }
271 | .leaflet-container a {
272 | color: #0078a8;
273 | }
274 | .leaflet-container a.leaflet-active {
275 | outline: 2px solid orange;
276 | }
277 | .leaflet-zoom-box {
278 | border: 2px dotted #38f;
279 | background: rgba(255, 255, 255, 0.5);
280 | }
281 |
282 | /* general typography */
283 | .leaflet-container {
284 | font: 12px/1.5 'Helvetica Neue', Arial, Helvetica, sans-serif;
285 | }
286 |
287 | /* general toolbar styles */
288 |
289 | .leaflet-bar {
290 | box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
291 | border-radius: 4px;
292 | }
293 | .leaflet-bar a,
294 | .leaflet-bar a:hover {
295 | background-color: #fff;
296 | border-bottom: 1px solid #ccc;
297 | width: 26px;
298 | height: 26px;
299 | line-height: 26px;
300 | display: block;
301 | text-align: center;
302 | text-decoration: none;
303 | color: black;
304 | }
305 | .leaflet-bar a,
306 | .leaflet-control-layers-toggle {
307 | background-position: 50% 50%;
308 | background-repeat: no-repeat;
309 | display: block;
310 | }
311 | .leaflet-bar a:hover {
312 | background-color: #f4f4f4;
313 | }
314 | .leaflet-bar a:first-child {
315 | border-top-left-radius: 4px;
316 | border-top-right-radius: 4px;
317 | }
318 | .leaflet-bar a:last-child {
319 | border-bottom-left-radius: 4px;
320 | border-bottom-right-radius: 4px;
321 | border-bottom: none;
322 | }
323 | .leaflet-bar a.leaflet-disabled {
324 | cursor: default;
325 | background-color: #f4f4f4;
326 | color: #bbb;
327 | }
328 |
329 | .leaflet-touch .leaflet-bar a {
330 | width: 30px;
331 | height: 30px;
332 | line-height: 30px;
333 | }
334 | .leaflet-touch .leaflet-bar a:first-child {
335 | border-top-left-radius: 2px;
336 | border-top-right-radius: 2px;
337 | }
338 | .leaflet-touch .leaflet-bar a:last-child {
339 | border-bottom-left-radius: 2px;
340 | border-bottom-right-radius: 2px;
341 | }
342 |
343 | /* zoom control */
344 |
345 | .leaflet-control-zoom-in,
346 | .leaflet-control-zoom-out {
347 | font: bold 18px 'Lucida Console', Monaco, monospace;
348 | text-indent: 1px;
349 | }
350 |
351 | .leaflet-touch .leaflet-control-zoom-in,
352 | .leaflet-touch .leaflet-control-zoom-out {
353 | font-size: 22px;
354 | }
355 |
356 | /* layers control */
357 |
358 | .leaflet-control-layers {
359 | box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);
360 | background: #fff;
361 | border-radius: 5px;
362 | }
363 | .leaflet-control-layers-toggle {
364 | background-image: url(images/layers.png);
365 | width: 36px;
366 | height: 36px;
367 | }
368 | .leaflet-retina .leaflet-control-layers-toggle {
369 | background-image: url(images/layers-2x.png);
370 | background-size: 26px 26px;
371 | }
372 | .leaflet-touch .leaflet-control-layers-toggle {
373 | width: 44px;
374 | height: 44px;
375 | }
376 | .leaflet-control-layers .leaflet-control-layers-list,
377 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle {
378 | display: none;
379 | }
380 | .leaflet-control-layers-expanded .leaflet-control-layers-list {
381 | display: block;
382 | position: relative;
383 | }
384 | .leaflet-control-layers-expanded {
385 | padding: 6px 10px 6px 6px;
386 | color: #333;
387 | background: #fff;
388 | }
389 | .leaflet-control-layers-scrollbar {
390 | overflow-y: scroll;
391 | overflow-x: hidden;
392 | padding-right: 5px;
393 | }
394 | .leaflet-control-layers-selector {
395 | margin-top: 2px;
396 | position: relative;
397 | top: 1px;
398 | }
399 | .leaflet-control-layers label {
400 | display: block;
401 | }
402 | .leaflet-control-layers-separator {
403 | height: 0;
404 | border-top: 1px solid #ddd;
405 | margin: 5px -10px 5px -6px;
406 | }
407 |
408 | /* Default icon URLs */
409 | .leaflet-default-icon-path {
410 | background-image: url(images/marker-icon.png);
411 | }
412 |
413 | /* attribution and scale controls */
414 |
415 | .leaflet-container .leaflet-control-attribution {
416 | background: #fff;
417 | background: rgba(255, 255, 255, 0.7);
418 | margin: 0;
419 | }
420 | .leaflet-control-attribution,
421 | .leaflet-control-scale-line {
422 | padding: 0 5px;
423 | color: #333;
424 | }
425 | .leaflet-control-attribution a {
426 | text-decoration: none;
427 | }
428 | .leaflet-control-attribution a:hover {
429 | text-decoration: underline;
430 | }
431 | .leaflet-container .leaflet-control-attribution,
432 | .leaflet-container .leaflet-control-scale {
433 | font-size: 11px;
434 | }
435 | .leaflet-left .leaflet-control-scale {
436 | margin-left: 5px;
437 | }
438 | .leaflet-bottom .leaflet-control-scale {
439 | margin-bottom: 5px;
440 | }
441 | .leaflet-control-scale-line {
442 | border: 2px solid #777;
443 | border-top: none;
444 | line-height: 1.1;
445 | padding: 2px 5px 1px;
446 | font-size: 11px;
447 | white-space: nowrap;
448 | overflow: hidden;
449 | -moz-box-sizing: border-box;
450 | box-sizing: border-box;
451 |
452 | background: #fff;
453 | background: rgba(255, 255, 255, 0.5);
454 | }
455 | .leaflet-control-scale-line:not(:first-child) {
456 | border-top: 2px solid #777;
457 | border-bottom: none;
458 | margin-top: -2px;
459 | }
460 | .leaflet-control-scale-line:not(:first-child):not(:last-child) {
461 | border-bottom: 2px solid #777;
462 | }
463 |
464 | .leaflet-touch .leaflet-control-attribution,
465 | .leaflet-touch .leaflet-control-layers,
466 | .leaflet-touch .leaflet-bar {
467 | box-shadow: none;
468 | }
469 | .leaflet-touch .leaflet-control-layers,
470 | .leaflet-touch .leaflet-bar {
471 | border: 2px solid rgba(0, 0, 0, 0.2);
472 | background-clip: padding-box;
473 | }
474 |
475 | /* popup */
476 |
477 | .leaflet-popup {
478 | position: absolute;
479 | text-align: center;
480 | margin-bottom: 20px;
481 | }
482 | .leaflet-popup-content-wrapper {
483 | padding: 1px;
484 | text-align: left;
485 | border-radius: 12px;
486 | }
487 | .leaflet-popup-content {
488 | margin: 13px 19px;
489 | line-height: 1.4;
490 | }
491 | .leaflet-popup-content p {
492 | margin: 18px 0;
493 | }
494 | .leaflet-popup-tip-container {
495 | width: 40px;
496 | height: 20px;
497 | position: absolute;
498 | left: 50%;
499 | margin-left: -20px;
500 | overflow: hidden;
501 | pointer-events: none;
502 | }
503 | .leaflet-popup-tip {
504 | width: 17px;
505 | height: 17px;
506 | padding: 1px;
507 |
508 | margin: -10px auto 0;
509 |
510 | -webkit-transform: rotate(45deg);
511 | -moz-transform: rotate(45deg);
512 | -ms-transform: rotate(45deg);
513 | transform: rotate(45deg);
514 | }
515 | .leaflet-popup-content-wrapper,
516 | .leaflet-popup-tip {
517 | background: white;
518 | color: #333;
519 | box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4);
520 | }
521 | .leaflet-container a.leaflet-popup-close-button {
522 | position: absolute;
523 | top: 0;
524 | right: 0;
525 | padding: 4px 4px 0 0;
526 | border: none;
527 | text-align: center;
528 | width: 18px;
529 | height: 14px;
530 | font: 16px/14px Tahoma, Verdana, sans-serif;
531 | color: #c3c3c3;
532 | text-decoration: none;
533 | font-weight: bold;
534 | background: transparent;
535 | }
536 | .leaflet-container a.leaflet-popup-close-button:hover {
537 | color: #999;
538 | }
539 | .leaflet-popup-scrolled {
540 | overflow: auto;
541 | border-bottom: 1px solid #ddd;
542 | border-top: 1px solid #ddd;
543 | }
544 |
545 | .leaflet-oldie .leaflet-popup-content-wrapper {
546 | -ms-zoom: 1;
547 | }
548 | .leaflet-oldie .leaflet-popup-tip {
549 | width: 24px;
550 | margin: 0 auto;
551 |
552 | -ms-filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)';
553 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
554 | }
555 | .leaflet-oldie .leaflet-popup-tip-container {
556 | margin-top: -1px;
557 | }
558 |
559 | .leaflet-oldie .leaflet-control-zoom,
560 | .leaflet-oldie .leaflet-control-layers,
561 | .leaflet-oldie .leaflet-popup-content-wrapper,
562 | .leaflet-oldie .leaflet-popup-tip {
563 | border: 1px solid #999;
564 | }
565 |
566 | /* div icon */
567 |
568 | .leaflet-div-icon {
569 | background: #fff;
570 | border: 1px solid #666;
571 | }
572 |
573 | /* Tooltip */
574 | /* Base styles for the element that has a tooltip */
575 | .leaflet-tooltip {
576 | position: absolute;
577 | padding: 6px;
578 | background-color: #fff;
579 | border: 1px solid #fff;
580 | border-radius: 3px;
581 | color: #222;
582 | white-space: nowrap;
583 | -webkit-user-select: none;
584 | -moz-user-select: none;
585 | -ms-user-select: none;
586 | user-select: none;
587 | pointer-events: none;
588 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
589 | }
590 | .leaflet-tooltip.leaflet-clickable {
591 | cursor: pointer;
592 | pointer-events: auto;
593 | }
594 | .leaflet-tooltip-top:before,
595 | .leaflet-tooltip-bottom:before,
596 | .leaflet-tooltip-left:before,
597 | .leaflet-tooltip-right:before {
598 | position: absolute;
599 | pointer-events: none;
600 | border: 6px solid transparent;
601 | background: transparent;
602 | content: '';
603 | }
604 |
605 | /* Directions */
606 |
607 | .leaflet-tooltip-bottom {
608 | margin-top: 6px;
609 | }
610 | .leaflet-tooltip-top {
611 | margin-top: -6px;
612 | }
613 | .leaflet-tooltip-bottom:before,
614 | .leaflet-tooltip-top:before {
615 | left: 50%;
616 | margin-left: -6px;
617 | }
618 | .leaflet-tooltip-top:before {
619 | bottom: 0;
620 | margin-bottom: -12px;
621 | border-top-color: #fff;
622 | }
623 | .leaflet-tooltip-bottom:before {
624 | top: 0;
625 | margin-top: -12px;
626 | margin-left: -6px;
627 | border-bottom-color: #fff;
628 | }
629 | .leaflet-tooltip-left {
630 | margin-left: -6px;
631 | }
632 | .leaflet-tooltip-right {
633 | margin-left: 6px;
634 | }
635 | .leaflet-tooltip-left:before,
636 | .leaflet-tooltip-right:before {
637 | top: 50%;
638 | margin-top: -6px;
639 | }
640 | .leaflet-tooltip-left:before {
641 | right: 0;
642 | margin-right: -12px;
643 | border-left-color: #fff;
644 | }
645 | .leaflet-tooltip-right:before {
646 | left: 0;
647 | margin-left: -12px;
648 | border-right-color: #fff;
649 | }
650 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
3 |
--------------------------------------------------------------------------------
/public/scrollbar.css:
--------------------------------------------------------------------------------
1 | /* ===== Scrollbar CSS ===== */
2 | /* Firefox */
3 | * {
4 | scrollbar-width: auto;
5 | scrollbar-color: #c0c1c0 #ffffff;
6 | }
7 |
8 | /* Chrome, Edge, and Safari */
9 | *::-webkit-scrollbar {
10 | width: 14px;
11 | }
12 |
13 | *::-webkit-scrollbar-track {
14 | background: #ffffff;
15 | }
16 |
17 | *::-webkit-scrollbar-thumb {
18 | background-color: #c0c1c0;
19 | border-radius: 15px;
20 | border: 3px solid #ffffff;
21 | }
22 |
--------------------------------------------------------------------------------
/public/sparkle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/stickers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/stickers.png
--------------------------------------------------------------------------------
/public/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: 'Phantom Sans', 'Inter', system-ui, 'Helvetica Neue', Roboto,
3 | sans-serif !important;
4 | font-size: 18px;
5 | }
6 |
7 | div + h1:first-of-type {
8 | margin-top: 2rem;
9 | }
10 |
11 | summary > p {
12 | display: inline;
13 | }
14 |
15 | .sidebar > ul > li:last-child {
16 | display: none;
17 | }
18 |
--------------------------------------------------------------------------------
/public/zoom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackclub/toolbox/90180bff0d78483ad6923235f45460c533234221/public/zoom.png
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --max-width: 1100px;
3 | --border-radius: 12px;
4 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
5 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
6 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
7 |
8 | --foreground-rgb: 0, 0, 0;
9 | --background-start-rgb: 214, 219, 220;
10 | --background-end-rgb: 255, 255, 255;
11 |
12 | --primary-glow: conic-gradient(
13 | from 180deg at 50% 50%,
14 | #16abff33 0deg,
15 | #0885ff33 55deg,
16 | #54d6ff33 120deg,
17 | #0071ff33 160deg,
18 | transparent 360deg
19 | );
20 | --secondary-glow: radial-gradient(
21 | rgba(255, 255, 255, 1),
22 | rgba(255, 255, 255, 0)
23 | );
24 |
25 | --tile-start-rgb: 239, 245, 249;
26 | --tile-end-rgb: 228, 232, 233;
27 | --tile-border: conic-gradient(
28 | #00000080,
29 | #00000040,
30 | #00000030,
31 | #00000020,
32 | #00000010,
33 | #00000010,
34 | #00000080
35 | );
36 |
37 | --callout-rgb: 238, 240, 241;
38 | --callout-border-rgb: 172, 175, 176;
39 | --card-rgb: 180, 185, 188;
40 | --card-border-rgb: 131, 134, 135;
41 | }
42 |
43 | @media (prefers-color-scheme: dark) {
44 | :root {
45 | --foreground-rgb: 255, 255, 255;
46 | --background-start-rgb: 0, 0, 0;
47 | --background-end-rgb: 0, 0, 0;
48 |
49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
50 | --secondary-glow: linear-gradient(
51 | to bottom right,
52 | rgba(1, 65, 255, 0),
53 | rgba(1, 65, 255, 0),
54 | rgba(1, 65, 255, 0.3)
55 | );
56 |
57 | --tile-start-rgb: 2, 13, 46;
58 | --tile-end-rgb: 2, 5, 19;
59 | --tile-border: conic-gradient(
60 | #ffffff80,
61 | #ffffff40,
62 | #ffffff30,
63 | #ffffff20,
64 | #ffffff10,
65 | #ffffff10,
66 | #ffffff80
67 | );
68 |
69 | --callout-rgb: 20, 20, 20;
70 | --callout-border-rgb: 108, 108, 108;
71 | --card-rgb: 100, 100, 100;
72 | --card-border-rgb: 200, 200, 200;
73 | }
74 | }
75 |
76 | * {
77 | box-sizing: border-box;
78 | padding: 0;
79 | margin: 0;
80 | }
81 |
82 | html,
83 | body {
84 | max-width: 100vw;
85 | overflow-x: hidden;
86 | scroll-behavior: smooth;
87 | }
88 |
89 | body {
90 | color: rgb(var(--foreground-rgb));
91 | background: linear-gradient(
92 | to bottom,
93 | transparent,
94 | rgb(var(--background-end-rgb))
95 | )
96 | rgb(var(--background-start-rgb));
97 | }
98 |
99 | a {
100 | color: inherit;
101 | text-decoration: none;
102 | }
103 |
104 | @media (prefers-color-scheme: dark) {
105 | html {
106 | color-scheme: dark;
107 | }
108 | }
109 |
110 | .leaflet-popup {
111 | z-index: 9999 !important;
112 | }
113 |
--------------------------------------------------------------------------------