├── .eslintrc.json
├── public
├── grid.png
├── nft.gif
├── nft-fc.gif
├── steps
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ └── 4.png
├── devfolio.png
├── grid-white.png
├── og-image.png
├── base-god-ss.png
├── favicon_io
│ ├── favicon.gif
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ └── site.webmanifest
├── onchain-m1.svg
├── onchain-m2.svg
├── onchain-m3.svg
├── onchain-m4.svg
├── onchain-tablet.svg
├── onchain-d1.svg
├── onchain-4k-1.svg
├── onchain-d2.svg
└── onchain-4k-2.svg
├── src
└── app
│ ├── fonts
│ ├── NyghtSerif-Bold.ttf
│ ├── NyghtSerif-Medium.ttf
│ ├── NyghtSerif-Regular.ttf
│ └── fonts.ts
│ ├── constants
│ └── urls.ts
│ ├── sections
│ ├── TypographySection.tsx
│ ├── FooterSection.tsx
│ ├── MintStepsGridSection.tsx
│ ├── VideoSection.tsx
│ ├── HomeSection.tsx
│ └── WhySection.tsx
│ ├── components
│ ├── Ui.tsx
│ ├── ApplePeekArea.tsx
│ ├── Heading.tsx
│ ├── Button.tsx
│ ├── AnimatedSection.tsx
│ ├── Footer.tsx
│ ├── Tooltip.tsx
│ ├── StepItem.tsx
│ ├── StepGrid.tsx
│ ├── YoutubePlayer.tsx
│ ├── AnimatedText.tsx
│ └── OnchainTypography.tsx
│ ├── globals.css
│ ├── hooks
│ └── useResponsive.tsx
│ ├── utils
│ └── shared.ts
│ ├── layout.tsx
│ ├── page.tsx
│ ├── frog
│ └── index.ts
│ ├── styles
│ └── app.css
│ └── api
│ └── [[...routes]]
│ └── route.tsx
├── .env.example
├── postcss.config.mjs
├── .prettierrc.js
├── next.config.mjs
├── .gitignore
├── tailwind.config.ts
├── tsconfig.json
├── LICENSE
├── package.json
└── README.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "prettier"]
3 | }
4 |
--------------------------------------------------------------------------------
/public/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/grid.png
--------------------------------------------------------------------------------
/public/nft.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/nft.gif
--------------------------------------------------------------------------------
/public/nft-fc.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/nft-fc.gif
--------------------------------------------------------------------------------
/public/steps/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/1.png
--------------------------------------------------------------------------------
/public/steps/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/2.png
--------------------------------------------------------------------------------
/public/steps/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/3.png
--------------------------------------------------------------------------------
/public/steps/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/4.png
--------------------------------------------------------------------------------
/public/devfolio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/devfolio.png
--------------------------------------------------------------------------------
/public/grid-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/grid-white.png
--------------------------------------------------------------------------------
/public/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/og-image.png
--------------------------------------------------------------------------------
/public/base-god-ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/base-god-ss.png
--------------------------------------------------------------------------------
/public/favicon_io/favicon.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon.gif
--------------------------------------------------------------------------------
/public/favicon_io/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon.ico
--------------------------------------------------------------------------------
/src/app/fonts/NyghtSerif-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/src/app/fonts/NyghtSerif-Bold.ttf
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | NEYNAR_API_KEY=abcd # get from https://docs.neynar.com/
2 | NEYNAR_SIGNER=abcd # get from https://docs.neynar.com/
3 |
--------------------------------------------------------------------------------
/public/favicon_io/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon_io/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon-32x32.png
--------------------------------------------------------------------------------
/src/app/fonts/NyghtSerif-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/src/app/fonts/NyghtSerif-Medium.ttf
--------------------------------------------------------------------------------
/src/app/fonts/NyghtSerif-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/src/app/fonts/NyghtSerif-Regular.ttf
--------------------------------------------------------------------------------
/public/favicon_io/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon_io/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon_io/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/android-chrome-512x512.png
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'avoid',
3 | printWidth: 120,
4 | singleQuote: true,
5 | tabWidth: 2,
6 | useTabs: false,
7 | bracketSpacing: true,
8 | trailingComma: 'es5',
9 | endOfLine: 'auto',
10 | };
11 |
--------------------------------------------------------------------------------
/src/app/constants/urls.ts:
--------------------------------------------------------------------------------
1 | export const BLOG_URL = 'https://devfolio.co/blog/supabald-jesse/';
2 | export const OPENSEA_COLLECTION = 'https://opensea.io/collection/supabald-jesse';
3 | export const PROJECTS_URL = 'https://onchain-summer.devfolio.co/projects'
4 |
--------------------------------------------------------------------------------
/public/favicon_io/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | async redirects() {
4 | return [
5 | {
6 | source: '/token/:tokenID',
7 | destination: '/',
8 | permanent: false,
9 | },
10 | ];
11 | },
12 | };
13 |
14 | export default nextConfig;
15 |
--------------------------------------------------------------------------------
/src/app/sections/TypographySection.tsx:
--------------------------------------------------------------------------------
1 | import OnchainTypography from '../components/OnchainTypography';
2 |
3 | const TypographySection = () => {
4 | return (
5 |
8 | );
9 | };
10 |
11 | export default TypographySection;
12 |
--------------------------------------------------------------------------------
/src/app/components/Ui.tsx:
--------------------------------------------------------------------------------
1 | import { inter } from '../fonts/fonts';
2 |
3 | interface UiProps {
4 | children: React.ReactNode;
5 | className?: string;
6 | }
7 |
8 | const Ui = ({ children, className }: UiProps) => {
9 | return
{children}
;
10 | };
11 |
12 | export default Ui;
13 |
--------------------------------------------------------------------------------
/src/app/components/ApplePeekArea.tsx:
--------------------------------------------------------------------------------
1 | /* In Apple devices, scroll has elastic effect and you can scroll beneath the end of page. Here you can customize that area */
2 |
3 | import { inter } from '../fonts/fonts';
4 |
5 | const ApplePeekArea = () => {
6 | /* Our little easter egg */
7 | return Never Stop Building
;
8 | };
9 |
10 | export default ApplePeekArea;
11 |
--------------------------------------------------------------------------------
/src/app/components/Heading.tsx:
--------------------------------------------------------------------------------
1 | import { nyghtMedium } from '../fonts/fonts';
2 |
3 | interface HeadingProps {
4 | children: React.ReactNode;
5 | className?: string;
6 | }
7 |
8 | const Heading = ({ children, className }: HeadingProps) => {
9 | return (
10 |
11 | {children}
12 |
13 | );
14 | };
15 |
16 | export default Heading;
17 |
--------------------------------------------------------------------------------
/src/app/fonts/fonts.ts:
--------------------------------------------------------------------------------
1 | import localFont from 'next/font/local';
2 | import { Inter } from 'next/font/google';
3 |
4 | export const nyghtRegular = localFont({
5 | src: '../fonts/NyghtSerif-Regular.ttf',
6 | display: 'swap',
7 | });
8 |
9 | export const nyghtBold = localFont({
10 | src: '../fonts/NyghtSerif-Bold.ttf',
11 | display: 'swap',
12 | });
13 |
14 | export const nyghtMedium = localFont({
15 | src: '../fonts/NyghtSerif-Medium.ttf',
16 | display: 'swap',
17 | });
18 |
19 | export const inter = Inter({ subsets: ['latin'] });
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | .env
39 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary: #638596;
3 | --text-color: #fff;
4 | }
5 |
6 | html {
7 | scroll-behavior: smooth;
8 | }
9 |
10 | body {
11 | color: var(--text-color);
12 | background: var(--primary);
13 | overflow-x: hidden;
14 | }
15 |
16 | * {
17 | box-sizing: border-box;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-osx-font-smoothing: grayscale;
20 | font-smooth: never;
21 | }
22 |
23 | @layer utilities {
24 | .text-balance {
25 | text-wrap: balance;
26 | }
27 | }
28 |
29 | @tailwind base;
30 | @tailwind components;
31 | @tailwind utilities;
32 |
--------------------------------------------------------------------------------
/src/app/sections/FooterSection.tsx:
--------------------------------------------------------------------------------
1 | import Button from '../components/Button';
2 | import Footer from '../components/Footer';
3 | import OnchainTypography from '../components/OnchainTypography';
4 | import { OPENSEA_COLLECTION } from '../constants/urls';
5 |
6 | const FooterSection = () => {
7 | return (
8 |
9 |
10 | View on OpenSea
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default FooterSection;
19 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 |
3 | const config: Config = {
4 | content: [
5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
14 | },
15 | screens: {
16 | '2k': '2560px',
17 | '4k': '3840px',
18 | },
19 | },
20 | },
21 | plugins: [],
22 | };
23 | export default config;
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "target": "ES2020",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/hooks/useResponsive.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useEffect, useState } from 'react';
3 |
4 | const useResponsive = () => {
5 | const [width, setWidth] = useState(0);
6 |
7 | const handleWindowSizeChange = () => {
8 | setWidth(window.innerWidth);
9 | };
10 |
11 | useEffect(() => {
12 | handleWindowSizeChange();
13 | window.addEventListener('resize', handleWindowSizeChange);
14 |
15 | // Clean Up
16 | return () => {
17 | window.removeEventListener('resize', handleWindowSizeChange);
18 | };
19 | }, []);
20 |
21 | return {
22 | isMobile: width <= 733,
23 | isTablet: width >= 744 && width <= 1024,
24 | isDesktop: width > 1024,
25 | is2K: width > 2648,
26 | };
27 | };
28 |
29 | export default useResponsive;
30 |
--------------------------------------------------------------------------------
/src/app/utils/shared.ts:
--------------------------------------------------------------------------------
1 | let _APP_URL = 'http://localhost:3000';
2 |
3 | if (process.env.VERCEL_ENV === 'production') {
4 | _APP_URL = `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`;
5 | } else if (process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development') {
6 | _APP_URL = `https://${process.env.VERCEL_URL}`;
7 | }
8 |
9 | export function isNumeric(str: string | number) {
10 | if (typeof str != 'string') return false; // we only process strings!
11 | return (
12 | !isNaN(str as unknown as number) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
13 | !isNaN(parseFloat(str))
14 | ); // ...and ensure strings of whitespace fail
15 | }
16 |
17 | export { _APP_URL as APP_URL };
18 |
--------------------------------------------------------------------------------
/src/app/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { nyghtBold } from '../fonts/fonts';
3 |
4 | interface ButtonProps {
5 | children: React.ReactNode;
6 | className?: string;
7 | href?: string;
8 | variant: 'outlined' | 'primary' | 'secondary';
9 | sameTab?: boolean;
10 | }
11 |
12 | const Button = ({ children, className, href = '/', variant, sameTab = false }: ButtonProps) => {
13 | return (
14 |
19 | {children}
20 |
21 | );
22 | };
23 |
24 | export default Button;
25 |
--------------------------------------------------------------------------------
/src/app/components/AnimatedSection.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { MotionProps, Variants, motion } from 'framer-motion';
3 |
4 | const variants: Variants = {
5 | hidden: {
6 | opacity: 0,
7 | y: 50,
8 | },
9 | visible: {
10 | opacity: 1,
11 | y: 0,
12 | transition: {
13 | duration: 0.4,
14 | },
15 | },
16 | };
17 |
18 | const AnimatedSection = ({ children, ...props }: MotionProps & React.ComponentProps<'input'>) => {
19 | return (
20 |
31 | {children}
32 |
33 | );
34 | };
35 |
36 | export default AnimatedSection;
37 |
--------------------------------------------------------------------------------
/src/app/sections/MintStepsGridSection.tsx:
--------------------------------------------------------------------------------
1 | import AnimatedSection from '../components/AnimatedSection';
2 | import Heading from '../components/Heading';
3 | import StepGrid from '../components/StepGrid';
4 | import Ui from '../components/Ui';
5 |
6 | const MintStepsGridSection = () => {
7 | return (
8 |
9 |
10 | Mint your SupaBald Jesse NFT
11 |
12 | For being based and boosting Base TVL, we've got a little reward for you!
13 |
14 |
15 | {/* The 4 step process to mint your NFT */}
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default MintStepsGridSection;
23 |
--------------------------------------------------------------------------------
/src/app/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { inter } from '../fonts/fonts';
3 |
4 | const Footer = () => {
5 | return (
6 |
9 |
10 | {`Made with <3 at`}
11 |
12 | Devfolio
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Twitter / X
21 |
22 |
23 | Farcaster
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default Footer;
31 |
--------------------------------------------------------------------------------
/src/app/components/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | const Tooltip = ({
2 | children,
3 | message,
4 | className,
5 | }: {
6 | children: React.ReactElement | string;
7 | message: React.ReactElement | string;
8 | className?: string;
9 | }) => {
10 | return (
11 |
12 | {children}
13 |
14 |
15 |
16 | {message}
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default Tooltip;
24 |
--------------------------------------------------------------------------------
/src/app/sections/VideoSection.tsx:
--------------------------------------------------------------------------------
1 | import AnimatedSection from '../components/AnimatedSection';
2 | import Heading from '../components/Heading';
3 | import { YoutubePlayer } from '../components/YoutubePlayer';
4 |
5 | const VideoSection = () => {
6 | return (
7 |
8 |
9 |
10 | We're not making this up. Hear it from Jesse himself!
11 |
12 | {/* This element adds invisible space for the next video element to be translated Y -50% */}
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default VideoSection;
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Devfolio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "supabald-jesse",
3 | "description": "The website that helps build the next based experience at the Onchain Summer Buildathon and make Jesse go bald",
4 | "repository": "https://github.com/devfolioco/supabald-jesse",
5 | "version": "1.0.0",
6 | "private": true,
7 | "scripts": {
8 | "dev": "next dev",
9 | "build": "next build",
10 | "start": "next start",
11 | "lint": "next lint",
12 | "format": "prettier --check --ignore-path .gitignore .",
13 | "format:fix": "prettier --write --ignore-path .gitignore ."
14 | },
15 | "dependencies": {
16 | "@neynar/nodejs-sdk": "^1.27.0",
17 | "@vercel/analytics": "^1.3.1",
18 | "framer-motion": "^11.2.6",
19 | "frog": "^0.11.4",
20 | "hono": "^4.3.11",
21 | "next": "14.2.3",
22 | "react": "^18",
23 | "react-dom": "^18",
24 | "react-player": "^2.16.0",
25 | "satori": "^0.10.13",
26 | "uuid": "^9.0.1"
27 | },
28 | "devDependencies": {
29 | "@types/node": "^20",
30 | "@types/react": "^18",
31 | "@types/react-dom": "^18",
32 | "@types/uuid": "^9.0.8",
33 | "eslint": "^8",
34 | "eslint-config-next": "14.2.3",
35 | "eslint-config-prettier": "^9.1.0",
36 | "eslint-plugin-prettier": "^5.1.3",
37 | "postcss": "^8",
38 | "prettier": "^3.2.5",
39 | "tailwindcss": "^3.4.1",
40 | "typescript": "^5"
41 | },
42 | "packageManager": "yarn@1.22.22"
43 | }
44 |
--------------------------------------------------------------------------------
/src/app/components/StepItem.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Variants, motion } from 'framer-motion';
3 | import Image from 'next/image';
4 | import Link from 'next/link';
5 | import { nyghtBold } from '../fonts/fonts';
6 |
7 | const variants: Variants = {
8 | hidden: {
9 | opacity: 0,
10 | y: 50,
11 | },
12 | visible: {
13 | opacity: 1,
14 | y: 0,
15 | transition: {
16 | duration: 0.5,
17 | },
18 | },
19 | };
20 |
21 | const StepItem = ({ step, label, image, href }: { step: number; label: string; image: string; href: string }) => {
22 | return (
23 |
24 |
27 | {step}
28 |
29 |
30 |
31 |
34 | {label}
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default StepItem;
42 |
--------------------------------------------------------------------------------
/src/app/components/StepGrid.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import StepItem from './StepItem';
4 | import { Variants, motion } from 'framer-motion';
5 |
6 | const containerVariants: Variants = {
7 | hidden: {},
8 | visible: {
9 | transition: {
10 | duration: 0.1,
11 | staggerChildren: 0.3,
12 | type: 'spring',
13 | stiffness: 0.5,
14 | },
15 | },
16 | };
17 |
18 | const StepGrid = () => {
19 | return (
20 |
31 |
37 |
43 |
49 |
55 |
56 | );
57 | };
58 |
59 | export default StepGrid;
60 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from '@vercel/analytics/react';
2 | import type { Metadata } from 'next';
3 | import './globals.css';
4 | import Head from 'next/head';
5 | import ApplePeekArea from './components/ApplePeekArea';
6 |
7 | export const metadata: Metadata = {
8 | title: 'Let’s get Jesse bald!',
9 | description: 'Participate in the buildathon. Get more assets on Base. Help us get Jesse bald.',
10 | openGraph: {
11 | type: 'website',
12 | url: 'https://letsgetjessebald.com/',
13 | title: 'SupaBald Jesse',
14 | description: 'Build the next based experience at the Onchain Summer Buildathon and watch Jesse go bald ;)',
15 | images: '/og-image.png',
16 | },
17 | twitter: {
18 | card: 'summary_large_image',
19 | title: 'SupaBald Jesse',
20 | description: 'Build the next based experience at the Onchain Summer Buildathon and watch Jesse go bald ;)',
21 | images: '/og-image.png',
22 | },
23 | icons: {
24 | icon: '/favicon_io/favicon.gif',
25 | apple: '/favicon_io/apple-touch-icon.png',
26 | },
27 | };
28 |
29 | export default function RootLayout({
30 | children,
31 | }: Readonly<{
32 | children: React.ReactNode;
33 | }>) {
34 | return (
35 |
36 |
37 |
38 |
39 |
40 | SupaBald Jesse
41 |
42 |
43 | {children}
44 |
45 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/sections/HomeSection.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Ui from '../components/Ui';
3 | import Button from '../components/Button';
4 | import Heading from '../components/Heading';
5 | import Link from 'next/link';
6 | import { BLOG_URL } from '../constants/urls';
7 |
8 | const HomeSection = ({ projectsUrl }: { projectsUrl: string }) => {
9 | return (
10 |
11 |
19 |
20 |
21 | Let’s get
22 | Jesse bald!
23 |
24 |
25 | Build the next based experience at the Onchain Summer Buildathon and watch{' '}
26 |
27 | Jesse
28 | {' '}
29 | go bald ;)
30 |
31 |
32 |
33 |
34 | View projects
35 |
36 |
37 | Why would he go bald?
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default HomeSection;
45 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import './styles/app.css';
2 | import { getFrameMetadata } from 'frog/next';
3 | import type { Metadata } from 'next';
4 | import HomeSection from './sections/HomeSection';
5 | import TypographySection from './sections/TypographySection';
6 | import WhySection from './sections/WhySection';
7 | import MintStepsGridSection from './sections/MintStepsGridSection';
8 | import VideoSection from './sections/VideoSection';
9 | import FooterSection from './sections/FooterSection';
10 |
11 | import { APP_URL } from './utils/shared';
12 |
13 | export async function generateMetadata(): Promise {
14 | const url = APP_URL;
15 | const frameMetadata = await getFrameMetadata(`${url}/api`);
16 |
17 | return {
18 | other: frameMetadata,
19 | };
20 | }
21 |
22 | export default function Home({ searchParams }: { searchParams: { ref_code?: string } }) {
23 | const referralCode = searchParams.ref_code;
24 | const registrationUrl = `https://onchain-summer.devfolio.co${referralCode ? '?ref_code=' + referralCode : ''}`;
25 | const projectsUrl = `https://onchain-summer.devfolio.co/projects${referralCode ? '?ref_code=' + referralCode : ''}`;
26 |
27 | return (
28 |
29 | {/* Let's get jesse bald */}
30 |
31 |
32 | {/* Animated Typography */}
33 |
34 |
35 | {/* Because that's what he said... */}
36 |
37 |
38 | {/* Mint your SupaBald Jesse NFT */}
39 |
40 |
41 | {/* We're not making this up. Hear it from Jesse himself! */}
42 |
43 |
44 | {/* Footer & Animated */}
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/components/YoutubePlayer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useEffect, useRef, useState } from 'react';
3 | import ReactPlayer from 'react-player';
4 |
5 | const YoutubePlayer = ({ url }: { url: string }) => {
6 | const [isMounted, setIsMounted] = useState(false);
7 | const videoContainerRef = useRef(null);
8 |
9 | useEffect(() => {
10 | if (!videoContainerRef.current) return;
11 | const resizeObserver = new ResizeObserver(() => {
12 | /* Store the updated height of the video container in --video-height css variable. (Used to translate the video element to -50% at Y-axis) */
13 | document.documentElement.style.setProperty('--video-height', `${videoContainerRef.current?.clientHeight ?? 0}px`);
14 | });
15 | resizeObserver.observe(videoContainerRef.current);
16 | return () => resizeObserver.disconnect();
17 | }, [isMounted]);
18 |
19 | useEffect(() => {
20 | setIsMounted(true);
21 | }, []);
22 |
23 | if (!isMounted) {
24 | return null;
25 | }
26 |
27 | return (
28 |
51 | );
52 | };
53 |
54 | export { YoutubePlayer };
55 |
--------------------------------------------------------------------------------
/src/app/frog/index.ts:
--------------------------------------------------------------------------------
1 | import { createSystem, defaultVars } from 'frog/ui';
2 |
3 | // Read the fonts from local file system (Not working in Production)
4 |
5 | // import fs from 'fs';
6 | // import path from 'path';
7 | // import { fileURLToPath } from 'url';
8 |
9 | // const nyghtSerifMedium = fs.readFileSync(
10 | // path.join(fileURLToPath(import.meta.url), '../../fonts/NyghtSerif-Medium.ttf')
11 | // );
12 | // const nyghtSerifBold = fs.readFileSync(path.join(fileURLToPath(import.meta.url), '../../fonts/NyghtSerif-Bold.ttf'));
13 |
14 | const nyghtSerifMedium = await fetch(
15 | 'https://assets-devrel.s3.ap-south-1.amazonaws.com/OCS/NyghtSerif-Medium.ttf'
16 | ).then(res => res.arrayBuffer());
17 |
18 | const nyghtSerifBold = await fetch('https://assets-devrel.s3.ap-south-1.amazonaws.com/OCS/NyghtSerif-Bold.ttf').then(
19 | res => res.arrayBuffer()
20 | );
21 |
22 | export const { Box, Columns, Column, Divider, Heading, HStack, Icon, Image, Rows, Row, Spacer, Text, VStack, vars } =
23 | createSystem({
24 | colors: {
25 | ...defaultVars.colors,
26 | text: '#F3F3F3',
27 | background: '#638596',
28 | castBackground: '#1B1423',
29 | castColor: '#C848FF',
30 | castGrey: '#8B99A4',
31 | },
32 | fonts: {
33 | default: [
34 | {
35 | name: 'Inter',
36 | source: 'google',
37 | weight: 300,
38 | },
39 | {
40 | name: 'Inter',
41 | source: 'google',
42 | weight: 600,
43 | },
44 | ],
45 | nyght: [
46 | {
47 | name: 'NyghtSerif-Medium',
48 | data: nyghtSerifMedium,
49 | source: 'buffer',
50 | weight: 400,
51 | },
52 | {
53 | name: 'NyghtSerif-Bold',
54 | data: nyghtSerifBold,
55 | source: 'buffer',
56 | weight: 700,
57 | },
58 | ],
59 | },
60 | });
61 |
--------------------------------------------------------------------------------
/public/onchain-m1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/app/sections/WhySection.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import AnimatedSection from '../components/AnimatedSection';
3 | import Button from '../components/Button';
4 | import Heading from '../components/Heading';
5 | import Tooltip from '../components/Tooltip';
6 | import Ui from '../components/Ui';
7 | import { BLOG_URL } from '../constants/urls';
8 |
9 | const baseGodScreenshot = (
10 |
17 | );
18 |
19 | const WhySection = ({ projectsUrl }: { projectsUrl: string }) => {
20 | return (
21 |
22 |
23 |
Because that’s what he said...
24 |
25 |
26 |
27 | In a galaxy not so far away, the{' '}
28 |
29 | Base God commune
30 |
31 |
32 | Base God commune
33 | {' '}
34 | to be precise, Jesse committed to shaving his head off when Base hits 10 billion in TVL. Join this quest
35 | to hold him accountable and claim a SupaBald Jesse NFT for your contribution.
36 |
37 |
38 |
39 | Participate in the buildathon, create breakout experiences, push Base TVL to the moon, and get Jesse bald.
40 |
41 |
42 |
43 |
44 |
45 | View Projects
46 |
47 |
48 | Read More
49 |
50 |
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | export default WhySection;
58 |
--------------------------------------------------------------------------------
/public/onchain-m2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/app/components/AnimatedText.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Variant, useAnimation, useInView } from 'framer-motion';
4 | import { useEffect, useRef } from 'react';
5 | import { motion } from 'framer-motion';
6 |
7 | type AnimatedTextProps = {
8 | text: string | string[];
9 | el?: keyof JSX.IntrinsicElements;
10 | className?: string;
11 | once?: boolean;
12 | repeatDelay?: number;
13 | animation?: {
14 | hidden: Variant;
15 | visible: Variant;
16 | };
17 | };
18 |
19 | const defaultAnimations = {
20 | hidden: {
21 | opacity: 0,
22 | y: 20,
23 | },
24 | visible: {
25 | opacity: 1,
26 | y: 0,
27 | transition: {
28 | duration: 0.1,
29 | },
30 | },
31 | };
32 |
33 | export const AnimatedText = ({
34 | text,
35 | className,
36 | once,
37 | repeatDelay,
38 | animation = defaultAnimations,
39 | }: AnimatedTextProps) => {
40 | const controls = useAnimation();
41 | const textArray = Array.isArray(text) ? text : [text];
42 | const ref = useRef(null);
43 | const isInView = useInView(ref, { amount: 0.5, once });
44 |
45 | useEffect(() => {
46 | let timeout: NodeJS.Timeout;
47 | const show = () => {
48 | controls.start('visible');
49 | if (repeatDelay) {
50 | timeout = setTimeout(async () => {
51 | await controls.start('hidden');
52 | controls.start('visible');
53 | }, repeatDelay);
54 | }
55 | };
56 |
57 | if (isInView) {
58 | show();
59 | } else {
60 | controls.start('hidden');
61 | }
62 |
63 | return () => clearTimeout(timeout);
64 | }, [controls, isInView, repeatDelay]);
65 |
66 | return (
67 |
68 | {textArray.join(' ')}
69 |
79 | {textArray.map((line, lineIndex) => (
80 |
81 | {line.split(' ').map((word, wordIndex) => (
82 |
83 | {word.split('').map((char, charIndex) => (
84 |
85 | {char}
86 |
87 | ))}
88 |
89 |
90 | ))}
91 |
92 | ))}
93 |
94 |
95 | );
96 | };
97 |
--------------------------------------------------------------------------------
/src/app/styles/app.css:
--------------------------------------------------------------------------------
1 | /* Background Styles */
2 |
3 | .bg-primary {
4 | background-color: var(--primary);
5 | }
6 |
7 | .bg-grid {
8 | background-image: url('/grid.png');
9 | }
10 |
11 | .bg-grid-white {
12 | background-image: url('/grid-white.png');
13 | }
14 |
15 | /* Design Text Styles */
16 |
17 | .hero-heading {
18 | color: #f3f3f3;
19 | font-style: normal;
20 | font-weight: normal;
21 | line-height: 110%;
22 | }
23 |
24 | .hero-heading-700 {
25 | color: #f3f3f3;
26 | font-weight: 400;
27 | line-height: 130%;
28 | }
29 |
30 | .ui-text {
31 | color: #f3f3f3;
32 | font-weight: 300;
33 | line-height: 150%;
34 | }
35 |
36 | /* Button and its variants */
37 |
38 | .button {
39 | display: flex;
40 | justify-content: center;
41 | align-items: center;
42 | border-radius: 8px;
43 | line-height: 32px;
44 | transition: 0.2s all linear;
45 | }
46 |
47 | .button:hover {
48 | opacity: 0.8;
49 | }
50 |
51 | .primary-button {
52 | background-color: white;
53 | color: var(--primary);
54 | }
55 |
56 | .secondary-button {
57 | background-color: var(--primary);
58 | color: white;
59 | }
60 |
61 | .outlined-button {
62 | background-color: transparent;
63 | color: white;
64 | border: 2px solid #7d98a6;
65 | }
66 |
67 | /* Step Grid Component Styles */
68 |
69 | .step-item .step-item-label {
70 | border: 3px solid #7d98a6;
71 | background: var(--primary);
72 | }
73 |
74 | .step-item a {
75 | width: 100%;
76 | }
77 |
78 | .step-item .step-item-id {
79 | background: #7da3b6;
80 | border-radius: 50%;
81 | display: flex;
82 | justify-content: center;
83 | align-items: center;
84 | box-shadow:
85 | 0px 1px 5px 0px rgba(3, 0, 92, 0.09),
86 | 0px 6px 16px 0px rgba(3, 0, 92, 0.12);
87 | position: relative;
88 | top: 21px;
89 | }
90 |
91 | /* Footer Styles */
92 |
93 | .footer {
94 | color: #8e989c;
95 | font-style: normal;
96 | line-height: 24px;
97 | border-top: 1px solid #e4eaeb;
98 | }
99 |
100 | /* Translate Video Container to -50% at Y-axis */
101 |
102 | .half-video-height {
103 | height: calc((var(--video-height)) / 2);
104 | }
105 |
106 | .transform-top-50-video {
107 | transform: translateY(calc(-1 * (var(--video-height) + 32px) / 2));
108 | }
109 |
110 | .negative-margin-50-video {
111 | margin-bottom: calc(-1 * (var(--video-height)) / 2);
112 | }
113 |
114 | /* Other Styles */
115 |
116 | .ios-peek {
117 | position: fixed;
118 | z-index: -1;
119 | bottom: 0;
120 | left: 0;
121 | padding-top: 50vh;
122 | width: 100vw;
123 | background: white;
124 | color: #aeb4b7;
125 | text-align: center;
126 | }
127 |
128 | .clip-bottom {
129 | clip-path: polygon(50% 0, 100% 100%, 0 100%);
130 | }
131 |
--------------------------------------------------------------------------------
/public/onchain-m3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SupaBald Jesse
2 |
3 | 
4 |
5 | This repository contains the website and frame for the "Let's get Jesse Bald" campaign by [Devfolio](https://devfolio.co/) for the [Onchain Summer Buildathon](https://onchain-summer.devfolio.co/)
6 |
7 | [](https://letsgetjessebald.com/)
8 |
9 | [](https://github.com/devfolioco/supabald-jesse/deployments/Production)
10 |
11 | # Getting Started
12 |
13 | ### Prerequisites
14 |
15 | - Node 18 +
16 | - Yarn
17 |
18 | ### Environment Variables
19 |
20 | - Create .env.local from .env.example
21 | - Get Neynar API Keys from https://docs.neynar.com
22 |
23 | ### Installation
24 |
25 | ```bash
26 | # Using yarn (Recommended)
27 | yarn
28 | yarn dev
29 | ```
30 |
31 | # Tech Stack
32 |
33 | - NextJS with the App Router - https://nextjs.org/
34 | - Frog.JS for Farcaster Frames - https://frog.fm/
35 | - TailwindCSS - tailwindcss.com/
36 | - Framer Motion - https://framer.com/motion/
37 | - Prettier / ESlint - https://prettier.io/ https://eslint.org/
38 |
39 | # Project Structure
40 |
41 | ```bash
42 | ├── .next/
43 | ├── node_modules/
44 | ├── public/
45 | ├── src/
46 | │ ├── app/
47 | │ │ ├── api/ # Endpoints for the farcaster frames
48 | │ │ ├── components/ # all resuable small components
49 | │ │ ├── fonts/
50 | │ │ ├── frog/ # contains frog ui for farcaster frames
51 | │ │ ├── hooks/ # common components logic as hooks
52 | │ │ ├── sections/ # Landing page sections
53 | │ │ ├── styles/ # all css
54 | │ │ ├── utils/ # common helper methods
55 | │ │ ├── globals.css
56 | │ │ ├── layout.tsx # html layout
57 | │ │ ├── page.tsx # main page
58 | ├── .env.local # local env
59 | ├── .eslintrc.json
60 | ├── .gitignore
61 | ├── .prettierrc.js
62 | ├── next-env.d.ts
63 | ├── next.config.mjs
64 | ├── package.json
65 | ├── postcss.config.mjs
66 | ├── README.md
67 | ├── tailwind.config.ts
68 | ├── tsconfig.json
69 | ├── yarn.lock
70 |
71 | ```
72 |
73 | # Contributing
74 |
75 | Feel free to open [issues](https://github.com/devfolioco/supabald-jesse/issues/new/choose) and [pull requests](https://github.com/devfolioco/supabald-jesse/pulls)!
76 |
77 | ## Contributors
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
89 |
90 |
91 | ## License
92 |
93 | [](https://github.com/devfolioco/supabald-jesse/blob/main/LICENSE)
94 |
--------------------------------------------------------------------------------
/public/onchain-m4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/app/components/OnchainTypography.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import Image from 'next/image';
3 | import useResponsive from '../hooks/useResponsive';
4 | import { Variants, motion, useScroll, useTransform } from 'framer-motion';
5 |
6 | const OnchainTypography = () => {
7 | const { isMobile, isTablet, isDesktop, is2K } = useResponsive();
8 |
9 | /* Typography left and right animation on scroll */
10 | const { scrollYProgress } = useScroll();
11 | const scrollYProgressInverted = useTransform(scrollYProgress, [0, 1], [1, 0]);
12 | const objectPositionMoveLeft = useTransform(scrollYProgress, value => `${-1 * value * 1000}px 100%`);
13 | const objectPositionMoveRight = useTransform(scrollYProgressInverted, value => `${-1 * value * 1000}px 100%`);
14 |
15 | const variants: Variants = {
16 | hidden: {
17 | opacity: 0,
18 | },
19 | visible: {
20 | opacity: 1,
21 | y: 0,
22 | transition: {
23 | duration: 0,
24 | },
25 | },
26 | };
27 |
28 | return (
29 |
40 | {/* All Desktop Screens */}
41 | {isDesktop && (
42 | <>
43 | {/* Desktop 1080p */}
44 | {!is2K && (
45 | <>
46 |
52 |
53 |
59 | >
60 | )}
61 |
62 | {/* Desktop 2k */}
63 | {is2K && (
64 | <>
65 |
71 |
72 |
78 | >
79 | )}
80 | >
81 | )}
82 |
83 | {/* Tablet */}
84 | {isTablet && (
85 |
93 | )}
94 |
95 | {/* Mobile */}
96 | {isMobile && (
97 | <>
98 |
106 |
114 |
122 |
130 | >
131 | )}
132 |
133 | );
134 | };
135 |
136 | export default OnchainTypography;
137 |
--------------------------------------------------------------------------------
/public/onchain-tablet.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/api/[[...routes]]/route.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource frog/jsx */
2 | import { Button, FrameResponse, Frog, TextInput } from 'frog';
3 | import { handle } from 'frog/next';
4 | import { neynar as neynarMiddleware, NeynarUser as NeynarMiddlewareUser } from 'frog/middlewares';
5 | import { devtools } from 'frog/dev';
6 | import { serveStatic } from 'frog/serve-static';
7 | import { v4 } from 'uuid';
8 |
9 | import { NeynarAPIClient } from '@neynar/nodejs-sdk';
10 | import type { User as NeynarUserV1 } from '@neynar/nodejs-sdk/build/neynar-api/v1';
11 | import type { User as NeynarUserV2 } from '@neynar/nodejs-sdk/build/neynar-api/v2';
12 |
13 | import { Box, Heading, Text, VStack, vars } from '../../frog';
14 |
15 | import { APP_URL, isNumeric } from '../../utils/shared';
16 | import { OPENSEA_COLLECTION, BLOG_URL, PROJECTS_URL } from '../../constants/urls';
17 |
18 | const NEYNAR_API_KEY = process.env.NEYNAR_API_KEY ?? '';
19 | const NEYNAR_SIGNER = process.env.NEYNAR_SIGNER ?? '';
20 |
21 | const neynarClient = new NeynarAPIClient(NEYNAR_API_KEY);
22 |
23 | type State = {
24 | confirm: {
25 | interactor: NeynarMiddlewareUser;
26 | devfolio: NeynarUserV1;
27 | searchUser: NeynarUserV2;
28 | } | null;
29 | };
30 |
31 | const app = new Frog<{ State: State }>({
32 | basePath: '/api',
33 | browserLocation: '/:path',
34 | ui: { vars },
35 | initialState: {},
36 | headers: {
37 | 'cache-control': 'max-age=0',
38 | },
39 | }).use(
40 | neynarMiddleware({
41 | apiKey: NEYNAR_API_KEY,
42 | features: ['interactor'],
43 | })
44 | );
45 |
46 | // COMPONENTS START
47 |
48 | const ErrorResponse = (error: string): FrameResponse => {
49 | return {
50 | title: 'Error',
51 | image: (
52 |
53 |
54 |
55 | Error occured :/
56 |
57 |
58 |
59 | {error}
60 |
61 |
62 |
63 | ),
64 | intents: [Try again ],
65 | };
66 | };
67 |
68 | // COMPONENTS END
69 |
70 | // FRAMES
71 |
72 | app.frame('/', c => {
73 | return c.res({
74 | title: 'SupaBald Jesse',
75 | image: `${APP_URL}/nft-fc.gif`,
76 | intents: [
77 |
78 | Mint your NFT
79 | ,
80 |
81 | Nominate a fren
82 | ,
83 |
84 | View collection
85 | ,
86 | ],
87 | });
88 | });
89 |
90 | // Disable Nomination
91 | app.frame('/nominate/:id', c => {
92 | return c.res({
93 | title: 'SupaBald Jesse | Nominate',
94 | image: (
95 |
96 |
97 |
98 | Buildathon has ended
99 |
100 |
101 |
102 | Here's how based it was!
103 |
104 | {/*
105 | // @ts-expect-error Ignore the error with */}
106 |
107 | Builders: 10,000+
108 |
109 | Projects built: 1200+
110 |
111 | SupaBald Jesse's claimed: 8850+
112 |
113 |
114 |
115 | ),
116 | intents: [
117 |
118 | 🛠 View projects
119 | ,
120 |
121 | 📖 Read more
122 | ,
123 | ],
124 | });
125 | });
126 |
127 | // app.frame('/nominate/:id', c => {
128 | // return c.res({
129 | // title: 'SupaBald Jesse | Nominate',
130 | // image: (
131 | //
132 | //
133 | //
134 | // Nominate a fren
135 | //
136 |
137 | //
138 | // Frens don’t let frens miss out on opportunities. Ask a fren to join you at the Onchain Summer Buildathon.
139 | //
140 | //
141 | //
142 | // ),
143 | // intents: [
144 | // ,
145 | // Back ,
146 | //
147 | // Send Cast
148 | // ,
149 | // ],
150 | // });
151 | // });
152 |
153 | app.frame('/confirm/:id', async c => {
154 | const interactor = c.var.interactor;
155 | if (!interactor) {
156 | return c.res(ErrorResponse('Interactor is not set!'));
157 | }
158 |
159 | const devfolioLookupResponse = await neynarClient.lookupUserByUsername('devfolio').catch(() => false);
160 | if (typeof devfolioLookupResponse === 'boolean') {
161 | return c.res(ErrorResponse('Devfolio profile not found!'));
162 | }
163 | const devfolio = devfolioLookupResponse.result.user;
164 |
165 | let searchUser: NeynarUserV2 | undefined;
166 |
167 | if (!c.inputText) {
168 | return c.res(ErrorResponse('Farcaster username is required!'));
169 | }
170 |
171 | const isSearchInputNumber = isNumeric(c.inputText);
172 |
173 | if (isSearchInputNumber) {
174 | const searchUserByFIDResult = await neynarClient.fetchBulkUsers([Number(c.inputText)]).catch(() => false);
175 |
176 | if (typeof searchUserByFIDResult != 'boolean' && searchUserByFIDResult.users.length > 0) {
177 | searchUser = searchUserByFIDResult.users?.[0];
178 | }
179 | } else {
180 | const searchUserByUsernameResult = await neynarClient.searchUser(c.inputText).catch(() => false);
181 |
182 | if (typeof searchUserByUsernameResult != 'boolean' && searchUserByUsernameResult?.result?.users?.length > 0) {
183 | searchUser = searchUserByUsernameResult?.result?.users?.[0];
184 | }
185 | }
186 |
187 | if (!searchUser) {
188 | return c.res(
189 | ErrorResponse(`We could not find @${c.inputText} on Farcaster. Please double check the username and try again.`)
190 | );
191 | }
192 |
193 | // Set the state
194 | const state = c.deriveState(previousState => {
195 | previousState.confirm = {
196 | devfolio: devfolio,
197 | interactor,
198 | searchUser,
199 | };
200 | });
201 |
202 | const confirmState = state.confirm;
203 |
204 | if (!confirmState) {
205 | return c.res(ErrorResponse('Invalid State'));
206 | }
207 |
208 | const cast = `🔵 gm @${confirmState.searchUser.username}. @${confirmState.interactor.username} thinks you're a super based builder, and has nominated you for the Onchain Summer Buildathon.
209 |
210 | Hop in, mint your SupaBald Jesse NFT, and just build it. LFG
211 |
212 | https://letsgetjessebald.com/`;
213 |
214 | await neynarClient.publishCast(NEYNAR_SIGNER, cast, {
215 | embeds: [{ url: 'https://letsgetjessebald.com/' }],
216 | });
217 |
218 | return c.res({
219 | title: 'SupaBald Jesse | Cast Sent',
220 | image: (
221 |
222 |
223 |
224 | Cast sent!
225 |
226 |
227 |
228 | You’re based.
229 |
230 |
231 |
232 | ),
233 | intents: [
234 | 👍 ,
235 |
236 | Nominate another fren
237 | ,
238 | ],
239 | });
240 | });
241 |
242 | app.frame('/cast', c => {
243 | const state = c.previousState;
244 | const confirmState = state.confirm;
245 |
246 | if (!confirmState) {
247 | return c.res(ErrorResponse('Invalid State'));
248 | }
249 |
250 | const cast = `🔵 gm @${confirmState.searchUser.username}. @${confirmState.interactor.username} thinks you're a super based builder, and has nominated you for the Onchain Summer Buildathon.
251 |
252 | Hop in, mint your SupaBald Jesse NFT, and just build it. LFG
253 |
254 | https://letsgetjessebald.com/`;
255 |
256 | neynarClient.publishCast(NEYNAR_SIGNER, cast, {
257 | embeds: [{ url: 'https://letsgetjessebald.com/' }],
258 | });
259 |
260 | return c.res({
261 | title: 'SupaBald Jesse | Cast Sent',
262 | image: (
263 |
264 |
265 |
266 | Cast sent!
267 |
268 |
269 |
270 | You’re based.
271 |
272 |
273 |
274 | ),
275 | intents: [
276 | 👍 ,
277 |
278 | Nominate another fren
279 | ,
280 | ],
281 | });
282 | });
283 |
284 | devtools(app, { serveStatic });
285 |
286 | export const GET = handle(app);
287 | export const POST = handle(app);
288 |
--------------------------------------------------------------------------------
/public/onchain-d1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/public/onchain-4k-1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/public/onchain-d2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/public/onchain-4k-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------