WIP Home page + layout
This commit is contained in:
parent
233372cb88
commit
4fa47b2ac3
|
@ -0,0 +1,10 @@
|
||||||
|
type Day = {
|
||||||
|
count: number;
|
||||||
|
day: number;
|
||||||
|
level: number;
|
||||||
|
month: string;
|
||||||
|
name: string;
|
||||||
|
year: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Contributions = Array<Day | null>;
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script>
|
||||||
|
import Scene from './Scene.svelte';
|
||||||
|
import { Canvas } from '@threlte/core';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- YOU CAN DELETE EVERYTHING IN THIS PAGE -->
|
||||||
|
|
||||||
|
<div class="container h-full mx-auto flex-col justify-center items-center">
|
||||||
|
<div class="space-y-10 text-center flex flex-col items-center">
|
||||||
|
<h1 class="h1">I make the wheels turn.</h1>
|
||||||
|
<!-- Animated Logo -->
|
||||||
|
<figure>
|
||||||
|
<section class="img-bg" />
|
||||||
|
<img src="/images/profile-pic.png" class="w-8 h-8 md:h-[200px] md:w-[200px]" />
|
||||||
|
</figure>
|
||||||
|
<!-- / -->
|
||||||
|
|
||||||
|
<div class="flex justify-center space-x-2">
|
||||||
|
<a class="btn variant-filled" href="/blog" target="_blank" rel="noreferrer">
|
||||||
|
Look at my blog
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p>Try editing the following:</p>
|
||||||
|
<p><code class="code">foo:bar</code></p>
|
||||||
|
<p><code class="code">ligma:ass</code></p>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src="/animations/infinity-loop-icon.svg"
|
||||||
|
alt="Icon"
|
||||||
|
class="w-16 md:w-32 lg:w-48 h-full rounded-full"
|
||||||
|
/>
|
||||||
|
<h2 class="h2">My github contributions</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scene">
|
||||||
|
<Canvas>
|
||||||
|
<Scene />
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
figure {
|
||||||
|
@apply flex relative flex-col;
|
||||||
|
}
|
||||||
|
figure svg,
|
||||||
|
.img-bg {
|
||||||
|
@apply w-64 h-64;
|
||||||
|
}
|
||||||
|
.img-bg {
|
||||||
|
@apply absolute z-[-1] rounded-full blur-[50px] transition-all;
|
||||||
|
animation: pulse 5s cubic-bezier(0, 0, 0, 0.5) infinite, glow 5s linear infinite;
|
||||||
|
}
|
||||||
|
.scene {
|
||||||
|
@apply relative inset-1 w-[100%vw] h-[150px] md:h-[600px] lg:h-[800px];
|
||||||
|
}
|
||||||
|
@keyframes glow {
|
||||||
|
0% {
|
||||||
|
@apply bg-primary-400/50;
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
@apply bg-secondary-400/50;
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
@apply bg-tertiary-400/50;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
@apply bg-primary-400/50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
50% {
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { tweened } from 'svelte/motion';
|
||||||
|
import { quadInOut } from 'svelte/easing';
|
||||||
|
import { T } from '@threlte/core';
|
||||||
|
import { Align, Grid, OrbitControls } from '@threlte/extras';
|
||||||
|
import type { Contributions } from '$lib/types/contributions';
|
||||||
|
|
||||||
|
let contributions: Contributions[] = [];
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const response = await fetch('/api/matthieu42morin/2023');
|
||||||
|
contributions = await response.json();
|
||||||
|
console.log(contributions);
|
||||||
|
});
|
||||||
|
|
||||||
|
const colorMap = ['#0e0e0e', '#00442a', '#006d35', '#00a648', '#00d35c'];
|
||||||
|
|
||||||
|
// function to normalize the height of the cubes
|
||||||
|
function normalize(count: number, base = 4, offset = 2) {
|
||||||
|
switch (true) {
|
||||||
|
case count === 0:
|
||||||
|
return base;
|
||||||
|
case count > 40:
|
||||||
|
return count;
|
||||||
|
default:
|
||||||
|
return count * (base * offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tweened value to animate the scaleY of the cubes, this should actually be Z, but
|
||||||
|
const scaleZ = tweened(0, { duration: 2000, easing: quadInOut });
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
$scaleZ = 1;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<T.PerspectiveCamera makeDefault position={[10, 100, 600]} fov={50}>
|
||||||
|
<OrbitControls enableDamping />
|
||||||
|
</T.PerspectiveCamera>
|
||||||
|
|
||||||
|
<T.AmbientLight color="#fff" intensity={0.4} />
|
||||||
|
<T.DirectionalLight position={[0, 200, 200]} intensity={2} color="#fff" />
|
||||||
|
<T.DirectionalLight position={[0, 200, -200]} intensity={2} color="#fff" />
|
||||||
|
|
||||||
|
<Align auto>
|
||||||
|
<Grid infiniteGrid sectionColor="#4a4b4a" sectionSize={40} cellSize={40} fadeDistance={800} />
|
||||||
|
{#if Array.isArray(contributions) && contributions.length > 0}
|
||||||
|
{#each contributions as row, i}
|
||||||
|
{#each row as day, j}
|
||||||
|
{#if day !== null}
|
||||||
|
{@const y = normalize(day.level)}
|
||||||
|
<T.Group position={[0, 0, 12 * i]} scale.y={$scaleZ}>
|
||||||
|
<T.Mesh position={[12 * j, y / 2, 0]}>
|
||||||
|
<T.BoxGeometry args={[10, y, 10]} />
|
||||||
|
<T.MeshStandardMaterial color={colorMap[day.level]} />
|
||||||
|
</T.Mesh>
|
||||||
|
</T.Group>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</Align>
|
|
@ -0,0 +1,96 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import '../app.postcss';
|
||||||
|
import { AppShell } from '@skeletonlabs/skeleton';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import MainFooter from '$lib/components/MainFooter.svelte';
|
||||||
|
|
||||||
|
// Floating UI for Popups
|
||||||
|
import { computePosition, autoUpdate, flip, shift, offset, arrow } from '@floating-ui/dom';
|
||||||
|
import { storePopup } from '@skeletonlabs/skeleton';
|
||||||
|
storePopup.set({ computePosition, autoUpdate, flip, shift, offset, arrow });
|
||||||
|
|
||||||
|
//Cookie, Identifiers and store
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { cookies } from '$lib/constants';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { isEurope, removeTrailingSlash } from '$lib/utils/helpers';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
Cookies.set(cookies.NECESSARY, 'true', {
|
||||||
|
expires: 365,
|
||||||
|
domain: '.mattmor.in'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Cookies.get(cookies.ANALYTICAL) !== 'false' && !isEurope()) {
|
||||||
|
Cookies.set(cookies.ANALYTICAL, 'true', {
|
||||||
|
expires: 365,
|
||||||
|
domain: '.mattmor.in'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Cookies.get(cookies.ANALYTICAL) === 'true') {
|
||||||
|
Cookies.set(cookies.VISITED, 'true', {
|
||||||
|
expires: 365,
|
||||||
|
domain: '.mattmor.in'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scroll to anchor
|
||||||
|
$: if ($page.url.pathname) {
|
||||||
|
// Workaround until https://github.com/sveltejs/kit/issues/2664 is fixed
|
||||||
|
if (typeof window !== 'undefined' && window.location.hash) {
|
||||||
|
const deepLinkedElement = document.getElementById(window.location.hash.substring(1));
|
||||||
|
if (deepLinkedElement) {
|
||||||
|
deepLinkedElement.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// View-Transition
|
||||||
|
import { onNavigate } from '$app/navigation';
|
||||||
|
|
||||||
|
onNavigate((navigation) => {
|
||||||
|
//@ts-expect-error - startViewTransition of View Transitions API, types don’t exist yet.
|
||||||
|
if (!document.startViewTransition) return;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
//@ts-expect-error - startViewTransition of View Transitions API, types don’t exist yet.
|
||||||
|
document.startViewTransition(async () => {
|
||||||
|
resolve();
|
||||||
|
await navigation.complete;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Highlight JS
|
||||||
|
import hljs from 'highlight.js';
|
||||||
|
import 'highlight.js/styles/github-dark.css';
|
||||||
|
import { storeHighlightJs } from '@skeletonlabs/skeleton';
|
||||||
|
import MainHeader from '$lib/components/MainHeader.svelte';
|
||||||
|
storeHighlightJs.set(hljs);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<link
|
||||||
|
rel="canonical"
|
||||||
|
href={removeTrailingSlash(`https://www.mattmor.in${$page.url.pathname}`)}
|
||||||
|
/>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<!-- <Analytics />
|
||||||
|
-->
|
||||||
|
<!-- App Shell -->
|
||||||
|
<AppShell>
|
||||||
|
<svelte:fragment slot="header">
|
||||||
|
<MainHeader />
|
||||||
|
</svelte:fragment>
|
||||||
|
<!-- Page Route Content -->
|
||||||
|
<slot />
|
||||||
|
<svelte:fragment slot="pageFooter">
|
||||||
|
<MainFooter />
|
||||||
|
</svelte:fragment>
|
||||||
|
</AppShell>
|
||||||
|
<!-- <CookieConsent />
|
||||||
|
|
||||||
|
<Segment /> -->
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { parseHTML } from 'linkedom';
|
||||||
|
import type { RouteParams } from './$types';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export async function GET({ params }) {
|
||||||
|
const html = await getContributions(params);
|
||||||
|
return json(parseContributions(html));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Scrape function that fetches the HTML response from GitHub.
|
||||||
|
* The response contains a table with all the contributions for the given year.
|
||||||
|
*/
|
||||||
|
async function getContributions({ user, year }: RouteParams) {
|
||||||
|
const api = `https://github.com/users/${user}/contributions?from=${year}-12-01&to=${year}-12-31`;
|
||||||
|
const response = await fetch(api);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.text();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This function parses the HTML response from a GitHub account page and returns an array of a user's contributions.
|
||||||
|
** Each contribution is an object with a date and a level.
|
||||||
|
** This function may stop working if GitHub changes the HTML structure, like they did in somewhen in October 2023.
|
||||||
|
*/
|
||||||
|
function parseContributions(html: string) {
|
||||||
|
const { document } = parseHTML(html);
|
||||||
|
const rows = document.querySelectorAll<HTMLTableRowElement>('tbody > tr');
|
||||||
|
const contributions: any[] = [];
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const days = row.querySelectorAll<HTMLTableCellElement>('td.ContributionCalendar-day');
|
||||||
|
const currentRow: any[] = [];
|
||||||
|
|
||||||
|
for (const day of days) {
|
||||||
|
const date = day.getAttribute('data-date');
|
||||||
|
const level = day.getAttribute('data-level');
|
||||||
|
|
||||||
|
if (date && level) {
|
||||||
|
const contribution = {
|
||||||
|
date,
|
||||||
|
level: parseInt(level, 10)
|
||||||
|
};
|
||||||
|
currentRow.push(contribution);
|
||||||
|
} else {
|
||||||
|
currentRow.push(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contributions.push(currentRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contributions;
|
||||||
|
}
|
Loading…
Reference in New Issue