diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..74cdf3ee
Binary files /dev/null and b/.DS_Store differ
diff --git a/www/app/meme/layout.tsx b/www/app/meme/layout.tsx
new file mode 100644
index 00000000..706c1ad3
--- /dev/null
+++ b/www/app/meme/layout.tsx
@@ -0,0 +1,81 @@
+import type { Metadata } from "next";
+import { Outfit, Space_Grotesk } from "next/font/google";
+import "./meme.css";
+
+import Script from "next/script";
+
+
+const outfit = Outfit({
+ subsets: ["latin"],
+ variable: "--font-outfit",
+ display: "swap",
+ weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
+});
+
+const spaceGrotesk = Space_Grotesk({
+ subsets: ["latin"],
+ variable: "--font-space-grotesk",
+ display: "swap",
+ weight: ["300", "400", "500", "600", "700"],
+});
+
+export const metadata: Metadata = {
+ title: "Devb.io - Effortless Portfolios for Developers",
+ description: "Effortless Portfolios for Developers",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+
+
+ {/* Google Analytics */}
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/www/app/meme/meme.css b/www/app/meme/meme.css
new file mode 100644
index 00000000..8a4d2a5e
--- /dev/null
+++ b/www/app/meme/meme.css
@@ -0,0 +1,14 @@
+body {
+ background-color: black;
+ height: 100%;
+
+}
+
+.body-container {
+ background-image: radial-gradient(circle at var(--x) var(--y), var(--color1) 0%, var(--color2) var(--position2)), url('/assets/gorilla-bg-min.jpg');
+ background-position: center top;
+ background-size: cover;
+ height: 100vh;
+
+
+}
\ No newline at end of file
diff --git a/www/app/meme/page.tsx b/www/app/meme/page.tsx
new file mode 100644
index 00000000..5d9e7379
--- /dev/null
+++ b/www/app/meme/page.tsx
@@ -0,0 +1,385 @@
+"use client"
+import { useEffect, useRef, useState } from "react";
+import "./meme.css";
+import { LinneChart } from "@/components/meme-comp/LinneChart";
+import MemeGenerator from "@/components/meme-comp/MemeGenerator";
+import { getGithubInfo, getMemeProfileData } from "@/lib/api";
+import { ContributionsData } from "@/types/types";
+import { Container } from "@/components/meme-comp/Container";
+
+import { ButtonArrow } from "@/public/assets/buttonArrow";
+import { AnimatePresence, motion } from "framer-motion";
+import html2canvas from "html2canvas";
+import { Loader } from "@/components/meme-comp/Loader";
+import { AxiosError } from "axios";
+
+function page(){
+ const [myUserName, setMyUserName] = useState("");
+ const [comparerUserInput, setComparerUserInput] = useState("");
+ const [myData, setMyData] = useState(null);
+ const [comparerData, setComparerData] = useState(null);
+ const [myName, setMyName] = useState("");
+ const [myTotalContribution, setMyTotalContribution] = useState(0);
+ const [comparertotalContri, setComparertotalContri] = useState(0);
+ const [comparerName, setComparerName] = useState("");
+ const [memeIndex, setMemeIndex] = useState(0);
+ const [isLoading, setIsLoading] = useState(false);
+ const [winner, setWinner] = useState(null);
+ const [loser, setLoser] = useState(null);
+ const [haveData, setHaveData] = useState(false);
+ const [myError, setMyError] = useState("");
+ const [comparerError, setComparerError] = useState("");
+ const headingContainer = useRef(null);
+ const containerRef = useRef(null);
+
+ const handleSubmit = async (e: React.FormEvent): Promise => {
+ e.preventDefault();
+
+ // Clear previous data
+ setMyData(null);
+ setComparerData(null);
+
+ // Start loading
+ setIsLoading(true);
+
+ if (myUserName === comparerUserInput) {
+ setMyError("You can't compare to yourself :')")
+ setIsLoading(false);
+ return;
+ }
+ try {
+ const [
+ myProfileData,
+ myGitHubInfo,
+ comparerProfileData,
+ comparerGitHubInfo,
+ ] = await Promise.all([
+ getMemeProfileData(myUserName),
+ getGithubInfo(myUserName),
+ getMemeProfileData(comparerUserInput),
+ getGithubInfo(comparerUserInput),
+ ]);
+
+ setMyData(myProfileData);
+ setMyName(myGitHubInfo?.name || myUserName);
+ setComparerData(comparerProfileData);
+ setComparerName(comparerGitHubInfo?.name || comparerUserInput);
+ } catch (error) {
+ const axiosError = error as AxiosError;
+ if (axiosError.response && axiosError.response.status === 404) {
+ if (axiosError.config?.url?.includes(myUserName)) {
+ setMyError("Username not found");
+ } else if (axiosError.config?.url?.includes(comparerUserInput)) {
+ setComparerError("Comparer username not found");
+ }
+ } else {
+ console.error("Error fetching data:", error);
+ }
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const getMemeIndex = (myContribution: number, comparerContribution: number): number => {
+ if (myContribution === 0 && comparerContribution === 0) {
+ return 0;
+ }
+ const maxContribution = Math.max(myContribution, comparerContribution);
+ const difference = Math.abs(myContribution - comparerContribution);
+ const percentageDifference = (difference / maxContribution) * 100;
+ const index = Math.floor(percentageDifference / 10);
+ return Math.min(index, 9);
+ };
+
+ const downloadImage = (e: React.MouseEvent): void => {
+ if (!containerRef.current || !headingContainer.current) return;
+
+ if (isMobile) {
+ headingContainer.current.style.display = 'none';
+ }
+ e.currentTarget.style.display = "none";
+ html2canvas(containerRef.current, { backgroundColor: "#2b3137" })
+ .then((canvas) => {
+ const link = document.createElement("a");
+ link.href = canvas.toDataURL("image/png");
+ link.download = "github-compare.png";
+ link.click();
+ })
+ .catch((error) => {
+ console.error("Error capturing the image:", error);
+ })
+ .finally(() => {
+ e.currentTarget.style.display = "flex";
+ if (headingContainer.current) {
+ headingContainer.current.style.display = 'flex';
+ }
+ });
+ };
+
+ useEffect(() => {
+ if (myData && comparerData) {
+ const arrOfMyContri = Object.values(myData.total);
+ const arrOfComparerContri = Object.values(comparerData.total);
+
+ const myTotalContribution = arrOfMyContri.reduce(
+ (total, curVal) => total + curVal,
+ 0
+ );
+ setMyTotalContribution(myTotalContribution);
+
+ const comparerTotalContribution = arrOfComparerContri.reduce(
+ (total, curVal) => total + curVal,
+ 0
+ );
+ setComparertotalContri(comparerTotalContribution);
+
+ const memeIndex = getMemeIndex(
+ myTotalContribution,
+ comparerTotalContribution
+ );
+ setMemeIndex(memeIndex);
+ const myfirstName = myName.split(" ")[0];
+ const comparerfirstName = comparerName.split(" ")[0];
+
+ if (myTotalContribution > comparerTotalContribution) {
+ setWinner(myfirstName);
+ setLoser(comparerfirstName);
+ } else {
+ setWinner(comparerfirstName);
+ setLoser(myfirstName);
+ }
+ setHaveData(true);
+ }
+ }, [myData, comparerData, myName, comparerName]);
+
+ const transition = { duration: 1, ease: "easeInOut" };
+
+ const [isMobile, setIsMobile] = useState(false);
+ useEffect(() => {
+ // Initialize mobile state after component mounts
+ const checkIsMobile = () => {
+ if (typeof window !== 'undefined') {
+ setIsMobile(window.innerWidth <= 768);
+ }
+ };
+
+ const handleResize = (): void => {
+ if (typeof window !== 'undefined') {
+ setIsMobile(window.innerWidth <= 768);
+ }
+ };
+
+ // Set initial value
+ checkIsMobile();
+
+ // Add event listener
+ if (typeof window !== 'undefined') {
+ window.addEventListener("resize", handleResize);
+ }
+
+ return () => {
+ if (typeof window !== 'undefined') {
+ window.removeEventListener("resize", handleResize);
+ }
+ };
+ }, []);
+
+ const fontSize = haveData
+ ? isMobile
+ ? "34px"
+ : "52px"
+ : isMobile
+ ? "44px"
+ : "68px";
+
+ const clearDataCallBack = (): void => {
+ setMyData(null);
+ setMyName('');
+ setComparerData(null);
+ setComparerName('');
+ setHaveData(false);
+ setLoser(null);
+ setWinner(null);
+ setMyError('');
+ setComparerError('');
+ };
+
+ return (
+
+
+
+
+
+
+ GitHub Profile
{" "}
+
+ Comparison
+
+
+
+
+ {!haveData && (
+
+
+ {myError && (
+
+ {myError}
+
+ )}
+
setMyUserName(e.currentTarget.value)}
+ placeholder="Your Username"
+ required
+ className={`border bg-white shadow-[0px_4px_50px_rgba(0,_255,_87,_0.5)] w-[294px] ${
+ myError ? " outline outline-red-500" : ""
+ } py-3 px-4 text-[18px] rounded-full`}
+ />
+
+
+ {isLoading ? : VS}
+
+
+ {comparerError && (
+
{comparerError}
+ )}
+
+ setComparerUserInput(e.currentTarget.value)
+ }
+ placeholder="Enter other persons username"
+ required
+ className={`border bg-white shadow-[0px_4px_50px_rgba(0,_255,_87,_0.5)] w-[294px] ${
+ comparerError ? " outline outline-red-500" : ""
+ } py-3 px-4 text-[18px] rounded-full`}
+ />
+
+
+
+
+
+ )}
+
+
+
+ {myData && comparerData && (
+
+
+
+
+
+
+
+
+ {myName}
+
+
+ Total Contribution : {myTotalContribution}
+
+
+
+
+
+
+
+ {comparerName}
+
+
+ Total Contribution : {comparertotalContri}
+
+
+
+
+
+
+
+
+ {winner && loser && (
+
+ )}
+
+
+
+
+ )}
+
+
+
+
+ );
+}
+
+export default page;
diff --git a/www/app/page.tsx b/www/app/page.tsx
index d8650ad9..3720414e 100644
--- a/www/app/page.tsx
+++ b/www/app/page.tsx
@@ -8,6 +8,7 @@ import ProfileCard from "@/components/profile-card/server";
import HowItWorksCard from "@/components/how-it-works-card/server";
import NextContributorCard from "@/components/next-contributor-card";
import { Compare } from "@/components/ui/compare";
+import CompareButton from "@/components/CompareButton";
// Types
interface Profile {
@@ -343,6 +344,9 @@ export default async function Home() {
)}
+
+
+
>
diff --git a/www/components/CompareButton.tsx b/www/components/CompareButton.tsx
new file mode 100644
index 00000000..28bf0b14
--- /dev/null
+++ b/www/components/CompareButton.tsx
@@ -0,0 +1,17 @@
+"use client"
+import { useRouter } from 'next/navigation';
+ import meme from '@/public/assets/smile.png';
+
+const CompareButton = () => {
+ const router = useRouter()
+
+ return (
+ router.push('/meme')} className='cursor-pointer hover:scale-105 transition-all duration-500 flex items-center gap-2 p-1 px-5 bg-gradient-to-r from-violet-700 to-fuchsia-500 rounded-full'>
+

+
Compare Github
+
+
+ )
+}
+
+export default CompareButton;
\ No newline at end of file
diff --git a/www/components/meme-comp/Container.tsx b/www/components/meme-comp/Container.tsx
new file mode 100644
index 00000000..50de7888
--- /dev/null
+++ b/www/components/meme-comp/Container.tsx
@@ -0,0 +1,12 @@
+interface ContainerProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+export function Container({ children, className = "" }: ContainerProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/www/components/meme-comp/InputText.tsx b/www/components/meme-comp/InputText.tsx
new file mode 100644
index 00000000..1608fce3
--- /dev/null
+++ b/www/components/meme-comp/InputText.tsx
@@ -0,0 +1,35 @@
+import React, { useId, forwardRef } from 'react'
+
+interface InputTextProps extends React.InputHTMLAttributes {
+ label?: string;
+ type?: string;
+ className?: string;
+}
+
+const InputText = forwardRef(function InputText({
+ label,
+ type = "text",
+ className = "",
+ ...props
+}, ref) {
+ const id = useId()
+ return (
+
+ {label &&
+ }
+
+
+ )
+})
+
+export default InputText
diff --git a/www/components/meme-comp/LinneChart.tsx b/www/components/meme-comp/LinneChart.tsx
new file mode 100644
index 00000000..f3b78b7b
--- /dev/null
+++ b/www/components/meme-comp/LinneChart.tsx
@@ -0,0 +1,101 @@
+import { Line } from "react-chartjs-2";
+import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, PointElement, LineElement, Tooltip, ChartData, ChartOptions } from "chart.js";
+import { useEffect, useState } from "react";
+
+
+ChartJS.register(CategoryScale, LinearScale, BarElement, PointElement, LineElement, Tooltip);
+
+interface UserNames {
+ myname: string;
+ comparerName: string;
+}
+
+interface LinneChartProps {
+ myData: { [year: number]: number };
+ comparerData: { [year: number]: number };
+ userNames: UserNames;
+}
+
+export function LinneChart({ myData, comparerData, userNames }: LinneChartProps) {
+ const [myContributions, setMyContributions] = useState<(number | null)[]>(Object.values(myData));
+ const [comparerContributions, setComparerContributions] = useState<(number | null)[]>(Object.values(comparerData));
+
+ const getUserYearsforLabel = (myData: { [year: number]: number }, comparerData: { [year: number]: number }): string[] => {
+ const myYears = Object.keys(myData);
+ const comparerYears = Object.keys(comparerData);
+ if (myYears.length > comparerYears.length) {
+ return myYears;
+ } else {
+ return comparerYears;
+ }
+ };
+
+ const mainLabel = getUserYearsforLabel(myData, comparerData);
+
+ useEffect(() => {
+ const maxLength = Math.max(myContributions.length, comparerContributions.length);
+
+ // Function to pad an array with null at the start
+ const padArray = (arr: (number | null)[], length: number): (number | null)[] => {
+ const padding = Array(length - arr.length).fill(null);
+ return [...padding, ...arr];
+ };
+
+ if (myContributions.length < maxLength) {
+ setMyContributions(padArray(myContributions, maxLength));
+ }
+
+ if (comparerContributions.length < maxLength) {
+ setComparerContributions(padArray(comparerContributions, maxLength));
+ }
+ }, [myContributions, comparerContributions]);
+
+ const LINECHART_DATA: ChartData<'line'> = {
+ labels: mainLabel,
+ datasets: [
+ {
+ label: userNames.myname,
+ data: myContributions,
+ borderColor: '#63FF60',
+ pointBackgroundColor: '#fff',
+ pointBorderColor: '#fff',
+ pointRadius: 5,
+ borderWidth: 4
+ },
+ {
+ label: userNames.comparerName,
+ data: comparerContributions,
+ borderColor: '#FFE55B',
+ pointBackgroundColor: '#fff',
+ pointBorderColor: '#fff',
+ pointRadius: 5,
+ borderWidth: 4
+ },
+ ],
+ };
+
+ const options: ChartOptions<'line'> = {
+ scales: {
+ x: {
+ grid: {
+ color: '#FFFFFF33',
+ },
+ ticks: {
+ color: '#fff'
+ }
+ },
+ y: {
+ grid: {
+ color: '#FFFFFF33',
+ },
+ ticks: {
+ color: '#fff'
+ }
+ },
+ },
+ };
+
+ return (
+
+ );
+}
diff --git a/www/components/meme-comp/Loader.tsx b/www/components/meme-comp/Loader.tsx
new file mode 100644
index 00000000..7a584370
--- /dev/null
+++ b/www/components/meme-comp/Loader.tsx
@@ -0,0 +1,25 @@
+
+
+export function Loader() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/www/components/meme-comp/MemeGenerator.tsx b/www/components/meme-comp/MemeGenerator.tsx
new file mode 100644
index 00000000..adbda2bd
--- /dev/null
+++ b/www/components/meme-comp/MemeGenerator.tsx
@@ -0,0 +1,159 @@
+import React, { useEffect, useRef } from 'react';
+import {
+ firstMeme,
+ secondMeme,
+ thirdMeme,
+ fourthMeme,
+ fifthMeme,
+ sixthMeme,
+ seventhMeme,
+ eighthMeme,
+ ninthMeme,
+ tenth
+} from '@/public/assets/memes';
+
+import type { StaticImageData } from 'next/image';
+
+interface MemeConfig {
+ image: string | StaticImageData;
+ titlePosition: { x: number; y: number };
+ winnerNamePosition: { x: number; y: number };
+ loserNamePosition: { x: number; y: number };
+ titleFontSize: number;
+ nameFontSize: number;
+}
+
+interface MemeGeneratorProps {
+ winnerName: string;
+ loserName: string;
+ memeIndex: number;
+}
+
+const MemeGenerator: React.FC = ({ winnerName, loserName, memeIndex }) => {
+ const canvasRef = useRef(null);
+
+ const memeArray: MemeConfig[] = [
+ {
+ image: firstMeme,
+ titlePosition: { x: 240, y: 275 },
+ winnerNamePosition: { x: 80, y: 220 },
+ loserNamePosition: { x: 250, y: 240 },
+ titleFontSize: 24,
+ nameFontSize: 28
+ },
+ {
+ image: secondMeme,
+ titlePosition: { x: 240, y: 275 },
+ winnerNamePosition: { x: 80, y: 120 },
+ loserNamePosition: { x: 220, y: 150 },
+ titleFontSize: 24,
+ nameFontSize: 28
+ },
+ {
+ image: thirdMeme,
+ titlePosition: { x: 45, y: 275 },
+ winnerNamePosition: { x: 120, y: 80 },
+ loserNamePosition: { x: 180, y: 200 },
+ titleFontSize: 14,
+ nameFontSize: 28
+ },
+ {
+ image: fourthMeme,
+ titlePosition: { x: 270, y: 275 },
+ winnerNamePosition: { x: 80, y: 180 },
+ loserNamePosition: { x: 250, y: 200 },
+ titleFontSize: 14,
+ nameFontSize: 28
+ },
+ {
+ image: fifthMeme,
+ titlePosition: { x: 80, y: 10 },
+ winnerNamePosition: { x: 120, y: 120 },
+ loserNamePosition: { x: 230, y: 220 },
+ titleFontSize: 12,
+ nameFontSize: 28
+ },
+ {
+ image: sixthMeme,
+ titlePosition: { x: 45, y: 275 },
+ winnerNamePosition: { x: 90, y: 140 },
+ loserNamePosition: { x: 240, y: 240 },
+ titleFontSize: 14,
+ nameFontSize: 28
+ },
+ {
+ image: seventhMeme,
+ titlePosition: { x: 270, y: 275 },
+ winnerNamePosition: { x: 80, y: 80 },
+ loserNamePosition: { x: 240, y: 100 },
+ titleFontSize: 14,
+ nameFontSize: 28
+ },
+ {
+ image: eighthMeme,
+ titlePosition: { x: 270, y: 275 },
+ winnerNamePosition: { x: 80, y: 100 },
+ loserNamePosition: { x: 250, y: 80 },
+ titleFontSize: 14,
+ nameFontSize: 26
+ },
+ {
+ image: ninthMeme,
+ titlePosition: { x: 270, y: 275 },
+ winnerNamePosition: { x: 120, y: 140 },
+ loserNamePosition: { x: 80, y: 250 },
+ titleFontSize: 14,
+ nameFontSize: 28
+ },
+ {
+ image: tenth,
+ titlePosition: { x: 45, y: 15 },
+ winnerNamePosition: { x: 220, y: 200 },
+ loserNamePosition: { x: 0, y: 0 },
+ titleFontSize: 14,
+ nameFontSize: 28
+ },
+ ];
+
+ const meme = memeArray[memeIndex];
+
+ useEffect(() => {
+ if (!winnerName || !loserName || !canvasRef.current) return; // Prevent drawing until winner and loser names are determined
+
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ const img = new Image();
+ img.src = typeof meme.image === 'string' ? meme.image : meme.image.src;
+
+ img.onload = () => {
+ canvas.width = img.width;
+ canvas.height = img.height;
+
+ ctx.drawImage(img, 0, 0);
+
+ // Draw title
+ ctx.font = `bold ${meme.titleFontSize}px Arial`;
+ ctx.fillStyle = 'white';
+ ctx.strokeStyle = 'black';
+ ctx.lineWidth = 2;
+ ctx.textAlign = 'center';
+ ctx.fillText('- @devb.io', meme.titlePosition.x, meme.titlePosition.y);
+ ctx.strokeText('- @devb.io', meme.titlePosition.x, meme.titlePosition.y);
+
+ // Draw winner name
+ ctx.font = `bold ${meme.nameFontSize}px Arial`;
+ ctx.fillText(winnerName, meme.winnerNamePosition.x, meme.winnerNamePosition.y);
+ ctx.strokeText(winnerName, meme.winnerNamePosition.x, meme.winnerNamePosition.y);
+
+ // Draw loser name
+ ctx.fillText(loserName, meme.loserNamePosition.x, meme.loserNamePosition.y);
+ ctx.strokeText(loserName, meme.loserNamePosition.x, meme.loserNamePosition.y);
+ };
+ }, [winnerName, loserName, meme]);
+
+ return ;
+};
+
+export default MemeGenerator;
diff --git a/www/lib/api.ts b/www/lib/api.ts
index 5ff7cf9d..423b7460 100644
--- a/www/lib/api.ts
+++ b/www/lib/api.ts
@@ -1,5 +1,7 @@
import {
LinkedInProfile,
+ ContributionsData,
+ GitHubUser,
MediumBlog,
Profile,
UserProject,
@@ -254,3 +256,27 @@ export const addUserToNocodb = async (user: Profile | null) => {
console.error("Error:", error);
}
};
+
+export const getMemeProfileData = async (username: string):Promise => {
+try {
+ const response = await fetch(`https://github-contributions-api.jogruber.de/v4/${username}`);
+ if (!response.ok) throw new Error("Network response was not ok");
+ const data = await response.json();
+ return data;
+} catch (error) {
+ console.error(`Error fetching profile data for ${username}:`, error);
+ return null;
+}
+};
+
+export const getGithubInfo = async (username: string):Promise => {
+ try {
+ const response = await fetch(`https://api.github.com/users/${username}`);
+ if (!response.ok) throw new Error("Network response was not ok");
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error(`Error fetching GitHub info for ${username}:`, error);
+ return null;
+ }
+};
\ No newline at end of file
diff --git a/www/package.json b/www/package.json
index d38f0605..e998487b 100644
--- a/www/package.json
+++ b/www/package.json
@@ -18,15 +18,19 @@
"@tsparticles/engine": "^3.8.1",
"@tsparticles/react": "^3.0.0",
"@tsparticles/slim": "^3.8.1",
+ "axios": "^1.11.0",
+ "chart.js": "^4.5.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.4.7",
+ "html2canvas": "^1.4.1",
"install": "^0.13.0",
"lodash": "^4.17.21",
"lucide-react": "^0.475.0",
"motion": "^12.4.13",
"next": "15.2.0-canary.69",
"react": "^19.0.0",
+ "react-chartjs-2": "^5.3.0",
"react-dom": "^19.0.0",
"react-intersection-observer": "^9.15.1",
"tailwind-merge": "^3.0.2",
diff --git a/www/pnpm-lock.yaml b/www/pnpm-lock.yaml
index 96289426..3f7f87e1 100644
--- a/www/pnpm-lock.yaml
+++ b/www/pnpm-lock.yaml
@@ -35,6 +35,12 @@ importers:
'@tsparticles/slim':
specifier: ^3.8.1
version: 3.8.1
+ axios:
+ specifier: ^1.11.0
+ version: 1.11.0
+ chart.js:
+ specifier: ^4.5.0
+ version: 4.5.0
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -44,6 +50,9 @@ importers:
framer-motion:
specifier: ^12.4.7
version: 12.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ html2canvas:
+ specifier: ^1.4.1
+ version: 1.4.1
install:
specifier: ^0.13.0
version: 0.13.0
@@ -62,6 +71,9 @@ importers:
react:
specifier: ^19.0.0
version: 19.0.0
+ react-chartjs-2:
+ specifier: ^5.3.0
+ version: 5.3.0(chart.js@4.5.0)(react@19.0.0)
react-dom:
specifier: ^19.0.0
version: 19.0.0(react@19.0.0)
@@ -305,6 +317,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@kurkle/color@0.3.4':
+ resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
+
'@next/env@15.2.0-canary.69':
resolution: {integrity: sha512-vFyniXXqRNL1AM8TFCAEh8YsPuphIHHg+FlpbFE40+AVvmuBcruhwgillpzoA/laa6HalKOZ8HdeaUz4387ZSA==}
@@ -1015,6 +1030,9 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -1023,6 +1041,9 @@ packages:
resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==}
engines: {node: '>=4'}
+ axios@1.11.0:
+ resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
+
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
@@ -1030,6 +1051,10 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ base64-arraybuffer@1.0.2:
+ resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
+ engines: {node: '>= 0.6.0'}
+
base64-js@0.0.8:
resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
engines: {node: '>= 0.4'}
@@ -1083,6 +1108,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
+ chart.js@4.5.0:
+ resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==}
+ engines: {pnpm: '>=8'}
+
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -1111,6 +1140,10 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -1121,6 +1154,9 @@ packages:
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+ css-line-break@2.1.0:
+ resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@@ -1167,6 +1203,10 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
@@ -1403,6 +1443,15 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+ follow-redirects@1.15.11:
+ resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
fontkit@2.0.4:
resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==}
@@ -1410,6 +1459,10 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
+ form-data@4.0.4:
+ resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
+ engines: {node: '>= 6'}
+
framer-motion@12.4.13:
resolution: {integrity: sha512-JHSXIdL7WOTCSEb2UUurHURV85pWTn6UIg+iWLBhH5SFndbjni8CEQcxwsBwOs3RHZ83TkE4xoxb9cHsFPY9yQ==}
peerDependencies:
@@ -1526,6 +1579,10 @@ packages:
hsl-to-rgb-for-reals@1.1.1:
resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==}
+ html2canvas@1.4.1:
+ resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
+ engines: {node: '>=8.0.0'}
+
hyphen@1.10.6:
resolution: {integrity: sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==}
@@ -1813,6 +1870,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1994,6 +2059,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -2004,6 +2072,12 @@ packages:
queue@6.0.2:
resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
+ react-chartjs-2@5.3.0:
+ resolution: {integrity: sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==}
+ peerDependencies:
+ chart.js: ^4.1.1
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react-dom@19.0.0:
resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
peerDependencies:
@@ -2256,6 +2330,9 @@ packages:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
+ text-segmentation@1.0.3:
+ resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
+
tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
@@ -2343,6 +2420,9 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ utrie@1.0.2:
+ resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
+
vite-compatible-readable-stream@3.6.1:
resolution: {integrity: sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==}
engines: {node: '>= 6'}
@@ -2565,6 +2645,8 @@ snapshots:
'@img/sharp-win32-x64@0.33.5':
optional: true
+ '@kurkle/color@0.3.4': {}
+
'@next/env@15.2.0-canary.69': {}
'@next/eslint-plugin-next@15.2.0-canary.69':
@@ -3382,16 +3464,28 @@ snapshots:
async-function@1.0.0: {}
+ asynckit@0.4.0: {}
+
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
axe-core@4.10.2: {}
+ axios@1.11.0:
+ dependencies:
+ follow-redirects: 1.15.11
+ form-data: 4.0.4
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+
axobject-query@4.1.0: {}
balanced-match@1.0.2: {}
+ base64-arraybuffer@1.0.2: {}
+
base64-js@0.0.8: {}
base64-js@1.5.1: {}
@@ -3451,6 +3545,10 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ chart.js@4.5.0:
+ dependencies:
+ '@kurkle/color': 0.3.4
+
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@@ -3478,6 +3576,10 @@ snapshots:
color-string: 1.9.1
optional: true
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
concat-map@0.0.1: {}
cross-spawn@7.0.6:
@@ -3488,6 +3590,10 @@ snapshots:
crypto-js@4.2.0: {}
+ css-line-break@2.1.0:
+ dependencies:
+ utrie: 1.0.2
+
csstype@3.1.3: {}
damerau-levenshtein@1.0.8: {}
@@ -3532,6 +3638,8 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ delayed-stream@1.0.0: {}
+
detect-libc@1.0.3: {}
detect-libc@2.0.3:
@@ -3908,6 +4016,8 @@ snapshots:
flatted@3.3.3: {}
+ follow-redirects@1.15.11: {}
+
fontkit@2.0.4:
dependencies:
'@swc/helpers': 0.5.15
@@ -3924,6 +4034,14 @@ snapshots:
dependencies:
is-callable: 1.2.7
+ form-data@4.0.4:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.2
+ mime-types: 2.1.35
+
framer-motion@12.4.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
motion-dom: 12.4.11
@@ -4034,6 +4152,11 @@ snapshots:
hsl-to-rgb-for-reals@1.1.1: {}
+ html2canvas@1.4.1:
+ dependencies:
+ css-line-break: 2.1.0
+ text-segmentation: 1.0.3
+
hyphen@1.10.6: {}
ignore@5.3.2: {}
@@ -4302,6 +4425,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -4479,6 +4608,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ proxy-from-env@1.1.0: {}
+
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@@ -4487,6 +4618,11 @@ snapshots:
dependencies:
inherits: 2.0.4
+ react-chartjs-2@5.3.0(chart.js@4.5.0)(react@19.0.0):
+ dependencies:
+ chart.js: 4.5.0
+ react: 19.0.0
+
react-dom@19.0.0(react@19.0.0):
dependencies:
react: 19.0.0
@@ -4782,6 +4918,10 @@ snapshots:
tapable@2.2.1: {}
+ text-segmentation@1.0.3:
+ dependencies:
+ utrie: 1.0.2
+
tiny-inflate@1.0.3: {}
tinyglobby@0.2.12:
@@ -4885,6 +5025,10 @@ snapshots:
util-deprecate@1.0.2: {}
+ utrie@1.0.2:
+ dependencies:
+ base64-arraybuffer: 1.0.2
+
vite-compatible-readable-stream@3.6.1:
dependencies:
inherits: 2.0.4
diff --git a/www/public/assets/buttonArrow.jsx b/www/public/assets/buttonArrow.jsx
new file mode 100644
index 00000000..245c4bf9
--- /dev/null
+++ b/www/public/assets/buttonArrow.jsx
@@ -0,0 +1,37 @@
+;
+
+import React from "react";
+
+export function ButtonArrow() {
+ return (
+
+ );
+}
diff --git a/www/public/assets/buttonArrow.tsx b/www/public/assets/buttonArrow.tsx
new file mode 100644
index 00000000..3ffa3e08
--- /dev/null
+++ b/www/public/assets/buttonArrow.tsx
@@ -0,0 +1,19 @@
+export function ButtonArrow() {
+ return (
+
+ );
+}
diff --git a/www/public/assets/compare meme collection.png b/www/public/assets/compare meme collection.png
new file mode 100644
index 00000000..f042558e
Binary files /dev/null and b/www/public/assets/compare meme collection.png differ
diff --git a/www/public/assets/gorilla-bg-min.jpg b/www/public/assets/gorilla-bg-min.jpg
new file mode 100644
index 00000000..0d9e8fd2
Binary files /dev/null and b/www/public/assets/gorilla-bg-min.jpg differ
diff --git a/www/public/assets/index.js b/www/public/assets/index.js
new file mode 100644
index 00000000..00082ad5
--- /dev/null
+++ b/www/public/assets/index.js
@@ -0,0 +1,5 @@
+import topContender from './compare meme collection.png'
+
+export {
+ topContender,
+}
\ No newline at end of file
diff --git a/www/public/assets/index.ts b/www/public/assets/index.ts
new file mode 100644
index 00000000..2287499a
--- /dev/null
+++ b/www/public/assets/index.ts
@@ -0,0 +1,5 @@
+import topContender from './compare meme collection.png'
+
+export {
+ topContender,
+}
diff --git a/www/public/assets/memes/eighth.png b/www/public/assets/memes/eighth.png
new file mode 100644
index 00000000..0b5b9dc3
Binary files /dev/null and b/www/public/assets/memes/eighth.png differ
diff --git a/www/public/assets/memes/fifth.png b/www/public/assets/memes/fifth.png
new file mode 100644
index 00000000..0016dc6e
Binary files /dev/null and b/www/public/assets/memes/fifth.png differ
diff --git a/www/public/assets/memes/first.png b/www/public/assets/memes/first.png
new file mode 100644
index 00000000..ca27b815
Binary files /dev/null and b/www/public/assets/memes/first.png differ
diff --git a/www/public/assets/memes/fourth.png b/www/public/assets/memes/fourth.png
new file mode 100644
index 00000000..695116b6
Binary files /dev/null and b/www/public/assets/memes/fourth.png differ
diff --git a/www/public/assets/memes/index.js b/www/public/assets/memes/index.js
new file mode 100644
index 00000000..cd72a89d
--- /dev/null
+++ b/www/public/assets/memes/index.js
@@ -0,0 +1,23 @@
+import firstMeme from "./first.png"
+import secondMeme from "./second.png"
+import thirdMeme from "./third.png"
+import fourthMeme from "./fourth.png"
+import fifthMeme from "./fifth.png"
+import sixthMeme from "./sixth.png"
+import seventhMeme from "./seventh.png"
+import eighthMeme from "./eighth.png"
+import ninthMeme from "./ninth.png"
+import tenth from "./tenth.png"
+
+export {
+ firstMeme,
+ secondMeme,
+ thirdMeme,
+ fourthMeme,
+ fifthMeme,
+ sixthMeme,
+ seventhMeme,
+ eighthMeme,
+ ninthMeme,
+ tenth
+}
\ No newline at end of file
diff --git a/www/public/assets/memes/index.ts b/www/public/assets/memes/index.ts
new file mode 100644
index 00000000..5e707126
--- /dev/null
+++ b/www/public/assets/memes/index.ts
@@ -0,0 +1,23 @@
+import firstMeme from "./first.png"
+import secondMeme from "./second.png"
+import thirdMeme from "./third.png"
+import fourthMeme from "./fourth.png"
+import fifthMeme from "./fifth.png"
+import sixthMeme from "./sixth.png"
+import seventhMeme from "./seventh.png"
+import eighthMeme from "./eighth.png"
+import ninthMeme from "./ninth.png"
+import tenth from "./tenth.png"
+
+export {
+ firstMeme,
+ secondMeme,
+ thirdMeme,
+ fourthMeme,
+ fifthMeme,
+ sixthMeme,
+ seventhMeme,
+ eighthMeme,
+ ninthMeme,
+ tenth
+}
diff --git a/www/public/assets/memes/ninth.png b/www/public/assets/memes/ninth.png
new file mode 100644
index 00000000..f9497d26
Binary files /dev/null and b/www/public/assets/memes/ninth.png differ
diff --git a/www/public/assets/memes/second.png b/www/public/assets/memes/second.png
new file mode 100644
index 00000000..72481d0d
Binary files /dev/null and b/www/public/assets/memes/second.png differ
diff --git a/www/public/assets/memes/seventh.png b/www/public/assets/memes/seventh.png
new file mode 100644
index 00000000..fb736d85
Binary files /dev/null and b/www/public/assets/memes/seventh.png differ
diff --git a/www/public/assets/memes/sixth.png b/www/public/assets/memes/sixth.png
new file mode 100644
index 00000000..321dbaf8
Binary files /dev/null and b/www/public/assets/memes/sixth.png differ
diff --git a/www/public/assets/memes/tenth.png b/www/public/assets/memes/tenth.png
new file mode 100644
index 00000000..aba96bcc
Binary files /dev/null and b/www/public/assets/memes/tenth.png differ
diff --git a/www/public/assets/memes/third.png b/www/public/assets/memes/third.png
new file mode 100644
index 00000000..2f22406b
Binary files /dev/null and b/www/public/assets/memes/third.png differ
diff --git a/www/public/assets/react.svg b/www/public/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/www/public/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/www/public/assets/smile.png b/www/public/assets/smile.png
new file mode 100644
index 00000000..eac1ea2e
Binary files /dev/null and b/www/public/assets/smile.png differ
diff --git a/www/types/types.ts b/www/types/types.ts
index 639968eb..6512a73b 100644
--- a/www/types/types.ts
+++ b/www/types/types.ts
@@ -117,3 +117,68 @@ export interface MediumBlog {
categories: string;
thumbnail?: string;
}
+
+export interface ContributionDay {
+ date: string;
+ count: number;
+ level: number;
+}
+
+export interface ContributionWeek {
+ contributionDays: ContributionDay[];
+ firstDay: string;
+}
+
+export interface ContributionCalendar {
+ totalContributions: number;
+ weeks: ContributionWeek[];
+}
+
+export interface ContributionsCollection {
+ contributionCalendar: ContributionCalendar;
+}
+
+export interface ContributionsData {
+ total: {
+ [year: number]: number;
+ };
+ contributions: ContributionDay[];
+}
+
+export type GitHubUser = {
+ login: string;
+ id: number;
+ node_id: string;
+ avatar_url: string;
+ gravatar_id: string;
+ url: string;
+ html_url: string;
+ followers_url: string;
+ following_url: string;
+ gists_url: string;
+ starred_url: string;
+ subscriptions_url: string;
+ organizations_url: string;
+ repos_url: string;
+ events_url: string;
+ received_events_url: string;
+ type: string;
+ user_view_type: string;
+ site_admin: boolean;
+ name: string | null;
+ company: string | null;
+ blog: string;
+ location: string | null;
+ email: string | null;
+ hireable: boolean | null;
+ bio: string | null;
+ twitter_username: string | null;
+ public_repos: number;
+ public_gists: number;
+ followers: number;
+ following: number;
+ created_at: string; // ISO date
+ updated_at: string; // ISO date
+};
+
+