[READ-ONLY] Mirror of https://github.com/flo-bit/flo-bit.github.io. my personal website, w/ astro, svelte, tailwind, typescript, threlte flo-bit.dev/
portfolio portfolio-website svelte sveltekit tailwind threejs threlte typescript
0

Configure Feed

Select the types of activity you want to include in your feed.

first version with project pages

+812 -358
+16
README.md
··· 14 14 15 15 https://github.com/flo-bit/flo-bit.github.io/assets/45694132/a251ab6f-a416-4a2d-905f-8dffb2e36e0f 16 16 17 + 18 + 19 + ## todo 20 + 21 + - add projects 22 + - [x] shmup 23 + - youtube party djay 24 + - instalingua 25 + - vr portfolio 26 + - setup projects v3 27 + - improve planet stuff 28 + - add favourite stack? 29 + - add images, descriptions, links, tags to all projects 30 + - update favicon 31 + - fix android bug 32 + - improve performance
+14
package-lock.json
··· 15 15 "@tailwindcss/typography": "^0.5.14", 16 16 "@threlte/rapier": "^2.0.1", 17 17 "@threlte/xr": "^0.1.4", 18 + "@use-gesture/vanilla": "^10.3.1", 18 19 "alea": "^1.0.1", 19 20 "autoprefixer": "^10.4.16", 20 21 "clsx": "^2.1.1", ··· 1913 1914 "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", 1914 1915 "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", 1915 1916 "dev": true 1917 + }, 1918 + "node_modules/@use-gesture/core": { 1919 + "version": "10.3.1", 1920 + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", 1921 + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==" 1922 + }, 1923 + "node_modules/@use-gesture/vanilla": { 1924 + "version": "10.3.1", 1925 + "resolved": "https://registry.npmjs.org/@use-gesture/vanilla/-/vanilla-10.3.1.tgz", 1926 + "integrity": "sha512-lT4scGLu59ovA3zmtUonukAGcA0AdOOh+iwNDS05Bsu7Lq9aZToDHhI6D8Q2qvsVraovtsLLYwPrWdG/noMAKw==", 1927 + "dependencies": { 1928 + "@use-gesture/core": "10.3.1" 1929 + } 1916 1930 }, 1917 1931 "node_modules/acorn": { 1918 1932 "version": "8.12.0",
+1
package.json
··· 46 46 "@tailwindcss/typography": "^0.5.14", 47 47 "@threlte/rapier": "^2.0.1", 48 48 "@threlte/xr": "^0.1.4", 49 + "@use-gesture/vanilla": "^10.3.1", 49 50 "alea": "^1.0.1", 50 51 "autoprefixer": "^10.4.16", 51 52 "clsx": "^2.1.1",
+1 -1
src/app.html
··· 42 42 </head> 43 43 <body 44 44 data-sveltekit-preload-data="hover" 45 - class="h-full bg-white dark:bg-black transition-colors duration-150 dark:transition-color dark:duration-150" 45 + class="h-full bg-white dark:bg-black transition-colors duration-150 dark:transition-color dark:duration-150 lowercase" 46 46 > 47 47 <div style="display: contents">%sveltekit.body%</div> 48 48 </body>
-56
src/lib/3D/FakeGlowMaterial/FakeGlowMaterial.svelte
··· 1 - <script lang="ts"> 2 - import { T, useThrelte, forwardEventHandlers } from '@threlte/core' 3 - import { Color, AdditiveBlending, ShaderMaterial } from 'three' 4 - import type { 5 - FakeGlowMaterialEvents, 6 - FakeGlowMaterialProps, 7 - FakeGlowMaterialSlots 8 - } from './FakeGlowMaterial.svelte' 9 - 10 - import { fragmentShader } from './fragment' 11 - import { vertexShader } from './vertex' 12 - 13 - type $$Props = Required<FakeGlowMaterialProps> 14 - type $$Events = FakeGlowMaterialEvents 15 - type $$Slots = FakeGlowMaterialSlots 16 - 17 - export let falloff: $$Props['falloff'] = 0.1 18 - export let glowInternalRadius: $$Props['glowInternalRadius'] = 6.0 19 - export let glowColor: $$Props['glowColor'] = 'green' 20 - export let glowSharpness: $$Props['glowSharpness'] = 1.0 21 - 22 - let material = new ShaderMaterial({ 23 - uniforms: { 24 - falloff: { value: falloff }, 25 - glowInternalRadius: { value: glowInternalRadius }, 26 - glowColor: { value: new Color(glowColor) }, 27 - glowSharpness: { value: glowSharpness } 28 - } 29 - }) 30 - 31 - let { invalidate } = useThrelte() 32 - 33 - $: { 34 - material.uniforms.falloff.value = falloff 35 - material.uniforms.glowInternalRadius.value = glowInternalRadius 36 - material.uniforms.glowColor.value = new Color(glowColor) 37 - material.uniforms.glowSharpness.value = glowSharpness 38 - 39 - invalidate() 40 - } 41 - 42 - const component = forwardEventHandlers() 43 - </script> 44 - 45 - <T 46 - is={material} 47 - bind:this={$component} 48 - {fragmentShader} 49 - {vertexShader} 50 - transparent={true} 51 - blending={AdditiveBlending} 52 - depthTest={false} 53 - {...$$restProps} 54 - > 55 - <slot ref={material} /> 56 - </T>
-19
src/lib/3D/FakeGlowMaterial/FakeGlowMaterial.svelte.d.ts
··· 1 - import type { Events, Props, Slots } from '@threlte/core' 2 - import { SvelteComponent } from 'svelte' 3 - import type { ShaderMaterial, ColorRepresentation } from 'three' 4 - 5 - export type FakeGlowMaterialProps = Props<ShaderMaterial> & { 6 - falloff?: number 7 - glowInternalRadius?: number 8 - glowColor?: ColorRepresentation 9 - glowSharpness?: number 10 - } 11 - 12 - export type FakeGlowMaterialEvents = Events<ShaderMaterial> 13 - export type FakeGlowMaterialSlots = Slots<ShaderMaterial> 14 - 15 - export default class FakeGlowMaterial extends SvelteComponent< 16 - FakeGlowMaterialProps, 17 - FakeGlowMaterialEvents, 18 - FakeGlowMaterialSlots 19 - > {}
-29
src/lib/3D/FakeGlowMaterial/fragment.ts
··· 1 - import { ShaderChunk } from 'three'; 2 - 3 - export const fragmentShader = ` 4 - uniform vec3 glowColor; 5 - uniform float falloffAmount; 6 - uniform float glowSharpness; 7 - uniform float glowInternalRadius; 8 - 9 - varying vec3 vPosition; 10 - varying vec3 vNormal; 11 - 12 - void main() 13 - { 14 - // Normal 15 - vec3 normal = normalize(vNormal); 16 - if(!gl_FrontFacing) 17 - normal *= - 1.0; 18 - vec3 viewDirection = normalize(cameraPosition - vPosition); 19 - float fresnel = dot(viewDirection, normal); 20 - fresnel = pow(fresnel, glowInternalRadius + 0.1); 21 - float falloff = smoothstep(0., falloffAmount, fresnel); 22 - float fakeGlow = fresnel; 23 - fakeGlow += fresnel * glowSharpness; 24 - fakeGlow *= falloff; 25 - gl_FragColor = vec4(clamp(glowColor * fresnel, 0., 1.0), clamp(fakeGlow, 0., 1.0) * 0.5); 26 - 27 - ${ShaderChunk.tonemapping_fragment} 28 - ${ShaderChunk.colorspace_fragment} 29 - }`;
-10
src/lib/3D/FakeGlowMaterial/vertex.ts
··· 1 - export const vertexShader = `varying vec3 vPosition; 2 - varying vec3 vNormal; 3 - 4 - void main() { 5 - vec4 modelPosition = modelMatrix * vec4(position, 1.0); 6 - gl_Position = projectionMatrix * viewMatrix * modelPosition; 7 - vec4 modelNormal = modelMatrix * vec4(normal, 0.0); 8 - vPosition = modelPosition.xyz; 9 - vNormal = modelNormal.xyz; 10 - }`
-2
src/lib/3D/PlanetModel.svelte
··· 8 8 import { T, type Props, type Events, type Slots, forwardEventHandlers } from '@threlte/core'; 9 9 import { useGltf } from '@threlte/extras'; 10 10 11 - import FakeGlowMaterial from './FakeGlowMaterial/FakeGlowMaterial.svelte'; 12 - 13 11 type $$Props = Props<THREE.Group>; 14 12 type $$Events = Events<THREE.Group>; 15 13 type $$Slots = Slots<THREE.Group> & { fallback: {}; error: { error: any } };
+1
src/lib/3D/Scene.svelte
··· 125 125 far={100} 126 126 /> 127 127 128 + <T.AmbientLight intensity={0.15} /> 128 129 <T.DirectionalLight intensity={2} position={[-pos * 10 + 5, 2 + pos * 3, 2]} /> 129 130 130 131 <T.Group
+2 -2
src/lib/components/About.svelte
··· 2 2 import Depth3D from '$lib/3D/Depth3D/Depth3D.svelte'; 3 3 4 4 // @ts-ignore 5 - import imageMe from '$lib/images/avatar.png?w=1024&format=webp'; 5 + import imageMe from '$lib/images/avatar.png?w=512&format=webp'; 6 6 // @ts-ignore 7 - import depthMe from '$lib/images/avatar-depth.png?w=1024&format=webp'; 7 + import depthMe from '$lib/images/avatar-depth.png?w=512&format=webp'; 8 8 9 9 import Resume from './Resume.svelte'; 10 10 </script>
+1 -1
src/lib/components/Hero.svelte
··· 1 - <div class="relative isolate overflow-hidden section h-screen pointer-events-none ml-52" id="home"> 1 + <div class="relative isolate overflow-hidden section h-screen pointer-events-none ml-48" id="home"> 2 2 <div class="mx-auto max-w-5xl px-6 py-40 lg:px-8"> 3 3 <h1 4 4 class="text-6xl mt-0 md:mt-36 font-bold tracking-tight text-right w-full text-zinc-900 dark:text-zinc-100 md:text-8xl"
-31
src/lib/components/Photos.svelte
··· 1 - <script lang="ts"> 2 - let rotations = ['rotate-2', '-rotate-2', 'rotate-2', 'rotate-2', '-rotate-2']; 3 - 4 - export let images: { href?: string; src: string; alt: string }[]; 5 - </script> 6 - 7 - <div class="bg-black overflow-x-scroll no-scrollbar pt-10"> 8 - <div class="flex gap-5 py-4 sm:gap-8 2xl:justify-center"> 9 - {#each images as image, index} 10 - <div 11 - class="relative aspect-[9/10] w-44 flex-none overflow-hidden rounded-xl ring-1 ring-white/10 bg-zinc-100 dark:bg-zinc-800 sm:w-72 sm:rounded-2xl {rotations[ 12 - index % rotations.length 13 - ]}" 14 - > 15 - {#if image.src.endsWith('.mp4')} 16 - <video autoplay loop muted playsinline class="absolute inset-0 h-full w-full object-cover rounded-xl" preload="none"> 17 - <source src={image.src} type="video/mp4" /> 18 - </video> 19 - {:else} 20 - <img src={image.src} alt={image.alt} class="absolute inset-0 h-full w-full object-cover rounded-xl" loading="lazy"/> 21 - {/if} 22 - {#if image.href} 23 - <a href={image.href} target="_blank"> 24 - <div class="sr-only">{image.alt}</div> 25 - <div class="absolute inset-0 rounded-xl"></div> 26 - </a> 27 - {/if} 28 - </div> 29 - {/each} 30 - </div> 31 - </div>
src/lib/components/ProjectBreadcrumb.svelte src/lib/components/project/ProjectBreadcrumb.svelte
src/lib/components/ProjectButtons.svelte src/lib/components/project/ProjectButtons.svelte
+32 -8
src/lib/components/ProjectV1.svelte src/lib/components/project/ProjectGrid.svelte
··· 1 1 <script lang="ts"> 2 - import type { Project } from '../../projects'; 3 - import Badge from './badge/badge.svelte'; 2 + import type { Project } from '$lib/projects'; 3 + import Badge from '../badge/badge.svelte'; 4 4 import ProjectBreadcrumb from './ProjectBreadcrumb.svelte'; 5 5 import ProjectButtons from './ProjectButtons.svelte'; 6 6 ··· 53 53 54 54 <!-- Product info --> 55 55 <div 56 - class="mx-auto max-w-2xl px-4 pb-16 pt-10 sm:px-6 lg:grid lg:max-w-5xl lg:grid-cols-3 lg:grid-rows-[auto,auto,1fr] lg:gap-x-8 lg:px-8 lg:pb-24 lg:pt-16" 56 + class="mx-auto max-w-2xl px-4 pb-0 pt-10 sm:px-6 lg:grid lg:max-w-5xl lg:grid-cols-3 lg:grid-rows-[auto,auto,1fr] lg:gap-x-8 lg:px-8 lg:pb-24 lg:pt-16" 57 57 > 58 58 <div class="lg:col-span-2 lg:border-r lg:border-neutral-200 dark:border-neutral-800 lg:pr-8"> 59 59 <h1 class="text-2xl font-bold tracking-tight text-neutral-900 dark:text-white sm:text-3xl"> ··· 65 65 <div class="mt-4 lg:row-span-3 lg:mt-0"> 66 66 <h2 class="sr-only">project information</h2> 67 67 68 - <div class="mt-10"> 68 + <div class="mt-0"> 69 69 {#if project.tags} 70 70 <!-- tags --> 71 - <div class="mt-10"> 72 - <div class="mt-4"> 71 + <div class="mt-0 sm:mt-4"> 73 72 <div class="flex flex-wrap gap-4"> 74 73 {#each project.tags as tag} 75 74 <Badge size="lg">{tag}</Badge> 76 75 {/each} 77 76 </div> 78 77 </div> 79 - </div> 80 78 {/if} 81 79 82 80 <div class="mt-8 flex flex-col gap-4"> ··· 120 118 <h2 class="text-sm font-medium text-neutral-900 dark:text-neutral-50">details</h2> 121 119 122 120 <div class="mt-4 space-y-6"> 123 - <p class="text-sm text-neutral-600 dark:text-neutral-300"> 121 + <p class="text-sm text-neutral-600 dark:text-neutral-300 leading-6"> 124 122 {project.details} 125 123 </p> 126 124 </div> ··· 128 126 {/if} 129 127 </div> 130 128 </div> 129 + 130 + {#if project.images} 131 + <div class="mx-auto mt-6 max-w-2xl sm:px-6 lg:hidden pb-24"> 132 + <div class="aspect-h-4 aspect-w-3 overflow-hidden sm:rounded-xl"> 133 + <img 134 + src={project.images[0]} 135 + alt="Two each of neutral, white, and black shirts laying flat." 136 + class="h-full w-full object-cover object-center" 137 + /> 138 + </div> 139 + <div class="aspect-h-2 aspect-w-3 overflow-hidden sm:rounded-xl mt-4"> 140 + <img 141 + src={project.images[1]} 142 + alt="Model wearing plain black basic tee." 143 + class="h-full w-full object-cover object-center" 144 + /> 145 + </div> 146 + <div class="aspect-h-2 aspect-w-3 overflow-hidden sm:rounded-xl mt-4"> 147 + <img 148 + src={project.images[2]} 149 + alt="Model wearing plain neutral basic tee." 150 + class="h-full w-full object-cover object-center" 151 + /> 152 + </div> 153 + </div> 154 + {/if} 131 155 </div> 132 156 </div>
+18 -15
src/lib/components/ProjectV2.svelte src/lib/components/project/ProjectCarousel.svelte
··· 1 1 <script lang="ts"> 2 - import type { Project } from '../../projects'; 3 - import { Badge } from './badge'; 2 + import type { Project } from '$lib/projects'; 3 + import { fade, slide } from 'svelte/transition'; 4 + import { Badge } from '../badge'; 4 5 import ProjectBreadcrumb from './ProjectBreadcrumb.svelte'; 5 6 import ProjectButtons from './ProjectButtons.svelte'; 6 7 ··· 9 10 let selectedImage = 0; 10 11 </script> 11 12 12 - <div class=""> 13 - <div class="mx-auto max-w-2xl px-4 py-8 sm:px-6 sm:py-24 lg:max-w-5xl lg:px-8"> 14 - <ProjectBreadcrumb name={project.name} /> 13 + <div class="mx-auto max-w-2xl py-8 sm:py-24 lg:max-w-5xl"> 14 + <ProjectBreadcrumb name={project.name} /> 15 + 16 + <div class="px-4 sm:px-6 lg:px-8"> 15 17 16 18 <div class="mt-6 lg:grid lg:grid-cols-2 lg:items-start lg:gap-x-8"> 17 19 <!-- Image gallery --> ··· 20 22 <!-- Image selector --> 21 23 22 24 {#if project.images.length > 0} 23 - <div class="mx-auto mt-6 hidden w-full max-w-2xl sm:block lg:max-w-none"> 25 + <div class="mx-auto mt-6 w-full max-w-2xl sm:block lg:max-w-none"> 24 26 <div class="grid grid-cols-4 gap-6" aria-orientation="horizontal" role="tablist"> 25 27 {#each project.images as image, index} 26 28 <button 27 - class="relative flex h-24 cursor-pointer items-center justify-center rounded-md bg-white/10 text-sm font-medium text-neutral-900 hover:bg-neutral-400 border {index == 29 + class="relative flex h-24 cursor-pointer items-center justify-center rounded-xl bg-white/10 text-sm font-medium text-neutral-900 transition-all duration-200 border {index == 28 30 selectedImage 29 31 ? 'border-accent-400' 30 - : 'border-accent-100/20 hover:border-accent-400'}" 32 + : 'border-accent-100/20 hover:opacity-80'}" 31 33 type="button" 32 34 on:click={() => (selectedImage = index)} 33 35 > 34 - <span class="absolute inset-0 overflow-hidden rounded-md"> 36 + <span class="absolute inset-0 overflow-hidden rounded-xl"> 35 37 <img src={image} alt="" class="h-full w-full object-cover object-center" /> 36 38 </span> 37 39 <!-- Selected: "ring-indigo-500", Not Selected: "ring-transparent" --> 38 - <span class="pointer-events-none absolute inset-0 rounded-md" aria-hidden="true" 40 + <span class="pointer-events-none absolute inset-0 rounded-xl" aria-hidden="true" 39 41 ></span> 40 42 </button> 41 43 ··· 49 51 50 52 <div class="aspect-h-1 aspect-w-1 w-full"> 51 53 <!-- Tab panel, show/hide based on tab state. --> 52 - <div id="tabs-1-panel-1" aria-labelledby="tabs-1-tab-1" role="tabpanel" tabindex="0"> 54 + {#key project.images[selectedImage]} 53 55 <img 56 + transition:fade 54 57 src={project.images[selectedImage]} 55 58 alt="Angled front view with bag zipped and handles upright." 56 - class="h-full w-full object-cover object-center sm:rounded-lg" 59 + class="h-full w-full object-cover object-center sm:rounded-xl" 57 60 /> 58 - </div> 59 61 62 + {/key} 60 63 <!-- More images... --> 61 64 </div> 62 65 </div> 63 66 {/if} 64 67 65 68 <!-- Product info --> 66 - <div class="mt-10 px-4 sm:mt-16 sm:px-0 lg:mt-0"> 69 + <div class="mt-10 sm:mt-16 sm:px-0 lg:mt-0"> 67 70 <h1 class="text-3xl font-bold tracking-tight text-neutral-900 dark:text-white"> 68 71 {project.name} 69 72 </h1> ··· 80 83 </div> 81 84 82 85 <div class="grid sm:grid-cols-2 gap-4"> 83 - <ProjectButtons codeUrl={project.code} projectUrl={project.projectUrl} /> 86 + <ProjectButtons codeUrl={project.codeUrl} projectUrl={project.projectUrl} /> 84 87 </div> 85 88 </div> 86 89 {/if}
+12 -12
src/lib/components/ProjectV3.svelte src/lib/components/project/ProjectV3.svelte
··· 1 1 <script lang="ts"> 2 - import Project from '$lib/vr/components/Project.svelte'; 2 + import { type Project } from '$lib/projects'; 3 3 import ProjectBreadcrumb from './ProjectBreadcrumb.svelte'; 4 4 import ProjectButtons from './ProjectButtons.svelte'; 5 5 ··· 31 31 </section> 32 32 </div> 33 33 34 - {#if project.images} 35 - <!-- Product image --> 36 - <div class="mt-10 lg:col-start-2 lg:row-span-2 lg:mt-0 lg:self-center"> 37 - <div class="aspect-h-1 aspect-w-1 overflow-hidden rounded-lg"> 38 - <img 39 - src={project.images[0]} 40 - alt="Model wearing light green backpack with black canvas straps and front zipper pouch." 41 - class="h-full w-full object-cover object-center" 42 - /> 34 + {#if project.images} 35 + <!-- Product image --> 36 + <div class="mt-10 lg:col-start-2 lg:row-span-2 lg:mt-0 lg:self-center"> 37 + <div class="aspect-h-1 aspect-w-1 overflow-hidden rounded-lg"> 38 + <img 39 + src={project.images[0]} 40 + alt="Model wearing light green backpack with black canvas straps and front zipper pouch." 41 + class="h-full w-full object-cover object-center" 42 + /> 43 + </div> 43 44 </div> 44 - </div> 45 - {/if} 45 + {/if} 46 46 </div> 47 47 </div>
+2 -1
src/lib/components/Projects.svelte
··· 1 1 <script lang="ts"> 2 - import { projects } from '../../projects'; 2 + import { browser } from '$app/environment'; 3 + import { projects } from '$lib/projects'; 3 4 </script> 4 5 5 6 <div id="projects" class="z-20 py-16 md:py-32 section bg-black relative isolate">
+1 -1
src/lib/components/badge/index.ts
··· 6 6 variants: { 7 7 variant: { 8 8 default: 9 - 'rounded-md bg-accent-50 dark:bg-accent-500/10 text-accent-700 dark:text-accent-400 ring-1 fill-accent-500 dark:fill-accent-400', 9 + 'rounded-xl bg-accent-50 dark:bg-accent-500/10 text-accent-700 dark:text-accent-400 ring-1 fill-accent-500 dark:fill-accent-400', 10 10 pill: 'bg-accent-50 dark:bg-accent-500/10 text-accent-700 dark:text-accent-400 ring-1 fill-accent-500 dark:fill-accent-400 rounded-full', 11 11 dot: 'rounded-md bg-accent-50 dark:bg-accent-500/10 text-accent-700 dark:text-accent-400 ring-1 fill-accent-500 dark:fill-accent-400', 12 12 pill_dot:
+117
src/lib/components/project/ProjectFull.svelte
··· 1 + <script lang="ts"> 2 + import type { Project } from '$lib/projects'; 3 + import Badge from '../badge/badge.svelte'; 4 + import ProjectBreadcrumb from './ProjectBreadcrumb.svelte'; 5 + import ProjectButtons from './ProjectButtons.svelte'; 6 + 7 + export let project: Project; 8 + </script> 9 + 10 + <div class=""> 11 + <div class="pt-6 sm:pt-24"> 12 + <ProjectBreadcrumb name={project.name} /> 13 + 14 + <!-- Image gallery --> 15 + {#if project.images || project.demo} 16 + <div class="mx-auto mt-6 max-w-2xl sm:px-6 lg:max-w-5xl lg:px-8 aspect-3"> 17 + {#if project.demo == 'fluid'} 18 + <iframe 19 + src="https://flo-bit.github.io/text_effect_fluid/" 20 + title="fluid text effect demo" 21 + class="w-full h-full" 22 + /> 23 + {:else if project.demo == 'mandala'} 24 + <iframe 25 + src="https://flo-bit.github.io/mandala/" 26 + title="fluid text effect demo" 27 + class="w-full h-full rounded-xl" 28 + /> 29 + {:else if project.images} 30 + <img 31 + src={project.images[0]} 32 + alt="Two each of neutral, white, and black shirts laying flat." 33 + class="h-full w-full object-cover object-center" 34 + /> 35 + {/if} 36 + </div> 37 + {/if} 38 + 39 + <!-- Product info --> 40 + <div 41 + class="mx-auto max-w-2xl px-4 pb-0 pt-10 sm:px-6 lg:grid lg:max-w-5xl lg:grid-cols-3 lg:grid-rows-[auto,auto,1fr] lg:gap-x-8 lg:px-8 lg:pb-24 lg:pt-16" 42 + > 43 + <div class="lg:col-span-2 lg:border-r lg:border-neutral-200 dark:border-neutral-800 lg:pr-8"> 44 + <h1 class="text-2xl font-bold tracking-tight text-neutral-900 dark:text-white sm:text-3xl"> 45 + {project.name} 46 + </h1> 47 + </div> 48 + 49 + <!-- Options --> 50 + <div class="mt-4 lg:row-span-3 lg:mt-0"> 51 + <h2 class="sr-only">project information</h2> 52 + 53 + <div class="mt-10"> 54 + {#if project.tags} 55 + <!-- tags --> 56 + <div class="mt-10"> 57 + <div class="mt-4"> 58 + <div class="flex flex-wrap gap-4"> 59 + {#each project.tags as tag} 60 + <Badge size="lg">{tag}</Badge> 61 + {/each} 62 + </div> 63 + </div> 64 + </div> 65 + {/if} 66 + 67 + <div class="mt-8 flex flex-col gap-4"> 68 + <ProjectButtons codeUrl={project.codeUrl} projectUrl={project.projectUrl} /> 69 + </div> 70 + </div> 71 + </div> 72 + 73 + <div 74 + class="py-10 lg:col-span-2 lg:col-start-1 lg:border-r lg:border-neutral-200 dark:border-neutral-800 lg:pb-16 lg:pr-8 lg:pt-6" 75 + > 76 + <!-- Description and details --> 77 + <div> 78 + <h3 class="sr-only">Description</h3> 79 + 80 + <div class="space-y-6"> 81 + <p class="text-base text-neutral-900 dark:text-neutral-200"> 82 + {project.description} 83 + </p> 84 + </div> 85 + </div> 86 + 87 + {#if project.highlights} 88 + <div class="mt-10"> 89 + <h3 class="text-sm font-medium text-neutral-900 dark:text-neutral-50">highlights</h3> 90 + 91 + <div class="mt-4"> 92 + <ul role="list" class="list-disc space-y-2 pl-4 text-sm"> 93 + {#each project.highlights as highlight} 94 + <li class="text-neutral-400 dark:text-neutral-600"> 95 + <span class="text-neutral-600 dark:text-neutral-300">{highlight}</span> 96 + </li> 97 + {/each} 98 + </ul> 99 + </div> 100 + </div> 101 + {/if} 102 + 103 + {#if project.details} 104 + <div class="mt-10"> 105 + <h2 class="text-sm font-medium text-neutral-900 dark:text-neutral-50">details</h2> 106 + 107 + <div class="mt-4 space-y-6"> 108 + <p class="text-sm text-neutral-600 dark:text-neutral-300 leading-6"> 109 + {project.details} 110 + </p> 111 + </div> 112 + </div> 113 + {/if} 114 + </div> 115 + </div> 116 + </div> 117 + </div>
+115
src/lib/components/project/ProjectTiered.svelte
··· 1 + <script lang="ts"> 2 + import type { Project } from '$lib/projects'; 3 + import Badge from '../badge/badge.svelte'; 4 + import Swiper from './demos/swiper.svelte'; 5 + import ProjectBreadcrumb from './ProjectBreadcrumb.svelte'; 6 + import ProjectButtons from './ProjectButtons.svelte'; 7 + 8 + export let project: Project; 9 + </script> 10 + 11 + <div class=""> 12 + <div class="pb-16 pt-6 sm:pt-24 sm:pb-24"> 13 + <ProjectBreadcrumb name={project.name} /> 14 + 15 + <div class="mx-auto mt-8 max-w-2xl px-4 sm:px-6 lg:max-w-5xl lg:px-8"> 16 + <div class="lg:grid lg:auto-rows-min lg:grid-cols-12 lg:gap-x-8"> 17 + <div class="lg:col-span-5 lg:col-start-8"> 18 + <div class="flex justify-between"> 19 + <h1 class="text-3xl font-bold text-neutral-900 dark:text-white">{project.name}</h1> 20 + </div> 21 + </div> 22 + 23 + <!-- Image gallery --> 24 + {#if project.images || project.demo} 25 + <div class="mt-8 lg:col-span-7 lg:col-start-1 lg:row-span-3 lg:row-start-1 lg:mt-0"> 26 + <h2 class="sr-only">Images</h2> 27 + 28 + <div 29 + class="grid grid-cols-1 overflow-hidden rounded-xl lg:grid-cols-2 {project.images && project.images 30 + .length > 1 31 + ? 'lg:grid-rows-3' 32 + : ''} lg:gap-8" 33 + > 34 + {#if project.demo == 'swiper'} 35 + <div class="rounded-xl h-[60vh] md:h-[70vh] lg:col-span-2 lg:row-span-2"> 36 + <Swiper /> 37 + </div> 38 + {:else if project.demo == 'autostereogram'} 39 + <div class="rounded-xl h-[60vh] md:h-[70vh] lg:col-span-2 lg:row-span-2"> 40 + <iframe 41 + src={project.projectUrl} 42 + title="autostereogram demo" 43 + class="w-full h-full" 44 + /> 45 + </div> 46 + {:else if project.images} 47 + <img 48 + src={project.images[0]} 49 + alt="Back of women&#039;s Basic Tee in black." 50 + class="rounded-xl lg:col-span-2 lg:row-span-2" 51 + /> 52 + {/if} 53 + {#if project.images && project.images.length > 1} 54 + <img 55 + src={project.images[1]} 56 + alt="Side profile of women&#039;s Basic Tee in black." 57 + class="hidden rounded-xl lg:block" 58 + /> 59 + {/if} 60 + {#if project.images && project.images.length > 2} 61 + <img 62 + src={project.images[2]} 63 + alt="Front of women&#039;s Basic Tee in black." 64 + class="hidden rounded-xl lg:block" 65 + /> 66 + {/if} 67 + </div> 68 + </div> 69 + {/if} 70 + 71 + <div class="mt-8 lg:col-span-5"> 72 + <div class="flex flex-col gap-4"> 73 + <ProjectButtons codeUrl={project.codeUrl} projectUrl={project.projectUrl} /> 74 + </div> 75 + 76 + <!-- Product details --> 77 + <div class="mt-10"> 78 + <h2 class="text-sm font-medium text-neutral-900 dark:text-neutral-100">Description</h2> 79 + 80 + <div class="prose prose-sm mt-4 text-neutral-500 dark:text-neutral-400"> 81 + <p>{project.description}</p> 82 + </div> 83 + </div> 84 + 85 + <div class="mt-8 border-t border-neutral-200 dark:border-neutral-800 pt-8"> 86 + <h2 class="text-sm font-medium text-neutral-900 dark:text-neutral-100">highlights</h2> 87 + 88 + {#if project.highlights} 89 + <div class="prose prose-sm mt-4 text-neutral-500 dark:text-neutral-400"> 90 + <ul role="list"> 91 + {#each project.highlights as highlight} 92 + <li>{highlight}</li> 93 + {/each} 94 + </ul> 95 + </div> 96 + {/if} 97 + </div> 98 + 99 + {#if project.tags} 100 + <!-- tags --> 101 + <div class="mt-10"> 102 + <div class="mt-4"> 103 + <div class="flex flex-wrap gap-4"> 104 + {#each project.tags as tag} 105 + <Badge size="lg">{tag}</Badge> 106 + {/each} 107 + </div> 108 + </div> 109 + </div> 110 + {/if} 111 + </div> 112 + </div> 113 + </div> 114 + </div> 115 + </div>
+28
src/lib/components/project/demos/CardSwiper/Card.svelte
··· 1 + <script lang="ts"> 2 + import { fade } from "svelte/transition"; 3 + 4 + export let element: HTMLElement; 5 + 6 + export let color: string = ''; 7 + export let title: string = ''; 8 + export let description: string = ''; 9 + export let image: string | undefined = undefined; 10 + </script> 11 + 12 + <div 13 + class="w-full h-full absolute cursor-grab ease-in-out rounded-xl touch-none select-none border border-white/20 bg-black {color}" 14 + bind:this={element} 15 + > 16 + {#key image} 17 + {#if image} 18 + <img class="w-full h-full rounded-xl object-cover" src={image} alt={title} /> 19 + {/if} 20 + {/key} 21 + <div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/0 rounded-b-xl"></div> 22 + <div class="p-4 absolute bottom-0 w-full flex justify-center text-white"> 23 + <div class="flex items-center flex-col"> 24 + <h3 class="text-3xl font-semibold pb-4">{title}</h3> 25 + <p>{description}</p> 26 + </div> 27 + </div> 28 + </div>
+159
src/lib/components/project/demos/CardSwiper/CardSwiper.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import { DragGesture, type FullGestureState } from '@use-gesture/vanilla'; 4 + import type { CardData, Direction } from '.'; 5 + import Card from './Card.svelte'; 6 + 7 + import { createEventDispatcher } from 'svelte'; 8 + 9 + const dispatch = createEventDispatcher(); 10 + 11 + let container: HTMLElement; 12 + 13 + let card1: HTMLElement, card2: HTMLElement; 14 + let card1Data: CardData, card2Data: CardData; 15 + 16 + let cardIndex = 0; 17 + let topCard: HTMLElement; 18 + let currentZ = 100000; 19 + 20 + onMount(async () => { 21 + card1Data = cardData(cardIndex++); 22 + card2Data = cardData(cardIndex++); 23 + 24 + [card1, card2].forEach(function (el) { 25 + el.style.zIndex = currentZ.toString(); 26 + currentZ--; 27 + 28 + new DragGesture(el, (state) => { 29 + onDrag(el, state); 30 + }); 31 + }); 32 + 33 + topCard = card1; 34 + container.classList.remove('hidden'); 35 + }); 36 + 37 + const cardSwiped = (el: HTMLElement, velocity: [number, number], movement: [number, number]) => { 38 + // move card out of the view 39 + el.classList.add('transition-transform', 'duration-300'); 40 + 41 + let direction: Direction = movement[0] > 0 ? 'right' : 'left'; 42 + let data = el === card1 ? card1Data : card2Data; 43 + dispatch('swiped', { direction, element: el, data, index: cardIndex - 2 }); 44 + thresholdPassed = movement[0] > 0 ? 1 : -1; 45 + 46 + let moveOutWidth = document.body.clientWidth; 47 + 48 + let endX = Math.max(Math.abs(velocity[0]) * moveOutWidth, moveOutWidth); 49 + let toX = movement[0] > 0 ? endX : -endX; 50 + let endY = Math.abs(velocity[1]) * moveOutWidth; 51 + let toY = movement[1] > 0 ? endY : -endY; 52 + 53 + let rotate = movement[0] * 0.03 * (movement[1] / 80); 54 + 55 + el.style.transform = `translate(${toX}px, ${toY + movement[1]}px) rotate(${rotate}deg)`; 56 + 57 + setTimeout(() => { 58 + thresholdPassed = 0; 59 + 60 + // move card back to start position at bottom of stack and update data 61 + if (el === card1) { 62 + card1Data = {}; 63 + card1Data = cardData(cardIndex++); 64 + topCard = card2; 65 + } else { 66 + card2Data = {}; 67 + card2Data = cardData(cardIndex++); 68 + topCard = card1; 69 + } 70 + 71 + currentZ--; 72 + el.style.zIndex = currentZ.toString(); 73 + 74 + el.classList.remove('transition-transform', 'duration-300'); 75 + el.style.transform = ''; 76 + }, 350); 77 + }; 78 + 79 + const onDrag = ( 80 + el: HTMLElement, 81 + state: Omit<FullGestureState<'drag'>, 'event'> & { 82 + event: PointerEvent | MouseEvent | TouchEvent | KeyboardEvent; 83 + } 84 + ) => { 85 + let elWidth = el.offsetWidth; 86 + 87 + if (state.pressed) { 88 + let rotate = state.movement[0] * 0.03 * (state.movement[1] / 80); 89 + 90 + // fix movement on a curved path if anchor is set 91 + if (anchor) { 92 + let vec = [state.movement[0], state.movement[1] - anchor]; 93 + let len = Math.sqrt(vec[0] ** 2 + vec[1] ** 2); 94 + vec = [vec[0] / len * anchor, vec[1] / len * anchor]; 95 + 96 + state.movement[0] = vec[0]; 97 + state.movement[1] = vec[1] + anchor; 98 + } 99 + 100 + el.style.transform = `translate(${state.movement[0]}px, ${state.movement[1]}px) rotate(${rotate}deg)`; 101 + 102 + if(Math.abs(state.movement[0]) / elWidth > minSwipeDistance) { 103 + thresholdPassed = state.movement[0] > 0 ? 1 : -1; 104 + } else { 105 + thresholdPassed = 0; 106 + } 107 + return; 108 + } 109 + // if dragging is finished 110 + let keep = 111 + Math.abs(state.movement[0]) / elWidth < minSwipeDistance && 112 + Math.abs(state.velocity[0]) < minSwipeVelocity; 113 + 114 + if (keep) { 115 + thresholdPassed = 0; 116 + el.classList.add('transition-transform', 'duration-300'); 117 + el.style.transform = ''; 118 + setTimeout(() => { 119 + el.classList.remove('transition-transform', 'duration-300'); 120 + }, 300); 121 + } else { 122 + cardSwiped(el, state.velocity, state.movement); 123 + } 124 + }; 125 + 126 + export const swipe = (direction: Direction = 'right') => { 127 + if(thresholdPassed !== 0) return; 128 + 129 + let dir = direction === 'left' ? -1 : 1; 130 + cardSwiped(topCard, [dir, 0.1], [dir, 1]); 131 + }; 132 + 133 + export let cardData: (index: number) => CardData; 134 + 135 + export let minSwipeDistance: number = 0.5; 136 + export let minSwipeVelocity: number = 0.5; 137 + 138 + export let arrowKeys = true; 139 + 140 + export let thresholdPassed = 0; 141 + 142 + export let anchor: number | null = null; 143 + </script> 144 + 145 + <svelte:body on:keydown={(e) => { 146 + if(!arrowKeys) return; 147 + if (e.key === 'ArrowLeft') { 148 + swipe('left'); 149 + } else if (e.key === 'ArrowRight') { 150 + swipe('right'); 151 + } 152 + }} /> 153 + 154 + <div class="w-full h-full"> 155 + <div class="w-full h-full relative hidden z-0" bind:this={container}> 156 + <svelte:component this={Card} bind:element={card1} {...card1Data} /> 157 + <svelte:component this={Card} bind:element={card2} {...card2Data} /> 158 + </div> 159 + </div>
src/lib/components/project/demos/CardSwiper/images/1.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/2.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/3.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/4.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/5.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/6.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/7.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/8.jpg

This is a binary file and will not be displayed.

src/lib/components/project/demos/CardSwiper/images/9.jpg

This is a binary file and will not be displayed.

+10
src/lib/components/project/demos/CardSwiper/index.ts
··· 1 + export type CardData = { 2 + title?: string; 3 + color?: string; 4 + description?: string; 5 + image?: string; 6 + }; 7 + 8 + export type Direction = 'left' | 'right'; 9 + 10 + export { default as CardSwiper } from './CardSwiper.svelte';
+60
src/lib/components/project/demos/swiper.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import { CardSwiper } from './CardSwiper'; 4 + 5 + //@ts-expect-error - not found 6 + import person1 from './CardSwiper/images/1.jpg?format=webp&w=512'; 7 + //@ts-expect-error - not found 8 + import person2 from './CardSwiper/images/2.jpg?format=webp&w=512'; 9 + //@ts-expect-error - not found 10 + import person3 from './CardSwiper/images/3.jpg?format=webp&w=512'; 11 + //@ts-expect-error - not found 12 + import person4 from './CardSwiper/images/4.jpg?format=webp&w=512'; 13 + //@ts-expect-error - not found 14 + import person5 from './CardSwiper/images/5.jpg?format=webp&w=512'; 15 + //@ts-expect-error - not found 16 + import person6 from './CardSwiper/images/6.jpg?format=webp&w=512'; 17 + //@ts-expect-error - not found 18 + import person7 from './CardSwiper/images/7.jpg?format=webp&w=512'; 19 + //@ts-expect-error - not found 20 + import person8 from './CardSwiper/images/8.jpg?format=webp&w=512'; 21 + //@ts-expect-error - not found 22 + import person9 from './CardSwiper/images/9.jpg?format=webp&w=512'; 23 + 24 + let people = [person1, person2, person3, person4, person5, person6, person7, person8, person9]; 25 + 26 + function getRandomPerson(): { firstName: string; lastName: string; age: number } { 27 + const firstNames = ['John', 'Jane', 'Alice', 'Bob', 'Mary', 'Tom']; 28 + const lastNames = ['Smith', 'Doe', 'Johnson', 'White', 'Brown', 'Davis']; 29 + 30 + const firstNameIndex = Math.floor(Math.random() * firstNames.length); 31 + const lastNameIndex = Math.floor(Math.random() * lastNames.length); 32 + 33 + const age = Math.floor(Math.random() * (60 - 18 + 1)) + 18; 34 + 35 + return { 36 + firstName: firstNames[firstNameIndex], 37 + lastName: lastNames[lastNameIndex], 38 + age: age 39 + }; 40 + } 41 + 42 + 43 + let data = (index: number) => { 44 + let person = getRandomPerson(); 45 + return { 46 + title: `${person.firstName} ${person.lastName}, ${person.age}`, 47 + description: 'swipe left or right', 48 + image: people[index % people.length] 49 + }; 50 + }; 51 + 52 + onMount(() => { 53 + // preload images 54 + people.forEach((person) => { 55 + new Image().src = person; 56 + }); 57 + }); 58 + </script> 59 + 60 + <CardSwiper cardData={data} />
src/lib/images/projects-new/shmup/image1.png

This is a binary file and will not be displayed.

src/lib/images/projects-new/shmup/image2.png

This is a binary file and will not be displayed.

src/lib/images/projects-new/shmup/image3.png

This is a binary file and will not be displayed.

src/lib/images/projects-new/shmup/image4.png

This is a binary file and will not be displayed.

+208
src/lib/projects.ts
··· 1 + import svelteswipecards from '$lib/images/projects-new/svelte-swiper-cards-demo.mp4'; 2 + import fluidtexteffect from '$lib/images/projects-new/text-effect-fluid-demo.mp4'; 3 + import fake3d from '$lib/images/projects-new/fake3dimage-demo.mp4'; 4 + import hyperlumen from '$lib/images/projects-new/hyperlumen-demo.mp4'; 5 + import marblellous from '$lib/images/projects-new/marblellous-demo.mp4'; 6 + import oldcode from '$lib/images/projects-new/old-code-demo.mp4'; 7 + import autostereogramrenderer from '$lib/images/projects-new/autostereogram-renderer-demo.mp4'; 8 + import mandala from '$lib/images/projects-new/mandala-demo.mp4'; 9 + import threltevrcodeeditor from '$lib/images/projects-new/threlte-vr-code-editor-demo.mp4'; 10 + 11 + import svelteswipecards1 from '$lib/images/projects-new/svelte-swiper-cards-image.png'; 12 + 13 + // @ts-expect-error - module not found 14 + import hyperlumen1 from '$lib/images/projects-new/hyperlumen/1.jpg?w=1024&format=webp'; 15 + // @ts-expect-error - module not found 16 + import hyperlumen2 from '$lib/images/projects-new/hyperlumen/2.jpg?w=1024&format=webp'; 17 + // @ts-expect-error - module not found 18 + import hyperlumen3 from '$lib/images/projects-new/hyperlumen/3.jpg?w=1024&format=webp'; 19 + // @ts-expect-error - module not found 20 + import hyperlumen4 from '$lib/images/projects-new/hyperlumen/4.jpg?w=1024&format=webp'; 21 + 22 + // @ts-expect-error - module not found 23 + import shadowshmup1 from '$lib/images/projects-new/shmup/image1.png?w=1024&format=webp'; 24 + // @ts-expect-error - module not found 25 + import shadowshmup2 from '$lib/images/projects-new/shmup/image2.png?w=1024&format=webp'; 26 + // @ts-expect-error - module not found 27 + import shadowshmup3 from '$lib/images/projects-new/shmup/image3.png?w=1024&format=webp'; 28 + // @ts-expect-error - module not found 29 + import shadowshmup4 from '$lib/images/projects-new/shmup/image4.png?w=1024&format=webp'; 30 + 31 + import shadowshmup from '$lib/images/projects-new/shmup/shadow-shmup-demo.mp4'; 32 + 33 + export type Project = { 34 + src: string; 35 + key: string; 36 + name: string; 37 + alt: string; 38 + 39 + href?: string; 40 + aspect?: string; 41 + description?: string; 42 + code?: string; 43 + images?: string[]; 44 + highlights?: string[]; 45 + details?: string; 46 + projectPageVersion?: 'grid' | 'carousel' | 'tiered' | 'full'; 47 + tags?: string[]; 48 + 49 + projectUrl?: string; 50 + codeUrl?: string; 51 + 52 + demo?: string; 53 + }; 54 + 55 + export const projects: Project[] = [ 56 + { 57 + src: svelteswipecards, 58 + key: 'svelte-swiper-cards', 59 + alt: 'svelte-swiper-cards', 60 + href: 'https://flo-bit.github.io/svelte-swiper-cards/', 61 + 62 + projectUrl: 'https://flo-bit.github.io/svelte-swiper-cards/', 63 + 64 + aspect: 'aspect-[9/18]', 65 + name: 'swipable card component for svelte', 66 + description: 'a swipeable tinder like card component for svelte. try the interactive demo!', 67 + code: 'https://github.com/flo-bit/svelte-swiper-cards/', 68 + 69 + codeUrl: 'https://github.com/flo-bit/svelte-swiper-cards/', 70 + 71 + projectPageVersion: 'tiered', 72 + tags: ['svelte', 'typescript', 'component', 'tailwindcss'], 73 + images: [svelteswipecards1], 74 + demo: 'swiper', 75 + highlights: [ 76 + 'tailwindcss', 77 + 'reuses cards', 78 + 'customizable', 79 + 'modern (uses @use-gesture/vanilla for gestures)' 80 + ] 81 + }, 82 + { 83 + src: fluidtexteffect, 84 + key: 'text-effect-fluid', 85 + alt: 'Text Fluid Effect', 86 + projectUrl: 'https://flo-bit.github.io/text_effect_fluid/', 87 + aspect: 'aspect-[23/9]', 88 + name: 'fluid text effect', 89 + codeUrl: 'https://github.com/flo-bit/text_effect_fluid', 90 + description: 91 + 'inspired by lumalabs landing page, adopted a cool fluid effect to work with text. sometimes still a bit broken (try refreshing the page once or twice if it is).', 92 + projectPageVersion: 'full', 93 + demo: 'fluid' 94 + }, 95 + { 96 + src: shadowshmup, 97 + key: 'shadow-shmup', 98 + codeUrl: 'https://flo-bit.github.io/shadow-shmup/', 99 + alt: 'shadow shmup', 100 + aspect: 'aspect-[13/9]', 101 + name: 'shadow shmup', 102 + description: 103 + "fun and casual top-down, roguelike shoot 'em up with shadow elements and colorful neon effects. made for piratejam 2024", 104 + tags: ['game', '2d', 'pixijs', 'rapier2d', 'typescript'], 105 + images: [shadowshmup1, shadowshmup2, shadowshmup3, shadowshmup4], 106 + projectPageVersion: 'carousel', 107 + projectUrl: 'https://flo-bit.itch.io/shadow-shmup' 108 + }, 109 + { 110 + src: hyperlumen, 111 + key: 'hyperlumen', 112 + alt: 'Hyperlumen', 113 + 114 + projectUrl: 'https://hyperlumen.de', 115 + codeUrl: '#', 116 + 117 + aspect: 'aspect-[9/14]', 118 + name: 'hyperlumen', 119 + description: 120 + "light-up clothes that make you feel like you're in a sci-fi movie. perfect for raves.", 121 + images: [hyperlumen1, hyperlumen2, hyperlumen3, hyperlumen4], 122 + highlights: [ 123 + 'controlled via wifi with your smartphone', 124 + 'all the colors, all the animations', 125 + 'powered by a powerbank', 126 + 'handmade in germany' 127 + ], 128 + details: 129 + 'for this project - apart from taking part in building the prototypes - i customized the awesome wled fireware, adding some effects and simplifying the web ui. i also made a simple website showcasing our prototypes using svelte and tailwindcss.', 130 + projectPageVersion: 'grid', 131 + tags: ['wled', 'wearable', 'leds', 'hardware', 'c++', 'svelte', 'hardware', 'tailwindcss'] 132 + }, 133 + { 134 + src: mandala, 135 + key: 'svg-mandala', 136 + alt: 'svg mandala drawer', 137 + projectUrl: 'https://flo-bit.github.io/mandala/', 138 + aspect: 'aspect-1', 139 + name: 'svg mandala drawer', 140 + description: 141 + 'one of my earlier projects, draw svg mandalas in the browser. works on mobile too. made with paperjs and bulma. try the interactive demo above!', 142 + tags: ['svg', 'paperjs', 'javascript', 'bulma'], 143 + projectPageVersion: 'full', 144 + demo: 'mandala', 145 + codeUrl: 'https://github.com/flo-bit/mandala' 146 + }, 147 + { 148 + src: fake3d, 149 + key: 'image2fake3d', 150 + alt: 'Image2Fake3D', 151 + projectUrl: 'https://flo-bit.github.io/image2fake3d/', 152 + aspect: 'aspect-[9/16]', 153 + name: 'image2fake3d', 154 + description: 155 + 'turns an image into a fake 3d image that you can rotate with your mouse or gyro sensor.', 156 + codeUrl: 'https://github.com/flo-bit/image2fake3d', 157 + tags: ['svelte', 'depthmap', '3d', 'threlte'] 158 + }, 159 + { 160 + src: marblellous, 161 + key: 'marblellous', 162 + alt: 'marblellous', 163 + projectUrl: 'https://flo-bit.github.io/ball-game/', 164 + aspect: 'aspect-[9/10]', 165 + name: 'marblellous', 166 + description: 167 + 'simple 3d browser game made with threlte. inspired by the game "marble blast gold". work in progress.', 168 + codeUrl: 'https://github.com/flo-bit/ball-game', 169 + tags: ['threlte', 'svelte', '3d', 'typescript', 'game'] 170 + }, 171 + { 172 + src: oldcode, 173 + key: 'old-code', 174 + alt: 'old projects', 175 + projectUrl: 'https://flo-bit.github.io/old-code/', 176 + aspect: 'aspect-[16/11]', 177 + name: 'old coding projects', 178 + codeUrl: 'https://github.com/flo-bit/old-code', 179 + description: 'a collection of my old coding projects. mostly made with p5.js', 180 + tags: ['p5.js', 'javascript', 'game', 'art', 'flowfield'] 181 + }, 182 + { 183 + src: threltevrcodeeditor, 184 + key: 'threlte-vr-code-editor', 185 + alt: 'threlte vr code editor', 186 + projectUrl: 'https://github.com/flo-bit/threlte-vr-code-editor', 187 + aspect: 'aspect-[16/11]', 188 + name: 'threlte vr code editor', 189 + description: 'a code editor in virtual reality made with threlte.', 190 + codeUrl: 'https://github.com/flo-bit/threlte-vr-code-editor', 191 + highlights: ['works with hot module replacement'], 192 + tags: ['threlte', 'svelte', 'vr', 'typescript', 'code editor'] 193 + }, 194 + { 195 + src: autostereogramrenderer, 196 + key: 'autostereogram-renderer', 197 + alt: 'autostereogram renderer', 198 + projectUrl: 'https://flo-bit.github.io/autostereogram-renderer/', 199 + codeUrl: 'https://github.com/flo-bit/autostereogram-renderer', 200 + aspect: 'aspect-1', 201 + name: 'autostereogram renderer', 202 + description: 'rendering a 3d scene as an autostereogram. try to see the hidden animal!', 203 + demo: 'autostereogram', 204 + projectPageVersion: 'tiered', 205 + highlights: ['creates autosteregram from rendered depth map'], 206 + tags: ['three.js', 'javascript', '3d', 'autostereogram'] 207 + } 208 + ];
-159
src/projects.ts
··· 1 - import svelteswipecards from '$lib/images/projects-new/svelte-swiper-cards-demo.mp4'; 2 - import fluidtexteffect from '$lib/images/projects-new/text-effect-fluid-demo.mp4'; 3 - import fake3d from '$lib/images/projects-new/fake3dimage-demo.mp4'; 4 - import hyperlumen from '$lib/images/projects-new/hyperlumen-demo.mp4'; 5 - import marblellous from '$lib/images/projects-new/marblellous-demo.mp4'; 6 - import oldcode from '$lib/images/projects-new/old-code-demo.mp4'; 7 - import autostereogramrenderer from '$lib/images/projects-new/autostereogram-renderer-demo.mp4'; 8 - import mandala from '$lib/images/projects-new/mandala-demo.mp4'; 9 - import threltevrcodeeditor from '$lib/images/projects-new/threlte-vr-code-editor-demo.mp4'; 10 - 11 - import svelteswipecards1 from '$lib/images/projects-new/svelte-swiper-cards-image.png'; 12 - 13 - import hyperlumen1 from '$lib/images/projects-new/hyperlumen/1.jpg'; 14 - import hyperlumen2 from '$lib/images/projects-new/hyperlumen/2.jpg'; 15 - import hyperlumen3 from '$lib/images/projects-new/hyperlumen/3.jpg'; 16 - import hyperlumen4 from '$lib/images/projects-new/hyperlumen/4.jpg'; 17 - 18 - import shadowshmup from '$lib/images/projects-new/shmup/shadow-shmup-demo.mp4'; 19 - 20 - export type Project = { 21 - src: string; 22 - key: string; 23 - name: string; 24 - alt: string; 25 - 26 - href?: string; 27 - aspect?: string; 28 - description?: string; 29 - code?: string; 30 - images?: string[]; 31 - highlights?: string[]; 32 - details?: string; 33 - projectPageVersion?: 'v1' | 'v2' | 'v3'; 34 - tags?: string[]; 35 - 36 - projectUrl?: string; 37 - codeUrl?: string; 38 - }; 39 - 40 - export const projects: Project[] = [ 41 - { 42 - src: svelteswipecards, 43 - key: 'svelte-swiper-cards', 44 - alt: 'svelte-swiper-cards', 45 - href: 'https://flo-bit.github.io/svelte-swiper-cards/', 46 - 47 - projectUrl: 'https://flo-bit.github.io/svelte-swiper-cards/', 48 - 49 - aspect: 'aspect-[9/18]', 50 - name: 'swipable card component', 51 - description: 'a swipeable tinder like card component for svelte.', 52 - code: 'https://github.com/flo-bit/svelte-swiper-cards/', 53 - 54 - codeUrl: 'https://github.com/flo-bit/svelte-swiper-cards/', 55 - 56 - projectPageVersion: 'v2', 57 - tags: ['svelte', 'typescript', 'component'], 58 - images: [svelteswipecards1, svelteswipecards1] 59 - }, 60 - { 61 - src: fluidtexteffect, 62 - key: 'text-effect-fluid', 63 - alt: 'Text Fluid Effect', 64 - projectUrl: 'https://flo-bit.github.io/text_effect_fluid/', 65 - aspect: 'aspect-[23/9]', 66 - name: 'fluid text effect', 67 - codeUrl: 'https://github.com/flo-bit/text_effect_fluid' 68 - }, 69 - { 70 - src: shadowshmup, 71 - key: 'shadow-shmup', 72 - codeUrl: 'https://flo-bit.github.io/shadow-shmup/', 73 - alt: 'shadow shmup', 74 - aspect: 'aspect-[13/9]', 75 - name: 'shadow shmup', 76 - description: 'a 2d shoot em up game with cool visuals. made for piratejam 2024', 77 - tags: ['game', '2d', 'pixijs'] 78 - }, 79 - { 80 - src: hyperlumen, 81 - key: 'hyperlumen', 82 - alt: 'Hyperlumen', 83 - 84 - projectUrl: 'https://hyperlumen.de', 85 - 86 - aspect: 'aspect-[9/14]', 87 - name: 'hyperlumen', 88 - description: 89 - "light-up clothes that make you feel like you're in a sci-fi movie. perfect for raves.", 90 - images: [hyperlumen1, hyperlumen2, hyperlumen3, hyperlumen4], 91 - highlights: [ 92 - 'controlled via wifi with your smartphone', 93 - 'all the colors, all the animations', 94 - 'powered by a powerbank', 95 - 'handmade in germany' 96 - ], 97 - projectPageVersion: 'v1', 98 - tags: ['wled', 'wearable', 'leds', 'hardware'] 99 - }, 100 - { 101 - src: mandala, 102 - key: 'svg-mandala', 103 - alt: 'svg mandala drawer', 104 - projectUrl: 'https://flo-bit.github.io/mandala/', 105 - aspect: 'aspect-1', 106 - name: 'svg mandala drawer', 107 - description: 'draw svg mandalas in the browser.' 108 - }, 109 - { 110 - src: fake3d, 111 - key: 'image2fake3d', 112 - alt: 'Image2Fake3D', 113 - projectUrl: 'https://flo-bit.github.io/image2fake3d/', 114 - aspect: 'aspect-[9/16]', 115 - name: 'image2fake3d', 116 - description: 117 - 'turns an image into a fake 3d image that you can rotate with your mouse or gyro sensor.', 118 - codeUrl: 'https://github.com/flo-bit/image2fake3d' 119 - }, 120 - { 121 - src: marblellous, 122 - key: 'marblellous', 123 - alt: 'marblellous', 124 - projectUrl: 'https://flo-bit.github.io/ball-game/', 125 - aspect: 'aspect-[9/10]', 126 - name: 'marblellous', 127 - description: 128 - 'simple 3d browser game made with threlte. inspired by the game "marble blast gold".', 129 - codeUrl: 'https://github.com/flo-bit/ball-game' 130 - }, 131 - { 132 - src: oldcode, 133 - key: 'old-code', 134 - alt: 'old projects', 135 - projectUrl: 'https://flo-bit.github.io/old-code/', 136 - aspect: 'aspect-[16/11]', 137 - name: 'old coding projects', 138 - code: 'https://github.com/flo-bit/old-code' 139 - }, 140 - { 141 - src: threltevrcodeeditor, 142 - key: 'threlte-vr-code-editor', 143 - alt: 'threlte vr code editor', 144 - projectUrl: 'https://github.com/flo-bit/threlte-vr-code-editor', 145 - aspect: 'aspect-[16/11]', 146 - name: 'threlte vr code editor', 147 - description: 'a virtual reality code editor made with threlte.', 148 - code: 'https://github.com/flo-bit/threlte-vr-code-editor' 149 - }, 150 - { 151 - src: autostereogramrenderer, 152 - key: 'autostereogram-renderer', 153 - alt: 'autostereogram renderer', 154 - projectUrl: 'https://flo-bit.github.io/autostereogram-renderer/', 155 - aspect: 'aspect-1', 156 - name: 'autostereogram renderer', 157 - description: 'rendering a 3d scene as an autostereogram.' 158 - } 159 - ];
-1
src/routes/+page.svelte
··· 14 14 import About from '$lib/components/About.svelte'; 15 15 import Learning from '$lib/components/Learning.svelte'; 16 16 import Posts from '$lib/components/Posts.svelte'; 17 - import Tools from '$lib/components/Tools.svelte'; 18 17 19 18 let active: 'home' | 'about' | 'projects' | 'learning' | 'contact' = 'home'; 20 19
+13 -9
src/routes/projects/[key]/+page.svelte
··· 1 1 <script lang="ts"> 2 - import Navbar from '$lib/components/nav/Navbar.svelte'; 3 - import ProjectV1 from '$lib/components/ProjectV1.svelte'; 4 - import ProjectV2 from '$lib/components/ProjectV2.svelte'; 5 - import ProjectV3 from '$lib/components/ProjectV3.svelte'; 6 2 import type { PageData } from './$types'; 3 + 4 + import Navbar from '$lib/components/nav/Navbar.svelte'; 5 + import ProjectTiered from '$lib/components/project/ProjectTiered.svelte'; 6 + import ProjectCarousel from '$lib/components/project/ProjectCarousel.svelte'; 7 + import ProjectGrid from '$lib/components/project/ProjectGrid.svelte'; 8 + import ProjectFull from '$lib/components/project/ProjectFull.svelte'; 7 9 8 10 export let data: PageData; 9 11 </script> ··· 11 13 <Navbar active="projects" /> 12 14 13 15 {#if data.project} 14 - {#if data.project.projectPageVersion === 'v2'} 15 - <ProjectV2 project={data.project} /> 16 - {:else if data.project.projectPageVersion === 'v3'} 17 - <ProjectV3 project={data.project} /> 16 + {#if data.project.projectPageVersion === 'carousel'} 17 + <ProjectCarousel project={data.project} /> 18 + {:else if data.project.projectPageVersion === 'tiered'} 19 + <ProjectTiered project={data.project} /> 20 + {:else if data.project.projectPageVersion === 'full'} 21 + <ProjectFull project={data.project} /> 18 22 {:else} 19 - <ProjectV1 project={data.project} /> 23 + <ProjectGrid project={data.project} /> 20 24 {/if} 21 25 {/if}
+1 -1
src/routes/projects/[key]/+page.ts
··· 1 - import { projects } from '../../../projects.js'; 1 + import { projects } from '$lib/projects.js'; 2 2 3 3 export const load = ({ params }) => { 4 4 const project = projects.find((project) => project.key === params.key);