Showing preview only (1,413K chars total). Download the full file or copy to clipboard to get everything.
Repository: hackclub/site
Branch: main
Commit: 63b1a0792795
Files: 274
Total size: 1.3 MB
Directory structure:
gitextract_sca6vlk2/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── attachment-warn.yml
│ ├── caniuse-update.yml
│ ├── ci.yml
│ └── validate-team-json.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── AGENT.md
├── LICENSE.md
├── README.md
├── components/
│ ├── AButton.ts
│ ├── analytics.tsx
│ ├── announcement.tsx
│ ├── announcements/
│ │ ├── amount.tsx
│ │ ├── cta.tsx
│ │ ├── elon.mdx
│ │ ├── hcb-mobile.mdx
│ │ ├── hcb-open-source.mdx
│ │ ├── hcb_cta.tsx
│ │ ├── holder.tsx
│ │ ├── pills.tsx
│ │ ├── preston-werner-2022.mdx
│ │ ├── preston-werner.mdx
│ │ └── relon.mdx
│ ├── arcade/
│ │ ├── footer.tsx
│ │ └── projects.tsx
│ ├── background-image.tsx
│ ├── bin/
│ │ ├── GalleryPosts.tsx
│ │ ├── PartTag.module.css
│ │ ├── PartTag.tsx
│ │ ├── nav.tsx
│ │ └── rsvp-form.tsx
│ ├── bio.tsx
│ ├── boardbio.tsx
│ ├── color-switcher.tsx
│ ├── comma.ts
│ ├── directoryModal.tsx
│ ├── donate/
│ │ ├── donors.json
│ │ └── sponsors.tsx
│ ├── dot.tsx
│ ├── elon.mdx
│ ├── fade-in.tsx
│ ├── fiscal-sponsorship/
│ │ ├── contact.tsx
│ │ ├── directory/
│ │ │ └── card.tsx
│ │ ├── features.tsx
│ │ ├── first/
│ │ │ ├── apply-button.tsx
│ │ │ ├── features.tsx
│ │ │ ├── start.tsx
│ │ │ ├── stats.tsx
│ │ │ └── testimonials.tsx
│ │ ├── open-source.tsx
│ │ ├── organization-spotlight.tsx
│ │ ├── sign-in.tsx
│ │ └── tooltip.tsx
│ ├── flag.tsx
│ ├── flex-col.tsx
│ ├── footer.tsx
│ ├── force-theme.ts
│ ├── hackathons/
│ │ ├── features/
│ │ │ ├── marketing.tsx
│ │ │ └── slack.tsx
│ │ ├── keep-exploring.tsx
│ │ ├── landing.tsx
│ │ ├── overview.tsx
│ │ ├── recap.tsx
│ │ └── scrolling-hackathons.tsx
│ ├── icon.tsx
│ ├── index/
│ │ ├── cards/
│ │ │ ├── beest.tsx
│ │ │ ├── button.tsx
│ │ │ ├── card-model.tsx
│ │ │ ├── clubs.tsx
│ │ │ ├── fallout.tsx
│ │ │ ├── flavortown.tsx
│ │ │ ├── hackathons.tsx
│ │ │ ├── haxidraw.tsx
│ │ │ ├── hcb.tsx
│ │ │ ├── hctg.tsx
│ │ │ ├── horizons.tsx
│ │ │ ├── jackpot.tsx
│ │ │ ├── macondo.tsx
│ │ │ ├── mailing-list.tsx
│ │ │ ├── sinerider.tsx
│ │ │ ├── slack.tsx
│ │ │ ├── sleepover.tsx
│ │ │ ├── sprig-console.tsx
│ │ │ ├── sprig.tsx
│ │ │ ├── stasis.tsx
│ │ │ └── workshops.tsx
│ │ ├── carousel-cards.tsx
│ │ ├── carousel.tsx
│ │ ├── ctas.tsx
│ │ ├── events.tsx
│ │ └── github.tsx
│ ├── letterhead.tsx
│ ├── mail-card.tsx
│ ├── marquee.tsx
│ ├── mention.tsx
│ ├── nav.tsx
│ ├── onboard/
│ │ ├── gallery-paginated.tsx
│ │ ├── item.tsx
│ │ ├── pagination-buttons.tsx
│ │ ├── recap.tsx
│ │ └── youtube-video.tsx
│ ├── particles.tsx
│ ├── photo.tsx
│ ├── posts/
│ │ ├── emoji.tsx
│ │ ├── index.tsx
│ │ └── mention.tsx
│ ├── press.mdx
│ ├── react-reveal-compat.tsx
│ ├── react-tooltip.ts
│ ├── replit/
│ │ ├── scale-up.tsx
│ │ └── token-instructions.tsx
│ ├── scroll-hint.tsx
│ ├── secret.tsx
│ ├── ship/
│ │ └── why.mdx
│ ├── signature.tsx
│ ├── signatures.tsx
│ ├── slack.tsx
│ ├── slide-down.tsx
│ ├── slide-up.tsx
│ ├── sparkles/
│ │ ├── index.tsx
│ │ └── money.tsx
│ ├── stage.tsx
│ ├── stat.tsx
│ ├── submit.tsx
│ ├── tilt.tsx
│ └── winter/
│ ├── breakdown-box.tsx
│ ├── breakdown.tsx
│ ├── footer.tsx
│ ├── info.tsx
│ ├── landing.tsx
│ ├── projects.tsx
│ ├── recap.tsx
│ └── timeline.tsx
├── eslint.config.mts
├── lib/
│ ├── cached-hcb-orgs.ts
│ ├── countries.json
│ ├── cta.json
│ ├── dates.ts
│ ├── fetcher.ts
│ ├── git-info.ts
│ ├── helpers.ts
│ ├── members.ts
│ ├── organization.ts
│ ├── slackData.ts
│ ├── sleep.ts
│ ├── theme.ts
│ ├── use-form.ts
│ ├── use-has-mounted.ts
│ ├── use-media.ts
│ ├── use-prefers-motion.ts
│ ├── use-prefers-reduced-motion.ts
│ └── use-random-interval.ts
├── next.config.ts
├── package.json
├── pages/
│ ├── 404.tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── acknowledged.tsx
│ ├── amas/
│ │ ├── geohot.tsx
│ │ ├── index.tsx
│ │ ├── sal.tsx
│ │ └── vitalik.tsx
│ ├── api/
│ │ ├── arcade/
│ │ │ ├── hack-hour/
│ │ │ │ └── inventory.ts
│ │ │ └── shop.ts
│ │ ├── bin/
│ │ │ ├── gallery/
│ │ │ │ ├── posts.ts
│ │ │ │ └── tags.ts
│ │ │ ├── rsvp.ts
│ │ │ └── wokwi/
│ │ │ ├── new/
│ │ │ │ ├── [parts].ts
│ │ │ │ └── index.ts
│ │ │ └── parts.ts
│ │ ├── bucky.ts
│ │ ├── channels/
│ │ │ └── resolve.ts
│ │ ├── contribute.ts
│ │ ├── first-team.ts
│ │ ├── games.ts
│ │ ├── github.ts
│ │ ├── join.ts
│ │ ├── mailing-list.ts
│ │ ├── onboard/
│ │ │ ├── p/
│ │ │ │ ├── [project]/
│ │ │ │ │ └── index.ts
│ │ │ │ ├── count.ts
│ │ │ │ └── index.ts
│ │ │ └── svg/
│ │ │ └── [board_url]/
│ │ │ ├── bottom.ts
│ │ │ ├── index.ts
│ │ │ └── top.ts
│ │ ├── replit/
│ │ │ └── signup.ts
│ │ ├── sprig-console.ts
│ │ ├── stars.ts
│ │ ├── steve.ts
│ │ ├── stickers.ts
│ │ ├── stuff.ts
│ │ ├── team.ts
│ │ └── winter-rsvp.ts
│ ├── arcade/
│ │ └── index.tsx
│ ├── bin/
│ │ ├── gallery.tsx
│ │ └── prelaunch.tsx
│ ├── brand.tsx
│ ├── clubs.tsx
│ ├── content/
│ │ ├── covid19.mdx
│ │ ├── it-admins.mdx
│ │ ├── sponsorship.mdx
│ │ ├── sunsetting-som.mdx
│ │ └── transparency/
│ │ └── may-2020.mdx
│ ├── deprecated/
│ │ └── [deprecated].tsx
│ ├── elon.tsx
│ ├── events.tsx
│ ├── fiscal-sponsorship/
│ │ ├── about.tsx
│ │ ├── climate/
│ │ │ ├── [region].tsx
│ │ │ └── index.tsx
│ │ ├── directory/
│ │ │ ├── [category]/
│ │ │ │ ├── [region].tsx
│ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── first.tsx
│ │ ├── index.tsx
│ │ ├── mobile/
│ │ │ └── index.tsx
│ │ └── open-source.tsx
│ ├── hackathons/
│ │ ├── grant.tsx
│ │ └── index.tsx
│ ├── imprint.tsx
│ ├── index.tsx
│ ├── jobs/
│ │ └── index.tsx
│ ├── minecraft.tsx
│ ├── night.tsx
│ ├── onboard/
│ │ ├── board/
│ │ │ └── [slug].tsx
│ │ ├── first.tsx
│ │ ├── gallery/
│ │ │ └── index.tsx
│ │ └── index.tsx
│ ├── opensource.tsx
│ ├── philanthropy/
│ │ ├── index.tsx
│ │ └── supporters.tsx
│ ├── philosophy.tsx
│ ├── pizza.tsx
│ ├── press.tsx
│ ├── preston-werner-2022.tsx
│ ├── preston-werner.tsx
│ ├── relon.tsx
│ ├── replit.tsx
│ ├── santa.tsx
│ ├── sharkbank/
│ │ └── index.tsx
│ ├── ship.tsx
│ ├── sitemap.xml.tsx
│ ├── steve.tsx
│ ├── stickers.tsx
│ ├── team.tsx
│ └── winter.tsx
├── public/
│ ├── .well-known/
│ │ └── security.txt
│ ├── acknowledged.json
│ ├── bin/
│ │ ├── data-loading.js
│ │ ├── index.html
│ │ ├── landing/
│ │ │ ├── index.html
│ │ │ ├── script.js
│ │ │ └── style.css
│ │ ├── landing-new/
│ │ │ ├── ascii-art.txt
│ │ │ ├── gambling.js
│ │ │ ├── script.js
│ │ │ └── style.css
│ │ ├── memes.js
│ │ ├── orph/
│ │ │ ├── orph.css
│ │ │ └── orph.js
│ │ ├── selector/
│ │ │ ├── index.html
│ │ │ ├── script.js
│ │ │ └── style.css
│ │ └── style/
│ │ ├── common.css
│ │ ├── footer.css
│ │ └── gallery.module.css
│ ├── carousel.json
│ ├── horizons/
│ │ └── Hypebuzz.otf
│ ├── robots.txt
│ ├── stickers-in-stock.html
│ └── team.json
├── tsconfig.json
├── types/
│ └── mdx.d.ts
└── vercel.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: 'bun'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
================================================
FILE: .github/workflows/attachment-warn.yml
================================================
name: Perhaps You Should Upload To CDN Instead?
on:
pull_request:
types: [opened, synchronize]
jobs:
warn:
runs-on: ubuntu-slim
permissions:
pull-requests: write
steps:
- uses: actions/github-script@v9
continue-on-error: true
with:
# this checks via the octokit, because just using pull_requests.paths would warn on every single commit, regardless of the files changed if the PR has at least 1 attachment in it
script: |
// dont comment again if already commented
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const existingComment = comments.data.find(c =>
c.body.includes("Heyo, maybe upload these to a CDN instead")
);
// unallowed file extensions
const EXTENSIONS = [
'png','jpg','jpeg','gif','webp',
'webm','pdf','mp4','mov','zip',
];
// paginate cause what if there's 100+ changed files, better safe than sorry
const files = await github.paginate(
github.rest.pulls.listFiles,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100
}
);
// check if any of the changed files have unallowed extension, and are not removed
// only exclude removed files, since modifying/copying/etc has the same hit to the repo size as a new file
const blobs = files.filter(f => {
if (!f.filename.includes('.')) return false;
const ext = f.filename.split('.').pop().toLowerCase();
return EXTENSIONS.includes(ext) && f.status !== 'removed';
});
if (blobs.length === 0) {
// if we already commented, but the files were removed, edit the comment to kinda mark as resolved
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: `## ~~Heyo, maybe upload these to a CDN instead~~\n\nThis has been resolved now.`
});
return;
}
return
};
const prefix = `## Heyo, maybe upload these to a CDN instead\n\nImages, videos or any attachments can unnecessarily bloat the repository size. Consider uploading these files to [Hack Club CDN](https://cdn.hackclub.com/) instead, and using the CDN links instead.\n\n---\n\n### Files Detected:\n\n`;
if (existingComment) {
// Update existing comment with new file list
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: `${prefix}${blobs.map(b => `- \`${b.filename}\``).join('\n')}`
});
return;
}
// create comment if there are any attachments
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `${prefix}${blobs.map(b => `- \`${b.filename}\``).join('\n')}`
});
================================================
FILE: .github/workflows/caniuse-update.yml
================================================
name: Update Browserslist database
on:
workflow_dispatch:
schedule:
- cron: '0 2 1,15 * *'
permissions:
contents: write
pull-requests: write
jobs:
update-browserslist-database:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Configure git
run: |
git config --global user.email "action@github.com"
git config --global user.name "GitHub Action"
- name: Update Browserslist database and create PR if applies
uses: c2corg/browserslist-update-action@v2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: browserslist-update
base_branch: main
commit_message: 'build: update Browserslist db'
title: 'build: update Browserslist db'
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.
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
- push
- pull_request
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun run lint
format:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun run format:check
================================================
FILE: .github/workflows/validate-team-json.yml
================================================
name: Validate Team JSON
on:
push:
paths:
- 'public/team.json'
pull_request:
paths:
- 'public/team.json'
jobs:
validate:
name: Validate team.json
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Validate JSON format
run: |
if ! jq empty public/team.json 2>/dev/null; then
echo "Error: team.json is not valid JSON"
exit 1
fi
echo "✓ team.json is valid JSON"
================================================
FILE: .gitignore
================================================
.now
.env*
.next
node_modules
.DS_Store
public/sitemap.xml
.vercel
.vscode
yarn-error.log
bun.lockb
.idea
.yarn
.yarnrc.yml
.env*.local
tsconfig.tsbuildinfo
next-env.d.ts
================================================
FILE: .prettierignore
================================================
.next
================================================
FILE: .prettierrc
================================================
{
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid",
"printWidth": 80,
"semi": false
}
================================================
FILE: AGENT.md
================================================
# AGENT.md - Hack Club Site Development Guide
## Commands
- **Dev**: `bun run dev` (start development server)
- **Build**: `bun run build` (production build)
- **Lint**: `bun run lint` (Next.js ESLint)
- **Format**: `bun run format` (Prettier formatting)
- **Start**: `bun run start` (production server)
- **Test**: No test framework configured
## Code Style
- **Imports**: Use relative imports (`../components/nav`), Theme UI components (`{ Box, Text }`)
- **Formatting**: Single quotes, no semicolons, no trailing commas, 80 char width
- **Components**: Functional components with destructured props, default exports
- **Styling**: Theme UI `sx` prop for styling, styled components with @emotion/styled
- **Types**: TypeScript enabled but strict mode off, prefer implicit typing
- **Naming**: camelCase for variables/functions, PascalCase for components
## Architecture
- Next.js app with pages/ directory structure
- MDX support for content pages
- Theme UI for consistent styling with Hack Club theme
- Components in components/ directory, modular cards in components/index/cards/
- Static assets in public/
- Configuration in next.config.mjs includes essential redirects/rewrites
================================================
FILE: LICENSE.md
================================================
_Code under MIT License, assets may not be re-used or re-distributed._
### MIT License
Copyright 2026 The Hack Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center"><img width="192" alt="Hack Club logo" src="https://assets.hackclub.com/flag-standalone.svg"></p>
<h1 align="center"><a href="https://hackclub.com/">Hack Club's Site (v3)</a></h1>
<p align="center"><i>The source code for hackclub.com</i></p>
Hack Club's new website. This codebase is what runs on [hackclub.com](https://hackclub.com). For new developers getting started, run the following in your terminal:
1. Download the code to your computer:
```bash
git clone https://github.com/hackclub/site && cd site
```
2. Install dependencies:
```bash
bun install
```
3. Start running the website on your computer:
```bash
bun run dev
```
4. Open up your web browser and go to [localhost:3000](http://localhost:3000)
> [!NOTE]
> There are a number of redirects and rewrites essential to the website's functionality, which you can see in [next.config.ts](./next.config.ts).
Powered by [Next.js] with [MDX], [Theme UI], & [Hack Club Theme].
Code under MIT License, assets may not be re-used or re-distributed.
---
<h1 align="center">Building <a href="https://hackclub.com/">hackclub.com</a></h1>
Join us in building Hack Club's homepage and show new hackers what Hack Club could be for them 💖.
See something that could be better? Make a PR! Have an easter egg idea? Make a PR! Is the site missing something? Make a PR! _(Do you see a trend? :))_
If you need to add content to the site, here's how you can:
<details> <summary>Create a new card</summary>
<img width="600" alt="Screenshot of the Sprig card" src="https://github.com/hackclub/site/assets/65808924/fed45800-c834-4e4c-ad87-a21e01414fa9">
Most things on the homepage are cards, modular components that can easily be added and removed according to relevancy to Hack Clubbers. There are 3 main sections: connection, open-source, and IRL community. Most new cards will likely fall within the first two sections!
First, you can create a new file under [components/index/cards](components/index/cards/) with the name of your new event/project. Next add `import CardModel from './card-model'` and add whatever you want :) Finally, use a `<Buttons>` component (`import Buttons from './button'`) to highlight call-to-action buttons. If it's the main button, use the primary prop to add a background color!
Your challenge: try and make the card as unique as possible, like a mini poster! Not sure where to start? Look at other cards on the page :)
</details>
<details>
<summary>Add to the carousel</summary>
<img width="600" alt="Screenshot of a carousel section" src="https://github.com/hackclub/site/assets/65808924/044660eb-fb3d-43b6-a270-64a3fe51f3ca">
If there's a Hack Club or Hack Club community-led project (past or present) that Hack Clubbers can get involved in, please add it to [public/carousel.json](public/carousel.json) and add your card to the end of the json file. An example looks like this:
```json
{
"background": "dark",
"titleColor": "white",
"descriptionColor": "white",
"title": "Hackers Wanted",
"description": "Our open love letter to hackers",
"img": "https://a.slack-edge.com/production-standard-emoji-assets/14.0/apple-large/1f4bb@2x.png",
"link": "/hackers-wanted"
}
```
</details>
Every week, thousands of people visit hackclub.com. What story do you want to tell?
_Have questions? Join us in [#hackclub-site-dev](https://hackclub.slack.com/archives/C036BTDGP43) and to learn more about the style guide at Hack Club check [this](https://hackclub.com/brand/) out_
---
Hack Club, 2026. MIT License.
[next.js]: https://nextjs.org
[mdx]: https://mdxjs.com
[theme ui]: https://theme-ui.com
[hack club theme]: https://theme.hackclub.com
================================================
FILE: components/AButton.ts
================================================
import { Button } from 'theme-ui'
export const AButton = Button as any
================================================
FILE: components/analytics.tsx
================================================
import Script from 'next/script'
const Analytics = () => (
<Script
defer
data-domain="hackclub.com"
src="https://plausible.io/js/script.pageview-props.tagged-events.js"
/>
)
export default Analytics
================================================
FILE: components/announcement.tsx
================================================
import { keyframes } from '@emotion/react'
import Image from 'next/image'
import { Box, Card, Text } from 'theme-ui'
import Icon from './icon'
export const unfold = keyframes({
from: { transform: 'scaleY(0)' },
to: { transform: 'scaleY(100%)' }
})
type AnnouncementProps = {
caption?: string
copy: string
iconLeft?: string
iconRight?: string
imgSrc?: string
imgAlt?: string
color?: string
textColor?: string
sx?: any
width?: number | string
href?: string
}
const Announcement = ({
caption,
copy,
iconLeft,
iconRight,
imgSrc,
imgAlt,
color = 'accent',
textColor = 'secondary',
sx = {},
width,
...props
}: AnnouncementProps) => (
<Card
as={props.href ? 'a' : 'div'}
variant="interactive"
sx={{
variant: 'cards.translucent',
mx: 'auto',
maxWidth: 'narrow',
width: width ? width : '100%',
textAlign: 'left',
textDecoration: 'none',
lineHeight: 'caption',
display: 'flex',
alignItems: 'center',
p: [2, 2],
px: 3,
mb: [3, 4],
mt: [null, -3, -5],
transform: 'scale(1)',
'@media (prefers-reduced-motion: no-preference)': {
animation: `${unfold} 0.5s ease-out`
},
svg: { flexShrink: 'none' },
...sx
}}
{...props}
>
{iconLeft && (
<Icon
glyph={iconLeft}
sx={{ mr: [2, 3], ml: 2, color, display: ['none', 'block'] }}
/>
)}
{imgSrc && (
<Box sx={{ mr: [2, 3], ml: 2, width: 32, flexShrink: 0 }}>
<Image
src={imgSrc}
alt={imgAlt}
width={32}
height={32}
style={{
maxWidth: '100%',
height: 'auto'
}}
/>
</Box>
)}
<Text
as="p"
sx={{
flex: '1 1 auto',
strong: { display: ['inline', 'block'], color: textColor }
}}
>
<strong>{copy}</strong>
{caption && (
<Text as="span" variant="caption" color={textColor}>
{' '}
{caption}
</Text>
)}
</Text>
{iconRight && <Icon glyph={iconRight} sx={{ ml: [2, 3], color }} />}
</Card>
)
export default Announcement
================================================
FILE: components/announcements/amount.tsx
================================================
import Sparkles from '../sparkles'
const Amount = ({ amount }) => (
<Sparkles
sx={{
WebkitTextStroke: 'currentColor',
WebkitTextStrokeWidth: ['2px', '3px'],
WebkitTextFillColor: 'transparent'
}}
>
{amount}
</Sparkles>
)
export default Amount
================================================
FILE: components/announcements/cta.tsx
================================================
import { Box, Button, Grid, Heading, Text } from 'theme-ui'
import Icon from '@hackclub/icons'
import NextLink from 'next/link'
import { thousands } from '../../lib/members'
export default function SlackCTA() {
return (
<Box
as="section"
sx={{
bg: 'orange',
backgroundImage: (t: any) => t.util.gx('yellow', 'orange'),
color: 'white',
py: [4, 5]
}}
>
<Grid gap={[3, 4]} columns={[null, 'auto 1fr']} variant="layout.copy">
<Icon glyph="welcome" size={72} />
<Box>
<Heading as="h2" variant="headline" mt={0}>
Teenager? New here? Welcome!
</Heading>
<Text variant="subtitle" sx={{ lineHeight: 'caption', mb: 3 }}>
Hack Club is a global community of high school makers & student-led
coding clubs. We’ve got a 24/7 Slack chatroom of {thousands}k+
teenagers learning to code & building amazing projects, & you’ll fit
right in.
</Text>
<br />
<br />
<NextLink href="/">
<Button bg="cyan">Learn more</Button>
</NextLink>
</Box>
</Grid>
</Box>
)
}
================================================
FILE: components/announcements/elon.mdx
================================================
import Amount from './amount'
import Signature from '../signature'
# Today, I’m proud to share: Elon Musk is donating <Amount amount="$500,000" /> to [Hack Club](https://hackclub.com).
Elon Musk is one of the most prolific and ambitious hackers of the last decade.
It was a huge honor last month to have Elon [spend an hour in an ask-me-anything call with our community of high schoolers](https://youtu.be/riru9OzScwk)—at one point he remarked we were “asking better questions than all the mainstream media” and called our community “very wholesome.”
**Afterwards, Elon wanted to support Hack Club further.**
When hackers see problems in the world, we don’t blame someone else: we try to take them on to solve. Elon is very selective about the nonprofits he supports and I’m proud Hack Club is one of them.
So…how will Hack Club invest $500,000? We want to use this to help 1,000 more students start and join Hack Clubs in their towns ([see the worldwide map](https://hackclub.com/map/)). For those already in Hack Club, we look to you to help us make a higher-quality experience. We plan to continue much of what we’re already doing (and [what I wrote about in January](https://zachinto2020.wordpress.com/2019/12/31/as-midnight-approaches/)): spending as little money as possible at all times, growing slowly, adding diverse staff to make Hack Club better (video game designers, software engineers, media producers, and more). We are pushing hard to try and make the [Hack Club Slack](https://hackclub.com/) the best place to be a teenager on the internet and expanding [HCB](https://hackclub.com/fiscal-sponsorship/).
We’ll be fully transparent in how we spend this money. One thing we’ve been working toward after winning the [Frank Grant](https://grant.frank.ly/) is open sourcing our finances. Hack Club HQ has been running on HCB since February, and starting today, [**you can see our finances publicly**](https://hcb.hackclub.com/hq). Through HCB, you can track how we spend every dollar of Elon’s gift. Soon, we’ll also launch [Frank’s](https://frank.ly/) transparency tools on [hackclub.com](https://hackclub.com/).
Hack Club’s mission is to build a new generation of hackers. This starts in high school, where Hack Club students learn to be technically proficient, build their friend network, learn to raise and spend money, and develop into kind, curious, thoughtful, optimistic, and honest leaders. And now Elon Musk is one of our largest supporters.
To every Hack Clubber: Elon is now supporting you and your work, so go forth and do amazing things. We can’t wait to show Elon what you make.
<Signature fname="Zach" lname="Latta" />
Zach Latta, Founder
================================================
FILE: components/announcements/hcb-mobile.mdx
================================================
I’m Mohamad, a 17-year-old from the SF Bay Area, and I just shipped the official mobile app for HCB.
If you haven't heard of it, HCB is the financial backbone for over **6,500 teenager-led nonprofits**, clubs, and hackathons. We provide **501(c)(3) nonprofit** status, access to a bank account, a donation collection platform, and debit cards for thousands of young people trying to do good in their communities.
HCB is currently processing an average of **$6 million per month** (over $80M in its lifetime).[^1] For the last year, I’ve led the project to build the first-ever mobile app for this entire community.
_The entire project is open source on [GitHub](https://github.com/hackclub/hcb-mobile) (we'd love a ⭐️!)._
## Why build this?
These teenagers are running run robotics teams, hackathons, and nonprofit projects that improve their community. They need a way to manage their organization's finances from their pocket.
With HCB Mobile, they'll be able to:
- Track their **organization's balance** and transactions on the go.
- Accept in-person **tap-to-pay donations**, perfect for an in-person fundraiser or event! No extra hardware required. Just tap any credit/debit card against your phone.
- **Issue new debit cards**, add them to **Apple / Google Wallet**, and freeze or cancel them directly from their phone.
- **Upload receipts** directly from their device or match existing receipts in Receipt Bin to transactions with a tap.
## The Technical Stuff
When I started working on this app, I wanted to build in native code like **SwiftUI** for iOS and **Kotlin/Jetpack Compose** for Android. However, I realized that it would be a pain for me, a **full-time student** with classes, to handle two codebases. I'd have to duplicate every feature I created for one OS to the other and deal with all the integration issues along the way. Then, I discovered **Expo** (a React Native framework) which allowed me to write one app that worked on multiple devices. Working with Expo, I learned about creating my own Expo Modules (to bridge native code features to Typescript) and optimization methods like memoization and component recycling.
The non-code side of this app was _no joke_, either. I had to work with the Apple and Google app review teams to obtain **restricted entitlements** for features like mobile **tap-to-pay terminal provisioning** (made possible by Stripe) and **push provisioning** (which allows users to add cards to their payment wallet directly from HCB Mobile). It took several months and many back-and-forth email chains to finally get the entitlements we needed.
After over 250 hours of development work, I can say that I'm incredibly proud of HCB Mobile because it's **built by teenagers** to make it easier for teenagers like me to run nonprofit organizations and projects with HCB. Beyond teenagers, HCB also supports hundreds of adult-ran organizations such as mutual aid groups, open source projects, and community spaces.
<br />
## Download the app!
<br />
<a href="https://apps.apple.com/us/app/hcb-by-hack-club/id6465424810">
<img
src="/fiscal-sponsorship/apple-web-badge.svg"
alt="Download on the App Store"
height="40"
/>
</a>
<a href="https://play.google.com/store/apps/details?id=com.hackclub.hcb">
<img
src="/fiscal-sponsorship/google-play-web-badge.png"
alt="Get it on Google Play"
height="40"
/>
</a>
[^1]: _This amount is excluding HQ (our own) [finances](https://hcb.hackclub.com/hq)._
================================================
FILE: components/announcements/hcb-open-source.mdx
================================================
Hack Club launched HCB in 2018 to enable hackathons to raise and spend money
through [fiscal sponsorship](https://hackclub.com/fiscal-sponsorship/). Since
then, we’ve expanded to all nonprofit projects; our 12,000 users have transacted
$50 million.
<p style={{ textAlign: 'center' }}>
<img
alt="HCB's user interface showing Hack Club HQ's transactions"
src="https://cdn.hackclub.com/019c76b7-a601-7e83-a9fb-e26595864142/flGLlg.png"
width="700px"
/>
</p>
Local student-ran hackathons, robotics teams, and community groups use HCB as a
nonprofit neobank to collect donations, send payments, issue debit cards, and
gain 501(c)(3) status.
When we started HCB, it was developed in private for security reasons. That
said, one of Hack Club’s core principles has always been transparency - we [open
source](https://github.com/hackclub/ledger) our
[finances](https://hcb.hackclub.com/hq), [document how we run
events](https://github.com/hackclub/assemble), and have 500+ public repositories
on [GitHub](https://github.com/hackclub).
**_[github.com/hackclub/hcb](https://github.com/hackclub/hcb) is now public -
check it out and star it._**
Paired with our technical documentation, it’s a great resource for anyone
interested in building financial software or applications with Ruby on Rails.
Our engineering work is also entirely public; the world can learn from our
successes and mistakes.
Since 2018, over fifty people have made 10k+ commits to HCB (thank you!); we
can’t wait for more contributors to join us:
<video width="100%" controls style={{ borderRadius: '8px' }}>
<source
src="https://noras-second-secret-cdn.hackclub.dev/gsource.mp4"
type="video/mp4"
/>
Your browser does not support the video tag.
</video>
PS: if you’re looking to start a nonprofit, we’re accepting applications! Head
over to [nonprofit.new](https://nonprofit.new/) and we’ll be in touch.
<img src="https://cdn.hackclub.com/019c76b9-f285-79f8-93af-2b43e0a67674/A8ZCLg.jpeg" />
<small>We're launching this live from SF!</small>
================================================
FILE: components/announcements/hcb_cta.tsx
================================================
import { Box, Button, Grid, Heading, Text } from 'theme-ui'
import Icon from '@hackclub/icons'
import NextLink from 'next/link'
export default function HCBCTA() {
return (
<Box
as="section"
sx={{
bg: 'orange',
backgroundImage: (t: any) => t.util.gx('yellow', 'orange'),
color: 'white',
py: [4, 5]
}}
>
<Grid gap={[3, 4]} columns={[null, 'auto 1fr']} variant="layout.copy">
<Icon glyph="bank-account" size={72} />
<Box>
<Heading as="h2" variant="headline" mt={0}>
Looking to start a nonprofit?
</Heading>
<Text variant="subtitle" sx={{ lineHeight: 'caption', mb: 3 }}>
We're accepting applications! No startup fees, no minimum balance,
and no long wait time.
</Text>
<br />
<br />
<NextLink href="/fiscal-sponsorship">
<Button bg="cyan">Learn more</Button>
</NextLink>
<NextLink href="https://nonprofit.new">
<Button bg="orange">Apply now</Button>
</NextLink>
</Box>
</Grid>
</Box>
)
}
================================================
FILE: components/announcements/holder.tsx
================================================
import { Container, BaseStyles } from 'theme-ui'
export default function AnnouncementHolder({ children }) {
return (
<Container
as={BaseStyles}
variant="copy"
sx={{
py: [4, 5],
fontSize: [2, 3],
h1: {
textAlign: ['left', 'center'],
color: 'cyan',
my: 4,
a: { color: 'inherit' }
},
'a[href^="#fn-"], a[href^="#fnref-"]': {
textDecoration: 'none',
color: 'inherit'
}
}}
>
{children}
</Container>
)
}
================================================
FILE: components/announcements/pills.tsx
================================================
import { Avatar, Badge, Flex } from 'theme-ui'
export function PillHolder({ children }) {
return (
<Flex
sx={{
flexWrap: 'wrap',
justifyContent: 'center',
alignItems: 'center',
div: {
mt: 0,
mb: 2,
color: 'muted',
border: '1px solid',
borderColor: 'border',
bg: 'snow',
fontSize: 2,
fontWeight: 'body',
lineHeight: '36px'
}
}}
>
{children}
</Flex>
)
}
export function AuthorPill({ tag, image, firstName }) {
return (
<Badge
variant="pill"
sx={{
mr: [2, 3],
pl: 2,
pr: 3,
display: 'inline-flex',
alignItems: 'center'
}}
>
<Avatar src={image} alt={firstName} size={36} mr={2} />
{tag}
</Badge>
)
}
export function DatePill({ tag }) {
return (
<Badge variant="pill" px={3}>
{tag}
</Badge>
)
}
================================================
FILE: components/announcements/preston-werner-2022.mdx
================================================
This gift means a lot to the Hack Club community, and we are grateful for Tom and Theresa’s continued support.
In 2014, Hack Club was founded, and Tom joined as Hack Club’s first board member. In the years since, he has contributed open source code, mentored Hack Clubbers 1:1, donated dozens of laptops to teenagers who didn't have access to computers, and been a constant advisor on the Hack Club community, strategy, and product.
Tom and Theresa also helped fund [The Hacker Zephyr](https://hack.af/zephyrdoc), an epic, cross-country train hackathon taken by 42 teen hackers in the summer of 2021. Tom even hacked alongside Hack Clubbers onboard.
With this gift, we will continue to build the engineering team at Hack Club, including a Tech Lead for [HCB](https://hackclub.com/fiscal-sponsorship), and new engineers to support clubs, the Hack Club online community, and events.
One of our goals in 2022 is to improve Hack Club and to support more teenagers in joining the community. Thank you Tom and Theresa for helping make this possible.
We thank Tom and Theresa for their generous gift and will carefully use each cent to advance our mission to create a new generation of young, highly-technical teen leaders capable of solving our world’s greatest problems. Every penny will be spent [transparently](https://hcb.hackclub.com/hq).
— Christina Asquith, COO, and Zach Latta, founder
================================================
FILE: components/announcements/preston-werner.mdx
================================================
import Amount from './amount'
# Today, we're proud to share: Tom and Theresa Preston-Werner are donating <Amount amount="$500,000" /> to [Hack Club](https://hackclub.com).
We are deeply grateful for this gift.
In the coming months, we hope you’ll share our excitement as we make 2 new hires directly serving Hack Clubbers. This gift increases Hack Club’s budget by 60%, and helps us build a diverse foundation at the leadership level of Hack Club as we grow.
We are so honored to be included among the many gifts the Preston-Werners make each year. This is the 3rd year the Preston-Werners have given a gift to Hack Club, and they've supported the organization from the beginning.
Tom and Theresa inspire Hack Club's values. They are self-made hackers, passionate about constructing a better world in creative ways. They are deeply committed to environmental protection, women’s rights, ending global poverty and injustice; and are tremendous collaborators in making Hack Club a place where all young people can build and create their own solutions through coding and technology, regardless of their background. They apply rigorous-thinking, curiosity, humility, transparency, and deep expertise in academia and coding to the problems that need solving in the world, and inspire us to do the same.
The Preston-Werners generously donate dozens of hours of their time each year to Hack Club HQ and Hack Clubbers. Theresa has been a steady champion of Hack Club, supporting us with feedback, advice, editing, and meeting with Hack Club students. Tom is a founding board member and a personal mentor of Zach’s for the last 5 years. Just some of the ways they support Hack Club is that they inspired the idea to launch Hack Club’s “Ask Me Anything” series, and Tom was our first speaker last April. In December 2019, they threw an amazing Christmas party at their San Francisco home for Hack Club.
Their incredible and generous gift ushers Hack Club into a big new year in which we get closer to our vision to build a new cultural institution for the 21st century akin to the Boy and Girl Scouts, in which we support high schoolers to gain critical computer science skills, healthy, fun and wholesome friendships, and a set of modern values that honor kindness, integrity, inclusivity, curiosity, optimism, and building and doing.
We send them a huge thank you. To every Hack Clubber: Tom and Theresa are now supporting you and your work, so go forth and do amazing things. We can’t wait to show them what you make.
—Christina Asquith, COO, and Zach Latta, founder
================================================
FILE: components/announcements/relon.mdx
================================================
import Signature from '../signature'
import Signatures from '../signatures'
import Image from 'theme-ui'
In March 2020, Elon Musk [spent an hour hanging out with Hack Clubbers](https://www.youtube.com/watch?v=riru9OzScwk). He [donated $500,000 to build our team](https://hackclub.com/elon/), [tweeted Hack Club was a cool group](https://twitter.com/elonmusk/status/1263275969743216640), and said that Hack Club makes him more optimistic about the future. This year, Hack Clubbers met SpaceX engineers and demoed projects on SpaceX's factory floor in Hawthorne, California.
This summer, Elon reached back out.
Today, we're excited to announce Elon is donating $1 million to Hack Club.
This gift will help launch a number of ideas we've been discussing, including helping more in-person hackathons get off the ground, providing more direct 1:1 technical support on the [Hack Club Slack](https://slack.hackclub.com), and starting up cool new projects like [The Hacker Zephyr](https://github.com/hackclub/the-hacker-zephyr). We also want to use his gift to help 1,000 more teenagers start and join Hack Clubs in their towns.
We will be spending every dollar as wisely as possible, growing thoughtfully, and adding diverse staff to make Hack Club better. We are pushing hard to try and make the Hack Club Slack the best place to be a teenager on the internet and expanding [HCB](https://hackclub.com/fiscal-sponsorship/).
Elon is very selective about the nonprofits he supports and we're proud Hack Club is one of them.
Hack Club will be fully transparent in how we spend this money. Hack Club HQ has been running on HCB since February 2020, and [you can see our finances publicly here](https://hcb.hackclub.com/hq).
Hack Club's mission is to help foster a new generation of hackers. This starts in high school, where Hack Clubbers learn to be technically proficient, build their friend network, learn to raise and spend money, and develop into kind, curious, thoughtful, optimistic, and honest leaders. And now Elon Musk is one of our largest supporters.
To every Hack Clubber: Elon continues to support you and your work, so go forth and do amazing things. We can't wait to share what you make.
<Signatures fileName="christina_and_zrl" width={320} />
Zach Latta, Founder & Executive Director
Christina Asquith, COO
================================================
FILE: components/arcade/footer.tsx
================================================
import { Box, Heading, Text, Link } from 'theme-ui'
import Footer from '../footer'
const Description = () => (
<Box sx={{ a: { color: '#FF5C00' }, pb: 4 }}>
<Heading as="h3" variant="subheadline" mb={2}>
A project by <a href="https://hackclub.com/">Hack Club</a>.
</Heading>
<Text as="p" variant="caption" mb={3} sx={{ width: ['85%', '75%', '60%'] }}>
<Link href="/arcade/power-hour">Previous edition: Power Hour</Link>
</Text>
<Text as="p" variant="caption" mb={3} sx={{ width: ['85%', '75%', '60%'] }}>
Hack Club is a registered 501(c)3 nonprofit organization that supports a
network of 20k+ technical high schoolers. We believe you learn best by
building so we're creating community and providing grants so you can make.
In the past few years, we've{' '}
<Link href="https://summer.hackclub.com" target="_blank">
given away 100k+ in hardware grants
</Link>
,{' '}
<Link
href="https://github.com/hackclub/the-hacker-zephyr"
target="_blank"
>
hosted the world's longest hackathon on land
</Link>
, and{' '}
<Link href="https://github.com/hackclub/assemble" target="_blank">
brought 183 teenagers to SF for a hackathon
</Link>
.
</Text>
</Box>
)
const ArcadeFooter = () => {
return (
<Footer>
<Description />
</Footer>
)
}
export default ArcadeFooter
================================================
FILE: components/arcade/projects.tsx
================================================
/** @jsxImportSource theme-ui */
import React, { useState } from 'react'
import styled from '@emotion/styled'
import {
Box,
Button,
Container,
Flex,
Heading,
Card,
Grid,
Text,
Avatar
} from 'theme-ui'
import NextImage from 'next/image'
import Marquee from '../marquee'
import Photo1 from '../../public/winter/1.jpeg'
import Photo2 from '../../public/winter/2.png'
import Photo3 from '../../public/winter/3.jpeg'
import Photo4 from '../../public/winter/4.jpeg'
import Photo5 from '../../public/winter/5.jpeg'
import Photo6 from '../../public/winter/6.jpeg'
import Photo7 from '../../public/winter/7.jpeg'
import Photo8 from '../../public/winter/8.jpeg'
import Photo9 from '../../public/winter/9.jpeg'
import Photo10 from '../../public/winter/10.jpeg'
import Photo12 from '../../public/winter/12.jpeg'
import Photo13 from '../../public/winter/13.jpeg'
import Photo14 from '../../public/winter/14.jpeg'
import Photo15 from '../../public/winter/15.jpeg'
import Photo16 from '../../public/winter/16.jpeg'
import Photo17 from '../../public/winter/17.jpeg'
import Photo18 from '../../public/winter/18.jpeg'
import Photo19 from '../../public/winter/19.jpeg'
import Photo20 from '../../public/winter/20.jpeg'
import Photo21 from '../../public/winter/21.jpeg'
const Header = styled(Box)`
background: url('/pattern.svg');
`
const PhotoRow = ({ photos }) => (
<Box sx={{ height: '225px', overflow: 'hidden' }}>
<Box sx={{ display: ['block', 'block', 'block', 'block', 'none'] }}>
<Marquee velocity={12} onInit={() => {}} onFinish={() => {}}>
{photos.map((photo, index) => (
<NextImage
placeholder="blur"
src={photo}
className="next-image"
height="225px"
width="300px"
alt="Hack Club students"
key={'image-' + index}
style={{
maxWidth: '100%',
height: 'auto',
objectFit: 'cover'
}}
/>
))}
</Marquee>
</Box>
<Box sx={{ display: ['none', 'none', 'none', 'none', 'block'] }}>
<Marquee velocity={12} onInit={() => {}} onFinish={() => {}}>
{photos.map((photo, index) => (
<NextImage
placeholder="blur"
src={photo}
className="next-image"
height="200px"
width="600px"
key={'image-' + index}
alt="Hack Club students"
style={{
maxWidth: '100%',
height: 'auto',
objectFit: 'cover'
}}
/>
))}
</Marquee>
</Box>
</Box>
)
const Cards = ({ avatar, username, description, image }) => {
return (
<Card
className="post"
sx={{ p: [3, 3], width: '100%', background: '#FAEFD6', color: '#5E3414' }}
>
<Flex
as="a"
href={
username !== 'cjmika110'
? `https://scrapbook.hackclub.com/${username}`
: 'https://scrapbook.hackclub.com'
}
sx={{
color: 'inherit',
textDecoration: 'none',
alignItems: 'center',
mb: 2
}}
>
<Avatar loading="lazy" src={avatar} alt="" mr={2} />
<Box>
<Text variant="subheadline" my={0} fontSize={[1, 1]}>
@{username}
</Text>
</Box>
</Flex>
<Text
as="p"
sx={{
fontSize: 1,
textAlign: 'left',
mb: 2,
a: {
color: 'primary',
wordBreak: 'break-all',
wordWrap: 'break-word'
},
'> div': { width: 18, height: 18 }
}}
>
{description}
</Text>
<Box
sx={{
position: 'relative',
width: '100%',
height: '160px',
overflow: 'hidden',
backgroundImage: `url('${image}')`,
backgroundSize: '100%',
backgroundPosition: 'bottom center',
backgroundRepeat: 'no-repeat'
}}
>
{/* <img src={image} sx={{ width: '100%' }} /> */}
</Box>
</Card>
)
}
export default function Projects() {
const [count, setCount] = useState(0)
const list = [
'Drawing robot',
'3D printer',
'DIY Electric Skateboard',
'Pixel art display',
'Smart Garden',
'CNC machine',
'Interactive LED Art',
'VR Escape Room',
'Image Recognition App',
'DIY Camera',
'Multiplayer AR Game',
'Drone Swarm Choreography'
]
if (count === list.length - 1) {
setCount(0)
}
const project_idea = list[count]
return (
<Box>
<Header sx={{ position: 'relative' }}>
<Box
sx={{
background: [
'#D0BF97 url(/arcade/white_bg.svg) no-repeat center center',
'#D0BF97 url(/arcade/white_bg.svg) no-repeat center center',
'rgba(0,0,0, 0.8)',
'rgba(0,0,0, 0.8)'
],
backgroundSize: 'cover',
zIndex: 1,
position: 'relative',
color: 'white!important',
height: ['auto', 'auto', '900px', '900px']
}}
pt={[5, 5, 5, '50px']}
pb={[5, 5, 0, 0]}
>
<Box
sx={{
maxWidth: '64rem',
mx: 'auto',
zIndex: 1,
position: 'relative'
}}
align="center"
py={2}
px={[1, 3]}
>
<Container sx={{ maxWidth: '48rem' }}>
<Heading
variant="headline"
sx={{
textShadow: '0px 0px 21px #FAEFD6',
color: '#FAEFD6',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1,
fontSize: [2, 4, 5]
}}
>
<Text>You could be building a</Text>
<Text
sx={{
backgroundColor: '#FF5C00',
py: 2,
px: 3,
borderRadius: 10,
position: 'relative',
width: 'fit-content',
height: 'min-content',
cursor: 'pointer',
userSelect: 'none',
color: '#FAEFD6'
}}
onClick={() => setCount(count + 1)}
>
<Box as="span" sx={{ whiteSpace: 'nowrap' }}>
{project_idea}
</Box>
</Text>
</Heading>
<Grid
columns={[1, 1, 3, 3]}
mt={4}
mx={['5vw', '5vw', 'auto', 'auto']}
>
<Cards
avatar="https://scrapbook.hackclub.com/_next/image?url=https%3A%2F%2Favatars.slack-edge.com%2F2024-05-06%2F7077145829972_8597fe575e09a698859c_192.png&w=48&q=75"
username="elijah"
description="Finally shipped my personal ai clone and had a ton of fun playing around with it and seeing what other people did with it! Personal favorite was when it threatened to kill me and got very unhinged when the person threatened to send screenshots to me"
image="https://scrapbook-into-the-redwoods.s3.amazonaws.com/4d4ecc40-c388-4b9d-997f-1f3d6a21302c-image.png"
/>
<Cards
avatar="https://scrapbook.hackclub.com/_next/image?url=https%3A%2F%2Favatars.slack-edge.com%2F2023-04-15%2F5116546887938_afb907f96fa13e434a49_192.png&w=48&q=75"
username="cupcakes"
description="Assembling blot robot! 🪛"
image="https://scrapbook-into-the-redwoods.s3.amazonaws.com/e75cf24a-46d9-45fa-92d3-b9e5862d0d47-img_2442.jpg"
/>
<Cards
avatar="https://scrapbook.hackclub.com/_next/image?url=https://secure.gravatar.com/avatar/c2e358d7bf4677cac086556035ce1dbc.jpg?s%3D192%26d%3Dhttps%253A%252F%252Fa.slack-edge.com%252Fdf10d%252Fimg%252Favatars%252Fava_0011-192.png&w=640&q=75"
username="KonstantinosFragkoulis"
description="Well, the drone now should be able to follow the biggest object that it sees with a specific color. I haven't tested it yet though 😞 (I'm too scared to crash it). Here is a clip from earlier today, my genuine reaction to the first takeoff ever (got a bit scared at the end) 👍 "
image="https://cloud-fshng6w8x-hack-club-bot.vercel.app/0videoframe_809.png"
/>
</Grid>
<Button
as="a"
variant="cta"
href="https://scrapbook.hackclub.com/"
sx={{
background:
'linear-gradient(32deg, rgba(51, 142, 218, 0.9) 0%, rgba(51, 214, 166, 0.9) 100%)',
mt: 4
}}
>
See more projects →
</Button>
</Container>
</Box>
</Box>
<Box
sx={{
position: 'absolute',
top: 0,
zIndex: 0,
width: '100%',
display: ['none', 'none', 'block', 'block']
}}
>
<PhotoRow photos={[Photo1, Photo2, Photo3, Photo4, Photo5]} />
<PhotoRow photos={[Photo6, Photo7, Photo8, Photo9, Photo10]} />
<PhotoRow photos={[Photo21, Photo12, Photo13, Photo14, Photo15]} />
<PhotoRow photos={[Photo16, Photo17, Photo18, Photo19, Photo20]} />
</Box>
</Header>
</Box>
)
}
================================================
FILE: components/background-image.tsx
================================================
import { Box } from 'theme-ui'
import Image, { StaticImageData } from 'next/image'
/*
* Use this component inside a container with CSS:
* `position: relative; overflow: hidden;`
* then pass width/height/alt/src as usual
* (you can pass `gradient` valueless to avoid gradient)
*/
type BGImgProps = {
gradient?: string | boolean
alt?: string
src: string | StaticImageData
placeholder?: 'blur' | 'empty'
}
export default function BGImg({
gradient = 'linear-gradient(rgba(0,0,0,0.25), rgba(0,0,0,0.5))',
alt = '',
...props
}: BGImgProps) {
return (
<Box
sx={{
position: 'absolute',
display: 'block',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 0,
'&:after': {
content: '""',
position: 'absolute',
...(typeof gradient === 'string'
? { backgroundImage: gradient }
: {}),
top: 0,
left: 0,
right: 0,
bottom: 0
},
img: { objectFit: 'cover', objectPosition: 'center center' },
'~ *': { position: 'relative' }
}}
>
<Image fill alt={alt} {...props} />
</Box>
)
}
================================================
FILE: components/bin/GalleryPosts.tsx
================================================
/** @jsxImportSource theme-ui */
import Image from 'next/image'
import styles from '../../public/bin/style/gallery.module.css'
import PartTag from './PartTag'
type BinPostProps = {
title: string
desc: string
slack: string
link: string
id: string
date: string
parts?: string[]
}
const BinPost = ({
title = 'Bin Post',
desc = 'Bin Project',
slack = '',
link = '',
id,
date,
parts
}: BinPostProps) => {
link = link.trim()
if (!/^https?:\/\//i.test(link)) {
link = 'https://' + link
}
const projectID = link.split('/')[4]
const imgLink = `https://thumbs.wokwi.com/projects/${projectID}/social/bin.png`
function handleClick() {
if (typeof window !== 'undefined') {
window.open(link, '_blank')
}
}
function formatDate(dateString) {
const inputDate = new Date(dateString)
const now = new Date()
const oneDay = 24 * 60 * 60 * 1000 // Number of milliseconds in one day
// Check if the input date is within the last 24 hours
if (now.getTime() - inputDate.getTime() < oneDay) {
const hours = inputDate.getHours().toString().padStart(2, '0')
const minutes = inputDate.getMinutes().toString().padStart(2, '0')
return `Today at ${hours}:${minutes}`
} else {
// Format the date to "Month day, year"
const options = {
year: 'numeric',
month: 'long',
day: 'numeric'
} as const
return inputDate.toLocaleDateString(undefined, options)
}
}
if (parts) {
parts = parts.filter(
part => part !== 'recvK14pXAY1tn3HQ' && part !== 'rec5TQNvkGkscsGuQ'
) //Filter out breadboards and raspberry pi
}
return (
<div alt={id} className={styles.gallery_card} onClick={handleClick}>
<h1 className={styles.card_title}>
{title}
<br />
</h1>
<div className={styles.img_container}>
<Image src={imgLink} alt="Project Image" width={1200} height={628} />
</div>
<p className={styles.card_desc}>{desc}</p>
<span className={styles.slack}>
{(slack ? (slack.startsWith('@') ? slack : `@${slack}`) : '') + ' '}
</span>
<span className={styles.date}>{formatDate(date)}</span>
<div className={styles.tag_container}>
{parts &&
parts.map(part => {
return <PartTag key={part} partID={part} />
})}
</div>
</div>
)
}
export default BinPost
================================================
FILE: components/bin/PartTag.module.css
================================================
.tag {
color: e1e1e1;
padding: 4px 10px;
border-radius: 20px;
width: fit-content;
max-width: 300px;
display: flex;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: transform 0.3s ease;
box-sizing: border-box;
}
.tag:hover {
cursor: pointer;
transform: scale(1.1);
}
.outlined {
border: 5px dotted #c5c5c5;
}
================================================
FILE: components/bin/PartTag.tsx
================================================
import React from 'react'
import styles from './PartTag.module.css'
import { useState } from 'react'
const PartTag = ({ partID, search = false, addFilter, removeFilter }) => {
const [isOutlined, setIsOutlined] = useState(false)
const handleClick = () => {
if (search) {
setIsOutlined(prevState => !prevState)
if (isOutlined) {
removeFilter(partID)
} else {
addFilter(partID)
}
}
}
let backgroundColor: string
let text: string
switch (partID) {
case 'recltWikgPdLvpJfe':
backgroundColor = '#0000FF' // Vibrant blue
text = 'Servo'
break
case 'recRzllr0dui91NLd':
backgroundColor = '#008000' // Vibrant green
text = 'LED'
break
case 'recM7OOofV9Bp7AM9':
backgroundColor = '#FF0000' // Vibrant red
text = 'ESP32'
break
case 'recALoD1CCKt3CxKE':
backgroundColor = '#800080' // Vibrant purple
text = 'Buzzer'
break
case 'rechtwyljZ5WR8DtR':
backgroundColor = '#FF4500' // Vibrant orange
text = 'Slider'
break
case 'recry1GsMO6QLakzw':
backgroundColor = '#8B4513' // Dark brown
text = 'Photoresistor'
break
case 'recjRu1vTAU3qDanE':
backgroundColor = '#FF1493' // Vibrant pink
text = 'LCD'
break
case 'recrgS7NnxS42tkmg':
backgroundColor = '#A52A2A' // Vibrant brown
text = 'LED Screen'
break
case 'recocuypi4xP0UgAj':
backgroundColor = '#000000' // Black
text = 'Joystick'
break
case 'recgLUxtFZHufN70W':
backgroundColor = '#1E90FF' // Dodger blue
text = 'LED Bar Graph'
break
case 'recKBAnftT9PgppUC':
backgroundColor = '#00FFFF' // Vibrant cyan
text = 'Shift Register'
break
case 'recibIXNCSdhDHjXD':
backgroundColor = '#FF00FF' // Vibrant magenta
text = 'Thermistor'
break
case 'recwSKHd3anpKqNbg':
backgroundColor = '#00FF00' // Vibrant lime
text = 'IR Receiver'
break
case 'recLRovQNumB1Et8B':
backgroundColor = '#008080' // Vibrant teal
text = 'Range Finder'
break
case 'recMVBkeJ4KQdZihl':
backgroundColor = '#808000' // Vibrant olive
text = 'Keypad'
break
case 'recGrj5GpSExI18Ff':
backgroundColor = '#000080' // Vibrant navy
text = 'Humidity'
break
case 'rec9G0CAXM0kdp7HY':
backgroundColor = '#800000' // Vibrant maroon
text = 'RTC'
break
case 'rec4vTiJIx4UP8Thl':
backgroundColor = '#DAA520' // Goldenrod
text = 'Motion Sensor'
break
case 'reczWN9rZOY95VXOT':
backgroundColor = '#FF8C00' // Dark orange
text = 'LED Matrix'
break
case 'recNjAmh8gF0gZNtI':
backgroundColor = '#FF6347' // Tomato red
text = 'Accelerometer'
break
case 'recPmyV5b8cvaMtTk':
backgroundColor = '#4B0082' // Vibrant indigo
text = 'Neopixel LED'
break
case 'recj5b4DKez4GNa8i':
backgroundColor = '#87CEEB' // Vibrant sky blue
text = 'Relay'
break
case 'rec5TQNvkGkscsGuQ':
backgroundColor = '#9932CC' // Vibrant orchid
text = 'Pico W'
break
case 'recqffGd1j1jRh56m':
backgroundColor = '#DDA0DD' // Vibrant plum
text = 'Multicolor LED'
break
case 'recJUolkJURydamzG':
backgroundColor = '#CD5C5C' // Vibrant light coral
text = 'Encoder'
break
case 'rec7lggt0DsgrWHzc':
backgroundColor = '#20B2AA' // Vibrant light sea green
text = 'Temp Sensor'
break
case 'rectVgu4kWbbaqccc':
backgroundColor = '#FFA07A' // Vibrant light salmon
text = 'Button'
break
case 'recWKEXSaByRvl68t':
backgroundColor = '#4682B4' // Vibrant light steel blue
text = '4 Digit Display'
break
default:
backgroundColor = 'gray' // Default gray
text = 'Invalid Tag'
console.log('invalid', partID)
}
return (
<div
style={{ backgroundColor }}
className={styles.tag + (isOutlined ? ` ${styles.outlined}` : '')}
onClick={handleClick}
>
{text}
</div>
)
}
export default PartTag
================================================
FILE: components/bin/nav.tsx
================================================
import React from 'react'
import styles from '../../public/bin/style/gallery.module.css'
const Nav = () => {
return (
<div className={styles.nav}>
<button
className={styles.nav_button}
onClick={() => (window.location.href = '/bin')}
>
Bin Home
</button>
<button
className={styles.nav_button}
onClick={() => (window.location.href = '/bin/gallery')}
>
Gallery
</button>
</div>
)
}
export default Nav
================================================
FILE: components/bin/rsvp-form.tsx
================================================
/** @jsxImportSource theme-ui */
import { Checkbox, Input, Label, Text, Box } from 'theme-ui'
import useForm from '../../lib/use-form'
import Submit from '../submit'
import { Slide } from '../react-reveal-compat'
export default function RsvpForm() {
const { status, formProps, useField } = useForm('/api/bin/rsvp', null, {
clearOnSubmit: 5000,
method: 'POST',
initData: {},
bearer: null
})
return (
<>
<form {...formProps}>
<Label>
<Text>Email</Text>
<Input
{...useField('email')}
placeholder="fiona@hackclub.com"
required
sx={{ border: '1px solid', borderColor: 'muted', mb: 2 }}
/>
</Label>
<Label variant="labelHoriz" sx={{ fontSize: 1, pt: 2 }}>
<Checkbox
{...useField('high_schooler', 'checkbox')}
defaultChecked={false}
/>
I am a current high school student or younger.
</Label>
<Label variant="labelHoriz" sx={{ fontSize: 1, pt: 2 }}>
<Checkbox {...useField('stickers', 'checkbox')} />I want a sticker
sheet.
</Label>
<Box
sx={{
display: (useField('stickers', 'checkbox') as any).checked
? 'block'
: 'none'
}}
>
<Slide left delay={20}>
<Label mt={2}>
Address (line 1)
<Input
{...useField('address_line_1')}
placeholder="1 Hacker Way"
sx={{ border: '1px solid', borderColor: 'muted' }}
/>
</Label>
<Label mt={2}>
Address (zip code)
<Input
{...useField('address_zip')}
placeholder="01337"
sx={{ border: '1px solid', borderColor: 'muted' }}
/>
</Label>
</Slide>
</Box>
<Submit
status={status}
labels={{
default: 'Submit',
error: 'Something went wrong',
success: 'Success!'
}}
/>
</form>
</>
)
}
================================================
FILE: components/bio.tsx
================================================
/** @jsxImportSource theme-ui */
import Icon from '@hackclub/icons'
import { useState } from 'react'
import { Box, Card, Flex, Text } from 'theme-ui'
export default function Bio({ popup = true, spanTwo = false, ...props }) {
const { name, teamRole, pronouns, text, subrole, email, href, video, img } =
props
const [expand, setExpand] = useState(false)
return (
<>
<Card
bg="snow"
p={popup ? [2, 2, 2] : [3, 3, 3]}
py={popup ? [3, 3, 3] : [4, 4, 4]}
sx={{
display: 'flex',
alignItems: popup ? 'center' : 'flex-start',
transition: 'transform 0.125s ease-in-out',
'&:hover': { transform: 'scale(1.025)' },
cursor: (text && popup) || href ? 'pointer' : null,
textDecoration: 'none',
maxWidth: '600px',
zIndex: !popup ? 1003 : 5,
maxHeight: '90vh',
overflowY: 'hidden',
overscrollBehavior: 'auto',
gridColumn: !spanTwo ? null : [null, null, '1 / span 2'],
position: 'relative'
}}
as={href && !text ? 'a' : 'div'}
href={href}
target="_blank"
onClick={() => {
if (text && popup) {
setExpand(true)
}
}}
>
{img && (
<Box
as="img"
src={img}
alt={name}
sx={{
width: 96,
height: 96,
minWidth: 96,
borderRadius: '50%',
objectFit: 'cover',
mr: 3
}}
/>
)}
<Box>
<Text sx={{ fontSize: [3, 3, 3] }} variant="headline" color="black">
{name}
</Text>
<Flex>
<Text>
<Text
color="#24B5A5"
variant="subheadline"
fontSize={2}
sx={{
mb: ['0px', '0px', '0px'],
fontSize: '1.1em',
width: 'fit-content'
}}
>
{teamRole}
</Text>
{subrole && (
<>
<br />
<Text
color="#24B5A5"
sx={{
mb: ['0px', '0px', '0px'],
fontSize: 1,
fontWeight: 400,
width: 'fit-content'
}}
>
{subrole}
</Text>
</>
)}
{pronouns && (
<Text fontSize={1} ml={1} color="muted" align="center">
({pronouns})
</Text>
)}
</Text>
</Flex>
{!popup &&
email &&
(email.includes('@') ? (
<Text color="muted" as={'a'} href={`mailto:${email}`}>
{email}
<br />
</Text>
) : (
<Text
color="muted"
as={'a'}
href={`mailto:${email}@hackclub.com`}
>
{email}@hackclub.com
<br />
</Text>
))}
{!popup && (
<>
<Text mt={2} mb={0} color="black">
{text}
</Text>
{video && (
<Flex
sx={{
marginTop: 4,
marginX: 5,
justifyContent: 'center',
aspectRatio: 4 / 3
}}
>
<iframe
width="100%"
src={video}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
/>
</Flex>
)}
</>
)}
{!popup && href && (
<Flex sx={{ alignItems: 'center' }}>
<Text
sx={{
transform: 'translateX(-4px) translateY(2px)',
display: 'inline-flex',
alignItems: 'center'
}}
>
<Icon glyph="external-fill" size={24} />
</Text>
<Text
mt={1}
mb={0}
color="black"
as={'a'}
target="_blank"
href={href}
sx={{ transform: 'translateX(-2px)' }}
>
{href}
</Text>
</Flex>
)}
</Box>
</Card>
{popup && expand && (
<Flex
sx={{
position: 'fixed',
zIndex: 1004,
top: 0,
left: 0,
height: '100vh',
width: '100vw',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(0,0,0,0.6)',
pb: 4
}}
>
<Bio {...props} popup={false} />
<Flex
sx={{
position: 'fixed',
zIndex: 1002,
top: 0,
left: 0,
height: '100vh',
width: '100vw',
alignItems: 'center',
justifyContent: 'center',
pb: 4
}}
onClick={() => setExpand(false)}
/>
</Flex>
)}
</>
)
}
================================================
FILE: components/boardbio.tsx
================================================
/** @jsxImportSource theme-ui */
import Icon from '@hackclub/icons'
import { useState } from 'react'
import { Avatar, Box, Card, Flex, Text } from 'theme-ui'
export default function BoardBox({ popup = true, ...props }) {
const { img, name, teamRole, pronouns, text, subrole, email, href, video } =
props
const [expand, setExpand] = useState(false)
return (
<>
<Card
bg="#d9f7ed"
sx={{
display: 'flex',
flexDirection: popup ? 'column' : 'row',
alignItems: popup ? 'center' : 'flex-start',
justifyContent: popup ? 'center' : 'flex-start',
transition: 'transform 0.125s ease-in-out',
'&:hover': { transform: 'scale(1.025)' },
cursor: (text && popup) || href ? 'pointer' : null,
textDecoration: 'none',
maxWidth: popup ? 'auto' : '600px',
zIndex: !popup ? 1003 : 5,
maxHeight: popup ? 'auto' : '90vh',
overflowY: 'hidden',
position: 'relative'
}}
as={href && !text ? 'a' : 'div'}
href={href}
target="_blank"
onClick={() => {
if (text && popup) {
setExpand(true)
}
}}
>
{popup ? (
<>
<Text
variant="headline"
sx={{
fontSize: subrole ? 3 : 4,
textAlign: 'center',
mb: subrole ? 0 : 1,
mt: subrole ? -3 : -2
}}
color="black"
>
{name}
</Text>
<Text
color="#24B5A5"
variant="subheadline"
sx={{
fontSize: subrole ? 1 : 3,
textAlign: 'center',
mb: subrole ? 0 : 2
}}
>
{teamRole}
</Text>
{subrole && (
<Text
color="#24B5A5"
sx={{
fontSize: 1,
textAlign: 'center',
mb: 2,
fontWeight: 400
}}
>
{subrole}
</Text>
)}
<Box
as="img"
src={img}
alt={name}
sx={{
width: '120px',
height: '120px',
borderRadius: '50%',
objectFit: 'cover',
mb: subrole ? -3 : -2
}}
/>
</>
) : (
<>
<Avatar
size={64}
width={64}
height={64}
mr={3}
src={img}
alt={name}
sx={{
overflow: 'hidden',
objectFit: 'cover',
transition: 'transform 0.125s ease-in-out',
'&:hover': { transform: 'rotate(-8deg) scale(1.25)' },
flexShrink: 0,
width: '64px',
height: '64px'
}}
/>
<Box>
<Text
sx={{ fontSize: [3, 3, 3] }}
variant="headline"
color="black"
>
{name}
</Text>
<Flex>
<Text>
<Text
color="#24B5A5"
variant="subheadline"
fontSize={2}
sx={{
mb: ['0px', '0px', '0px'],
fontSize: '1.1em',
width: 'fit-content'
}}
>
{teamRole}
</Text>
{subrole && (
<>
<br />
<Text
color="#24B5A5"
sx={{
mb: ['0px', '0px', '0px'],
fontSize: 1,
fontWeight: 400,
width: 'fit-content'
}}
>
{subrole}
</Text>
</>
)}
{pronouns && (
<Text fontSize={1} ml={1} color="muted" align="center">
({pronouns})
</Text>
)}
</Text>
</Flex>
{email &&
(email.includes('@') ? (
<Text color="muted" as={'a'} href={`mailto:${email}`}>
{email}
<br />
</Text>
) : (
<Text
color="muted"
as={'a'}
href={`mailto:${email}@hackclub.com`}
>
{email}@hackclub.com
<br />
</Text>
))}
<Text mt={2} mb={0} color="black">
{text}
</Text>
{video && (
<Flex
sx={{
marginTop: 4,
marginX: 5,
justifyContent: 'center',
aspectRatio: 4 / 3
}}
>
<iframe
width="100%"
src={video}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
/>
</Flex>
)}
{href && (
<Flex sx={{ alignItems: 'center' }}>
<Text
sx={{
transform: 'translateX(-4px) translateY(2px)',
display: 'inline-flex',
alignItems: 'center'
}}
>
<Icon glyph="external-fill" size={24} />
</Text>
<Text
mt={1}
mb={0}
color="black"
as={'a'}
target="_blank"
href={href}
sx={{ transform: 'translateX(-2px)' }}
>
{href}
</Text>
</Flex>
)}
</Box>
</>
)}
</Card>
{popup && expand && (
<Flex
sx={{
position: 'fixed',
zIndex: 1004,
top: 0,
left: 0,
height: '100vh',
width: '100vw',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(0,0,0,0.6)',
pb: 4
}}
>
<BoardBox {...props} popup={false} />
<Flex
sx={{
position: 'fixed',
zIndex: 1002,
top: 0,
left: 0,
height: '100vh',
width: '100vw',
alignItems: 'center',
justifyContent: 'center',
pb: 4
}}
onClick={() => setExpand(false)}
/>
</Flex>
)}
</>
)
}
================================================
FILE: components/color-switcher.tsx
================================================
import { IconButton, useColorMode } from 'theme-ui'
const ColorSwitcher = props => {
const [mode, setMode] = useColorMode()
return (
<IconButton
onClick={() => setMode(mode === 'dark' ? 'light' : 'dark')}
title={`Switch to ${mode === 'dark' ? 'light' : 'dark'} mode`}
sx={{
position: 'absolute',
top: [2, 3],
right: [2, 3],
color: 'primary',
cursor: 'pointer',
borderRadius: 'circle',
transition: 'box-shadow .125s ease-in-out',
':hover,:focus': {
boxShadow: '0 0 0 3px',
outline: 'none'
}
}}
{...props}
>
<svg viewBox="0 0 32 32" width={24} height={24} fill="currentcolor">
<circle
cx={16}
cy={16}
r={14}
fill="none"
stroke="currentcolor"
strokeWidth={4}
/>
<path d="M 16 0 A 16 16 0 0 0 16 32 z" />
</svg>
</IconButton>
)
}
export default ColorSwitcher
================================================
FILE: components/comma.ts
================================================
export default function Comma({ children }) {
return children
? children.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
: ''
}
================================================
FILE: components/directoryModal.tsx
================================================
/** @jsxImportSource theme-ui */
export const badges = [
{
label: 'Transparent',
id: 'Transparent',
tooltip: 'Transparent',
color: 'purple',
icon: 'explore',
match: org => org.isTransparent
}
]
export function getBadgesForOrg(org: { [key: string]: any }): typeof badges {
return badges.filter(badge => badge.match?.(org))
}
import { Badge as ThemeBadge, Box, Flex } from 'theme-ui'
import { Text, Button, Card } from 'theme-ui'
import Icon from '@hackclub/icons'
import Image from 'next/image'
type OrganizationModalProps = {
organization: {
name: string
location: {
country: string
}
branding: {
backgroundImage: string
logo?: string
description?: string
}
links: {
website?: string
financials?: string
donations: string
}
raw: {
transparent?: boolean
}
}
onClose: () => void
}
export function OrganizationModal({
organization,
onClose
}: OrganizationModalProps) {
return (
<Box
sx={{
position: 'fixed',
top: '0px',
left: '0px',
right: '0px',
bottom: '0px',
zIndex: 1000,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
bg: '#00000044'
}}
onClick={onClose}
>
<Card
sx={{
width: 'min(640px, 90%)',
bg: 'elevated',
borderRadius: '10px',
position: 'relative',
display: 'flex',
boxSizing: 'border-box',
flexDirection: 'column',
maxHeight: '90vh',
overflowY: 'scroll'
}}
onClick={e => {
e.stopPropagation()
}}
>
<Flex
sx={{
position: 'absolute',
top: '10px',
right: '10px',
width: '40px',
height: '40px',
bg: 'smoke',
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer'
}}
>
<Icon glyph="view-close" size={32} onClick={onClose} />
</Flex>
<Flex
sx={{
flexDirection: 'column',
alignItems: 'stretch',
gap: 3
}}
>
<Flex
sx={{
flexDirection: 'column',
justifyContent: 'end',
minHeight: '200px',
m: -4,
p: '24px',
boxSizing: 'content-box',
backgroundPosition: 'center center',
color: 'white',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
backgroundImage: `url('${organization.branding.backgroundImage}')`
}}
>
<Flex
sx={{
flexDirection: ['column', 'row', 'row'],
alignItems: 'center',
justifyContent: 'start',
gap: '24px'
}}
>
{organization.branding.logo && (
<Image
alt={`${organization.name}'s logo`}
src={organization.branding.logo}
width={128}
height={128}
sx={{
width: 'auto',
height: '96px',
borderRadius: '8px'
}}
/>
)}
<Flex
sx={{
flexDirection: 'column'
}}
>
<Text
variant="title"
sx={{
wordBreak: 'break-word',
marginBottom: '12px',
textAlign: ['center', 'left', 'left'],
fontSize: ['36px', '36px', '36px'],
color: 'white',
textShadow: '0px 10px 10px rgba(0, 0, 0, 0.25)'
}}
>
{organization.name}
</Text>
<Text
variant="subheadline"
sx={{
whiteSpace: 'nowrap',
textAlign: ['center', 'left', 'left'],
color: 'white',
textShadow: '0px 10px 10px rgba(0, 0, 0, 0.25)',
marginBottom: '0px'
}}
>
{organization.location.country}
</Text>
</Flex>
</Flex>
</Flex>
{/* Badges */}
<Flex
sx={{
flexDirection: 'row',
justifyContent: ['center', 'start', 'start'],
alignItems: 'center',
gap: 2,
width: '100%',
mt: '40px',
mb: -2,
flexWrap: 'wrap'
}}
>
{/* hardcoded "nonprofit" badge */}
<ThemeBadge
as="span"
aria-label="Nonprofit"
sx={{
bg: 'blue',
color: 'snow',
fontSize: '20px',
textShadow: 'none',
borderRadius: '15px',
px: 2,
display: 'block'
}}
>
Nonprofit
</ThemeBadge>
{organization.raw.transparent && (
<ThemeBadge
as="span"
aria-label="Transparent"
sx={{
bg: 'purple',
color: 'snow',
fontSize: '20px',
textShadow: 'none',
borderRadius: '15px',
px: 2,
display: 'block'
}}
>
Transparent
</ThemeBadge>
)}
</Flex>
<Flex
sx={{
flexDirection: 'row',
alignItems: 'start',
gap: 4,
width: '100%'
}}
>
{/* info & buttons */}
<Flex
sx={{
flexDirection: 'column',
alignItems: 'start',
flex: '1'
}}
>
{organization.branding.description && (
<Text variant="lead" style={{ fontSize: '22px' }}>
{organization.branding.description}
</Text>
)}
<Flex
sx={{
flexDirection: 'column',
alignItems: 'start',
my: 2,
ml: -1,
gap: 1
}}
>
{organization.links.website && (
<Flex
as="a"
target="_blank"
href={organization.links.website}
sx={{
flexDirection: 'row',
justifyContent: 'start',
alignItems: 'center',
color: 'slate',
textDecoration: 'none'
}}
>
<Icon glyph="web" size={38} />
<Text style={{ fontSize: '20px', marginLeft: '6px' }}>
Website
</Text>
<Icon
glyph="external"
size={20}
style={{ marginLeft: '2px', marginBottom: '6px' }}
/>
</Flex>
)}
{organization.links.financials && (
<Flex
as="a"
target="_blank"
href={organization.links.financials}
sx={{
flexDirection: 'row',
justifyContent: 'start',
alignItems: 'center',
color: 'slate',
textDecoration: 'none'
}}
>
<Icon glyph="explore" size={38} />
<Text style={{ fontSize: '20px', marginLeft: '6px' }}>
Transparent Finances
</Text>
<Icon
glyph="external"
size={20}
style={{ marginLeft: '2px', marginBottom: '6px' }}
/>
</Flex>
)}
</Flex>
</Flex>
</Flex>
<Flex
sx={{
flexDirection: 'row',
width: '100%',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<Text sx={{ display: ['none', 'none', 'block'] }}>
All donations are tax-deductible.
</Text>
<Button
as="a"
variant="lg"
href={organization.links.donations}
target="_blank"
sx={{
backgroundImage: t => t.util.gx('green', 'blue'),
width: ['100%', 'auto', 'auto']
}}
>
<Flex
sx={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
width: '24px',
height: '24px',
marginLeft: -1,
marginRight: 2
}}
>
<Icon glyph="friend" size={20} style={{ scale: '2.5' }} />
</Flex>
Make a Donation
</Button>
</Flex>
</Flex>
</Card>
</Box>
)
}
================================================
FILE: components/donate/donors.json
================================================
{
"1517": "http://www.1517fund.com/",
"Aakash Adesara": null,
"Abby Fischler": null,
"Achal Srinivasan": null,
"Adora Svitak": null,
"Adrienne Tran": null,
"Alex Koren": "https://twitter.com/alexekoren",
"Alex Peña": "http://www.alexaaronpena.com/home.html",
"Alexander Turin": null,
"Alishaan Ali": "https://alishaan.io",
"Allyson Dias": "https://twitter.com/AllysonDias",
"Amanda Mae-an Wofford": null,
"Amanda Southworth": "https://twitter.com/amndasuthwrth",
"Amogh Chaubey": "https://amogh.sh",
"Amritha Jayanti": null,
"Amy Sorto": null,
"Andrew Breckenridge": "https://github.com/AndrewSB",
"Andrew Downing": null,
"Andrew Ninh": null,
"Andrew Zoerb": null,
"Andy Haden": "https://github.com/andyh2",
"Angus Jyu": null,
"Ankit Ranjan": "http://www.ankit.io/",
"Ankit Shah": "https://twitter.com/AnkitShah",
"Ann Mazuk": null,
"Arjun Dileep": null,
"Asta Lasf": null,
"Athul Blesson": "https://www.linkedin.com/in/athul-blesson-92ab3b115/",
"Avi Romanoff": null,
"Ben Yu": "https://twitter.com/intenex",
"Beyang Liu": "https://www.linkedin.com/in/beyang-liu-07651227",
"Bhargav Yadavalli": "https://www.linkedin.com/in/bhargavy/",
"Bigglesworth Family Foundation": "https://www.bigglesworthff.org/",
"Brayden McLean": null,
"Brett Neese": null,
"Brian Nguyen": null,
"Cayce Beames": null,
"Chaleb Pommells": null,
"Changbai Li": "https://changbai.li/",
"Chaoyi Zha": null,
"Chris Van Pelt": "https://twitter.com/vanpelt",
"Chris Walker": "https://twitter.com/EnDimensions",
"Christian Zenaty": null,
"Christina Kim": null,
"Christina Lewis Halpern": null,
"Clara Tsao": null,
"Connie Liu": null,
"Daniel Sinclair": null,
"David C Farnan-Williams": null,
"Dennis Ashendorf": null,
"Dhruv Maheshwari": null,
"Doris Capet": null,
"Edward Jiang": null,
"Elon Musk": "https://en.wikipedia.org/wiki/Elon_Musk",
"Emily Pries": null,
"Emily Tseng": null,
"Erik Batista": null,
"Ethan Resnick": null,
"Evan Shui": null,
"Fast Forward": "https://www.ffwd.org/",
"Fern Ray": null,
"Ferran Arricivita": null,
"Fiona Carty": "https://dribbble.com/nonafiona",
"Garrett Wesley": null,
"Gautam Bhargava": null,
"Gautam Mittal": null,
"Gemma Busoni": "https://twitter.com/gemmabusoni",
"Geoff Ralston": null,
"Gisela Kottmeier": null,
"Google.org": "https://www.google.org/",
"Gordon Smith": null,
"Guilherme de Souza": null,
"Hallie Lomax": "http://lomax.ninja/",
"Hamza bnr Bellucci": null,
"Hortensia Gomez-tirella": null,
"Ishaan Parikh": null,
"Jack Chak": null,
"Jackcheal Dang": null,
"Jacob Haap": "https://jacobhaap.com",
"Jake Brownson": null,
"Jamsheed Mistri": null,
"Jason Marmon": null,
"Jay Freeman": "http://www.saurik.com/",
"Jeff Hilnbrand": null,
"Jeffrey Owens": null,
"Jennifer Kakuske": null,
"Jevin Sidhu": null,
"Jim Latta": null,
"Joe Lonsdale": null,
"Joseph Douglas": "https://twitter.com/JosephRDouglas",
"Josh & Michelle Weatherspoon": null,
"Julie Latta": null,
"Junius Sim": "https://www.linkedin.com/in/juniussim/",
"Justin Brezhnev": null,
"Justin Harris": "https://envisionwithjustin.com/",
"Kartik Talwar": null,
"Katie Latta": null,
"Keala Lusk": "http://kea.la/",
"Kelly Peng": null,
"Kevin Chu": null,
"Kevin Conner": null,
"Kevin Wang": "https://twitter.com/kevinverse",
"Kunal Batra": null,
"Kyle Emile": null,
"Lachlan Campbell": "https://lachlanjc.com",
"LạiDuy": null,
"Lan Paje": "https://twitter.com/lanpaje",
"Larry Weiss": null,
"Lia Stanciu Gregory": null,
"Liam Horne": "https://twitter.com/liamihorne",
"Mackenzie Burnett": null,
"Maria Choi": null,
"Mark Prideaux": null,
"Matthew Stanciu": "https://matthewstanciu.me",
"Megan Cui": "https://megancui.com/",
"Michael Akilian": null,
"Michael Copley": null,
"Michael Hulet": null,
"Michael Yoo": null,
"Michelle Leveille": null,
"Mick Donahue": null,
"Mike Swift": "https://twitter.com/swiftalphaone",
"Mingjie Jiang": "https://mingjie.info",
"Mohit Bhatia": "https://github.com/mohitbhatia1994",
"Myles Byrne": "https://twitter.com/quackingduck",
"Nate Wienert": "https://github.com/natew",
"Nelson Gomez": null,
"Nick Quinlan": null,
"Nikhil Srinivasan": null,
"Oliver Belanger": null,
"Patrick Pistor": "https://github.com/yogert96",
"Paul Cichocki": null,
"Phat Le": "https://www.phatle.com/",
"Phil Hedayatnia": "http://www.hedayatnia.com/",
"Polly Schneider": null,
"Prisma Data, Inc.": "https://prismic.io/",
"Quinn Slack": "https://qslack.com/",
"Rashid Al-Abri": null,
"Reid Workman": null,
"Robert Gregory": null,
"Rohith Varanasi": null,
"Rush Wofford": null,
"Ryan Orbuch": "https://twitter.com/orbuch",
"Saharsh Yeruva": "https://saharsh.tech",
"Samay Shamdasani": "https://shamdasani.org/",
"Samuel Escapa": "https://github.com/saescapa",
"Santiago Siri": "https://twitter.com/santisiri",
"Scott Chow": null,
"Scott Motte": "https://www.scottmotte.com/",
"Sean Kim": null,
"Selynna Sun": "https://www.selynnasun.com/",
"Shariq Hashme": "https://shar.iq/",
"Sheryl Kern-Jones": null,
"Shrav Mehta": null,
"Shriya Nevatia": null,
"Shriyash Jalukar": "https://www.linkedin.com/in/shriyashjalukar/",
"Siddhartha Desai": null,
"Sina Hamedian": null,
"Sourcegraph": "https://sourcegraph.com/",
"Spencer Yen": null,
"Stephanie He": null,
"Steven Duong": null,
"Tanya Latta": null,
"Taylor Otwell": null,
"Tejas Manohar": "https://tejas.io/",
"The Reva & David Logan Foundation": "http://www.loganfdn.org/",
"The Thiel Fellowship": "http://thielfellowship.org/",
"Thiel Foundation": "http://www.thielfoundation.org/",
"Tiffany Yin": null,
"Tom Preston-Werner": "http://tom.preston-werner.com/",
"Truman Chan": null,
"Tyler Hilliard": null,
"Victor Truong": "https://victortruong.net",
"Waj Waj": null,
"Will Gaybrick": null,
"William Wold": null,
"Xavier Shay": "https://xaviershay.com/",
"Yacoub Oulad Daoud": null,
"Yev Barkalov": "http://www.yev.sh/",
"Zach Holman": "https://zachholman.com/",
"Zane Davis-Barrs": "https://zane.sh/",
"Zane Sindhu": null
}
================================================
FILE: components/donate/sponsors.tsx
================================================
/** @jsxImportSource theme-ui */
import styled from '@emotion/styled'
import { Box, Image, Link } from 'theme-ui'
const Base = styled(Box)`
display: grid;
grid-gap: 18px;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
align-items: center;
justify-content: center;
img {
margin: auto;
max-width: 12rem;
}
`
const Sponsor = ({ name, href, img, ...props }) => (
<Link href={href || `https://${name.toLowerCase()}.com`} target="_blank">
<Image
src={`/inkind_logos/${img || name.toLowerCase() + '.svg'}`}
alt={name}
{...props}
/>
</Link>
)
const Sponsors = props => (
<Base maxWidth={48} {...props}>
{[
'Vercel',
'Slack',
'Netlify',
'FullStory',
'BrowserStack',
'Stripe',
'Segment',
'Bugsnag',
'Google',
'Dialpad'
].map(name => (
<Sponsor name={name} key={name} />
))}
<Sponsor name="Checkly" href="https://checklyhq.com" img="checkly.svg" />
<Sponsor
name="Fast Forward"
href="https://ffwd.org"
img="fastforward.png"
/>
<Sponsor
name="Intercom"
href="https://www.intercom.com"
img="intercom.png"
/>
</Base>
)
export default Sponsors
================================================
FILE: components/dot.tsx
================================================
import { Text } from 'theme-ui'
import { keyframes } from '@emotion/react'
const flashing = keyframes({
from: { opacity: 0 },
'50%': { opacity: 1 },
to: { opacity: 0 }
})
export default function Dot({ hideOnMobile }) {
return (
<Text
sx={{
bg: 'green',
color: 'white',
borderRadius: 'circle',
lineHeight: 0,
width: '.4em',
height: '.4em',
marginRight: '.4em',
marginBottom: '.12em',
animationName: `${flashing}`,
animationDuration: '3s',
animationTimingFunction: 'ease-in-out',
animationIterationCount: 'infinite',
display: hideOnMobile ? ['none', 'default'] : 'default'
}}
/>
)
}
================================================
FILE: components/elon.mdx
================================================
Elon Musk holds a special place amongst hackers. After growing up in a difficult
family situation in South Africa, working his way in small jobs until reaching
Los Angeles, teaching himself to code, and making hundreds of millions
co-founding PayPal, he kept on building.
It was a huge honor last month to have Elon [spend an hour in a Hack Club
AMA](https://youtu.be/riru9OzScwk)—at one point he remarked we were “asking
better questions than all the mainstream media.”
Afterwards, Elon wanted to support Hack Club further.
# Today, I’m proud to share: Elon Musk is donating $500,000 to Hack Club.
In so many ways, this is a milestone for every Hack Clubber. 6 years ago, I
started Hack Club as a 16-year-old programmer living on my own, scraping by,
barely able to make rent. Now Elon Musk is one of our largest supporters.
Elon is supporting us because we are a community of builders. When hackers see
problems in the world, we don’t blame someone else: we try to take them on
ourselves to solve. Elon is very selective about the nonprofits he supports and
I’m proud Hack Club is one of them.
So…how is Hack Club going to invest $500k? We want to use this to help 1000 more
students start and join Hack Clubs in their communities. For those already in
Hack Clubs, we look to you to help us make a more high-quality experience. We’re
a lot of what we’ve already been doing (and [what I wrote about at the beginning
of the year](https://zachinto2020.wordpress.com/2019/12/31/as-midnight-approaches/)):
we’ll spend as little money as possible at all times, and we’ll hire a small
number of diverse staff from video game engineers to media producers to make
Hack Club better. We are pushing hard now to expand users of [HCB](https://hackclub.com/fiscal-sponsorship/),
and continuing to try and make the Hack Club Slack the best place to be a teenager on the intenet.
We’ll have a proper announcement in a few weeks, but one thing we’re doing after
winning the [Frank Grant](https://grant.frank.ly/) and now receiving Elon’s
gift, is open sourcing our finances. Hack Club HQ has been running on HCB
since February and starting today, you can see our account publicly at
https://hcb.hackclub.com/hq. You can track how we spend every single dollar of
Elon’s gift. Soon, we will also launch https://frank.ly/ on Hack Club’s
website.
Hack Club’s mission is to build a new generation of hackers. This starts in high
school, where Hack Club students learn to be technically proficient, build their
friend network, learn to raise and spend money, and develop into kind, curious,
thoughtful, optimistic, and honest leaders.
Elon Musk is now supporting you and your work, so go out and do amazing things.
Elon can’t wait to see what you make.
—Zach
================================================
FILE: components/fade-in.tsx
================================================
import React from 'react'
import { Box } from 'theme-ui'
import styled from '@emotion/styled'
import { keyframes } from '@emotion/react'
const fadeIn = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })
const Wrapper = styled(Box)`
@media (prefers-reduced-motion: no-preference) {
animation-name: ${fadeIn};
animation-fill-mode: backwards;
}
`
const FadeIn = ({ duration = 300, delay = 0, ...props }) => (
<Wrapper
{...props}
style={{
...(props.style || {}),
animationDuration: duration + 'ms',
animationDelay: delay + 'ms'
}}
/>
)
export default FadeIn
================================================
FILE: components/fiscal-sponsorship/contact.tsx
================================================
import Icon from '../icon'
import { Flex, Link, Text } from 'theme-ui'
const phoneNumber = '+1 (844) 237-2290'
const phoneNumberUri = '+1-844-237-2290'
const email = 'hcb@hackclub.com'
export default function ContactBanner({ sx }) {
return (
<Flex
sx={{
display: ['none', 'flex'],
bg: 'sunken',
color: 'slate',
alignItems: 'center',
p: 3,
gap: [3, 2],
...sx
}}
>
<Icon
glyph="message"
sx={{ color: 'inherit', flexShrink: 0, my: -1 }}
aria-hidden
/>
<Text
sx={{
textWrap: 'balance',
a: { color: 'inherit', mx: '0.125em', whiteSpace: 'nowrap' }
}}
>
Questions? Email <Link href={`mailto:${email}`}>{email}</Link>{' '}
or call <Link href={`tel:${phoneNumberUri}`}>{phoneNumber}</Link>
</Text>
</Flex>
)
}
================================================
FILE: components/fiscal-sponsorship/directory/card.tsx
================================================
import { Card, Badge as ThemeBadge, Box, Heading, Text, Image } from 'theme-ui'
import { Organization } from '../../../lib/organization'
import Tilt from '../../tilt'
import Icon from '@hackclub/icons'
import Tooltip from '../tooltip'
export const Badge = ({ badge }) =>
badge.image ? (
<ThemeBadge
as="span"
sx={{
backgroundImage: `url("${badge.image}")`,
backgroundSize: 'contain',
backgroundColor: 'unset',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
fontSize: 'inherit',
textShadow: 'none',
borderRadius: 5,
display: 'block',
height: 30,
width: 38
}}
>
<span style={{ opacity: '0' }}>.</span>
</ThemeBadge>
) : (
<ThemeBadge
as="span"
sx={{
bg: badge.color,
color: 'snow',
fontSize: 'inherit',
textShadow: 'none',
borderRadius: 5,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Icon glyph={badge.icon} size={30} />
</ThemeBadge>
)
type OrganizationCardProps = {
organization: Organization
openModal: (organization: Organization) => void
badges: any[]
}
/**
*
* @param {{
* organization: Organization,
* showTags: boolean
* }} props
* @returns
*/
export const OrganizationCard = ({
openModal,
badges,
organization
}: OrganizationCardProps) => (
<Tilt>
<Card
onClick={() => openModal(organization)}
rel="noopener noreferrer"
itemScope
itemType="http://schema.org/Event"
variant="event"
sx={{
justifyContent: 'center',
alignItems: 'center',
minHeight: 128,
color: 'white',
cursor: 'pointer',
textShadow: '0 1px 4px rgba(0, 0, 0, 0.375)',
textDecoration: 'none',
backgroundColor: 'black',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
borderRadius: 'extra',
overflow: 'hidden',
position: 'relative',
p: 3,
height: '100%',
display: 'flex',
px: 3,
backgroundImage: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,0.375) 75%), url('${organization.branding.backgroundImage}')`,
textAlign: 'center',
flexDirection: 'column'
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'end',
alignItems: 'center',
width: '100%',
gap: 2,
flexDirection: 'row',
mb: 3
}}
>
{badges.map((badge, i) => (
<Tooltip.W key={i} text={badge.label} id={badge.id}>
<span className={`tooltipped-${badge.id}`}>
<Badge badge={badge} />
</span>
</Tooltip.W>
))}
</Box>
{organization.branding.logo && (
<Image
src={organization.branding.logo}
alt={`${organization.name} logo`}
loading="lazy"
sx={{
// minWidth: 64,
height: 64,
objectFit: 'contain',
objectPosition: 'left',
borderRadius: 'default',
mt: 'auto'
}}
/>
)}
<Heading
as={'h3'}
itemProp="name"
sx={{
fontSize: [3, 4],
mt: 2,
mb: 3,
overflowWrap: 'anywhere',
width: '100%',
display: 'block'
}}
>
{organization.name}
</Heading>
<Box
as="footer"
sx={{
mt: 'auto',
mb: 0,
width: '100%',
opacity: 0.875,
textTransform: 'none'
}}
>
<>
<Text
as="span"
itemProp="location"
itemScope
itemType="http://schema.org/Place"
>
<span itemProp="address">
{organization.raw.location.continent}
</span>
</Text>
</>
</Box>
<Box sx={{ display: 'none' }}>
<span itemProp="url">{organization.links.website}</span>
</Box>
</Card>
</Tilt>
)
export default OrganizationCard
================================================
FILE: components/fiscal-sponsorship/features.tsx
================================================
import { Box, Heading, Link, Text, Container, Grid } from 'theme-ui'
import Icon from '../icon'
import { Balancer } from 'react-wrap-balancer'
import Image from 'next/image'
import imgLaptop from '../../public/fiscal-sponsorship/laptop.png'
export default function Features() {
return (
<Box sx={{ pt: 5, pb: [5, 6] }}>
<Container>
<Heading as="h2" variant="title" sx={{ mb: 3, maxWidth: 'copyUltra' }}>
<Balancer>
Powerful financial tools built by our nonprofit, for yours.
</Balancer>
</Heading>
<Text as="p" variant="lead" sx={{ color: 'slate', maxWidth: '55ch' }}>
Since day one, we’ve built beautiful, self-serve software to empower
you to raise and spend money without administrative hassle. We’re also
open source!
</Text>
<Grid columns={[null, 2, 3]} sx={{ mt: 4, rowGap: 3, columnGap: 4 }}>
<Module
icon="bank-account"
name="Receive foundation grants"
body="with tax-deductible 501(c)(3) status."
/>
{/* Send money & reimburse via check, ACH, bank wire, & more.
Operate globally with a US Entity.
Issue physical & virtual debit cards to your team.
Get 24 hour support on weekdays.
Pay team members with built-in payroll.
Embed a custom donation form on your website.
We file all your taxes automatically, including Form 990. " */}
<Module
icon="card"
name="Issue physical & virtual debit cards"
body="with receipt tracking & Apple Pay."
/>
<Module
icon="web"
name="Operate globally"
body="with a U.S. legal entity."
/>
<Module
icon="payment-transfer"
name="Send money & reimburse"
body="via check, ACH, bank wire, & more."
/>
<Module
icon="explore"
name="Make your finances transparent"
body="to your team and optionally, public."
/>
<Module
icon="docs"
name="We file all your taxes"
body="automatically, including Form 990."
/>
<Module
icon="admin"
name="Pay team members"
body="with built-in payroll."
/>
<Module
icon="support"
name="Accept donations of any size"
body="with a custom, embeddable online form."
/>
<Module
icon="leader"
name="Get 24-hour support"
body="on weekdays with a dedicated point of contact."
/>
</Grid>
</Container>
<Container variant="copy" sx={{ mt: [4, 5] }}>
<Laptop
href="https://hcb.hackclub.com/reboot"
title="See Reboot’s finances in public"
/>
<Link
href="https://github.com/hackclub/hcb"
title="Open Source"
sx={{ textAlign: 'center' }}
>
<Text variant="caption" as="p" sx={{ color: 'primary', mt: 2 }}>
See our open source on GitHub
</Text>
</Link>
</Container>
</Box>
)
}
function Module({ icon, name, body }) {
return (
<Box
sx={{
display: 'flex',
alignItems: 'start',
columnGap: 3
}}
>
<Box
sx={{
flexShrink: 0,
width: 40,
height: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Icon
size={40}
glyph={icon}
sx={{
color: 'primary',
flexShrink: 0,
display: 'block',
width: 40,
height: 40
}}
/>
</Box>
<Text
as="p"
sx={{
color: 'slate',
lineHeight: '1.375',
fontSize: 20,
m: 0
}}
>
<Balancer>
<Text as="strong" color="slate">
{name}
</Text>{' '}
{body}
</Balancer>
</Text>
</Box>
)
}
function Laptop({ href, title }) {
return (
<Link href={href} title={title} sx={{ textAlign: 'center' }}>
<Image
src={imgLaptop}
alt="Laptop"
unoptimized
style={{
width: '100%',
maxWidth: '100%',
height: 'auto'
}}
/>
<Text variant="caption" as="p" sx={{ color: 'primary', mt: 2 }}>
See <i>Reboot</i>’s finances in Transparency Mode
</Text>
</Link>
)
}
================================================
FILE: components/fiscal-sponsorship/first/apply-button.tsx
================================================
import { Button, Text, Flex } from 'theme-ui'
import Icon from '../../icon'
import Link from 'next/link'
export default function ApplyButton() {
return (
<Link href="https://hcb.hackclub.com/applications/new">
<Button
variant="ctaLg"
sx={{
width: '100%',
height: '4.2rem'
// borderRadius: '1.5rem',
}}
>
<Flex
sx={{
alignItems: 'center',
gap: 3,
mr: '-32px' // Man...
}}
>
<Text color="white" sx={{ fontWeight: 'bold', fontSize: 4 }}>
Apply now
</Text>
<Icon glyph="view-forward" size={46} color="white" />
</Flex>
</Button>
</Link>
)
}
================================================
FILE: components/fiscal-sponsorship/first/features.tsx
================================================
/** @jsxImportSource theme-ui */
import { Box, Heading, Link, Text, Container, Card, Image } from 'theme-ui'
import Icon from '../../icon'
import Masonry from 'react-masonry-css'
import NextImage from 'next/image'
import { Fade } from '../../react-reveal-compat'
export default function Features() {
return (
<Box sx={{ py: 5 }}>
<Box as="a" href="#testimonials">
<Image
src="/fiscal-sponsorship/meet-teams-using-hcb.svg"
alt="yeah"
width={200}
height={100}
sx={{
position: 'absolute',
right: 2,
mt: -36,
display: ['none', 'none', 'none', 'block'],
'&:hover': {
transform: 'scale(1.05)'
}
}}
/>
</Box>
<Container>
<Text variant="heading" sx={{ fontSize: 50 }}>
Everything you'll need.
</Text>
<br />
<br />
<Text sx={{ color: 'muted', maxWidth: '48', fontSize: 28 }}>
Organize your team's finances in real time, receive grants, gain
nonprofit status, & more.
<br />
Use features engineered by <i>FIRST</i> alumni to help you run a
successful team.
</Text>
<br />
<br />
</Container>
<Container>
<Masonry
breakpointCols={{
10000: 3,
640: 2,
480: 1,
default: 1
}}
className="masonry-posts"
columnClassName="masonry-posts-column"
>
<Module
icon="bank-account"
name="Nonprofit status"
body="Become part of Hack Club's legal entity, getting the benefits of our 501(c)(3) tax status."
/>
<ModuleDetails>
<Link
href="https://hcb.hackclub.com/poseidon-robotics"
target="_blank"
>
<NextImage
src="/fiscal-sponsorship/poseidon-dashboard.png"
alt="iPad"
width={500}
height={300}
style={{
maxWidth: '100%',
height: 'auto'
}}
/>
</Link>
</ModuleDetails>
<Module
icon="analytics"
name="Balance & history"
body="Keep everyone on your team and beyond up to date with real-time balance and transaction history."
/>
<Module
icon="card"
name="Debit cards"
body="Issue physical debit cards to all your teammates."
/>
<Module
icon="payment"
name="Grants & donations"
body="Easily receive and deposit money from grants and donations into your account. You'll also get a customizable online donation form to share with friends and family."
/>
<Module
icon="payment-transfer"
name="Reimbursement flow"
body="Reimburse teammates for expenses with flexible money transfer options including ACH, mailed checks, and more."
/>
{/* <Fade bottom>
<Tilt>
<Card
as="div"
sx={{
backgroundImage:
'url("https://cloud-ehtgzdn7u-hack-club-bot.vercel.app/0card.png")',
height: '230px',
backgroundSize: 'cover',
boxShadow: '0 8px 32px rgba(255, 255, 255, 0.0625)'
}}
/>
</Tilt>
</Fade> */}
<Module
icon="support"
name="Support anytime"
body="With 24-hour response time on weekdays, we'll never leave you hanging."
/>
{/* <Tilt>
<Card
as="div"
sx={{
borderRadius: 0,
backgroundColor: '#193046',
boxShadow:
'0 0 2px 0 rgb(0 0 0 / 6%), 0 6px 12px 0 rgb(0 0 0 / 25%)',
'&::before': {
position: 'absolute',
content: '""',
top: 0,
left: 0,
right: 0,
bottom: 0,
margin: '0.5rem',
border: '1px dotted #8492a6'
}
}}
>
<Flex sx={{ justifyContent: 'end' }}>
<Text
sx={{
textTransform: 'uppercase',
fontSize: '10px',
lineHeight: 1
}}
>
Date
</Text>
<Text sx={{ fontFamily: 'cursive' }}>10-10-2020</Text>
</Flex>
<Flex sx={{ width: '100%' }}>
<Text
sx={{
textTransform: 'uppercase',
fontSize: '10px',
lineHeight: 1
}}
>
Pay to the <br />
order of
</Text>
<Text
as="span"
sx={{ fontFamily: 'cursive', textAlign: 'left', ml: 2 }}
>
Hack Club
</Text>
<Text
sx={{
textAlign: 'right',
ml: 'auto'
}}
>
$
<Text
as="span"
sx={{
border: '1px solid rgba(255, 255, 255, 0.25)',
fontFamily: 'cursive'
}}
>
1000
</Text>
</Text>
</Flex>
<Flex sx={{ justifyContent: 'space-between', alignItems: 'end' }}>
<Text sx={{ fontFamily: 'cursive' }}>One thousand only</Text>
<Text
sx={{
textTransform: 'uppercase',
fontSize: '10px',
lineHeight: 1
}}
>
Dollars
</Text>
</Flex>
<Flex
sx={{
justifyContent: 'space-between',
alignItems: 'center',
pb: 1
}}
>
<Text
sx={{
textTransform: 'uppercase',
fontSize: '10px',
lineHeight: 1
}}
>
Memo
<Text
as="span"
sx={{
fontFamily: 'cursive',
fontSize: '12px',
textTransform: 'none'
}}
>
{' '}
Grant for Poseidon Robotics
</Text>
</Text>
<Image
src="/signatures/prophet_orpheus-light.png"
alt="Prophet Orpheus signature"
width={80}
height={30}
/>
</Flex>
<Flex>
<Text
sx={{
fontFamily: 'monospace',
fontSize: '10px',
lineHeight: 1,
pt: 1,
mb: -3
}}
>
⑆ 00000000000 ⑆ 123456789 ⑆
</Text>
</Flex>
</Card>
</Tilt> */}
{/* <Module
icon="rep"
name="No start-up costs"
body="All fees waived on your first $25k until September 1st, 2023. Then, just 7% of revenue (as compared to 10-14% charged by other fiscal sponsors). "
/> */}
</Masonry>
</Container>
<Container
variant="narrow"
sx={{
pt: 3,
borderColor: 'muted',
textAlign: 'center'
}}
>
<Text variant="caption" sx={{ color: 'muted' }}>
Hack Club does not directly provide banking services. Banking services
are provided by FDIC-certified financial institutions.
</Text>
</Container>
<style>{`
.masonry-posts {
display: flex;
width: 100%;
max-width: 100%;
}
.masonry-posts-column {
background-clip: padding-box;
}
.post {
margin-bottom: 16px;
}
@media (max-width: 32em) {
.post:nth-child(8) ~ .post {
display: none;
}
}
@media (min-width: 32em) {
.masonry-posts {
padding-right: 12px;
}
.masonry-posts-column {
padding-left: 12px;
}
.post {
border-radius: 12px;
margin-bottom: 12px;
}
}
@media (min-width: 64em) {
.masonry-posts {
padding-right: 24px;
}
.masonry-posts-column {
padding-left: 24px;
}
.post {
margin-bottom: 24px;
}
}
`}</style>
</Box>
)
}
type ModuleProps = {
icon: string
name: string
body: string
iconColor?: string
}
function Module({ icon, name, body, iconColor }: ModuleProps) {
return (
<Fade bottom>
<Card
variant="primary"
sx={{
display: 'flex',
flexDirection: 'column',
p: [4, null, 4]
}}
className="post"
>
<Box
as="span"
sx={{
width: 'fit-content',
background: iconColor || 'primary',
borderRadius: 'default',
lineHeight: 0,
p: 1,
mb: 1,
display: 'inline-block',
transform: ['scale(0.75)', 'none'],
transformOrigin: 'bottom left',
boxShadow:
'inset 2px 2px 6px rgba(255,255,255,0.2), inset -2px -2px 6px rgba(0,0,0,0.1), 0 1px 4px rgba(0,0,0,0.1), 0 4px 8px rgba(0,0,0,0.1)'
}}
>
<Icon glyph={icon} size={28} />
</Box>
<Box>
<Heading sx={{ color: 'snow', lineHeight: '1.5' }}>{name}</Heading>
<Text
sx={{
color: 'muted',
lineHeight: '1.375',
fontSize: 17
}}
>
{body}
</Text>
</Box>
</Card>
</Fade>
)
}
function ModuleDetails({ children }) {
return (
<Fade bottom>
<Box
sx={{
bg: 'none',
color: 'smoke',
boxShadow: '0 8px 32px rgba(255, 255, 255, 0.0625)',
borderRadius: 'default',
p: 0,
mb: 3
}}
>
{children}
</Box>
</Fade>
)
}
function Document({ name, cost }) {
return (
<Box sx={{ display: 'flex' }}>
<Icon
size={28}
mr={1}
glyph="payment"
sx={{ flexShrink: 0, color: 'green' }}
/>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Text fontSize={2}>{name}</Text>
{cost && (
<Text fontSize={1} color="muted" style={{ lineHeight: '1.375' }}>
{cost}
</Text>
)}
</Box>
</Box>
)
}
function Laptop({ href, title, sx }) {
return (
<Link href={href} title={title} sx={sx}>
<Box
sx={{
display: 'block',
width: '100%',
height: '100%',
minHeight: '16rem',
backgroundSize: 'auto 115%',
backgroundImage:
"url('https://cloud-az94fzpyw-hack-club-bot.vercel.app/1poseidon.png')",
backgroundPosition: 'center top',
backgroundRepeat: 'no-repeat'
}}
></Box>
</Link>
)
}
================================================
FILE: components/fiscal-sponsorship/first/start.tsx
================================================
import { Box, Link, Text, Heading, Flex } from 'theme-ui'
import Stats from './stats'
import ApplyButton from './apply-button'
export default function Start({ stats }) {
return (
<>
<Box as="section" id="apply" py={6}>
<Flex
sx={{ flexDirection: 'column', alignItems: 'center', gap: 5, mx: 4 }}
>
<Flex
sx={{
flexDirection: 'column',
textAlign: 'center',
gap: 3
}}
>
<Heading variant="ultratitle" color="white">
Sign up for HCB.
</Heading>
<Text color="muted" variant="lead" m="0 !important">
Open to Hack Clubs, hackathons, and charitable organizations in
the US and Canada.
</Text>
</Flex>
<Stats stats={stats} />
<Flex
sx={{ flexDirection: 'column', textAlign: 'center', gap: 4, mx: 3 }}
>
<ApplyButton />
<Text color="muted" sx={{ fontSize: 18 }}>
We run Hack Club HQ on HCB!{' '}
<Link href="https://hcb.hackclub.com/hq" color="primary">
See our finances.
</Link>
</Text>
</Flex>
</Flex>
</Box>
</>
)
}
================================================
FILE: components/fiscal-sponsorship/first/stats.tsx
================================================
import { Text, Box, Flex } from 'theme-ui'
import { useEffect, useState } from 'react'
const easeInOutExpo = x =>
x === 0
? 0
: x === 1
? 1
: x < 0.5
? Math.pow(2, 20 * x - 10) / 2
: (2 - Math.pow(2, -20 * x + 10)) / 2
function startMoneyAnimation(
setBalance,
amount,
duration = 2_000,
moneyFormatter
) {
const startTime = performance.now()
function animate() {
const time = performance.now() - startTime
const progress = time / duration
const easedProgress = easeInOutExpo(progress)
setBalance(moneyFormatter(amount * easedProgress))
if (progress < 1) {
requestAnimationFrame(animate)
} else {
setBalance(moneyFormatter(amount))
}
}
requestAnimationFrame(animate)
}
function formatMoney(amount) {
const normalisedAmount = amount / 100
return normalisedAmount
.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
.split('.')
}
const Stats = ({ stats }) => {
const [balance, setBalance] = useState(0) // A formatted balance string, split by decimal
useEffect(() => {
const observer = new IntersectionObserver(
e => {
if (e[0].isIntersecting) {
console.info('intersecting')
startMoneyAnimation(
setBalance,
stats.transactions_volume,
2_500,
formatMoney
)
}
},
{ threshold: 1.0 }
)
observer.observe(document.querySelector('#parent'))
return () => observer.disconnect()
}, [stats.transactions_volume])
if (stats.transactions_volume === undefined) {
return null
}
return (
<Box id="parent">
<Flex sx={{ flexDirection: 'column', alignItems: 'center' }}>
<Text sx={{ fontSize: [3, 4] }}>So far we have enabled</Text>
{stats ? (
<>
<Text
variant="title"
color="green"
sx={{
color: 'green',
fontSize: [5, 6]
}}
>
{balance[0]}
<Text sx={{ fontSize: [3, 4] }}>.{balance[1]}</Text>
</Text>
</>
) : (
<Text
variant="title"
color="green"
sx={{
color: 'green',
fontSize: [5, 6]
}}
>
...
</Text>
)}
<Text sx={{ fontSize: [3, 4] }}>in transactions</Text>
</Flex>
</Box>
)
}
export async function getStaticProps() {
const res = await fetch(`https://hcb.hackclub.com/stats`)
try {
const stats = await res.json()
return {
props: {
stats
},
revalidate: 10
}
} catch (e) {
return {
props: {
stats: {}
},
revalidate: 10
}
}
}
export default Stats
================================================
FILE: components/fiscal-sponsorship/first/testimonials.tsx
================================================
import {
Box,
Image,
Text,
Heading,
Container,
Grid,
Link,
Avatar,
Button
} from 'theme-ui'
import { Slide } from '../../react-reveal-compat'
export default function Testimonials() {
return (
<>
<Container
variant="copy"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center'
}}
>
<Heading variant="title">
<i>FIRST</i> teams all over the country run on HCB.
</Heading>
<Text variant="lead" color="muted">
Everywhere from San Jose to Boston to New York, HCB powers teams of
all sizes.
</Text>
</Container>
<Container>
<Grid
gap={4}
sx={{
gridTemplateColumns: ['100%', null, null, '1fr 1fr']
}}
>
<Organization
logo="https://cloud-ab81zjlm9-hack-club-bot.vercel.app/0image.png"
name="Poseidon Robotics"
teamNum="FTC Team #16898"
teamLocation="San Jose, CA"
budget="$1,000,000"
budgetLabel="in grants"
website="evposeidon.wixsite.com"
url="https://evposeidon.wixsite.com/robo/home"
imgSrc="https://cloud-qtng6088u-hack-club-bot.vercel.app/0image.png"
quote="Overall, [HCB] has opened more opportunities for Poseidon, allowing us to undertake larger projects, both on the playing field and in our community."
hackerName="Ian Marwong"
hackerRole="Team Lead"
hackerAvatarUrl="/hackers/ian-marwong.jpg"
transparency="poseidon-robotics"
/>
<Organization
logo="https://cloud-ga0lm1r8d-hack-club-bot.vercel.app/0image.png"
name="Killabytez"
teamNum="FTC Team #14663"
teamLocation="Fremont, CA"
budget="$1,000,000"
budgetLabel="in grants"
website="killabytez.club"
url="http://www.killabytez.club/"
hackerAvatarUrl="/hackers/brian-cisto.jpeg"
hackerName="Brian Cisto"
hackerRole="Team Captain & Software Lead"
imgSrc="https://cloud-oelh6sp7b-hack-club-bot.vercel.app/0screen_shot_2022-11-06_at_8.45.37_pm.png"
quote="[HCB] has been essential to keeping track of our finances as well as giving us the opportunity to establish ourselves as a nonprofit."
/>
</Grid>
</Container>
</>
)
}
function Organization({
logo,
name,
website,
teamNum,
teamLocation,
budget: _budget,
budgetLabel: _budgetLabel,
url,
imgSrc,
quote,
hackerName,
hackerAvatarUrl,
hackerRole,
transparency = undefined
}) {
return (
<Slide bottom>
<Box
sx={{
backgroundColor: 'darkless',
color: 'smoke',
borderRadius: 'extra',
mx: 'auto'
}}
>
<Container sx={{ padding: 0, margin: 0 }}>
<Image
src={imgSrc}
alt="Robots team"
width={800}
height={450}
sx={{
borderRadius: 'default',
objectFit: 'cover'
}}
/>
<Box p={[3, null, 4]}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Image
src={logo}
alt={`${name} logo`}
sx={{
height: '4rem',
width: '4rem',
objectFit: 'cover',
borderRadius: 'default'
}}
/>
<Box sx={{ ml: 3 }}>
<Text
color="white"
variant="headline"
sx={{
fontSize: [28, null, 38],
lineHeight: 1,
letterSpacing: -0.1
}}
>
{name}
</Text>
<br />
<Link
href={url || `https://${website}`}
sx={{
textDecoration: 'none',
color: 'muted',
'&:hover': { textDecoration: 'underline' }
}}
>
{teamNum}
</Link>{' '}
• {teamLocation}
</Box>
</Box>
</Box>
<br />
<Text
sx={{
color: 'snow',
textIndent: '-.375em',
lineHeight: 'caption',
fontSize: 18
}}
>
"{quote}"
</Text>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginTop: ['0px', 3]
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
mt: ['16px', '0px']
}}
>
<Avatar
src={hackerAvatarUrl}
size={48}
sx={{
mr: 2,
borderRadius: '100%'
}}
alt="Photo of ${hackerName}"
/>
<Text
color="white"
sx={{
fontSize: 19,
display: 'flex',
flexDirection: 'column'
}}
>
<Text sx={{ fontWeight: 'bold', lineHeight: 1.125 }}>
{hackerName}
</Text>
<Text>{hackerRole}</Text>
</Text>
</Box>
{transparency && (
<Link
href={`https://hcb.hackclub.com/${transparency}`}
target="_blank"
rel="noreferrer"
sx={{ mt: ['16px', '0px'] }}
>
<Button
mt={[null, null, 4, 0]}
ml={[0, 'auto']}
sx={{ textTransform: 'none' }}
variant="primary"
title="🎶 take a look, it's in our books 🎵"
>
See Finances
</Button>
</Link>
)}
</Box>
</Box>
</Container>
</Box>
</Slide>
)
}
================================================
FILE: components/fiscal-sponsorship/open-source.tsx
================================================
/** @jsxImportSource theme-ui */
import { Box, Heading, Button, Text, Container, Grid, Flex } from 'theme-ui'
import Icon from '../icon'
import Photo from '../photo'
import HCBGource from '../../public/fiscal-sponsorship/hcb-gource.gif'
export default function OpenSource() {
return (
<Box as="section" sx={{ py: [4, 5], bg: 'snow' }}>
<Container>
<Grid columns={[1, 2]} gap={[4, 5]} sx={{ alignItems: 'center' }}>
<div>
<Heading as="h2" variant="headline" sx={{ mb: 3 }}>
Open source infrastructure for fiscally sponsored organizations.
</Heading>
<Text as="p" sx={{ mb: 3 }}>
HCB is open source and built in public, like many other Hack Club
projects. Join us in building the infrastructure powering fiscally
sponsored organizations around the world.
</Text>
<Flex
sx={{
flexWrap: 'wrap',
gap: 3
}}
>
<Button
as="a"
sx={{ flexShrink: 0, gap: 14, paddingLeft: 25 }}
variant="outline"
target="_blank"
href="https://github.com/hackclub/hcb"
>
Star on GitHub
<Icon glyph="github" />
</Button>
<Button
as="a"
sx={{
flexShrink: 0,
gap: 1,
paddingLeft: 25,
paddingRight: '5px'
}}
href="https://hackclub.com/hcb/open-source"
target="_blank"
>
Read our blog post
<Icon glyph="view-forward" />
</Button>
</Flex>
</div>
<Photo
src={HCBGource}
width={888}
height={500}
unoptimized
sx={{
maxWidth: '100%',
width: 'auto !important',
height: '500 !important',
boxShadow: 'elevated'
}}
alt="Since open-sourcing, we've merged over 1,800 pull requests from contributors!"
showAlt
/>
</Grid>
</Container>
</Box>
)
}
================================================
FILE: components/fiscal-sponsorship/organization-spotlight.tsx
================================================
/** @jsxImportSource theme-ui */
import Tilt from '../tilt'
import { Card, Heading, Text } from 'theme-ui'
import Image from 'next/image'
import { Balancer } from 'react-wrap-balancer'
export default function OrganizationSpotlight({ organization }) {
return (
<Tilt>
<Card
as="a"
href={`https://hcb.hackclub.com/${organization.slug}`}
sx={{
justifyContent: 'center',
alignItems: 'center',
minHeight: 128,
color: 'white',
cursor: 'pointer',
textShadow: '0 1px 4px rgba(0, 0, 0, 0.5)',
textDecoration: 'none',
backgroundColor: 'black',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
borderRadius: 'extra',
overflow: 'hidden',
position: 'relative',
p: 3,
height: '100%',
display: 'grid',
gridTemplateColumns: '64px 1fr',
columnGap: 3,
rowGap: 2
}}
style={{
backgroundImage: `linear-gradient(rgba(0,0,0,0.375) 0%, rgba(0,0,0,0.5) 75%), url('${organization.background_image}')`
}}
>
<Image
src={organization.logo}
alt={`${organization.name} logo`}
loading="lazy"
width={64}
height={64}
style={{
borderRadius: '16px',
maxWidth: '100%',
height: 'auto'
}}
/>
<div>
<Heading
as="h3"
sx={{
fontSize: [3, 4],
m: 0,
overflowWrap: 'anywhere',
width: '100%',
display: 'block'
}}
>
{organization.name}
</Heading>
<Text
variant="caption"
sx={{
color: 'white',
opacity: 0.875
}}
>
{organization.location.readable}
</Text>
</div>
<Text as="p" sx={{ gridColumn: ['span 2', '2'] }}>
<Balancer>{organization.description}</Balancer>
</Text>
</Card>
</Tilt>
)
}
================================================
FILE: components/fiscal-sponsorship/sign-in.tsx
================================================
/** @jsxImportSource theme-ui */
import { useEffect, useState } from 'react'
import { Button, Image } from 'theme-ui'
export default function SignIn() {
const [user, setUser] = useState(null)
useEffect(() => {
;(async () => {
const _user = await fetch('https://hcb.hackclub.com/api/current_user', {
credentials: 'include'
})
.then(r => (r.ok ? r.json() : null))
.catch(() => {})
if (_user) setUser(_user)
})()
}, [])
return (
<Button
as="a"
href="https://hcb.hackclub.com"
variant="outline"
sx={{ color: 'white' }}
>
{user ? (
<>
<Image
src={user.avatar}
alt={`${user.name}'s HCB avatar`}
width={30}
sx={{ borderRadius: 'circle', mr: 2, boxShadow: 'elevated' }}
/>
Continue to HCB
</>
) : (
'Sign in'
)}
</Button>
)
}
================================================
FILE: components/fiscal-sponsorship/tooltip.tsx
================================================
import React from 'react'
const tooltip = direction =>
function Tooltip({ children, text, id }) {
const escapedText = text.replace(/'/g, "\\'")
const directionalStyles = {
e: `
left: 100%;
bottom: 50%;
right: 0;
margin-left: 0.5rem;
transform: translateY(50%);
`,
w: `
right: 100%;
bottom: 50%;
margin-right: 0.5rem;
transform: translateY(50%);
`,
n: `
right: 50%;
bottom: 100%;
margin-bottom: 0.5rem;
transform: translateX(50%);
`,
s: `
right: 50%;
top: 100%;
margin-top: 0.5rem;
transform: translateX(50%);
`
}[direction || 'e']
return (
<>
<style>{`.tooltipped${id ? '-' + id : ''} {
position: relative;
}
@media (min-width: 56em) {
.tooltipped${id ? '-' + id : ''}:after {
background-color: rgba(31, 45, 61, 0.875);
border-radius: 0.5rem;
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.0625),
0 4px 8px 0 rgba(0, 0, 0, 0.125);
color: #ffffff;
content: '${escapedText}';
font-family: $font-family;
font-size: 0.875rem;
font-weight: 500;
height: min-content;
letter-spacing: 0;
line-height: 1.375;
max-width: 16rem;
min-height: 1.25rem;
opacity: 0;
padding: 0.25rem 0.75rem;
pointer-events: none;
position: absolute;
right: 100%;
text-align: center;
transform: translateY(50%);
transition: 0.125s all ease-in-out;
width: max-content;
z-index: 1000000;
}
.tooltipped${id ? '-' + id : ''}:hover:after,
.tooltipped${id ? '-' + id : ''}:active:after,
.tooltipped${id ? '-' + id : ''}:focus:after {
opacity: 1;
z-index: 9000000;
backdrop-filter: blur(2px);
}
.tooltipped${id ? '-' + id : ''}:after {
${directionalStyles}
}
}`}</style>
{children}
</>
)
}
type TooltipComponent = React.FC<{ children: any; text: any; id: any }> & {
N: React.FC<{ children: any; text: any; id: any }>
S: React.FC<{ children: any; text: any; id: any }>
E: React.FC<{ children: any; text: any; id: any }>
W: React.FC<{ children: any; text: any; id: any }>
}
const Tooltip = tooltip('e') as TooltipComponent
Tooltip.N = tooltip('n')
Tooltip.S = tooltip('s')
Tooltip.E = tooltip('e')
Tooltip.W = tooltip('w')
export { Tooltip }
export default Tooltip
================================================
FILE: components/flag.tsx
================================================
import theme from '../lib/theme'
import styled from '@emotion/styled'
import { css, keyframes } from '@emotion/react'
import Link from 'next/link'
const waveFlag = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(-5deg);
}
`
const waveFlagScaled = keyframes`
from {
transform: scale(.875) rotate(0deg);
}
to {
transform: scale(.875) rotate(-5deg);
}
`
const scrolled = props =>
props.scrolled
? css`
transform: scale(0.875);
height: 56px;
&:hover,
&:focus {
animation: ${waveFlagScaled} 0.5s linear infinite alternate;
}
`
: undefined
const Base = styled(Link)<{ uwu?: boolean }>`
background-image: ${props =>
props.uwu
? 'url(/stickers/hack-club-anime.png)'
: 'url(https://assets.hackclub.com/flag-orpheus-top.svg)'};
background-repeat: no-repeat;
background-position: top left;
background-size: contain;
cursor: pointer;
flex-shrink: 0;
width: 112px;
height: 48px;
transition: ${3 / 16}s cubic-bezier(0.375, 0, 0.675, 1) transform;
transform-origin: top left;
@media (min-width: ${theme.breakpoints[1]}) {
width: 172px;
height: 64px;
}
&:hover,
&:focus {
animation: ${waveFlag} 0.5s linear infinite alternate;
}
@media (prefers-reduced-motion: reduce) {
animation: none !important;
}
${scrolled};
`
const Flag = props => <Base href="/" title="Homepage" {...props} />
export default Flag
================================================
FILE: components/flex-col.tsx
================================================
import { Flex } from 'theme-ui'
export default function FlexCol({ children, ...props }) {
return <Flex sx={{ flexDirection: 'column', ...props }}>{children}</Flex>
}
================================================
FILE: components/footer.tsx
================================================
import React from 'react'
import styled from '@emotion/styled'
import { Box, Container, Grid, Heading, Link, Text } from 'theme-ui'
import NextLink from 'next/link'
import theme from '@hackclub/theme'
import Icon from './icon'
import { getGitSha, getGitShaShort } from '../lib/git-info'
const Base = styled(Box, { shouldForwardProp: prop => prop !== 'dark' })<{
dark?: boolean
}>`
background: ${props =>
props.dark
? `${theme.colors.darker} radial-gradient(${theme.colors.black} 1px, transparent 1px)`
: `${theme.colors.snow} url('/pattern.svg') repeat`};
${props =>
props.dark &&
`
background-size: ${theme.space[4]}px ${theme.space[4]}px;
`} @media print {
display: none;
}
`
const Logo = props => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="256"
height="90"
fill="#8492A6"
viewBox="0 0 256 90"
{...props}
>
<path d="M75.156 38.08l6.475 1.105s1.798-11.402-.224-10.199l-6.251 9.094zM204.85 34.495l2.161 5.06s5.237-2.106 4.619-4.915c-.537-2.442-3.098-1.496-5.641-.557h-.001c-.382.142-.764.282-1.138.412zM207.752 43.455s1.483 6.212 1.421 5.93c-.007-.093.397-.247 1.002-.477 2.014-.766 6.257-2.379 4.999-5.453-1.636-3.997-7.422 0-7.422 0z" />
<path
fillRule="evenodd"
d="M7.205 89.303c-.022-2.227-.161-16.553 3.072-32.54 15.846-2.401 28.778.144 54.94 7.37 5.142 1.42 10.135 2.927 15.139 4.437 21.52 6.494 43.238 13.047 77.819 13.047 39.513 0 89.839-46.125 96.97-52.854.321-.303.07-.804-.37-.798a895.798 895.798 0 01-22.817-.006.484.484 0 01-.422-.707L241.991 6.9c.186-.36-.392-.91-.737-.696-10.403 6.44-68.291 38.655-125.701 11.127C62.987-7.874 36.693.801 29.405 4.381c.206-.647.195-1.355-.559-1.45-.953-.121-1.458.46-1.458.46-.955.738-11.701 20.409-18.91 41.665C1.272 66.313-.092 87.361.006 89.551h7.202c0-.049 0-.132-.002-.248zm33.522-73.187c-.647 3.657-1.888 9.939-4.497 18.056-5.42 12.948 3.823 10.836 6.47 5.457 1.569-2.97 3.182-6.194 3.182-6.194l8.307 3.185s-.669 3.783-1.353 6.912c-2.61 8.118 4.998 7.144 7.102 1.146.177-.583.477-1.518.856-2.697 1.62-5.045 4.672-14.553 5.648-20.073 1.814-4.357-4.395-8.336-7.205-1.295-1.502 2.593-3.941 8.27-3.941 8.27s-6.857-2.534-6.938-2.81c-.14-.362.021-1.024.212-1.812.177-.727.38-1.562.397-2.37-.418-11.655-7.37-10.693-8.24-5.775zm36.6 9.076c2.114-4.209 4.542-4.915 6.347-4.723.779.065 1.838 1.648 2.648 3.17 2.651 10.02-2.1 28.448-2.94 29.686-2.892 4.671-7.967 3.788-6.04-1.259.901-3.066 1.865-5.852 1.865-5.852l-6.568-.734c-5.162 10.028-9.802 5.829-7.128 1.497 2.861-5.074 8.956-16.183 11.816-21.785zm33.437 10.102c.857-2.414-.924-7.875-7.149-6.964-9.016.065-12.136 15.862-12.136 15.862s-1.498 7.65.867 12.865c1.971 4.611 6.52 5.007 8.041 5.139.137.012.25.022.334.032 5.917-1.78 3.891-5.722 2.879-5.849-.221-.011-.456-.014-.701-.018-1.178-.015-2.578-.034-3.746-.988-2.393-1.928-1.967-6.824-1.447-9.457 1.224-4.429 3.918-13.223 8.213-11.07 2.577 3.293 4.386 1.78 4.845.448zm5.93-.406c-.608 1.855-.691 3.748-.785 5.895-.151 3.458-.332 7.576-2.777 13.261-.68 1.62-2.071 4.212-2.9 5.756-.323.602-.561 1.045-.638 1.21-2.196 4.16 2.263 6.611 7.175-.657 1.19-1.664 2.501-5.919 2.501-5.919l2.137-.24s1.867 8.216 2.296 11.736c.46 3.396 6.476 5.328 6.564-1.338-.215-2.285-1.011-5.374-2.509-9.298 0 0-.978-2.874-1.925-3.247 0 0 6.713-6.677 7.353-9.268.67-2.714-.552-4.6-5.802-.172-5.249 4.428-5.858 5.846-5.858 5.846s1.248-5.583 1.123-9.812c.456-4.473-4.584-7.73-5.955-3.753zm33.811 8.412c-2.253 2.233-3.67 6.425-3.512 12.767.314 9.466 4.236 14.906 10.933 13.822 6.697-1.083 5.12-5.915 4.503-6.075-.088-.022-.163-.059-.244-.098-.376-.181-.861-.415-3.12.435-2.746 1.032-4.814-.173-6.545-4.375-1.144-2.843-1.764-8.367.302-11.452.537-.795 1.051-1.088 1.378-1.275l.075-.042.039-.024.019-.011c1.235-.753 2.5-.023 2.717.166 3.458 2.504 4.135-.27 2.899-2.736-2.44-3.446-5.681-4.15-9.444-1.102zm14.971.143c-.033-3.593 3.677-6.363 4.981 1.672.926 2.985 1.185 7.574 1.384 11.111.147 2.614.262 4.655.59 5.05.773.93 6.526-.368 8.084-.892 1.558-.524 4.428.164 3.78 1.724-.423 1.281-1.467 1.63-2.02 1.814-.134.045-.239.08-.3.116-.309.187-13.313 4.042-13.796 1.475-.342-1.815-.457-2.938-.667-4.986h-.001c-.087-.848-.19-1.854-.332-3.133-.178-1.594-.448-3.404-.721-5.234h-.001c-.475-3.187-.961-6.434-.981-8.717zm15.594-3.216c-.282-2.598 2.367-4.185 3.927-1.396.534.974 1.107 3.415 1.752 6.165.788 3.354 1.682 7.167 2.746 9.337 1.06 1.599 3.243 1.887 4.271.42 1.214-2.218.338-7.759-.413-12.204a62.31 62.31 0 00-.479-1.777v-.001c-.361-1.286-.655-2.334-.634-3.168.466-4.003 3.677-3.055 5.175 1.049 1.249 4.572 2.551 11.959 1.898 14.585l-.074.3c-.604 2.447-1.329 5.39-4.442 6.131-.842.185-7.855 1.196-10.321-6.477l-.757-2.562c-1.783-6.024-2.399-8.103-2.649-10.402zm21.992-8.576c4.312-2.607 7.547-3.502 10.075-2.589 1.48.91 2.436 3.407 2.037 5.558-.461 1.87-1.231 3.396-1.231 3.396 2.559.258 4.432 2.811 4.918 6.153.487 3.341-2.661 6.486-8.515 8.433-1.972.556-4.067.549-4.16-.138-.063-1.341-5.033-17.326-5.033-17.326-.015-.096-.034-.193-.053-.29-.175-.892-.37-1.884 1.962-3.197z"
clipRule="evenodd"
/>
</svg>
)
const Service = ({ href, icon, name = '', ...props }) => (
<Link
target="_blank"
rel="noopener me"
href={href}
title={`Hack Club on ${name ? name : icon}`}
{...props}
>
<Icon glyph={icon} />
</Link>
)
const Footer = ({
dark = false,
email = 'team@hackclub.com',
children = undefined,
...props
}) => (
<Base
color={dark ? 'muted' : 'slate'}
py={[4, 5]}
dark={dark}
sx={{ textAlign: 'left' }}
as="footer"
{...props}
>
<Container px={[3, null, 4]}>
{children}
<Grid
as="article"
gap={[2, 4]}
columns={[2, 3, 4]}
sx={{
px: 0,
a: {
textDecoration: 'none',
color: 'muted',
transition: '0.125s color ease-in-out',
':hover,:focus': { color: 'slate', textDecoration: 'underline' }
},
'> div > a': {
display: 'block',
mb: 2
},
'h2,p': { color: 'muted' },
h2: { fontSize: 3 },
'a,p': { fontSize: 2 }
}}
>
<Box>
<Heading as="h2" variant="subheadline" mb={3}>
Hack Club
</Heading>
<Link as={NextLink} href="/philosophy">
Philosophy
</Link>
<Link as={NextLink} href="/team">
Our Team & Board
</Link>
<Link as={NextLink} href="/jobs">
Jobs
</Link>
<Link as={NextLink} href="/brand">
Brand Guide
</Link>
<Link as={NextLink} href="/press">
Press Inquiries
</Link>
<Link as={NextLink} href="/philanthropy">
Donate
</Link>
<Link as={NextLink} href="/imprint">
Imprint
</Link>
</Box>
<Box>
<Heading as="h2" variant="subheadline" mb={3}>
Resources
</Heading>
<Link href="https://events.hackclub.com/">Community Events</Link>
<Link href="https://jams.hackclub.com/">Jams</Link>
<Link href="https://toolbox.hackclub.com/">Toolbox</Link>
<Link href="https://hackclub.com/map">Clubs Map</Link>
<Link href="https://hackclub.com/conduct/">Code of Conduct</Link>
<Link href="https://hackclub.com/privacy/">Privacy & Terms</Link>
</Box>
<Box sx={{ gridColumn: ['span 2', 'span 1'] }}>
<Box
sx={{ display: 'flex', alignItems: 'end', flexDirection: 'row' }}
>
<Logo aria-label="Hack Club logo" width={128} height={45} />
<Text as="span" color="muted" sx={{ marginBottom: '-.5em' }}>
<Link
sx={{ fontSize: 'inherit' }}
href={`https://github.com/hackclub/site/commit/${getGitSha()}`}
target="_blank"
rel="noopener noreferrer"
>
<code style={{ fontFamily: 'monospace', fontSize: '13px' }}>
{getGitShaShort()}
</code>
</Link>
</Text>
</Box>
<Grid
columns={[8, 4]}
gap={2}
sx={{
alignItems: 'center',
ml: -1,
my: 3,
maxWidth: [null, 192],
svg: { fill: 'currentColor', width: 32, height: 32 },
a: {
lineHeight: 0,
mb: 0,
transition:
'transform .125s ease-in-out, color .125s ease-in-out',
':hover,:focus': { transform: 'scale(1.125)' }
},
placeItems: 'center'
}}
>
<Service
href="https://slack.hackclub.com"
icon="slack-fill"
name="Slack"
target="_self"
/>
<Service
href="https://twitter.com/hackclub"
icon="twitter"
name="Twitter"
/>
<Service
href="https://github.com/hackclub"
icon="github"
name="GitHub"
/>
<Service
href="https://figma.com/@hackclub"
icon="figma-fill"
name="Figma"
/>
<Service
href="https://social.dino.icu/@hackclub"
icon="mastodon"
name="Mastodon"
/>
<Service
href="https://www.youtube.com/c/HackClubHQ"
icon="youtube"
name="YouTube"
/>
<Service
href="https://www.instagram.com/starthackclub"
icon="instagram"
name="Instagram"
/>
<Service href={`mailto:${email}`} icon="email-fill" name="Email" />
</Grid>
<Text my={2}>
<Link href="tel:1-855-625-4225">1-855-625-HACK</Link>
<br />
<Text as="span" color="muted">
(call toll-free)
</Text>
</Text>
</Box>
</Grid>
<Text as="p" variant="caption" sx={{ mt: 3 }}>
© {new Date().getFullYear()} Hack Club. 501(c)(3) nonprofit (EIN:
81-2908499)
</Text>
</Container>
</Base>
)
export default Footer
================================================
FILE: components/force-theme.ts
================================================
import { useEffect } from 'react'
import { useColorMode } from 'theme-ui'
const ForceTheme = ({ theme }) => {
const [_colorMode, setColorMode] = useColorMode()
useEffect(() => {
setColorMode(theme)
}, [setColorMode, theme])
return null
}
export default ForceTheme
================================================
FILE: components/hackathons/features/marketing.tsx
================================================
/** @jsxImportSource theme-ui */
import { Button, Box, Container, Heading, Text } from 'theme-ui'
import usePrefersMotion from '../../../lib/use-prefers-motion'
import useHasMounted from '../../../lib/use-has-mounted'
import Link from 'next/link'
const Content = () => (
<Container
sx={{
zIndex: 999,
py: 6,
color: 'white',
'h2,p': { textShadow: 'text' },
textAlign: [null, 'center'],
position: 'relative',
overflow: 'hidden'
}}
>
<Text as="p" variant="eyebrow" sx={{ color: 'white', opacity: 0.75 }}>
hackathons.hackclub.com
</Text>
<Heading as="h2" variant="title">
Spread the word about your hackathon.
</Heading>
<Text as="p" variant="lead" sx={{ maxWidth: 'copyPlus', mx: 'auto' }}>
Reach hackers worldwide by listing your event on hackathons.hackclub.com,
the first Google search result for "high school hackathons." Your event
will also be emailed to a network of high school hackers in your area.
</Text>
<Link href="https://hackathons.hackclub.com">
<Button
variant="ctaLg"
sx={{
backgroundImage: (theme: any) => theme.util.gx('yellow', 'red')
}}
>
Add your hackathon →
</Button>
</Link>
</Container>
)
const Cover = () => (
<Box
sx={{
position: 'absolute',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundImage: (t: any) => t.util.gx('slate', 'black'),
opacity: 0.7,
zIndex: 1
}}
/>
)
const Static = ({
img = 'https://cloud-ateizv565-hack-club-bot.vercel.app/0screen_shot_2022-07-27_at_2.57.41_pm.png'
}) => (
<Box
as="section"
id="slack"
sx={{
position: 'relative',
overflow: 'hidden',
backgroundImage: `url(${img})`,
backgroundSize: 'cover'
}}
>
<Cover />
<Content />
</Box>
)
const Marketing = () => {
const hasMounted = useHasMounted()
const prefersMotion = usePrefersMotion()
if (hasMounted && prefersMotion) {
return (
<Box
as="section"
id="slack"
sx={{ overflow: 'hidden', position: 'relative' }}
>
<Box
as="video"
autoPlay
muted
loop
playsInline
poster="https://cloud-ateizv565-hack-club-bot.vercel.app/0screen_shot_2022-07-27_at_2.57.41_pm.png"
duration={2000}
sx={{
position: 'absolute',
bottom: 0,
top: 0,
left: 0,
right: 0,
height: '100%',
zIndex: -1,
width: '100vw',
objectFit: 'cover'
}}
>
<source
src="https://cloud-55tm7eveg-hack-club-bot.vercel.app/0screen_recording_2022-07-27_at_2.48.43_pm.mp4"
type="video/mp4; codecs=hevc"
/>
<source
src="https://cloud-r9u5vfqcv-hack-club-bot.vercel.app/0screen_recording_2022-07-27_at_2.48.43_pm.webm"
type="video/webm; codecs=vp9,opus"
/>
<source
src="https://cloud-r9u5vfqcv-hack-club-bot.vercel.app/1screen_recording_2022-07-27_at_2.48.43_pm.mp4"
type="video/quicktime"
/>
</Box>
<Cover />
<Content />
</Box>
)
} else {
return <Static />
}
}
export default Marketing
================================================
FILE: components/hackathons/features/slack.tsx
================================================
/** @jsxImportSource theme-ui */
import { Button, Box, Container, Heading, Text, Link } from 'theme-ui'
import usePrefersMotion from '../../../lib/use-prefers-motion'
import useHasMounted from '../../../lib/use-has-mounted'
import NextLink from 'next/link'
const Content = () => (
<Container
sx={{
zIndex: 999,
py: 6,
color: 'white',
'h2,p': { textShadow: 'text' },
textAlign: [null, 'center'],
position: 'relative',
overflow: 'hidden'
}}
>
<Text as="p" variant="eyebrow" sx={{ color: 'white', opacity: 0.75 }}>
The Hack Club Community
</Text>
<Heading as="h2" variant="title">
A hackathon organizer's{' '}
<Text
as="span"
sx={{
borderRadius: 'default',
px: 2,
mx: [-2, 0],
whiteSpace: 'nowrap',
color: 'currentColor',
bg: 'green'
}}
>
best friend
</Text>
.
</Heading>
<Text as="p" variant="lead" sx={{ maxWidth: 'copyPlus', mx: 'auto' }}>
The{' '}
<Box
as="span"
sx={{
bg: 'rgb(245, 233, 181, .3)',
px: 1,
borderRadius: 5
}}
>
<Link
href="https://hackclub.slack.com/archives/C03QSGGCJN7"
sx={{ textDecoration: 'none', color: 'currentColor' }}
target="_blank"
>
#hackathon-organizers
</Link>
</Box>{' '}
channel is where teenagers around the world ask questions and share their
own hackathon organizing experiences—from finding a venue to securing
sponsorships to ordering food.
</Text>
<NextLink href="https://slack.hackclub.com">
<Button
variant="ctaLg"
sx={{
background: 'linear-gradient(-132deg, #338eda 14%, #33d6a6 82%)'
}}
>
Join us on Slack →
</Button>
</NextLink>
</Container>
)
const Cover = () => (
<Box
sx={{
position: 'absolute',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundImage: (t: any) => t.util.gx('cyan', 'purple'),
opacity: 0.825,
zIndex: 1
}}
/>
)
const Static = ({
// screenshot of messages from #hackathon-organizers
img = 'https://cloud-8611bon87-hack-club-bot.vercel.app/0screen_shot_2022-08-05_at_2.27.38_pm.png'
}) => (
<Box
as="section"
id="slack"
sx={{
position: 'relative',
overflow: 'hidden',
backgroundImage: `url(${img})`,
backgroundSize: 'cover'
}}
>
<Cover />
<Content />
</Box>
)
const Slack = () => {
const hasMounted = useHasMounted()
const prefersMotion = usePrefersMotion()
if (hasMounted && prefersMotion) {
return (
<Box
as="section"
id="slack"
sx={{ overflow: 'hidden', position: 'relative' }}
>
<Box
as="video"
autoPlay
muted
loop
playsInline
// screenshot of messages from #hackathon-organizers
poster="https://cloud-iwkoq2544-hack-club-bot.vercel.app/0screen_shot_2022-07-30_at_9.03.43_am.png"
duration={2000}
sx={{
position: 'absolute',
bottom: 0,
top: 0,
left: 0,
right: 0,
height: '100%',
zIndex: -1,
width: '100vw',
objectFit: 'cover'
}}
>
<source
src="https://cloud-hsc3k1am6-hack-club-bot.vercel.app/0screen_recording_2022-08-03_at_9.50.26_am.mp4"
type="video/mp4; codecs=hevc"
/>
<source
src="https://cloud-azjxx4vqu-hack-club-bot.vercel.app/0have-finally-figured-it-out-hell-yeah.webm"
type="video/webm; codecs=vp9,opus"
/>
<source
src="https://cloud-hsc3k1am6-hack-club-bot.vercel.app/0screen_recording_2022-08-03_at_9.50.26_am.mp4"
type="video/quicktime"
/>
</Box>
<Cover />
<Content />
</Box>
)
} else {
return <Static />
}
}
export default Slack
================================================
FILE: components/hackathons/keep-exploring.tsx
================================================
import { Box, Heading, Button, Text, Grid, Container } from 'theme-ui'
import Link from 'next/link'
import Icon from '../icon'
export default function KeepExploring() {
return (
<>
<Box
sx={{
backgroundImage: (t: any) => t.util.gx('orange', 'red'),
margin: 'auto',
maxWidth: '90%',
my: 4,
borderRadius: 8,
color: 'white',
textAlign: 'center',
py: 5
}}
>
<Heading
as="h1"
sx={{
fontSize: 6,
mb: 3,
display: 'flex',
justifyContent: 'center',
alignContent: 'center'
}}
>
Keep exploring{' '}
<Icon
glyph="explore"
size={70}
sx={{ display: ['none', 'flex', 'flex'] }}
/>
</Heading>
<Link href="https://slack.hackclub.com">
<Button
sx={{
bg: 'white',
color: 'red',
mr: [0, 3],
mb: [3, 0],
fontSize: [2, 3]
}}
>
Meet other hackers
</Button>
</Link>
<Link href="https://hackathons.hackclub.com">
<Button sx={{ bg: 'white', color: 'red', fontSize: [2, 3] }}>
Discover more hackathons
</Button>
</Link>
</Box>
<Container>
<Grid
columns={[null, '1fr 1fr']}
my={[3, 5]}
sx={{ maxWidth: 'copyUltra', mx: 'auto' }}
>
<Heading as="h3" variant="headline" sx={{ fontSize: [4, 5], mb: 0 }}>
Behind the scenes...
</Heading>
<Text
as="p"
variant="lead"
sx={{ mt: 0, a: { variant: 'styles.a', color: 'blue' } }}
>
Teenagers organize hackathons like{' '}
<a
href="https://assemble.hackclub.com"
target="_blank"
rel="noreferrer"
>
Assemble
</a>{' '}
&{' '}
<a href="https://windyhacks.com" target="_blank" rel="noreferrer">
Windy City Hacks
</a>
. The hack’s the limit.
</Text>
</Grid>
</Container>
</>
)
}
================================================
FILE: components/hackathons/landing.tsx
================================================
import { Box, Button, Heading, Text, Card } from 'theme-ui'
import { Fade } from '../react-reveal-compat'
import ScrollHint from '../scroll-hint'
import Image from 'next/image'
import Icon from '../icon'
export default function Landing() {
return (
<>
<Slide>
<BlueGradientFilter />
<Box
sx={{
position: 'absolute',
flexDirection: 'column',
justifyContent: 'center',
bottom: 140,
mx: 'auto',
width: '100%'
}}
>
<Box
sx={{
zIndex: 999,
paddingTop: 96
}}
>
<Fade duration={625} bottom>
<Card
variant="translucent"
sx={{
variant: 'layout.container',
maxWidth: [null, 700, 1000],
borderRadius: 'extra',
p: [3, 4],
position: 'relative',
color: 'black'
}}
>
<Button
as="a"
{...({ target: '_blank' } as any)}
variant="cta"
href="https://hackathons.hackclub.com"
sx={{
backgroundImage: (t: any) => t.util.gx('yellow', 'pink'),
position: 'absolute',
right: [0, -3],
top: -3,
transform: [
'translateY(-50%) rotate(8deg)',
'translateX(15%) rotate(12deg)'
],
fontSize: [2, 3],
display: ['none', 'inline', 'inline']
}}
>
Looking for hackathons?{' '}
<Icon glyph="external" size={30} sx={{ pl: 1 }} />
</Button>
<Heading
as="h2"
variant="title"
sx={{
color: 'black',
span: { color: 'white', display: 'block' }
}}
>
Welcome to the{' '}
<Text
as="span"
variant="ultratitle"
sx={{
WebkitTextStroke: 'currentColor',
WebkitTextStrokeWidth: '2px',
WebkitTextFillColor: '#33D6A6',
whiteSpace: [null, null, 'nowrap']
}}
>
high school hackathon.
</Text>
</Heading>
<Text
as="p"
variant="subtitle"
sx={{
mt: 3,
fontSize: [2, 3]
}}
>
<strong>
It's not an extracurricular or a club. It's not a class or a
lecture.
</strong>{' '}
Hackathons are a place to build things for fun and meet others
doing the same.
</Text>
</Card>
</Fade>
</Box>
<br />
<br />
<ScrollHint />
</Box>
</Slide>
</>
)
}
function Slide({ children }) {
return (
<Box
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'end',
backgroundColor: '#000000',
boxShadow: 'inset 0 0 4rem 1rem rgba(0, 0, 0, 0.5)',
backgroundPosition: 'center',
backgroundSize: 'cover',
width: '100%',
minHeight: '100vh',
position: 'relative'
}}
>
<Image
// public/hackathons/assemble.JPG
src="/hackathons/assemble.JPG"
alt="Dark room with a stage and students sitting below"
// placeholder="blur"
priority
fill
sizes="100vw"
style={{
objectFit: 'cover'
}}
/>
{children}
</Box>
)
}
function BlueGradientFilter() {
return (
<Box
style={{
backgroundImage:
'linear-gradient(to bottom,rgba(51, 142, 218, .9),rgba(51, 142, 218, 0.7) 35%, rgba(91, 192, 222, 0.2) 100%)',
height: '100vh',
left: '0',
right: '0',
position: 'absolute',
zIndex: '0'
}}
></Box>
)
}
================================================
FILE: components/hackathons/overview.tsx
================================================
import { Box, Heading, Container, Text, Grid } from 'theme-ui'
export default function Overview() {
return (
<>
<Box as="section" sx={{ py: [4, 5], color: 'black' }}>
<Container sx={{ width: '95vw' }}>
<Heading
sx={{
fontSize: [36, 50, 50, 50, 48],
mb: 3,
color: 'purple'
}}
>
A hackathon is a social coding marathon where teenagers{' '}
<Highlight>come together</Highlight> to{' '}
<Highlight>build projects</Highlight> for a weekend and{' '}
<Highlight>share them with the world</Highlight>.
</Heading>
<Grid columns={[null, null, 2]} gap={[3, 4]}>
<Text as="p" variant="subtitle">
<Box
as="span"
sx={{ display: 'block', color: 'blue', fontSize: 28, mb: 2 }}
>
The best way to learn is by <b>building</b>.
</Box>
A hackathon is a space that helps give makers everything they need
to start building–mentors, collaborators, inspiration, and a goal
to work towards. Hackers will leave a hackathon with a project of
their own, ready and excited to keep hacking once they get home.
</Text>
<Text as="p" variant="subtitle">
<Box
as="span"
sx={{ display: 'block', color: 'green', fontSize: 28, mb: 2 }}
>
We're at our best when we're <b>making</b>.
</Box>
Hack Club is a global community of thousands of high school
makers. We're organizers, coders, hackers, painters, engineers,
musicians, writers, volunteers. We make things. We want others to
make things too.
</Text>
</Grid>
<Grid columns={[null, null, 2]} gap={[3, 4]} mt={4}>
<iframe
width="100%"
height="300px"
src="https://www.youtube.com/embed/PnK4gzO6S3Q"
title="YouTube video player"
style={{ border: 0 }}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
></iframe>
<iframe
width="100%"
height="300px"
src="https://www.youtube.com/embed/KLx4NZZPzMc"
title="YouTube video player"
style={{ border: 0 }}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
></iframe>
</Grid>
</Container>
</Box>
</>
)
}
function Highlight({ children }) {
return (
<Text
as="span"
sx={{
bg: 'yellow',
borderRadius: 'default',
px: 1,
color: '#5d114c',
lineHeight: '1.3'
}}
>
{children}
</Text>
)
}
================================================
FILE: components/hackathons/recap.tsx
================================================
/** @jsxImportSource theme-ui */
import { Card, Box, Heading, Grid, Text } from 'theme-ui'
import Stage from '../stage'
export default function Recap() {
return (
<>
<Box as="header" sx={{ textAlign: [null, 'center'], pt: [4, 5] }}>
<Text as="p" variant="eyebrow">
Get started today
</Text>
<Heading as="h2" variant="title">
Resources so you can organize an{' '}
<Text
as="span"
sx={{
borderRadius: 'default',
px: 2,
mx: [-2, 0],
bg: 'rgb(91, 255, 205)',
color: '#095365',
display: 'inline-block',
whiteSpace: ['wrap', 'nowrap']
}}
>
amazing
</Text>{' '}
hackathon.
</Heading>
</Box>
<Grid
pt={[3, 4]}
pb={[5, 6]}
gap={[4, 3, 4]}
columns={[1, null, 2]}
sx={{
textAlign: 'left',
'> a, > div': {
borderRadius: 'extra',
boxShadow: 'elevated',
px: [3, null, 4],
py: [4, null, 5]
},
span: {
boxShadow:
'-2px -2px 6px rgba(255,255,255,0.125), inset 2px 2px 6px rgba(0,0,0,0.1), 2px 2px 8px rgba(0,0,0,0.0625)'
},
svg: { fill: 'currentColor' }
}}
>
<Card
variant="interactive"
as="a"
href="https://slack.hackclub.com"
sx={{
background: 'linear-gradient(-32deg, #6f31b7 14%, #fb558e 82%)',
color: 'white',
svg: { color: '#fb558e' }
}}
>
<Stage
icon="slack"
color="white"
name="Slack community"
desc="Chat in Slack for support with organizing your hackathon, from finding a venue to marketing your event."
/>
</Card>
<Card
variant="interactive"
as="a"
href="https://hackathons.hackclub.com/"
sx={{
background:
'linear-gradient(32deg, rgba(51, 142, 218, 0.9) 0%, rgba(51, 214, 166, 0.9) 100%)',
color: 'white',
svg: { color: 'rgb(51, 142, 218)' }
}}
>
<Stage
icon="event-check"
color="white"
name="Marketing"
desc="Get your event listed on Google's front page, emailed to nearby teens, and seen by our hackathon calendar's 3K+ monthly users."
/>
</Card>
</Grid>
</>
)
}
================================================
FILE: components/hackathons/scrolling-hackathons.tsx
================================================
/** @jsxImportSource theme-ui */
import Ticker from 'react-ticker'
import {
Box,
Card,
Text,
Heading,
Badge,
Container,
Image,
Link
} from 'theme-ui'
import { useState } from 'react'
import { keyframes } from '@emotion/react'
import Tilt from '../tilt'
import PageVisibility from 'react-page-visibility'
import { formatAddress } from '../../lib/helpers'
export default function ScrollingHackathons({
eventData,
mode,
title,
...props
}) {
const [pageIsVisible, setPageIsVisible] = useState(true)
const handleVisibilityChange = isVisible => {
setPageIsVisible(isVisible)
}
return (
<>
{title ? (
<Container>
<Heading
sx={{
fontSize: [36, 64],
color: 'black',
textAlign: 'center',
maxWidth: ['95vw', '66vw'],
margin: 'auto',
mt: 4
}}
>
Join other high-schoolers at an upcoming hackathon.
</Heading>
<Box
sx={{
maxWidth: ['95vw', '66vw'],
margin: 'auto',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mb: 2
}}
>
<Text sx={{ display: ['none', 'flex'], alignItems: 'center' }}>
<Dot />
</Text>
<Text
variant="lead"
sx={{ color: 'muted', mr: 2, textAlign: 'center' }}
>
from{' '}
<Link
href="https://hackathons.hackclub.com"
sx={{ color: 'currentcolor' }}
>
hackathons.hackclub.com
</Link>
, last updated just now.
</Text>
</Box>
</Container>
) : (
<></>
)}
<PageVisibility onChange={handleVisibilityChange}>
{pageIsVisible && (
<Ticker mode={mode || 'string'} {...props}>
{() => (
<Box as="div" sx={{ display: 'flex', py: 3 }}>
{eventData.map((event: any) => (
<EventCard key={event.website} {...event} />
))}
</Box>
)}
</Ticker>
)}
</PageVisibility>
</>
)
}
const flashing = keyframes({
from: { opacity: 0 },
'50%': { opacity: 1 },
to: { opacity: 0 }
})
function Dot() {
return (
<Text
sx={{
bg: 'green',
color: 'white',
borderRadius: 'circle',
display: 'inline-block',
lineHeight: 0,
width: '.5em',
height: '.5em',
marginRight: '.4em',
marginBottom: '.12em',
animationName: `${flashing}`,
animationDuration: '3s',
animationTimingFunction: 'ease-in-out',
animationIterationCount: 'infinite'
}}
/>
)
}
type EventCardProps = {
name: string
website: string
start: string
end: string
city?: string
state?: string
country?: string
countryCode?: string
banner: string
logo?: string
virtual?: boolean
hybrid?: boolean
footer?: React.ReactNode
}
function EventCard({
name,
website,
start,
end,
city,
state,
country,
countryCode,
banner,
logo,
virtual,
hybrid,
footer
}: EventCardProps) {
return (
<Tilt>
<Card
as="a"
href={website}
target="_blank"
rel="noopener noreferrer"
itemScope
itemType="http://schema.org/Event"
sx={{
display: 'flex',
flexDirection: 'column',
px: 4,
mx: 4,
borderRadius: 'extra',
width: '400px',
height: '200px',
textDecoration: 'none',
color: 'white'
}}
style={{
backgroundImage: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,0.375) 75%), url('${banner}')`,
textAlign: 'center',
backgroundSize: 'cover'
}}
>
<Badge
as="span"
itemType="VirtualLocation"
sx={{
position: 'absolute',
top: 16,
right: 16,
bg: 'snow',
color: virtual ? 'red' : hybrid ? 'orange' : 'blue',
fontSize: 'inherit',
textShadow: 'none',
borderRadius: 5
}}
>
{virtual ? 'Online' : hybrid ? 'Hybrid' : 'In-Person'}
</Badge>
{logo && (
<Image
src={logo}
alt={`${name} logo`}
loading="lazy"
sx={{
minWidth: 64,
height: 64,
objectFit: 'contain',
borderRadius: 'default',
mt: 'auto'
}}
/>
)}
<Heading
variant="headline"
as="h3"
itemProp="name"
sx={{
fontSize: [3, 4],
mt: 2,
mb: 3,
overflowWrap: 'anywhere',
width: '100%',
color: 'white',
textDecoration: 'none'
}}
>
{name}
</Heading>
<Box
as="footer"
sx={{
mt: 'auto',
mb: 0,
width: '100%',
opacity: 0.875
}}
>
{footer ? (
footer
) : (
<>
<Text
as="span"
itemProp="location"
itemScope
itemType="http://schema.org/Place"
>
{!virtual && (
<span itemProp="address">
{formatAddress(city, state, country, countryCode)}
</span>
)}
</Text>
</>
)}
</Box>
<Box sx={{ display: 'none' }}>
<span itemProp="eventAttendanceMode">
{virtual
? 'https://schema.org/OnlineEventAttendanceMode'
: 'https://schema.org/OfflineEventAttendanceMode'}
</span>
<span itemProp="url">{website}</span>
<span itemProp="startDate" content={start}>
{start}
</span>
<span itemProp="endDate" content={end}>
{end}
</span>
</Box>
</Card>
</Tilt>
)
}
================================================
FILE: components/icon.tsx
================================================
import React from 'react'
import Icon from '@hackclub/icons'
export default function IconComponent(props: any): React.ReactElement {
return <Icon {...props} />
}
================================================
FILE: components/index/cards/beest.tsx
================================================
/** @jsxImportSource theme-ui */
import CardModel from './card-model'
import { Box, Image, Text } from 'theme-ui'
import { keyframes } from '@emotion/react'
const slideIn = keyframes({
from: { transform: 'translateX(0)' },
to: { transform: 'translateX(-70%)' }
})
export default function Beest() {
return (
<CardModel
color="black"
sx={{
background: '#A7C1D6',
backgroundImage:
'url(https://cdn.hackclub.com/019d88c4-02ec-7b2a-844a-02dcb9f02b99/beestbg.webp)',
backgroundSize: 'cover',
backgroundPosition: 'bottom',
borderRadius: '16px',
paddingTop: '3em !important',
paddingBottom: '3em !important',
position: 'relative',
overflow: 'hidden'
}}
position={[null, 'bottom', 'bottom']}
visible={true}
>
<Image
alt="strandbeest"
src="https://cdn.hackclub.com/019d87ae-ead2-7b25-8d39-2730ac702452/beest-sticker.webp"
sx={{
position: 'absolute',
bottom: '45px',
right: '-170px',
maxWidth: '21em',
zIndex: 1,
display: ['none', 'none', 'none', 'block'],
// Explicitly handle the animation
'@supports (animation-timeline: scroll(root))': {
animationName: `${slideIn}`,
animationTimingFunction: 'ease-in-out',
animationFillMode: 'both',
animationTimeline: 'scroll(root)',
animationRange: 'entry 10% contain 50%'
},
// Firefox Fallback
'@supports not (animation-timeline: scroll(root))': {
right: '10px',
transform: 'none'
}
}}
/>
<Box
sx={{
paddingInline: '2em',
maxWidth: ['100%', '100%', '100%', '75%'],
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
height: '100%',
zIndex: 2,
position: 'relative'
}}
>
<Image
alt="beest"
src="https://cdn.hackclub.com/019d87e3-965d-75b0-83dd-c73469f47911/beest-cropped.png"
sx={{
maxWidth: ['80%', '60%', '50%', '45%']
}}
/>
<Text
variant="subtitle"
sx={{
color: '#4C483C',
fontSize: ['18px', '24px'],
fontFamily: 'system-ui, sans-serif',
fontWeight: '600',
lineHeight: 1.3,
mb: 5,
display: 'block',
textAlign: 'left'
}}
>
Spend 40 hours building projects, fly to the Netherlands, build a
mechanical animal!
</Text>
<Box
sx={{
height: '48px'
}}
>
<Box
as="a"
href="https://beest.hackclub.com/"
sx={{
display: 'inline-block',
padding: '8px 22px',
background: '#c48382',
color: '#fff',
fontFamily: '"Courier New", monospace',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '0.04em',
textDecoration: 'none',
textTransform: 'uppercase',
border: '3px solid #a06a69',
borderBottom: '8px solid #8a5857',
boxShadow: '4px 4px 0 #3a3832',
transition:
'transform 0.1s ease, box-shadow 0.1s ease, border-bottom-width 0.1s ease',
'&:hover': {
transform: 'translate(-1px, -1px)',
boxShadow: '5px 5px 0 #3a3832'
},
'&:active': {
transform: 'translateY(5px)',
borderBottomWidth: '3px',
boxShadow: '2px 1px 0 #3a3832'
}
}}
>
Get building!
</Box>
</Box>
</Box>
</CardModel>
)
}
================================================
FILE: components/index/cards/button.tsx
================================================
/** @jsxImportSource theme-ui */
import { Box, Button, Text } from 'theme-ui'
import ReactTooltip from '../../react-tooltip'
import Icon from '@hackclub/icons'
type ButtonsProps = {
children: React.ReactNode
icon?: string
customIcon?: React.ReactNode
id: string
content?: React.ReactNode
link?: string
primary?: boolean | string
overrideColor?: string
zIndex?: number
sx?: any
}
export default function Buttons({
children,
icon,
customIcon,
id,
content,
link,
primary,
overrideColor,
zIndex,
sx,
...props
}: ButtonsProps) {
const fontWeight = primary ? '700' : '400'
return (
<Box
as="button"
sx={{
background: 'transparent',
border: 'none',
color: 'white',
zIndex: zIndex || 0,
...sx
}}
py={1}
tabIndex={-1}
>
<Button
data-place="right"
data-for={id}
data-effect="solid"
data-tip
sx={{
background:
(typeof primary === 'string' ? primary : undefined) ||
overrideColor ||
'rgb(255, 255, 255, 0.3)',
borderRadius: '100px',
border: 'none',
display: 'flex',
alignItems: 'center',
color: 'inherit',
px: '3',
py: primary ? '12px' : 2,
width: 'fit-content',
textTransform: 'none',
fontSize: primary ? ['18px', '20px', '22px'] : [1, '16px', '18px'],
backdropFilter: 'blur(2px)',
fontWeight: fontWeight,
zIndex: 999
}}
as="a"
href={link || '/'}
target="_blank"
rel="noreferrer"
{...props}
>
{customIcon ? (
<Box sx={{ marginRight: 2, display: 'flex', alignItems: 'center' }}>
{customIcon}
</Box>
) : (
<Icon
glyph={(icon || 'plus-fill') as any}
size={24}
style={{ color: 'inherit', marginRight: 2 }}
/>
)}
<Text sx={{ fontFamily: 'Phantom Sans', textAlign: 'left' }}>
{children}
</Text>
</Button>
<ReactTooltip
id={id}
delayShow={150}
delayHide={100}
delayUpdate={500}
clickable={true}
getContent={() => {
return null
}}
className="custom-tooltip-radius custom-arrow-radius"
arrowRadius="2"
tooltipRadius="10"
>
{content}
</ReactTooltip>
</Box>
)
}
================================================
FILE: components/index/cards/card-model.tsx
================================================
import Icon from '../../icon'
import { Box, Card, Flex, Image, Link, Text } from 'theme-ui'
import ReactTooltip from '../../react-tooltip'
import Comma from '../../comma'
/** @jsxImportSource theme-ui */
const CardModel = ({
background,
children,
image,
image_fit,
link,
highlight,
github_link,
badge,
text,
color,
stars,
delay,
position,
filter,
visible = false,
...props
}: {
[x: string]: any
background?: any
children?: any
image?: any
image_fit?: any
link?: any
highlight?: any
github_link?: any
badge?: any
text?: any
color?: any
stars?: any
delay?: any
position?: any
filter?: any
visible?: boolean
}) => (
// <Zoom delay={delay}>
<Card
sx={{
position: 'relative',
width: '100%',
color: color,
my: [4, 4],
p: '24px',
backgroundSize: 'cover',
backgroundImage: background ? `url(${background})` : undefined,
backgroundPosition: 'center bottom',
backgroundRepeat: 'no-repeat',
'& p': {
fontSize: ['18px', '20px', '22px']
},
overflow: visible ? 'visible' : 'hidden'
}}
{...props}
>
{badge && (
<Box
sx={{
position: ['relative', 'relative', 'relative', 'absolute'],
width: 'fit-content',
right: [0, 0, 0, 3],
top: [0, 0, 0, 3],
zIndex: 3,
px: '12px',
py: '4px',
mb: 2,
float: [null, 'right', null],
// background: 'rgba(255,255,255,0.2)',
border: 'rgba(255,255,255,0.2) dashed 1px',
borderRadius: 'circle',
fontWeight: 'bold'
}}
>
{text || 'Happening now'}
</Box>
)}
{github_link && (
<Box>
{position === 'bottom' ? (
<Flex
sx={{
position: 'absolute',
left: 3,
bottom: 2,
alignItems: 'center',
zIndex: 2
}}
>
<Link
href={github_link}
sx={{ mr: 2 }}
target="_blank"
rel="noopener"
>
<Icon
glyph="github"
size={42}
color="#2351fs"
sx={{
color: '#000',
'&:hover': {
color: highlight || color
}
}}
/>
</Link>
{stars ? (
<Text as="h2">
⭐️ <Comma>{stars}</Comma>
</Text>
) : (
<></>
)}
</Flex>
) : (
<Flex
sx={{
position: 'absolute',
right: 2,
top: 2,
alignItems: 'center',
zIndex: 2
// flexDirection: ['column', 'row', 'row']
}}
>
{stars ? (
<Text as="h2" sx={{ fontSize: ['20px', '24px', '28px'] }}>
⭐️ <Comma>{stars}</Comma>
</Text>
) : (
<></>
)}
<Link href={github_link} sx={{ ml: 2 }}>
<Icon
glyph="github"
size={42}
sx={{
color: color,
transition: '0.4s',
'&:hover': {
color: highlight || color
}
}}
/>
</Link>
</Flex>
)}
</Box>
)}
{image && (
<Image
src={image}
draggable="false"
sx={{
objectFit: image_fit ? image_fit : 'cover',
position: 'absolute',
width: '100%',
height: '100%',
ml: ['-24px', '-32px', '-32px', '-32px'],
mt: ['-24px', '-32px', '-32px', '-32px'],
zIndex: 0,
filter
}}
alt=""
/>
)}
{children}
<ReactTooltip />
</Card>
// </Zoom>
)
export default CardModel
================================================
FILE: components/index/cards/clubs.tsx
================================================
/** @jsxImportSource theme-ui */
import Buttons from './button'
import CardModel from './card-model'
import { Box, Grid, Flex, Image, Text } from 'theme-ui'
const Cover = () => (
<Box
sx={{
position: 'absolute',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundImage:
'linear-gradient(to bottom,rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.8))',
opacity: 0.8,
zIndex: 1
}}
/>
)
export default function Clubs() {
// let [fooRef, setFooRef] = useState('')
// let [toggle, setToggle] = useState(true)
return (
<CardModel
color="white"
sx={{
backgroundColor: 'red'
}}
>
<Image
src="https://cloud-5pdwvchgm-hack-club-bot.vercel.app/05851864a.jpg"
alt="Summer Creek Hack Club meeting, February 2020"
sx={{
objectFit: 'cover',
position: 'absolute',
width: '120%',
height: '120%',
ml: ['-24px', '-32px', '-32px', '-32px'],
mt: ['-24px', '-32px', '-32px', '-32px'],
zIndex: 0
}}
/>
<Cover />
<Text
variant="title"
as="h3"
sx={{
borderRadius: 'default',
px: 2,
mx: [-2, 0],
whiteSpace: [null, 'nowrap', 'nowrap'],
fontSize: ['36px', 4, 5],
position: 'relative',
zIndex: 2,
width: 'fit-content'
}}
>
A Network of 1000+ Coding Clubs
</Text>
<Grid columns={[1, 1, 2]} sx={{ position: 'relative', zIndex: 2 }}>
<Box>
<Text
as="p"
variant="subtitle"
sx={{ textShadow: '1px 1px 5px black' }}
>
Join or start a Hack Club and be part of a network of high
quality coding clubs where you learn to code entirely through
building things.
</Text>
<Text
as="p"
variant="subtitle"
sx={{ textShadow: '1px 1px 5px black' }}
>
You can start with no experience and build and ship a project every
meeting.
</Text>
<Flex sx={{ flexDirection: 'column', mt: [3, 3, 4] }}>
<Buttons
content="we'll support you with meeting content, stickers, and more"
id="2"
icon="welcome"
link="https://apply.hackclub.com/"
primary="red"
>
Start a club
</Buttons>
</Flex>
</Box>
</Grid>
</CardModel>
)
}
================================================
FILE: components/index/cards/fallout.tsx
================================================
/** @jsxImportSource theme-ui */
import { Box, Text, Image } from 'theme-ui'
import CardModel from './card-model'
import Buttons from './button'
export default function Fallout() {
return (
<CardModel
color="black"
sx={{
borderRadius: '16px',
border: '2px solid #61453A',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.1)',
position: 'relative',
backgroundImage:
'url("https://cdn.hackclub.com/019ce02f-ed7b-7b9f-bf91-84503a43c535/bg.webp")',
backgroundSize: 'cover',
backgroundPosition: 'center'
}}
position={[null, 'bottom', 'bottom']}
visible={true}
>
<video
autoPlay
loop
muted
playsInline
style={{
width: '300px',
maxWidth: '30%',
position: 'absolute',
bottom: 0,
right: 10,
height: 'auto',
zIndex: 10
}}
>
<source
src="https://cdn.hackclub.com/019ce0b8-d90a-7f80-866c-185a5ccd74d9/soup.webm"
type="video/webm"
/>
</video>
<Box
sx={{
position: 'relative',
zIndex: 2,
paddingInline: '20px',
maxWidth: ['100%', '100%', '100%', '50%'],
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
height: '100%'
}}
>
<Image
src="https://cdn.hackclub.com/019cdfd0-6f09-7c8c-bd01-d0349e421c32/logo2.svg"
alt="Fallout"
sx={{
maxWidth: '400px',
marginTop: '20px',
width: '100%'
}}
/>
<Text
variant="subtitle"
as="p"
sx={{
fontFamily: 'system-ui, -apple-system, sans-serif',
color: 'white',
fontSize: ['20px', '24px'],
fontWeight: 600,
lineHeight: 1.5,
mb: 4,
maxWidth: '400px',
display: 'block',
textAlign: 'left'
}}
>
Build hardware projects, track your hours, then{' '}
<span style={{ fontWeight: 700 }}>
attend a hardware hackathon in Shenzhen!
</span>
</Text>
<Buttons
id="fallout-join"
icon="enter"
link="https://fallout.hackclub.com/?utm_source=site_card"
rel="noopener"
sx={{
background: '#9F715D',
color: '#EDD1B0',
border: '2px solid #61453A',
borderRadius: '100px',
px: 3,
py: '10px',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontWeight: '600'
}}
>
Start Building
</Buttons>
</Box>
</CardModel>
)
}
================================================
FILE: components/index/cards/flavortown.tsx
================================================
/** @jsxImportSource theme-ui */
import CardModel from './card-model'
import { Box, Flex, Grid, Image, Text } from 'theme-ui'
import Buttons from './button'
export default function Flavortown() {
return (
<CardModel
color="white"
sx={{
background:
'url("https://flavortown.hackclub.com/assets/mask/project-card-bd9acd6b.webp"), linear-gradient(to top, rgba(123,73,66,0.9), rgba(123,73,66,0.9))',
borderRadius: '24px',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.1)'
}}
position={[null, 'bottom', 'bottom']}
visible={true}
>
<Grid
columns={[1, 1, '1.5fr 1fr']}
sx={{
position: 'relative',
alignItems: 'center',
zIndex: 2,
paddingInline: '50px'
}}
>
<Box sx={{ textAlign: ['left', 'left', 'left'] }}>
<Image
src="https://cdn.hackclub.com/019c76b8-4f54-7de9-ae34-90b2190c2440/TeQ27w.png"
alt="Flavortown Text Logo"
sx={{
height: '70px'
}}
/>
<Text
variant="subtitle"
sx={{
color: '#f0dcc8ff',
fontSize: ['18px', '20px'],
fontWeight: 500,
lineHeight: 1.5,
mb: 3,
display: 'block',
textAlign: 'left'
}}
>
Make a website, game, hardware project, or anything your heart
desires, share your project for others to experience and to get
cookies - our virtual currency, and exchange your cookies for iPads,
MacBooks, Raspberry Pis and so many more things - all for free!
</Text>
<Buttons
id="join-flavortown"
icon="enter"
gitextract_sca6vlk2/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── attachment-warn.yml │ ├── caniuse-update.yml │ ├── ci.yml │ └── validate-team-json.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── AGENT.md ├── LICENSE.md ├── README.md ├── components/ │ ├── AButton.ts │ ├── analytics.tsx │ ├── announcement.tsx │ ├── announcements/ │ │ ├── amount.tsx │ │ ├── cta.tsx │ │ ├── elon.mdx │ │ ├── hcb-mobile.mdx │ │ ├── hcb-open-source.mdx │ │ ├── hcb_cta.tsx │ │ ├── holder.tsx │ │ ├── pills.tsx │ │ ├── preston-werner-2022.mdx │ │ ├── preston-werner.mdx │ │ └── relon.mdx │ ├── arcade/ │ │ ├── footer.tsx │ │ └── projects.tsx │ ├── background-image.tsx │ ├── bin/ │ │ ├── GalleryPosts.tsx │ │ ├── PartTag.module.css │ │ ├── PartTag.tsx │ │ ├── nav.tsx │ │ └── rsvp-form.tsx │ ├── bio.tsx │ ├── boardbio.tsx │ ├── color-switcher.tsx │ ├── comma.ts │ ├── directoryModal.tsx │ ├── donate/ │ │ ├── donors.json │ │ └── sponsors.tsx │ ├── dot.tsx │ ├── elon.mdx │ ├── fade-in.tsx │ ├── fiscal-sponsorship/ │ │ ├── contact.tsx │ │ ├── directory/ │ │ │ └── card.tsx │ │ ├── features.tsx │ │ ├── first/ │ │ │ ├── apply-button.tsx │ │ │ ├── features.tsx │ │ │ ├── start.tsx │ │ │ ├── stats.tsx │ │ │ └── testimonials.tsx │ │ ├── open-source.tsx │ │ ├── organization-spotlight.tsx │ │ ├── sign-in.tsx │ │ └── tooltip.tsx │ ├── flag.tsx │ ├── flex-col.tsx │ ├── footer.tsx │ ├── force-theme.ts │ ├── hackathons/ │ │ ├── features/ │ │ │ ├── marketing.tsx │ │ │ └── slack.tsx │ │ ├── keep-exploring.tsx │ │ ├── landing.tsx │ │ ├── overview.tsx │ │ ├── recap.tsx │ │ └── scrolling-hackathons.tsx │ ├── icon.tsx │ ├── index/ │ │ ├── cards/ │ │ │ ├── beest.tsx │ │ │ ├── button.tsx │ │ │ ├── card-model.tsx │ │ │ ├── clubs.tsx │ │ │ ├── fallout.tsx │ │ │ ├── flavortown.tsx │ │ │ ├── hackathons.tsx │ │ │ ├── haxidraw.tsx │ │ │ ├── hcb.tsx │ │ │ ├── hctg.tsx │ │ │ ├── horizons.tsx │ │ │ ├── jackpot.tsx │ │ │ ├── macondo.tsx │ │ │ ├── mailing-list.tsx │ │ │ ├── sinerider.tsx │ │ │ ├── slack.tsx │ │ │ ├── sleepover.tsx │ │ │ ├── sprig-console.tsx │ │ │ ├── sprig.tsx │ │ │ ├── stasis.tsx │ │ │ └── workshops.tsx │ │ ├── carousel-cards.tsx │ │ ├── carousel.tsx │ │ ├── ctas.tsx │ │ ├── events.tsx │ │ └── github.tsx │ ├── letterhead.tsx │ ├── mail-card.tsx │ ├── marquee.tsx │ ├── mention.tsx │ ├── nav.tsx │ ├── onboard/ │ │ ├── gallery-paginated.tsx │ │ ├── item.tsx │ │ ├── pagination-buttons.tsx │ │ ├── recap.tsx │ │ └── youtube-video.tsx │ ├── particles.tsx │ ├── photo.tsx │ ├── posts/ │ │ ├── emoji.tsx │ │ ├── index.tsx │ │ └── mention.tsx │ ├── press.mdx │ ├── react-reveal-compat.tsx │ ├── react-tooltip.ts │ ├── replit/ │ │ ├── scale-up.tsx │ │ └── token-instructions.tsx │ ├── scroll-hint.tsx │ ├── secret.tsx │ ├── ship/ │ │ └── why.mdx │ ├── signature.tsx │ ├── signatures.tsx │ ├── slack.tsx │ ├── slide-down.tsx │ ├── slide-up.tsx │ ├── sparkles/ │ │ ├── index.tsx │ │ └── money.tsx │ ├── stage.tsx │ ├── stat.tsx │ ├── submit.tsx │ ├── tilt.tsx │ └── winter/ │ ├── breakdown-box.tsx │ ├── breakdown.tsx │ ├── footer.tsx │ ├── info.tsx │ ├── landing.tsx │ ├── projects.tsx │ ├── recap.tsx │ └── timeline.tsx ├── eslint.config.mts ├── lib/ │ ├── cached-hcb-orgs.ts │ ├── countries.json │ ├── cta.json │ ├── dates.ts │ ├── fetcher.ts │ ├── git-info.ts │ ├── helpers.ts │ ├── members.ts │ ├── organization.ts │ ├── slackData.ts │ ├── sleep.ts │ ├── theme.ts │ ├── use-form.ts │ ├── use-has-mounted.ts │ ├── use-media.ts │ ├── use-prefers-motion.ts │ ├── use-prefers-reduced-motion.ts │ └── use-random-interval.ts ├── next.config.ts ├── package.json ├── pages/ │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── acknowledged.tsx │ ├── amas/ │ │ ├── geohot.tsx │ │ ├── index.tsx │ │ ├── sal.tsx │ │ └── vitalik.tsx │ ├── api/ │ │ ├── arcade/ │ │ │ ├── hack-hour/ │ │ │ │ └── inventory.ts │ │ │ └── shop.ts │ │ ├── bin/ │ │ │ ├── gallery/ │ │ │ │ ├── posts.ts │ │ │ │ └── tags.ts │ │ │ ├── rsvp.ts │ │ │ └── wokwi/ │ │ │ ├── new/ │ │ │ │ ├── [parts].ts │ │ │ │ └── index.ts │ │ │ └── parts.ts │ │ ├── bucky.ts │ │ ├── channels/ │ │ │ └── resolve.ts │ │ ├── contribute.ts │ │ ├── first-team.ts │ │ ├── games.ts │ │ ├── github.ts │ │ ├── join.ts │ │ ├── mailing-list.ts │ │ ├── onboard/ │ │ │ ├── p/ │ │ │ │ ├── [project]/ │ │ │ │ │ └── index.ts │ │ │ │ ├── count.ts │ │ │ │ └── index.ts │ │ │ └── svg/ │ │ │ └── [board_url]/ │ │ │ ├── bottom.ts │ │ │ ├── index.ts │ │ │ └── top.ts │ │ ├── replit/ │ │ │ └── signup.ts │ │ ├── sprig-console.ts │ │ ├── stars.ts │ │ ├── steve.ts │ │ ├── stickers.ts │ │ ├── stuff.ts │ │ ├── team.ts │ │ └── winter-rsvp.ts │ ├── arcade/ │ │ └── index.tsx │ ├── bin/ │ │ ├── gallery.tsx │ │ └── prelaunch.tsx │ ├── brand.tsx │ ├── clubs.tsx │ ├── content/ │ │ ├── covid19.mdx │ │ ├── it-admins.mdx │ │ ├── sponsorship.mdx │ │ ├── sunsetting-som.mdx │ │ └── transparency/ │ │ └── may-2020.mdx │ ├── deprecated/ │ │ └── [deprecated].tsx │ ├── elon.tsx │ ├── events.tsx │ ├── fiscal-sponsorship/ │ │ ├── about.tsx │ │ ├── climate/ │ │ │ ├── [region].tsx │ │ │ └── index.tsx │ │ ├── directory/ │ │ │ ├── [category]/ │ │ │ │ ├── [region].tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── first.tsx │ │ ├── index.tsx │ │ ├── mobile/ │ │ │ └── index.tsx │ │ └── open-source.tsx │ ├── hackathons/ │ │ ├── grant.tsx │ │ └── index.tsx │ ├── imprint.tsx │ ├── index.tsx │ ├── jobs/ │ │ └── index.tsx │ ├── minecraft.tsx │ ├── night.tsx │ ├── onboard/ │ │ ├── board/ │ │ │ └── [slug].tsx │ │ ├── first.tsx │ │ ├── gallery/ │ │ │ └── index.tsx │ │ └── index.tsx │ ├── opensource.tsx │ ├── philanthropy/ │ │ ├── index.tsx │ │ └── supporters.tsx │ ├── philosophy.tsx │ ├── pizza.tsx │ ├── press.tsx │ ├── preston-werner-2022.tsx │ ├── preston-werner.tsx │ ├── relon.tsx │ ├── replit.tsx │ ├── santa.tsx │ ├── sharkbank/ │ │ └── index.tsx │ ├── ship.tsx │ ├── sitemap.xml.tsx │ ├── steve.tsx │ ├── stickers.tsx │ ├── team.tsx │ └── winter.tsx ├── public/ │ ├── .well-known/ │ │ └── security.txt │ ├── acknowledged.json │ ├── bin/ │ │ ├── data-loading.js │ │ ├── index.html │ │ ├── landing/ │ │ │ ├── index.html │ │ │ ├── script.js │ │ │ └── style.css │ │ ├── landing-new/ │ │ │ ├── ascii-art.txt │ │ │ ├── gambling.js │ │ │ ├── script.js │ │ │ └── style.css │ │ ├── memes.js │ │ ├── orph/ │ │ │ ├── orph.css │ │ │ └── orph.js │ │ ├── selector/ │ │ │ ├── index.html │ │ │ ├── script.js │ │ │ └── style.css │ │ └── style/ │ │ ├── common.css │ │ ├── footer.css │ │ └── gallery.module.css │ ├── carousel.json │ ├── horizons/ │ │ └── Hypebuzz.otf │ ├── robots.txt │ ├── stickers-in-stock.html │ └── team.json ├── tsconfig.json ├── types/ │ └── mdx.d.ts └── vercel.json
SYMBOL INDEX (287 symbols across 154 files)
FILE: components/announcement.tsx
type AnnouncementProps (line 11) | type AnnouncementProps = {
FILE: components/announcements/cta.tsx
function SlackCTA (line 6) | function SlackCTA() {
FILE: components/announcements/hcb_cta.tsx
function HCBCTA (line 5) | function HCBCTA() {
FILE: components/announcements/holder.tsx
function AnnouncementHolder (line 3) | function AnnouncementHolder({ children }) {
FILE: components/announcements/pills.tsx
function PillHolder (line 3) | function PillHolder({ children }) {
function AuthorPill (line 28) | function AuthorPill({ tag, image, firstName }) {
function DatePill (line 46) | function DatePill({ tag }) {
FILE: components/arcade/projects.tsx
function Projects (line 148) | function Projects() {
FILE: components/background-image.tsx
type BGImgProps (line 11) | type BGImgProps = {
function BGImg (line 18) | function BGImg({
FILE: components/bin/GalleryPosts.tsx
type BinPostProps (line 6) | type BinPostProps = {
function handleClick (line 32) | function handleClick() {
function formatDate (line 38) | function formatDate(dateString) {
FILE: components/bin/rsvp-form.tsx
function RsvpForm (line 8) | function RsvpForm() {
FILE: components/bio.tsx
function Bio (line 6) | function Bio({ popup = true, spanTwo = false, ...props }) {
FILE: components/boardbio.tsx
function BoardBox (line 6) | function BoardBox({ popup = true, ...props }) {
FILE: components/comma.ts
function Comma (line 1) | function Comma({ children }) {
FILE: components/directoryModal.tsx
function getBadgesForOrg (line 14) | function getBadgesForOrg(org: { [key: string]: any }): typeof badges {
type OrganizationModalProps (line 23) | type OrganizationModalProps = {
function OrganizationModal (line 45) | function OrganizationModal({
FILE: components/dot.tsx
function Dot (line 10) | function Dot({ hideOnMobile }) {
FILE: components/fiscal-sponsorship/contact.tsx
function ContactBanner (line 8) | function ContactBanner({ sx }) {
FILE: components/fiscal-sponsorship/directory/card.tsx
type OrganizationCardProps (line 45) | type OrganizationCardProps = {
FILE: components/fiscal-sponsorship/features.tsx
function Features (line 7) | function Features() {
function Module (line 95) | function Module({ icon, name, body }) {
function Laptop (line 146) | function Laptop({ href, title }) {
FILE: components/fiscal-sponsorship/first/apply-button.tsx
function ApplyButton (line 5) | function ApplyButton() {
FILE: components/fiscal-sponsorship/first/features.tsx
function Features (line 9) | function Features() {
type ModuleProps (line 327) | type ModuleProps = {
function Module (line 334) | function Module({ icon, name, body, iconColor }: ModuleProps) {
function ModuleDetails (line 381) | function ModuleDetails({ children }) {
function Document (line 400) | function Document({ name, cost }) {
function Laptop (line 422) | function Laptop({ href, title, sx }) {
FILE: components/fiscal-sponsorship/first/start.tsx
function Start (line 5) | function Start({ stats }) {
FILE: components/fiscal-sponsorship/first/stats.tsx
function startMoneyAnimation (line 13) | function startMoneyAnimation(
function formatMoney (line 38) | function formatMoney(amount) {
function getStaticProps (line 108) | async function getStaticProps() {
FILE: components/fiscal-sponsorship/first/testimonials.tsx
function Testimonials (line 14) | function Testimonials() {
function Organization (line 78) | function Organization({
FILE: components/fiscal-sponsorship/open-source.tsx
function OpenSource (line 8) | function OpenSource() {
FILE: components/fiscal-sponsorship/organization-spotlight.tsx
function OrganizationSpotlight (line 7) | function OrganizationSpotlight({ organization }) {
FILE: components/fiscal-sponsorship/sign-in.tsx
function SignIn (line 5) | function SignIn() {
FILE: components/fiscal-sponsorship/tooltip.tsx
type TooltipComponent (line 87) | type TooltipComponent = React.FC<{ children: any; text: any; id: any }> & {
FILE: components/flex-col.tsx
function FlexCol (line 3) | function FlexCol({ children, ...props }) {
FILE: components/hackathons/keep-exploring.tsx
function KeepExploring (line 5) | function KeepExploring() {
FILE: components/hackathons/landing.tsx
function Landing (line 7) | function Landing() {
function Slide (line 111) | function Slide({ children }) {
function BlueGradientFilter (line 144) | function BlueGradientFilter() {
FILE: components/hackathons/overview.tsx
function Overview (line 3) | function Overview() {
function Highlight (line 72) | function Highlight({ children }) {
FILE: components/hackathons/recap.tsx
function Recap (line 5) | function Recap() {
FILE: components/hackathons/scrolling-hackathons.tsx
function ScrollingHackathons (line 19) | function ScrollingHackathons({
function Dot (line 100) | function Dot() {
type EventCardProps (line 122) | type EventCardProps = {
function EventCard (line 138) | function EventCard({
FILE: components/icon.tsx
function IconComponent (line 4) | function IconComponent(props: any): React.ReactElement {
FILE: components/index/cards/beest.tsx
function Beest (line 11) | function Beest() {
FILE: components/index/cards/button.tsx
type ButtonsProps (line 6) | type ButtonsProps = {
function Buttons (line 19) | function Buttons({
FILE: components/index/cards/clubs.tsx
function Clubs (line 22) | function Clubs() {
FILE: components/index/cards/fallout.tsx
function Fallout (line 6) | function Fallout() {
FILE: components/index/cards/flavortown.tsx
function Flavortown (line 6) | function Flavortown() {
FILE: components/index/cards/hackathons.tsx
function Hackathons (line 24) | function Hackathons({ data, stars }) {
FILE: components/index/cards/haxidraw.tsx
function Haxidraw (line 6) | function Haxidraw({ stars }) {
FILE: components/index/cards/hcb.tsx
function Bank (line 6) | function Bank({ data }) {
FILE: components/index/cards/hctg.tsx
function HackClubTheGame (line 5) | function HackClubTheGame() {
FILE: components/index/cards/horizons.tsx
function Horizons (line 9) | function Horizons() {
FILE: components/index/cards/jackpot.tsx
function Jackpot (line 5) | function Jackpot() {
FILE: components/index/cards/macondo.tsx
function Macondo (line 6) | function Macondo() {
FILE: components/index/cards/sinerider.tsx
function Sinerider (line 6) | function Sinerider({ stars }) {
FILE: components/index/cards/slack.tsx
function Slack (line 44) | function Slack({ data, events }) {
FILE: components/index/cards/sleepover.tsx
function Sleepover (line 5) | function Sleepover() {
FILE: components/index/cards/sprig-console.tsx
function SprigConsole (line 7) | function SprigConsole({ stars, consoleCount }) {
FILE: components/index/cards/sprig.tsx
function Game (line 7) | function Game({ game, gameImage, gameImage1, ...props }) {
function Sprig (line 168) | function Sprig({ stars, game, gameImage, gameImage1 }) {
FILE: components/index/cards/stasis.tsx
function Stasis (line 5) | function Stasis() {
FILE: components/index/cards/workshops.tsx
function Workshops (line 54) | function Workshops({ stars }) {
FILE: components/index/carousel-cards.tsx
function CarouselCards (line 4) | function CarouselCards({
FILE: components/index/carousel.tsx
function Carousel (line 8) | function Carousel({ cards }) {
FILE: components/index/ctas.tsx
function CTAS (line 6) | function CTAS({ cards }) {
FILE: components/index/events.tsx
function Events (line 79) | function Events({ events }) {
FILE: components/index/github.tsx
type GitHubProps (line 4) | type GitHubProps = {
function GitHub (line 14) | function GitHub({
FILE: components/mail-card.tsx
function MailCard (line 3) | function MailCard({ body, date, link, issue }) {
FILE: components/marquee.tsx
type Props (line 28) | type Props = {
function Marquee (line 35) | function Marquee({
FILE: components/nav.tsx
type HeaderProps (line 171) | type HeaderProps = {
function Header (line 179) | function Header({
FILE: components/photo.tsx
type PhotoProps (line 20) | type PhotoProps = {
FILE: components/posts/emoji.tsx
type CustomEmojiProps (line 41) | type CustomEmojiProps = {
FILE: components/posts/index.tsx
function Posts (line 140) | function Posts({ data = [] }) {
FILE: components/react-reveal-compat.tsx
type RevealProps (line 55) | type RevealProps = {
function RevealWrap (line 70) | function RevealWrap({
function Fade (line 88) | function Fade({
function Slide (line 127) | function Slide({
function Zoom (line 150) | function Zoom({ children, delay = 0, duration = 500 }: RevealProps) {
FILE: components/secret.tsx
function Secret (line 6) | function Secret({ reveal, ...props }) {
FILE: components/sparkles/index.tsx
type SparklesProps (line 25) | type SparklesProps = {
FILE: components/sparkles/money.tsx
type MSparklesProps (line 25) | type MSparklesProps = {
FILE: components/stage.tsx
type StageProps (line 4) | type StageProps = {
function Stage (line 13) | function Stage({
FILE: components/stat.tsx
type StatProps (line 4) | type StatProps = {
FILE: components/winter/breakdown-box.tsx
function BreakdownBox (line 6) | function BreakdownBox({
FILE: components/winter/breakdown.tsx
function Breakdown (line 6) | function Breakdown() {
FILE: components/winter/info.tsx
function InfoGrid (line 16) | function InfoGrid() {
function BulletItem (line 169) | function BulletItem({ children, iconGlyph, iconColor, iconSize }) {
FILE: components/winter/landing.tsx
function Landing (line 6) | function Landing() {
FILE: components/winter/projects.tsx
function Projects (line 163) | function Projects() {
FILE: components/winter/recap.tsx
function Recap (line 6) | function Recap() {
FILE: components/winter/timeline.tsx
function TimelineStep (line 6) | function TimelineStep({ children }) {
function Circle (line 39) | function Circle({ children }) {
function Step (line 61) | function Step({ icon, name, duration, href }) {
function RealTimeline (line 112) | function RealTimeline() {
FILE: lib/cached-hcb-orgs.ts
constant CACHE_FILENAME (line 1) | const CACHE_FILENAME = 'hcb-orgs-cache.json'
function fetchAllOrganizations (line 3) | async function fetchAllOrganizations() {
FILE: lib/dates.ts
function formatChunk (line 54) | function formatChunk(type, date) {
type FormatDate (line 104) | type FormatDate = {
FILE: lib/fetcher.ts
function fetcher (line 4) | async function fetcher(...args: Parameters<typeof fetch>) {
FILE: lib/helpers.ts
function formatChunk (line 131) | function formatChunk(type, date) {
FILE: lib/organization.ts
class Organization (line 5) | class Organization {
method constructor (line 12) | constructor(rawOrganization: any) {
method id (line 24) | get id() {
method name (line 32) | get name() {
method slug (line 40) | get slug() {
method isTransparent (line 48) | get isTransparent() {
method isDemo (line 56) | get isDemo() {
method users (line 64) | get users() {
method acceptsDonations (line 72) | get acceptsDonations() {
method branding (line 84) | get branding() {
method tags (line 102) | get tags() {
method createdAt (line 114) | get createdAt() {
method links (line 125) | get links() {
method location (line 145) | get location() {
method update (line 157) | async update() {
FILE: lib/slackData.ts
type SlackData (line 1) | type SlackData = {
FILE: lib/use-has-mounted.ts
function useHasMounted (line 4) | function useHasMounted() {
FILE: lib/use-media.ts
function useMedia (line 3) | function useMedia(query) {
FILE: lib/use-prefers-motion.ts
constant QUERY (line 4) | const QUERY = '(prefers-reduced-motion: no-preference)'
function usePrefersMotion (line 15) | function usePrefersMotion() {
FILE: lib/use-prefers-reduced-motion.ts
constant QUERY (line 4) | const QUERY = '(prefers-reduced-motion: no-preference)'
function usePrefersReducedMotion (line 15) | function usePrefersReducedMotion() {
FILE: next.config.ts
method redirects (line 34) | async redirects() {
method rewrites (line 225) | async rewrites() {
method headers (line 353) | async headers() {
FILE: pages/_document.tsx
class MyDocument (line 26) | class MyDocument extends Document {
method getInitialProps (line 27) | static async getInitialProps(ctx) {
method render (line 32) | render() {
FILE: pages/acknowledged.tsx
function Acknowleged (line 11) | function Acknowleged({ team }) {
FILE: pages/amas/geohot.tsx
function Geohot (line 8) | function Geohot() {
FILE: pages/amas/sal.tsx
function Sal (line 8) | function Sal() {
FILE: pages/amas/vitalik.tsx
function Vitalik (line 9) | function Vitalik() {
FILE: pages/api/arcade/hack-hour/inventory.ts
type InventoryRecord (line 25) | type InventoryRecord = {
type FlavorRecord (line 38) | type FlavorRecord = {
function handler (line 51) | async function handler(req, res) {
FILE: pages/api/arcade/shop.ts
function handler (line 31) | async function handler(req, res) {
FILE: pages/api/bin/gallery/posts.ts
function handler (line 34) | async function handler(req, res) {
FILE: pages/api/bin/gallery/tags.ts
function handler (line 28) | async function handler(req, res) {
FILE: pages/api/bin/rsvp.ts
function handler (line 19) | async function handler(req, res) {
FILE: pages/api/bin/wokwi/new/[parts].ts
function handler (line 3) | async function handler(req, res) {
FILE: pages/api/bin/wokwi/new/index.ts
function handler (line 155) | async function handler(req, res) {
FILE: pages/api/bin/wokwi/parts.ts
function handler (line 26) | async function handler(req, res) {
FILE: pages/api/bucky.ts
function handler (line 3) | async function handler(
FILE: pages/api/channels/resolve.ts
function handler (line 1) | async function handler(req, res) {
FILE: pages/api/contribute.ts
type OrgQueryResponse (line 5) | interface OrgQueryResponse {
function handler (line 15) | async function handler(
FILE: pages/api/first-team.ts
function handler (line 3) | async function handler(
FILE: pages/api/games.ts
function getGames (line 3) | async function getGames() {
function Games (line 16) | async function Games(
FILE: pages/api/github.ts
function fetchGitHub (line 35) | async function fetchGitHub() {
function github (line 59) | async function github(
FILE: pages/api/join.ts
function postData (line 14) | async function postData(url = '', data = {}, headers = {}) {
function handler (line 31) | async function handler(
FILE: pages/api/mailing-list.ts
function submit (line 3) | async function submit(
FILE: pages/api/onboard/p/[project]/index.ts
function getReadmeData (line 5) | async function getReadmeData(url) {
function handler (line 47) | async function handler(req, res) {
FILE: pages/api/onboard/p/count.ts
function onboardProjectCount (line 3) | async function onboardProjectCount() {
function handler (line 8) | async function handler(req, res) {
FILE: pages/api/onboard/p/index.ts
function handler (line 60) | async function handler(req, res) {
FILE: pages/api/onboard/svg/[board_url]/bottom.ts
function handler (line 5) | async function handler(req, res) {
FILE: pages/api/onboard/svg/[board_url]/index.ts
function handler (line 80) | async function handler(req, res) {
FILE: pages/api/onboard/svg/[board_url]/top.ts
function handler (line 5) | async function handler(req, res) {
FILE: pages/api/replit/signup.ts
function handler (line 1) | async function handler(req, res) {
FILE: pages/api/sprig-console.ts
function check (line 3) | function check(val: any) {
function getConsoles (line 7) | async function getConsoles() {
function SprigConsoles (line 30) | async function SprigConsoles(
FILE: pages/api/stars.ts
type GitHubStarsResponse (line 4) | interface GitHubStarsResponse {
function fetchStars (line 16) | async function fetchStars() {
function Stars (line 76) | async function Stars(
FILE: pages/api/stickers.ts
function handler (line 15) | async function handler(
FILE: pages/api/stuff.ts
function stuff (line 3) | async function stuff(
FILE: pages/api/team.ts
type TeamMember (line 5) | interface TeamMember {
function fetchTeam (line 21) | async function fetchTeam() {
function fetchAcknowledged (line 25) | async function fetchAcknowledged() {
function handler (line 29) | async function handler(
FILE: pages/api/winter-rsvp.ts
function handler (line 10) | async function handler(
FILE: pages/arcade/index.tsx
function generateProjectIdea (line 769) | async function generateProjectIdea() {
function getStaticProps (line 1835) | async function getStaticProps() {
FILE: pages/bin/gallery.tsx
function getStaticProps (line 11) | async function getStaticProps() {
function Gallery (line 35) | function Gallery({ posts = [], tags = [] }) {
FILE: pages/bin/prelaunch.tsx
function crunch (line 98) | function crunch() {
function spinIt (line 121) | function spinIt(el) {
function Bin (line 126) | function Bin() {
FILE: pages/deprecated/[deprecated].tsx
type DeprecatedPageProps (line 8) | type DeprecatedPageProps = {
FILE: pages/fiscal-sponsorship/about.tsx
type BulletProps (line 13) | type BulletProps = {
function Bullet (line 20) | function Bullet({ glow = true, icon, href, children }: BulletProps) {
function BulletBox (line 131) | function BulletBox({ padding = '2rem', children }) {
function Section (line 149) | function Section({ id, children }) {
function FiscalSponsorship (line 157) | function FiscalSponsorship() {
FILE: pages/fiscal-sponsorship/climate/[region].tsx
function ClimateRegionalPage (line 12) | function ClimateRegionalPage({ rawOrganizations, pageRegion }) {
FILE: pages/fiscal-sponsorship/climate/index.tsx
function getBadgesForOrg (line 49) | function getBadgesForOrg(org: Organization): typeof badges {
function getTagsForOrg (line 68) | function getTagsForOrg(org: Organization): typeof tags {
type RegionPanelProps (line 293) | type RegionPanelProps = {
type FilteringProps (line 459) | type FilteringProps = {
function ClimatePage (line 476) | function ClimatePage({ rawOrganizations, pageRegion }) {
function fetchRawClimateOrganizations (line 1157) | async function fetchRawClimateOrganizations() {
FILE: pages/fiscal-sponsorship/directory/[category]/[region].tsx
function DirectoryRegionalPage (line 14) | function DirectoryRegionalPage({
FILE: pages/fiscal-sponsorship/directory/[category]/index.tsx
function DirectoryRegionalPage (line 9) | function DirectoryRegionalPage({
FILE: pages/fiscal-sponsorship/directory/index.tsx
type Region (line 31) | type Region = {
type FilteringProps (line 331) | type FilteringProps = {
function Directory (line 364) | function Directory({ rawOrganizations, pageRegion, category }) {
function fetchRawOrganizations (line 666) | async function fetchRawOrganizations() {
FILE: pages/fiscal-sponsorship/first.tsx
function First (line 19) | function First({ stats }) {
function getStaticProps (line 205) | async function getStaticProps(context) {
FILE: pages/fiscal-sponsorship/index.tsx
function MobileAppAlert (line 76) | function MobileAppAlert() {
function Page (line 178) | function Page() {
FILE: pages/hackathons/grant.tsx
type RequirementProps (line 23) | type RequirementProps = {
FILE: pages/hackathons/index.tsx
function Hackathons (line 16) | function Hackathons({ data }) {
function getStaticProps (line 47) | async function getStaticProps() {
FILE: pages/index.tsx
type Window (line 52) | interface Window {
function Page (line 65) | function Page({
function getStaticProps (line 1324) | async function getStaticProps() {
FILE: pages/jobs/index.tsx
function getStaticProps (line 193) | async function getStaticProps() {
FILE: pages/onboard/board/[slug].tsx
type ProjectType (line 13) | type ProjectType = {
type BoardPageProps (line 24) | type BoardPageProps = {
function getStaticPaths (line 223) | async function getStaticPaths(_context) {
function getStaticProps (line 238) | async function getStaticProps(context: any) {
FILE: pages/onboard/gallery/index.tsx
function Index (line 6) | function Index({ projects, itemCount }) {
function getStaticProps (line 16) | async function getStaticProps() {
FILE: pages/opensource.tsx
function getStaticProps (line 244) | async function getStaticProps() {
FILE: pages/philanthropy/index.tsx
type PropPilled (line 209) | type PropPilled = {
constant FIRST_IMAGE (line 275) | const FIRST_IMAGE = {
constant SECOND_IMAGE (line 278) | const SECOND_IMAGE = {
FILE: pages/philanthropy/supporters.tsx
function Donate (line 100) | function Donate() {
FILE: pages/philosophy.tsx
function Philosophy (line 100) | function Philosophy() {
FILE: pages/santa.tsx
function Base (line 139) | function Base({ children, action, target, method }) {
function Field (line 165) | function Field({ placeholder, label, name, type, value, onChange }) {
type valuesType (line 189) | type valuesType = {
function Signup (line 195) | function Signup() {
FILE: pages/sharkbank/index.tsx
function DesktopMode (line 7) | function DesktopMode({ billboardBottom }) {
function TabletMode (line 318) | function TabletMode() {
function MobileMode (line 621) | function MobileMode() {
type SectionProps (line 935) | type SectionProps = {
function Section (line 942) | function Section({ bg, minHeight, minWidth, children }: SectionProps) {
function SharkBank (line 981) | function SharkBank() {
FILE: pages/sitemap.xml.tsx
constant SITE_URL (line 5) | const SITE_URL = 'https://hackclub.com'
function getPages (line 7) | function getPages(dir: string, base = ''): string[] {
function e (line 31) | function e(s: string): string {
function generateSitemap (line 40) | function generateSitemap(pages: string[]): string {
function Sitemap (line 67) | function Sitemap() {
FILE: pages/stickers.tsx
function customStartCase (line 14) | function customStartCase(st) {
FILE: pages/team.tsx
function Team (line 45) | function Team({ team }) {
FILE: pages/winter.tsx
function Winter (line 16) | function Winter() {
FILE: public/bin/data-loading.js
function pullFromStorage (line 3) | async function pullFromStorage() {
function setToStorage (line 20) | async function setToStorage(data) {
function fetchPartsData (line 25) | async function fetchPartsData() {
function removeItemByAttribute (line 35) | function removeItemByAttribute(arr, attr, value) {
function partsDataLoader (line 39) | async function partsDataLoader() {
function partsData (line 59) | async function partsData() {
FILE: public/bin/landing-new/gambling.js
function removeItemByAttribute (line 5) | function removeItemByAttribute(arr, attr, value) {
function addComponentsToPage (line 8) | function addComponentsToPage(data) {
function sample (line 59) | function sample(arr) {
function rollPartsAnimation (line 63) | function rollPartsAnimation(ms = 1000) {
function randomizeParts (line 77) | function randomizeParts() {
function rollParts (line 121) | function rollParts(el) {
function generateBuildLink (line 138) | async function generateBuildLink(e) {
function generateProjectIdea (line 160) | async function generateProjectIdea() {
FILE: public/bin/landing-new/script.js
function init (line 1) | function init() {
function getRandomInt (line 8) | function getRandomInt(max) {
function opf (line 12) | function opf(number) {
function onScroll (line 16) | function onScroll() {
function fetchAndLogTextFile (line 23) | async function fetchAndLogTextFile(url) {
function recalculateSectionHeight (line 39) | function recalculateSectionHeight() {
FILE: public/bin/landing/script.js
function rainbowColor (line 1) | function rainbowColor(index, total) {
function rainbow (line 6) | function rainbow(element) {
FILE: public/bin/selector/script.js
function getSelectedItems (line 16) | function getSelectedItems() {
function recalculateSelected (line 19) | function recalculateSelected() {
function addPartToPage (line 48) | function addPartToPage(part) {
Condensed preview — 274 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,441K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 529,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/attachment-warn.yml",
"chars": 3650,
"preview": "name: Perhaps You Should Upload To CDN Instead?\n\non:\n pull_request:\n types: [opened, synchronize]\n\njobs:\n warn:\n "
},
{
"path": ".github/workflows/caniuse-update.yml",
"chars": 1056,
"preview": "name: Update Browserslist database\n\non:\n workflow_dispatch:\n schedule:\n - cron: '0 2 1,15 * *'\n\npermissions:\n cont"
},
{
"path": ".github/workflows/ci.yml",
"chars": 493,
"preview": "name: CI\non:\n - push\n - pull_request\n\njobs:\n lint:\n name: Lint\n runs-on: ubuntu-latest\n steps:\n - uses:"
},
{
"path": ".github/workflows/validate-team-json.yml",
"chars": 488,
"preview": "name: Validate Team JSON\n\non:\n push:\n paths:\n - 'public/team.json'\n pull_request:\n paths:\n - 'public/t"
},
{
"path": ".gitignore",
"chars": 170,
"preview": ".now\n.env*\n.next\nnode_modules\n.DS_Store\npublic/sitemap.xml\n.vercel\n.vscode\nyarn-error.log\nbun.lockb\n.idea\n.yarn\n.yarnrc."
},
{
"path": ".prettierignore",
"chars": 5,
"preview": ".next"
},
{
"path": ".prettierrc",
"chars": 116,
"preview": "{\n \"singleQuote\": true,\n \"trailingComma\": \"none\",\n \"arrowParens\": \"avoid\",\n \"printWidth\": 80,\n \"semi\": false\n}\n"
},
{
"path": "AGENT.md",
"chars": 1191,
"preview": "# AGENT.md - Hack Club Site Development Guide\n\n## Commands\n\n- **Dev**: `bun run dev` (start development server)\n- **Buil"
},
{
"path": "LICENSE.md",
"chars": 1148,
"preview": "_Code under MIT License, assets may not be re-used or re-distributed._\n\n### MIT License\n\nCopyright 2026 The Hack Foundat"
},
{
"path": "README.md",
"chars": 3692,
"preview": "<p align=\"center\"><img width=\"192\" alt=\"Hack Club logo\" src=\"https://assets.hackclub.com/flag-standalone.svg\"></p>\n<h1 a"
},
{
"path": "components/AButton.ts",
"chars": 72,
"preview": "import { Button } from 'theme-ui'\n\nexport const AButton = Button as any\n"
},
{
"path": "components/analytics.tsx",
"chars": 217,
"preview": "import Script from 'next/script'\n\nconst Analytics = () => (\n <Script\n defer\n data-domain=\"hackclub.com\"\n src=\""
},
{
"path": "components/announcement.tsx",
"chars": 2186,
"preview": "import { keyframes } from '@emotion/react'\nimport Image from 'next/image'\nimport { Box, Card, Text } from 'theme-ui'\nimp"
},
{
"path": "components/announcements/amount.tsx",
"chars": 280,
"preview": "import Sparkles from '../sparkles'\n\nconst Amount = ({ amount }) => (\n <Sparkles\n sx={{\n WebkitTextStroke: 'curr"
},
{
"path": "components/announcements/cta.tsx",
"chars": 1193,
"preview": "import { Box, Button, Grid, Heading, Text } from 'theme-ui'\nimport Icon from '@hackclub/icons'\nimport NextLink from 'nex"
},
{
"path": "components/announcements/elon.mdx",
"chars": 2737,
"preview": "import Amount from './amount'\nimport Signature from '../signature'\n\n# Today, I’m proud to share: Elon Musk is donat"
},
{
"path": "components/announcements/hcb-mobile.mdx",
"chars": 3501,
"preview": "I’m Mohamad, a 17-year-old from the SF Bay Area, and I just shipped the official mobile app for HCB.\n\nIf you haven't hea"
},
{
"path": "components/announcements/hcb-open-source.mdx",
"chars": 2039,
"preview": "Hack Club launched HCB in 2018 to enable hackathons to raise and spend money\nthrough [fiscal sponsorship](https://hackcl"
},
{
"path": "components/announcements/hcb_cta.tsx",
"chars": 1177,
"preview": "import { Box, Button, Grid, Heading, Text } from 'theme-ui'\nimport Icon from '@hackclub/icons'\nimport NextLink from 'nex"
},
{
"path": "components/announcements/holder.tsx",
"chars": 553,
"preview": "import { Container, BaseStyles } from 'theme-ui'\n\nexport default function AnnouncementHolder({ children }) {\n return (\n"
},
{
"path": "components/announcements/pills.tsx",
"chars": 961,
"preview": "import { Avatar, Badge, Flex } from 'theme-ui'\n\nexport function PillHolder({ children }) {\n return (\n <Flex\n sx"
},
{
"path": "components/announcements/preston-werner-2022.mdx",
"chars": 1394,
"preview": "This gift means a lot to the Hack Club community, and we are grateful for Tom and Theresa’s continued support.\n\nIn 2014,"
},
{
"path": "components/announcements/preston-werner.mdx",
"chars": 2579,
"preview": "import Amount from './amount'\n\n# Today, we're proud to share: Tom and Theresa Preston-Werner are donating <Amount amount"
},
{
"path": "components/announcements/relon.mdx",
"chars": 2325,
"preview": "import Signature from '../signature'\nimport Signatures from '../signatures'\nimport Image from 'theme-ui'\n\nIn March 2020,"
},
{
"path": "components/arcade/footer.tsx",
"chars": 1436,
"preview": "import { Box, Heading, Text, Link } from 'theme-ui'\nimport Footer from '../footer'\n\nconst Description = () => (\n <Box s"
},
{
"path": "components/arcade/projects.tsx",
"chars": 9731,
"preview": "/** @jsxImportSource theme-ui */\nimport React, { useState } from 'react'\nimport styled from '@emotion/styled'\nimport {\n "
},
{
"path": "components/background-image.tsx",
"chars": 1191,
"preview": "import { Box } from 'theme-ui'\nimport Image, { StaticImageData } from 'next/image'\n\n/*\n * Use this component inside a co"
},
{
"path": "components/bin/GalleryPosts.tsx",
"chars": 2415,
"preview": "/** @jsxImportSource theme-ui */\nimport Image from 'next/image'\nimport styles from '../../public/bin/style/gallery.modul"
},
{
"path": "components/bin/PartTag.module.css",
"chars": 387,
"preview": ".tag {\n color: e1e1e1;\n padding: 4px 10px;\n border-radius: 20px;\n width: fit-content;\n max-width: 300px;\n display:"
},
{
"path": "components/bin/PartTag.tsx",
"chars": 4164,
"preview": "import React from 'react'\nimport styles from './PartTag.module.css'\nimport { useState } from 'react'\n\nconst PartTag = ({"
},
{
"path": "components/bin/nav.tsx",
"chars": 496,
"preview": "import React from 'react'\nimport styles from '../../public/bin/style/gallery.module.css'\n\nconst Nav = () => {\n return ("
},
{
"path": "components/bin/rsvp-form.tsx",
"chars": 2160,
"preview": "/** @jsxImportSource theme-ui */\n\nimport { Checkbox, Input, Label, Text, Box } from 'theme-ui'\nimport useForm from '../."
},
{
"path": "components/bio.tsx",
"chars": 5639,
"preview": "/** @jsxImportSource theme-ui */\nimport Icon from '@hackclub/icons'\nimport { useState } from 'react'\nimport { Box, Card,"
},
{
"path": "components/boardbio.tsx",
"chars": 7441,
"preview": "/** @jsxImportSource theme-ui */\nimport Icon from '@hackclub/icons'\nimport { useState } from 'react'\nimport { Avatar, Bo"
},
{
"path": "components/color-switcher.tsx",
"chars": 993,
"preview": "import { IconButton, useColorMode } from 'theme-ui'\n\nconst ColorSwitcher = props => {\n const [mode, setMode] = useColor"
},
{
"path": "components/comma.ts",
"chars": 139,
"preview": "export default function Comma({ children }) {\n return children\n ? children.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/"
},
{
"path": "components/directoryModal.tsx",
"chars": 9819,
"preview": "/** @jsxImportSource theme-ui */\n\nexport const badges = [\n {\n label: 'Transparent',\n id: 'Transparent',\n toolt"
},
{
"path": "components/donate/donors.json",
"chars": 6218,
"preview": "{\n \"1517\": \"http://www.1517fund.com/\",\n \"Aakash Adesara\": null,\n \"Abby Fischler\": null,\n \"Achal Srinivasan\": null,\n "
},
{
"path": "components/donate/sponsors.tsx",
"chars": 1237,
"preview": "/** @jsxImportSource theme-ui */\nimport styled from '@emotion/styled'\nimport { Box, Image, Link } from 'theme-ui'\n\nconst"
},
{
"path": "components/dot.tsx",
"chars": 717,
"preview": "import { Text } from 'theme-ui'\nimport { keyframes } from '@emotion/react'\n\nconst flashing = keyframes({\n from: { opaci"
},
{
"path": "components/elon.mdx",
"chars": 2768,
"preview": "Elon Musk holds a special place amongst hackers. After growing up in a difficult\nfamily situation in South Africa, worki"
},
{
"path": "components/fade-in.tsx",
"chars": 609,
"preview": "import React from 'react'\nimport { Box } from 'theme-ui'\nimport styled from '@emotion/styled'\nimport { keyframes } from "
},
{
"path": "components/fiscal-sponsorship/contact.tsx",
"chars": 899,
"preview": "import Icon from '../icon'\nimport { Flex, Link, Text } from 'theme-ui'\n\nconst phoneNumber = '+1 (844) 237-2290'\nconst ph"
},
{
"path": "components/fiscal-sponsorship/directory/card.tsx",
"chars": 4219,
"preview": "import { Card, Badge as ThemeBadge, Box, Heading, Text, Image } from 'theme-ui'\nimport { Organization } from '../../../l"
},
{
"path": "components/fiscal-sponsorship/features.tsx",
"chars": 4698,
"preview": "import { Box, Heading, Link, Text, Container, Grid } from 'theme-ui'\nimport Icon from '../icon'\nimport { Balancer } from"
},
{
"path": "components/fiscal-sponsorship/first/apply-button.tsx",
"chars": 744,
"preview": "import { Button, Text, Flex } from 'theme-ui'\nimport Icon from '../../icon'\nimport Link from 'next/link'\n\nexport default"
},
{
"path": "components/fiscal-sponsorship/first/features.tsx",
"chars": 11975,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Heading, Link, Text, Container, Card, Image } from 'theme-ui'\nimport Icon"
},
{
"path": "components/fiscal-sponsorship/first/start.tsx",
"chars": 1309,
"preview": "import { Box, Link, Text, Heading, Flex } from 'theme-ui'\nimport Stats from './stats'\nimport ApplyButton from './apply-b"
},
{
"path": "components/fiscal-sponsorship/first/stats.tsx",
"chars": 2833,
"preview": "import { Text, Box, Flex } from 'theme-ui'\nimport { useEffect, useState } from 'react'\n\nconst easeInOutExpo = x =>\n x ="
},
{
"path": "components/fiscal-sponsorship/first/testimonials.tsx",
"chars": 6877,
"preview": "import {\n Box,\n Image,\n Text,\n Heading,\n Container,\n Grid,\n Link,\n Avatar,\n Button\n} from 'theme-ui'\nimport { S"
},
{
"path": "components/fiscal-sponsorship/open-source.tsx",
"chars": 2313,
"preview": "/** @jsxImportSource theme-ui */\n\nimport { Box, Heading, Button, Text, Container, Grid, Flex } from 'theme-ui'\nimport Ic"
},
{
"path": "components/fiscal-sponsorship/organization-spotlight.tsx",
"chars": 2185,
"preview": "/** @jsxImportSource theme-ui */\nimport Tilt from '../tilt'\nimport { Card, Heading, Text } from 'theme-ui'\nimport Image "
},
{
"path": "components/fiscal-sponsorship/sign-in.tsx",
"chars": 938,
"preview": "/** @jsxImportSource theme-ui */\nimport { useEffect, useState } from 'react'\nimport { Button, Image } from 'theme-ui'\n\ne"
},
{
"path": "components/fiscal-sponsorship/tooltip.tsx",
"chars": 3133,
"preview": "import React from 'react'\n\nconst tooltip = direction =>\n function Tooltip({ children, text, id }) {\n const escapedTe"
},
{
"path": "components/flag.tsx",
"chars": 1480,
"preview": "import theme from '../lib/theme'\nimport styled from '@emotion/styled'\nimport { css, keyframes } from '@emotion/react'\nim"
},
{
"path": "components/flex-col.tsx",
"chars": 169,
"preview": "import { Flex } from 'theme-ui'\n\nexport default function FlexCol({ children, ...props }) {\n return <Flex sx={{ flexDire"
},
{
"path": "components/footer.tsx",
"chars": 10365,
"preview": "import React from 'react'\nimport styled from '@emotion/styled'\nimport { Box, Container, Grid, Heading, Link, Text } from"
},
{
"path": "components/force-theme.ts",
"chars": 278,
"preview": "import { useEffect } from 'react'\nimport { useColorMode } from 'theme-ui'\n\nconst ForceTheme = ({ theme }) => {\n const ["
},
{
"path": "components/hackathons/features/marketing.tsx",
"chars": 3369,
"preview": "/** @jsxImportSource theme-ui */\nimport { Button, Box, Container, Heading, Text } from 'theme-ui'\nimport usePrefersMotio"
},
{
"path": "components/hackathons/features/slack.tsx",
"chars": 4111,
"preview": "/** @jsxImportSource theme-ui */\nimport { Button, Box, Container, Heading, Text, Link } from 'theme-ui'\nimport usePrefer"
},
{
"path": "components/hackathons/keep-exploring.tsx",
"chars": 2338,
"preview": "import { Box, Heading, Button, Text, Grid, Container } from 'theme-ui'\nimport Link from 'next/link'\nimport Icon from '.."
},
{
"path": "components/hackathons/landing.tsx",
"chars": 4452,
"preview": "import { Box, Button, Heading, Text, Card } from 'theme-ui'\nimport { Fade } from '../react-reveal-compat'\nimport ScrollH"
},
{
"path": "components/hackathons/overview.tsx",
"chars": 3069,
"preview": "import { Box, Heading, Container, Text, Grid } from 'theme-ui'\n\nexport default function Overview() {\n return (\n <>\n "
},
{
"path": "components/hackathons/recap.tsx",
"chars": 2593,
"preview": "/** @jsxImportSource theme-ui */\nimport { Card, Box, Heading, Grid, Text } from 'theme-ui'\nimport Stage from '../stage'\n"
},
{
"path": "components/hackathons/scrolling-hackathons.tsx",
"chars": 6343,
"preview": "/** @jsxImportSource theme-ui */\nimport Ticker from 'react-ticker'\nimport {\n Box,\n Card,\n Text,\n Heading,\n Badge,\n "
},
{
"path": "components/icon.tsx",
"chars": 165,
"preview": "import React from 'react'\nimport Icon from '@hackclub/icons'\n\nexport default function IconComponent(props: any): React.R"
},
{
"path": "components/index/cards/beest.tsx",
"chars": 3948,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Image, Text } from 'theme-ui'\nimport"
},
{
"path": "components/index/cards/button.tsx",
"chars": 2509,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Button, Text } from 'theme-ui'\nimport ReactTooltip from '../../react-tool"
},
{
"path": "components/index/cards/card-model.tsx",
"chars": 4050,
"preview": "import Icon from '../../icon'\nimport { Box, Card, Flex, Image, Link, Text } from 'theme-ui'\nimport ReactTooltip from '.."
},
{
"path": "components/index/cards/clubs.tsx",
"chars": 2584,
"preview": "/** @jsxImportSource theme-ui */\nimport Buttons from './button'\nimport CardModel from './card-model'\nimport { Box, Grid,"
},
{
"path": "components/index/cards/fallout.tsx",
"chars": 2819,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Text, Image } from 'theme-ui'\nimport CardModel from './card-model'\nimport"
},
{
"path": "components/index/cards/flavortown.tsx",
"chars": 3637,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Flex, Grid, Image, Text } from 'them"
},
{
"path": "components/index/cards/hackathons.tsx",
"chars": 7506,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Flex, Grid, Image, Link, Text } from"
},
{
"path": "components/index/cards/haxidraw.tsx",
"chars": 2213,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Flex, Grid, Text } from 'theme-ui'\ni"
},
{
"path": "components/index/cards/hcb.tsx",
"chars": 4263,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Grid, Heading, Text } from 'theme-ui"
},
{
"path": "components/index/cards/hctg.tsx",
"chars": 3588,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Image, Text } from 'theme-ui'\n\nexpor"
},
{
"path": "components/index/cards/horizons.tsx",
"chars": 3405,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Text, Box, Image, Button } from 'theme-ui"
},
{
"path": "components/index/cards/jackpot.tsx",
"chars": 4566,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Text, Image, Grid, Flex } from 'theme-ui'\nimport CardModel from './card-m"
},
{
"path": "components/index/cards/macondo.tsx",
"chars": 2802,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Text, Image } from 'theme-ui'\nimport CardModel from './card-model'\nimport"
},
{
"path": "components/index/cards/mailing-list.tsx",
"chars": 7736,
"preview": "import Icon from '@hackclub/icons'\nimport { useEffect, useRef, useState } from 'react'\nimport { Box, Button, Card, Flex,"
},
{
"path": "components/index/cards/sinerider.tsx",
"chars": 2095,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Flex, Grid, Image, Text } from 'them"
},
{
"path": "components/index/cards/slack.tsx",
"chars": 4796,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Flex, Grid, Heading, Image, Link, Te"
},
{
"path": "components/index/cards/sleepover.tsx",
"chars": 5655,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Image, Text } from 'theme-ui'\nimport CardModel from './card-model'\n\nexpor"
},
{
"path": "components/index/cards/sprig-console.tsx",
"chars": 3721,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Grid, Image, Text } from 'theme-ui'\nimport Buttons from './button'\nimport"
},
{
"path": "components/index/cards/sprig.tsx",
"chars": 8935,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Flex, Grid, Image, Link, Text } from"
},
{
"path": "components/index/cards/stasis.tsx",
"chars": 2991,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Flex, Image, Text } from 'theme-ui'\nimport CardModel from './card-model'\n"
},
{
"path": "components/index/cards/workshops.tsx",
"chars": 3186,
"preview": "/** @jsxImportSource theme-ui */\nimport CardModel from './card-model'\nimport { Box, Card, Flex, Grid, Heading, Image, Te"
},
{
"path": "components/index/carousel-cards.tsx",
"chars": 2574,
"preview": "import { Box, Card, Image, Link, Text } from 'theme-ui'\nimport Icon from '../icon'\n\nexport default function CarouselCard"
},
{
"path": "components/index/carousel.tsx",
"chars": 1469,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Text } from 'theme-ui'\nimport CarouselCards from './carousel-cards'\nimpor"
},
{
"path": "components/index/ctas.tsx",
"chars": 8128,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Text, Image, Card } from 'theme-ui'\nimport React, { useState } from 'reac"
},
{
"path": "components/index/events.tsx",
"chars": 2432,
"preview": "import { Box, Text, Grid, Badge, Flex, Avatar, Heading } from 'theme-ui'\nimport tt from 'tinytime'\nimport Link from 'nex"
},
{
"path": "components/index/github.tsx",
"chars": 1958,
"preview": "import { Badge, Image, Text } from 'theme-ui'\nimport RelativeTime from 'react-relative-time'\n\ntype GitHubProps = {\n typ"
},
{
"path": "components/letterhead.tsx",
"chars": 2517,
"preview": "/** @jsxImportSource theme-ui */\nimport { Avatar, Badge, Box, Container, Flex, Heading } from 'theme-ui'\nimport Head fro"
},
{
"path": "components/mail-card.tsx",
"chars": 1486,
"preview": "import { Box, Card, Link, Text } from 'theme-ui'\n\nexport default function MailCard({ body, date, link, issue }) {\n body"
},
{
"path": "components/marquee.tsx",
"chars": 1402,
"preview": "import {\n useRef,\n useEffect,\n useState,\n Children,\n cloneElement,\n isValidElement,\n type ReactNode,\n type React"
},
{
"path": "components/mention.tsx",
"chars": 805,
"preview": "import Image from 'next/image'\nimport Link from 'next/link'\nimport { memo } from 'react'\n\nconst StaticMention = memo(fun"
},
{
"path": "components/nav.tsx",
"chars": 6544,
"preview": "import React, { useEffect, useState } from 'react'\nimport styled from '@emotion/styled'\nimport { css, keyframes } from '"
},
{
"path": "components/onboard/gallery-paginated.tsx",
"chars": 4153,
"preview": "/** @jsxImportSource theme-ui */\nimport { useEffect, useRef } from 'react'\nimport PaginationButtons from './pagination-b"
},
{
"path": "components/onboard/item.tsx",
"chars": 723,
"preview": "/** @jsxImportSource theme-ui */\nimport Image from 'next/image'\nimport { Heading, Card } from 'theme-ui'\n\nconst Item = ("
},
{
"path": "components/onboard/pagination-buttons.tsx",
"chars": 1239,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Button, Text } from 'theme-ui'\n\nconst PaginationButtons = ({ currentPage,"
},
{
"path": "components/onboard/recap.tsx",
"chars": 12198,
"preview": "/** @jsxImportSource theme-ui */\nimport { useEffect, useRef } from 'react'\nimport usePrefersReducedMotion from '../../li"
},
{
"path": "components/onboard/youtube-video.tsx",
"chars": 777,
"preview": "import { useMemo } from 'react'\n\nconst YoutubeVideo = ({\n 'youtube-id': youtubeID = 'dQw4w9WgXcQ',\n list,\n title = 'Y"
},
{
"path": "components/particles.tsx",
"chars": 1750,
"preview": "import Particles from 'react-tsparticles'\nimport React from 'react'\n\nconst Particle = React.memo(function Particle() {\n "
},
{
"path": "components/photo.tsx",
"chars": 1932,
"preview": "import styled from '@emotion/styled'\nimport { Card, Text } from 'theme-ui'\nimport Image, { StaticImageData } from 'next/"
},
{
"path": "components/posts/emoji.tsx",
"chars": 1452,
"preview": "import { memo, useState, useEffect } from 'react'\nimport Image from 'next/image'\n\nconst stripColons = str => {\n const c"
},
{
"path": "components/posts/index.tsx",
"chars": 5566,
"preview": "/** @jsxImportSource theme-ui */\nimport { Button, Box, Card, Text, Grid, Avatar, Flex } from 'theme-ui'\nimport { formatD"
},
{
"path": "components/posts/mention.tsx",
"chars": 902,
"preview": "import { Link, Avatar } from 'theme-ui'\nimport { memo, useState, useEffect } from 'react'\nimport { trim } from 'lodash'\n"
},
{
"path": "components/press.mdx",
"chars": 1479,
"preview": "import { Grid } from 'theme-ui'\n\nHack Club is a global nonprofit network of high school makers & student-led coding club"
},
{
"path": "components/react-reveal-compat.tsx",
"chars": 3522,
"preview": "import React from 'react'\nimport styled from '@emotion/styled'\nimport { keyframes } from '@emotion/react'\n\nconst kFade ="
},
{
"path": "components/react-tooltip.ts",
"chars": 143,
"preview": "import dynamic from 'next/dynamic'\n\nconst ReactTooltip = dynamic(() => import('react-tooltip'), {\n ssr: false\n})\n\nexpor"
},
{
"path": "components/replit/scale-up.tsx",
"chars": 1135,
"preview": "import React, { useState, useEffect, useRef } from 'react'\n\nconst easeInOutExpo = x =>\n x === 0\n ? 0\n : x === 1\n "
},
{
"path": "components/replit/token-instructions.tsx",
"chars": 3343,
"preview": "'use client'\n\nimport Icon from '@hackclub/icons'\nimport { useState } from 'react'\nimport { Box, Card, Text, Image, Headi"
},
{
"path": "components/scroll-hint.tsx",
"chars": 914,
"preview": "import { Box } from 'theme-ui'\nimport { animate } from 'animejs'\n\nconst handleClick = () => {\n const scroll = { x: docu"
},
{
"path": "components/secret.tsx",
"chars": 3736,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Text } from 'theme-ui'\nimport { useState, useEffect } from 'react'\nimport"
},
{
"path": "components/ship/why.mdx",
"chars": 493,
"preview": "import { Card } from 'theme-ui'\n\n<Card>\n\n## Your first ship your first day.\n\nStudents in many traditional computer scien"
},
{
"path": "components/signature.tsx",
"chars": 497,
"preview": "import { Image, useColorMode } from 'theme-ui'\n\nconst Signature = ({ fname, lname, width }) => {\n // enforce a sane col"
},
{
"path": "components/signatures.tsx",
"chars": 483,
"preview": "import { Image, useColorMode } from 'theme-ui'\n\nconst Signatures = ({ fileName, width }) => {\n // enforce a sane color "
},
{
"path": "components/slack.tsx",
"chars": 4246,
"preview": "import { Button, Box, Container, Heading, Text } from 'theme-ui'\nimport styled from '@emotion/styled'\nimport usePrefersM"
},
{
"path": "components/slide-down.tsx",
"chars": 685,
"preview": "import React from 'react'\nimport { Box } from 'theme-ui'\nimport styled from '@emotion/styled'\nimport { keyframes } from "
},
{
"path": "components/slide-up.tsx",
"chars": 676,
"preview": "import React from 'react'\nimport { Box } from 'theme-ui'\nimport styled from '@emotion/styled'\nimport { keyframes } from "
},
{
"path": "components/sparkles/index.tsx",
"chars": 3235,
"preview": "// Full credit to https://joshwcomeau.com/react/animated-sparkles-in-react/\nimport { useState } from 'react'\nimport styl"
},
{
"path": "components/sparkles/money.tsx",
"chars": 3882,
"preview": "// Full credit to https://joshwcomeau.com/react/animated-sparkles-in-react/\nimport { useState } from 'react'\nimport styl"
},
{
"path": "components/stage.tsx",
"chars": 1286,
"preview": "import { Box, Heading, Text, ThemeUIStyleObject } from 'theme-ui'\nimport Icon from './icon'\n\ntype StageProps = {\n icon:"
},
{
"path": "components/stat.tsx",
"chars": 2057,
"preview": "import { Flex, Text } from 'theme-ui'\nimport { isEmpty } from 'lodash'\n\ntype StatProps = {\n value: string | number\n la"
},
{
"path": "components/submit.tsx",
"chars": 1326,
"preview": "import { Button } from 'theme-ui'\nimport theme from '../lib/theme'\n\nconst bg = {\n default: {\n bg: 'blue',\n backgr"
},
{
"path": "components/tilt.tsx",
"chars": 778,
"preview": "import React, { useEffect, useRef } from 'react'\nimport VanillaTilt from 'vanilla-tilt'\nimport useMedia from '../lib/use"
},
{
"path": "components/winter/breakdown-box.tsx",
"chars": 1861,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Card, Heading, Text } from 'theme-ui'\nimport { Zoom } from '../react-reve"
},
{
"path": "components/winter/breakdown.tsx",
"chars": 2754,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Container, Heading, Grid } from 'theme-ui'\nimport { Fade, Slide } from '."
},
{
"path": "components/winter/footer.tsx",
"chars": 1377,
"preview": "import { Box, Heading, Text, Link } from 'theme-ui'\nimport Footer from '../footer'\n\nconst Description = () => (\n <Box s"
},
{
"path": "components/winter/info.tsx",
"chars": 5484,
"preview": "/** @jsxImportSource theme-ui */\nimport {\n Card,\n Grid,\n Box,\n Container,\n Heading,\n Text,\n Flex,\n Avatar\n} from"
},
{
"path": "components/winter/landing.tsx",
"chars": 2926,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Heading, Button, Link, Text, Container } from 'theme-ui'\nimport Snowfall "
},
{
"path": "components/winter/projects.tsx",
"chars": 9453,
"preview": "/** @jsxImportSource theme-ui */\nimport { useState } from 'react'\nimport styled from '@emotion/styled'\nimport {\n Box,\n "
},
{
"path": "components/winter/recap.tsx",
"chars": 2346,
"preview": "/** @jsxImportSource theme-ui */\nimport { Button, Container, Heading, Grid, Card, Text } from 'theme-ui'\nimport { Slide,"
},
{
"path": "components/winter/timeline.tsx",
"chars": 3492,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Flex, Container, Text, Badge, Link } from 'theme-ui'\nimport { Slide } fro"
},
{
"path": "eslint.config.mts",
"chars": 914,
"preview": "import { defineConfig, globalIgnores } from 'eslint/config'\nimport nextVitals from 'eslint-config-next/core-web-vitals'\n"
},
{
"path": "lib/cached-hcb-orgs.ts",
"chars": 864,
"preview": "const CACHE_FILENAME = 'hcb-orgs-cache.json'\n\nexport async function fetchAllOrganizations() {\n const fs = require('fs')"
},
{
"path": "lib/countries.json",
"chars": 5903,
"preview": "{\n \"countries\": [\n \"United States of America (US)\",\n \"Canada (CA)\",\n \"United Kingdom of Great Britain and Nort"
},
{
"path": "lib/cta.json",
"chars": 4310,
"preview": "[\n {\n \"title\": \"Flavortown\",\n \"logo\": \"https://cdn.hackclub.com/019c76b8-4f54-7de9-ae34-90b2190c2440/TeQ27w.png\","
},
{
"path": "lib/dates.ts",
"chars": 3149,
"preview": "export const dt = d => new Date(d).toLocaleDateString()\n\nconst year = new Date().getFullYear()\nexport const tinyDt = d ="
},
{
"path": "lib/fetcher.ts",
"chars": 166,
"preview": "/**\n * useSWR() fetcher\n */\nexport default async function fetcher(...args: Parameters<typeof fetch>) {\n const res = awa"
},
{
"path": "lib/git-info.ts",
"chars": 235,
"preview": "export const getGitSha = (): string => {\n return process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || 'dev'\n}\n\nexport const"
},
{
"path": "lib/helpers.ts",
"chars": 6180,
"preview": "export const dt = d => new Date(d).toLocaleDateString()\n\nconst year = new Date().getFullYear()\nexport const tinyDt = d ="
},
{
"path": "lib/members.ts",
"chars": 220,
"preview": "// this could use the slackData lib, but apparently top level awaits are risky\nexport const count: number = 103897\nexpor"
},
{
"path": "lib/organization.ts",
"chars": 3950,
"preview": "import GeoPattern from 'geopattern'\n/**\n * Represents an organization.\n */\nexport class Organization {\n public raw: any"
},
{
"path": "lib/slackData.ts",
"chars": 354,
"preview": "type SlackData = {\n active_users_28d?: number\n full_members_count?: number\n total_members_count?: number\n ds?: strin"
},
{
"path": "lib/sleep.ts",
"chars": 139,
"preview": "// Beloved classic utility function :3\nconst sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))\n\nex"
},
{
"path": "lib/theme.ts",
"chars": 334,
"preview": "import base from '@hackclub/theme'\nimport { merge } from 'lodash'\n\nconst theme = base\n\ntheme.config.useColorSchemeMediaQ"
},
{
"path": "lib/use-form.ts",
"chars": 2138,
"preview": "import { useState, useEffect } from 'react'\n\nconst useForm = (\n submitURL = '/',\n callback,\n options = { clearOnSubmi"
},
{
"path": "lib/use-has-mounted.ts",
"chars": 304,
"preview": "// Full credit to https://joshwcomeau.com/snippets/react-hooks/use-has-mounted\nimport React from 'react'\n\nfunction useHa"
},
{
"path": "lib/use-media.ts",
"chars": 414,
"preview": "import { useState, useEffect } from 'react'\n\nexport default function useMedia(query) {\n const [matches, setMatches] = u"
},
{
"path": "lib/use-prefers-motion.ts",
"chars": 1028,
"preview": "// Inspired by https://joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion\nimport React from 'react'\n\nconst "
},
{
"path": "lib/use-prefers-reduced-motion.ts",
"chars": 1077,
"preview": "// Full credit to https://joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion\nimport React from 'react'\n\ncon"
},
{
"path": "lib/use-random-interval.ts",
"chars": 1075,
"preview": "// Full credit to https://joshwcomeau.com/snippets/react-hooks/use-random-interval\nimport React from 'react'\n\n// Utility"
},
{
"path": "next.config.ts",
"chars": 11332,
"preview": "import withMDX from '@next/mdx'\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n reactStrictMo"
},
{
"path": "package.json",
"chars": 2427,
"preview": "{\n \"name\": \"@hackclub/v3\",\n \"version\": \"0.0.1\",\n \"author\": \"Lachlan Campbell <lachlan@hackclub.com>\",\n \"license\": \"M"
},
{
"path": "pages/404.tsx",
"chars": 4635,
"preview": "import React from 'react'\nimport styled from '@emotion/styled'\nimport { keyframes } from '@emotion/react'\nimport { Headi"
},
{
"path": "pages/_app.tsx",
"chars": 866,
"preview": "import Head from 'next/head'\n\nimport Analytics from '../components/analytics'\nimport { Analytics as VercelAnalytics } fr"
},
{
"path": "pages/_document.tsx",
"chars": 1249,
"preview": "import Document, { Html, Head, Main, NextScript } from 'next/document'\n\nconst org = {\n '@context': 'http://schema.org',"
},
{
"path": "pages/acknowledged.tsx",
"chars": 3233,
"preview": "/** @jsxImportSource theme-ui */\nimport { Box, Container, Grid, Text } from 'theme-ui'\nimport Meta from '@hackclub/meta'"
},
{
"path": "pages/amas/geohot.tsx",
"chars": 10885,
"preview": "import { Box, Button, Image, Grid, Text, Link } from 'theme-ui'\nimport Head from 'next/head'\nimport Meta from '@hackclub"
},
{
"path": "pages/amas/index.tsx",
"chars": 7361,
"preview": "import { Box, Button, Card, Flex, Grid, Heading, Link, Text } from 'theme-ui'\nimport Meta from '@hackclub/meta'\nimport H"
},
{
"path": "pages/amas/sal.tsx",
"chars": 9515,
"preview": "import { Box, Button, Image, Grid, Text, Link } from 'theme-ui'\nimport Head from 'next/head'\nimport Meta from '@hackclub"
},
{
"path": "pages/amas/vitalik.tsx",
"chars": 10516,
"preview": "import { Box, Button, Image, Grid, Text, Link } from 'theme-ui'\nimport Head from 'next/head'\nimport Meta from '@hackclub"
},
{
"path": "pages/api/arcade/hack-hour/inventory.ts",
"chars": 2972,
"preview": "import AirtablePlus from 'airtable-plus'\n\nconst flavorText = async () => {\n const airtable = new AirtablePlus({\n api"
},
{
"path": "pages/api/arcade/shop.ts",
"chars": 1206,
"preview": "import AirtablePlus from 'airtable-plus'\n\nexport const shopParts = async () => {\n const baseID = 'app4kCWulfB02bV8Q'\n "
},
{
"path": "pages/api/bin/gallery/posts.ts",
"chars": 1062,
"preview": "import AirtablePlus from 'airtable-plus'\n\nconst fetchPosts = async () => {\n try {\n const airtable = new AirtablePlus"
},
{
"path": "pages/api/bin/gallery/tags.ts",
"chars": 779,
"preview": "import AirtablePlus from 'airtable-plus'\n\nconst fetchTags = async () => {\n try {\n const airtable = new AirtablePlus("
},
{
"path": "pages/api/bin/rsvp.ts",
"chars": 1127,
"preview": "// https://airtable.com/appKjALSnOoA0EmPk/tblYYhxN9TaPPMMRV/viwJFvTlmRNHj0Toh?blocks=hide\nimport AirtablePlus from 'airt"
},
{
"path": "pages/api/bin/wokwi/new/[parts].ts",
"chars": 215,
"preview": "import { findOrCreateProject } from '.'\n\nexport default async function handler(req, res) {\n const parts = req.query.par"
},
{
"path": "pages/api/bin/wokwi/new/index.ts",
"chars": 4368,
"preview": "import AirtablePlus from 'airtable-plus'\n\nexport const findOrCreateProject = async (partsList = []) => {\n const airtabl"
},
{
"path": "pages/api/bin/wokwi/parts.ts",
"chars": 689,
"preview": "import AirtablePlus from 'airtable-plus'\nimport camelcase from 'camelcase'\n\nconst camelizeObject = obj => {\n Object.key"
},
{
"path": "pages/api/bucky.ts",
"chars": 379,
"preview": "import { NextApiRequest, NextApiResponse } from 'next'\n\nexport default async function handler(\n req: NextApiRequest,\n "
},
{
"path": "pages/api/channels/resolve.ts",
"chars": 618,
"preview": "export default async function handler(req, res) {\n // get a public channel name by id\n const channelDataReq = await fe"
},
{
"path": "pages/api/contribute.ts",
"chars": 1952,
"preview": "import { graphql } from '@octokit/graphql'\nimport { createAppAuth } from '@octokit/auth-app'\nimport { NextApiRequest, Ne"
},
{
"path": "pages/api/first-team.ts",
"chars": 574,
"preview": "import { NextApiRequest, NextApiResponse } from 'next'\n\nexport default async function handler(\n req: NextApiRequest,\n "
},
{
"path": "pages/api/games.ts",
"chars": 428,
"preview": "import { NextApiRequest, NextApiResponse } from 'next'\n\nexport async function getGames() {\n try {\n const games = awa"
},
{
"path": "pages/api/github.ts",
"chars": 1745,
"preview": "import { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils'\nimport { normalizeGitHubCommitUrl } from '."
},
{
"path": "pages/api/join.ts",
"chars": 3761,
"preview": "import AirtablePlus from 'airtable-plus'\nimport { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils'\n\nc"
},
{
"path": "pages/api/mailing-list.ts",
"chars": 651,
"preview": "import { NextApiRequest, NextApiResponse } from 'next'\n\nexport default async function submit(\n req: NextApiRequest,\n r"
},
{
"path": "pages/api/onboard/p/[project]/index.ts",
"chars": 1489,
"preview": "// return a project's metadata\n\nimport { getAllOnboardProjects } from '..'\n\nasync function getReadmeData(url) {\n const "
},
{
"path": "pages/api/onboard/p/count.ts",
"chars": 285,
"preview": "import { getAllOnboardProjects } from '.'\n\nexport async function onboardProjectCount() {\n const projects = await getAll"
},
{
"path": "pages/api/onboard/p/index.ts",
"chars": 2036,
"preview": "// returns a list of all projects\n\nexport const getAllOnboardProjects = async () => {\n const url = 'https://api.github."
},
{
"path": "pages/api/onboard/svg/[board_url]/bottom.ts",
"chars": 662,
"preview": "// test me with: curl http://localhost:3000/api/board/svg/https%3A%2F%2Fgithub.com%2Fhackclub%2FOnBoard%2Fraw%2Fmain%2Fp"
},
{
"path": "pages/api/onboard/svg/[board_url]/index.ts",
"chars": 2807,
"preview": "import JSZip from 'jszip'\nimport {\n read,\n plot,\n renderLayers,\n renderBoard,\n stringifySvg\n} from '@tracespace/cor"
},
{
"path": "pages/api/onboard/svg/[board_url]/top.ts",
"chars": 729,
"preview": "// test me with: curl http://localhost:3000/api/board/svg/https%3A%2F%2Fgithub.com%2Fhackclub%2FOnBoard%2Fraw%2Fmain%2Fp"
},
{
"path": "pages/api/replit/signup.ts",
"chars": 944,
"preview": "export default async function handler(req, res) {\n if (req.method === 'POST') {\n try {\n const { token, email } "
},
{
"path": "pages/api/sprig-console.ts",
"chars": 873,
"preview": "import { NextApiRequest, NextApiResponse } from 'next'\n\nfunction check(val: any) {\n return val === 'Pending' || val ==="
},
{
"path": "pages/api/stars.ts",
"chars": 2160,
"preview": "import { graphql } from '@octokit/graphql'\nimport { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils'\n"
},
{
"path": "pages/api/steve.ts",
"chars": 2286,
"preview": "import type { NextApiRequest, NextApiResponse } from 'next'\n\nconst steveApiHandler = async (_req: NextApiRequest, res: N"
},
{
"path": "pages/api/stickers.ts",
"chars": 2429,
"preview": "import AirtablePlus from 'airtable-plus'\nimport { NextApiRequest, NextApiResponse } from 'next'\n\nconst peopleTable = new"
},
{
"path": "pages/api/stuff.ts",
"chars": 925,
"preview": "import type { NextApiRequest, NextApiResponse } from 'next'\n\nexport default async function stuff(\n _req: NextApiRequest"
},
{
"path": "pages/api/team.ts",
"chars": 762,
"preview": "import teamMembers from '../../public/team.json'\nimport acknowledgedMembers from '../../public/acknowledged.json'\nimport"
},
{
"path": "pages/api/winter-rsvp.ts",
"chars": 1062,
"preview": "import type { NextApiRequest, NextApiResponse } from 'next'\nimport AirtablePlus from 'airtable-plus'\n\nconst airtable = n"
},
{
"path": "pages/arcade/index.tsx",
"chars": 53691,
"preview": "/** @jsxImportSource theme-ui */\nimport React, { useState } from 'react'\nimport { useRouter } from 'next/router'\nimport "
},
{
"path": "pages/bin/gallery.tsx",
"chars": 3643,
"preview": "/** @jsxImportSource theme-ui */\n\nimport BinPost from '../../components/bin/GalleryPosts'\nimport styles from '../../publ"
},
{
"path": "pages/bin/prelaunch.tsx",
"chars": 15655,
"preview": "/** @jsxImportSource theme-ui */\nimport {\n Box,\n Container,\n Text,\n Heading,\n Card,\n Flex,\n Image,\n Link,\n Butt"
},
{
"path": "pages/brand.tsx",
"chars": 9471,
"preview": "import {\n Box,\n Card,\n Container,\n Flex,\n Grid,\n Heading,\n Image as ThemeImage,\n Input,\n Link as A,\n Text\n} fr"
},
{
"path": "pages/clubs.tsx",
"chars": 22801,
"preview": "import {\n Badge,\n Box,\n Button,\n Card,\n Container,\n Grid,\n Heading,\n Link,\n Text\n} from 'theme-ui'\nimport style"
},
{
"path": "pages/content/covid19.mdx",
"chars": 3371,
"preview": "import Letterhead from '../../components/letterhead'\n\n<Letterhead\ntitle=\"COVID-19\"\ndesc=\"An update from Hack Club HQ on "
}
]
// ... and 74 more files (download for full content)
About this extraction
This page contains the full source code of the hackclub/site GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 274 files (1.3 MB), approximately 358.9k tokens, and a symbol index with 287 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.