get routing working, add index for poetry, list first 6 poems on index, let each take you to their page with the poem's content

This commit is contained in:
2024-05-16 02:52:32 -04:00
parent 2cf484aa46
commit 3d22344e7f
44 changed files with 1134 additions and 178 deletions

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { T, useTask, useThrelte } from '@threlte/core';
import { World } from '@threlte/rapier';
import { Inspector } from 'three-inspect';
import { dev } from '$app/environment';
import SpaceSkysphere from './SpaceSkysphere.svelte';
import { Group, type Object3DEventMap } from 'three';
import DollyCam from './DollyCam.svelte';
import Planet from '$lib/components/scenes/app/Planet.svelte';
const { invalidate } = useThrelte();
let spaceObjects: Group<Object3DEventMap>;
useTask(
'updateSpaceObjects',
(delta) => {
if (!spaceObjects) return;
spaceObjects.rotation.y += 0.3 * delta;
spaceObjects.position.y = Math.sin(spaceObjects.rotation.y);
invalidate();
},
{ autoInvalidate: false }
);
</script>
<World gravity={[0, 0, 0]}>
<DollyCam />
<T.Group bind:ref={spaceObjects}>
<SpaceSkysphere size={100} count={500} />
<Planet />
<slot />
</T.Group>
</World>
{#if false}
<Inspector />
{/if}

View File

@@ -0,0 +1,26 @@
<!-- src/lib/CanvasLayout.svelte -->
<script lang="ts">
import { Canvas } from '@threlte/core';
</script>
<div class="canvas">
<Canvas>
<slot />
</Canvas>
</div>
<style>
:global(body) {
margin: 0;
}
.canvas {
width: 100%;
height: 100%;
background: rgb(0, 36, 6);
background: linear-gradient(180deg, rgba(0, 36, 6, 1) 0%, rgba(0, 0, 0, 1) 100%);
position: absolute;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import { T, useTask, useThrelte } from '@threlte/core';
import { onMount } from 'svelte';
import { PerspectiveCamera, Vector3 } from 'three';
const { invalidate } = useThrelte();
let camLookatPosition: Vector3 = new Vector3();
let camCurrentPosition: Vector3 = new Vector3();
let camDamping: number = 1;
let camera: PerspectiveCamera;
const handleMouseMove = (event: MouseEvent) => {
// normalize the mouse position to [-1, 1], and smoothly interpolate the camera's lookAt position
camLookatPosition.set(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1,
0
);
};
useTask(
'dollyCam',
(delta) => {
if (!camera) return;
camCurrentPosition.lerp(camLookatPosition, camDamping * delta);
camera.lookAt(camCurrentPosition);
if (camera && camCurrentPosition.distanceTo(camLookatPosition) > 0.1) {
invalidate();
}
},
{ autoInvalidate: false }
);
onMount(() => {
document.addEventListener('mousemove', handleMouseMove);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
};
});
</script>
<T.PerspectiveCamera position.z={25} makeDefault fov={50} far={10000} bind:ref={camera} />

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import { HTML } from '@threlte/extras';
import { Attractor } from '@threlte/rapier';
export let position: [number, number, number] = [0, 0, 0];
export let range: number = 100;
export let clickHandler: (() => void) | undefined = undefined;
export let htmlContent: HTMLElement | string = 'Hello!';
export let href: string = '/';
let isHovering = false;
let isPointerDown = false;
const onClick = () => {
console.log('clicked!');
if (clickHandler) {
clickHandler();
}
};
</script>
<HTML position.x={position[0]} position.y={position[1]} position.z={position[2]}>
<a
{href}
on:pointerenter={() => (isHovering = true)}
on:pointerleave={() => {
isPointerDown = false;
isHovering = false;
}}
on:pointerdown={() => (isPointerDown = true)}
on:pointerup={() => (isPointerDown = false)}
on:pointercancel={() => {
isPointerDown = false;
isHovering = false;
}}
on:click={onClick}
class="bg-green-700 px-3 py-3 text-white opacity-50 hover:opacity-90 active:opacity-100"
style="transform: translate(-50%, 50%); display: block;"
>
{htmlContent}
</a>
</HTML>
<Attractor
position.x={position[0]}
position.y={position[1]}
position.z={position[2]}
{range}
strength={isHovering ? 1 : 0}
gravityType={'static'}
/>

View File

@@ -0,0 +1,19 @@
<script lang="ts" context="module">
import { T } from '@threlte/core';
import { Attractor, Collider, RigidBody } from '@threlte/rapier';
import { MeshBasicMaterial, SphereGeometry } from 'three';
const geometry = new SphereGeometry(1, 8, 4);
const material = new MeshBasicMaterial({ color: '#339933', wireframe: true });
const position: [number, number, number] = [0, 0, 0];
const scale: number = 2;
const offset = position.map((el) => el + 1.5);
</script>
<Attractor {position} range={100} strength={0.3} gravityType={'linear'} />
<T.Group position.x={-offset[0]} position.y={-offset[1]} position.z={offset[2]}>
<RigidBody linearDamping={0.3}>
<Collider shape="ball" args={[scale]} mass={scale} />
<T.Mesh scale={[scale, scale, scale]} {geometry} {material} />
</RigidBody>
</T.Group>

View File

@@ -5,7 +5,7 @@
import { Studio } from '@threlte/theatre';
</script>
<Studio enabled={false} />
<Studio enabled={dev} />
<Canvas>
<Spinners />

View File

@@ -8,18 +8,18 @@
let sequence: SequenceController;
let box: Mesh;
let ball: Mesh;
let spotlight: SpotLight;
let lastBoxPosition = new Vector3();
let currentBoxPosition = new Vector3();
let lastBallPosition = new Vector3();
let currentBallPosition = new Vector3();
let config = spinnersJson as IProjectConfig;
const boxMoved: any = (props: { position: { x: number; y: number; z: number } }) => {
if (box && spotlight) {
const ballMoved: any = (props: { position: { x: number; y: number; z: number } }) => {
if (ball && spotlight) {
const { x, y, z } = props.position;
currentBoxPosition.set(x, y, z);
spotlight.lookAt(currentBoxPosition);
lastBoxPosition.copy(currentBoxPosition);
currentBallPosition.set(x, y, z);
spotlight.lookAt(currentBallPosition);
lastBallPosition.copy(currentBallPosition);
}
};
</script>
@@ -33,10 +33,10 @@
<!-- create a T.SpotLight that looks at box-->
<T.SpotLight position={[0, 5, 3]} intensity={10} bind:ref={spotlight}></T.SpotLight>
<SheetObject key="Box" let:Transform let:Sync on:change={boxMoved}>
<SheetObject key="Box" let:Transform let:Sync on:change={ballMoved}>
<Transform>
<T.Mesh position.y={0.5} bind:ref={box}>
<T.BoxGeometry args={[1, 1, 1]} />
<T.Mesh position.y={0.5} bind:ref={ball}>
<T.SphereGeometry args={[1, 8, 4]} />
<T.MeshStandardMaterial color="#b00d03">
<Sync color roughness metalness />
</T.MeshStandardMaterial>

View File

@@ -1,32 +0,0 @@
<script lang="ts">
import { Canvas, T } from '@threlte/core';
import { OrbitControls } from '@threlte/extras';
import OrbitingSpheres from '$lib/components/scenes/home/OrbitingSpheres.svelte';
import { World } from '@threlte/rapier';
import { Inspector } from 'three-inspect';
import { dev } from '$app/environment';
import SpaceSkysphere from './SpaceSkysphere.svelte';
</script>
<Canvas>
<World gravity={[0, 0, 0]}>
<T.PerspectiveCamera position.y={15} position.z={25} makeDefault fov={70} far={10000}>
<OrbitControls
enableZoom={false}
enablePan={false}
enableRotate={false}
enableDamping
target.y={0}
autoRotate
/>
</T.PerspectiveCamera>
<SpaceSkysphere size={100} count={500} />
<OrbitingSpheres position={[0, 0, 0]} range={10} />
</World>
{#if dev}
<Inspector />
{/if}
</Canvas>

View File

@@ -1,86 +0,0 @@
<script lang="ts" context="module">
const geometry = new SphereGeometry(1, 8, 4);
const material = new MeshBasicMaterial({ color: '#339933' });
</script>
<script lang="ts">
import { T } from '@threlte/core';
import { HTML } from '@threlte/extras';
import { Attractor, Collider, RigidBody } from '@threlte/rapier';
import { MeshBasicMaterial, SphereGeometry, Vector3 } from 'three';
export let range: number = 10;
export let position: [number, number, number] = [0, 0, 0];
let isHovering = false;
let isPointerDown = false;
const bodyPositions = [
new Vector3(position[0] - range, position[0] - range, position[0] - range).toArray(),
new Vector3(position[1] + range / 2, position[1] + range, position[1] + range).toArray(),
new Vector3(position[2] + range, position[2] - range / 2, position[2] + range).toArray()
];
const getId = () => {
return Math.random().toString(16).slice(2);
};
const getRandomSize = (): number => {
return Math.random() / 4 + 0.25;
};
const generateBodies = (c: number) => {
return Array(c)
.fill('x')
.map((_, index) => {
return {
id: getId(),
position: bodyPositions[index],
size: getRandomSize()
};
});
};
const onClick = () => {
console.log('clicked');
};
$: bodies = generateBodies(2);
</script>
<HTML position.x={position[0]} position.y={position[1]} position.z={position[2]}>
<button
on:pointerenter={() => (isHovering = true)}
on:pointerleave={() => {
isPointerDown = false;
isHovering = false;
}}
on:pointerdown={() => (isPointerDown = true)}
on:pointerup={() => (isPointerDown = false)}
on:pointercancel={() => {
isPointerDown = false;
isHovering = false;
}}
on:click={onClick}
class="rounded-full bg-orange-500 px-3 text-white hover:opacity-90 active:opacity-70"
>
Hello!
</button>
</HTML>
<Attractor
position.x={position[0]}
position.y={position[1]}
position.z={position[2]}
range={200}
strength={isHovering ? 1 : 0.1}
gravityType={'linear'}
/>
{#each bodies as body (body.id)}
<T.Group position={body.position}>
<RigidBody>
<Collider shape="ball" args={[body.size]} mass={body.size} />
<Attractor range={50} strength={1} gravityType={'newtonian'} />
<T.Mesh scale={[body.size, body.size, body.size]} {geometry} {material} />
</RigidBody>
</T.Group>
{/each}

View File

@@ -0,0 +1,63 @@
<script context="module">
import { tick } from 'svelte';
/**
* Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}>
*
* @param {HTMLElement} el
* @param {HTMLElement|string} target DOM Element or CSS Selector
*/
export function portal(el, target = 'body') {
let targetEl;
/**
* @param {string | HTMLElement} newTarget
*/
async function update(newTarget) {
target = newTarget;
if (typeof target === 'string') {
targetEl = document.querySelector(target);
if (targetEl === null) {
await tick();
targetEl = document.querySelector(target);
}
if (targetEl === null) {
throw new Error(`No element found matching css selector: "${target}"`);
}
} else if (target instanceof HTMLElement) {
targetEl = target;
} else {
throw new TypeError(
`Unknown portal target type: ${
target === null ? 'null' : typeof target
}. Allowed types: string (CSS selector) or HTMLElement.`
);
}
targetEl.appendChild(el);
el.hidden = false;
}
function destroy() {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
}
update(target);
return {
update,
destroy
};
}
</script>
<script>
/**
* DOM Element or CSS Selector
* @type { HTMLElement|string}
*/
export let target = 'body';
</script>
<div use:portal={target} hidden style="display: contents;">
<slot />
</div>

80
src/lib/utils/index.ts Normal file
View File

@@ -0,0 +1,80 @@
export interface Metadata {
title: string;
date: string;
content: string;
categories?: string[];
section?: SectionKey;
}
export interface Section {
poetry: 'poetry';
thoughts: 'thoughts';
projects: 'projects';
}
type SectionKey = keyof Section;
export interface Post {
meta: Metadata;
path: string;
}
interface Data {
metadata: Metadata;
}
function isData(obj: unknown): obj is Data {
if (typeof obj !== 'object' || obj === null) {
return false;
}
const dataObj = obj as Data;
return (
'metadata' in dataObj &&
typeof dataObj.metadata.title === 'string' &&
typeof dataObj.metadata.date === 'string'
);
}
export const fetchMarkdownPosts = async (
section: SectionKey,
limit: number,
offset: number
): Promise<Post[]> => {
let posts: Record<string, () => Promise<unknown>>;
switch (section) {
case 'poetry':
posts = import.meta.glob('/src/posts/poetry/*.md');
break;
case 'projects':
posts = import.meta.glob('/src/routes/(app)/projects/posts/*.md');
break;
case 'thoughts':
posts = import.meta.glob('/src/routes/(app)/thoughts/posts/*.md');
break;
default:
throw new Error('Could not find this section');
}
const iterablePostFiles = Object.entries(posts);
const allPosts = await Promise.all(
iterablePostFiles.map(async ([path, resolver]) => {
const data = await resolver();
if (isData(data)) {
const { metadata } = data;
const postPath = path.slice(11, -3);
return {
meta: { ...metadata, section: section },
path: postPath
};
} else {
throw new Error('Could not properly parse this post');
}
})
);
const paginatedPosts = allPosts.slice(offset, offset + limit);
return paginatedPosts;
};