this repo has no description
0
fork

Configure Feed

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

Remove all the old guff

+5 -1151
-190
src/components/blog/Balloons.svelte
··· 1 - <script lang="ts"> 2 - import { cubicInOut } from "svelte/easing"; 3 - import { Tween } from "svelte/motion"; 4 - import { config } from "@/stores/blog"; 5 - 6 - const { 7 - // popable, 8 - id, 9 - single, 10 - pop, 11 - boundingWidth, 12 - boundingHeight, 13 - colour, 14 - }: { 15 - // popable: boolean; 16 - id: 0 | 1; 17 - single?: true; 18 - pop: (id: number, start: () => void, climax: () => void) => void; 19 - boundingWidth: number; 20 - boundingHeight: number; 21 - colour: string; 22 - } = $props(); 23 - 24 - const random = { 25 - cableX: Math.random(), 26 - cableLength: Math.random(), 27 - }; 28 - 29 - interface Values { 30 - cableX: number; 31 - cableLength: number; 32 - cableAngle: Tween<number>; 33 - balloonX: number; 34 - balloonY: number; 35 - balloonAngle: number; 36 - } 37 - 38 - const { 39 - cableX, 40 - cableLength, 41 - cableAngle, 42 - balloonX, 43 - balloonY, 44 - balloonAngle, 45 - }: Values = $derived.by((): Values => { 46 - const fallbackVal = { 47 - cableX: 0, 48 - cableLength: 0, 49 - cableAngle: new Tween(0), 50 - balloonX: 0, 51 - balloonY: 0, 52 - balloonAngle: 0, 53 - }; 54 - if (!$config) return fallbackVal; 55 - const out: Partial<Values> = {}; 56 - 57 - out.cableX = 58 - (random.cableX * $config.cable.range + 59 - (!id 60 - ? $config.cable.padding 61 - : 1 - $config.cable.padding - $config.cable.range)) * 62 - boundingWidth; 63 - 64 - if (single) 65 - out.cableX = 66 - (random.cableX * $config.cable.range + (1 - $config.cable.range) / 2) * 67 - boundingWidth; 68 - 69 - out.cableLength = 70 - random.cableLength * 71 - ($config.cable.length.max - $config.cable.length.min) + 72 - $config.cable.length.min; 73 - 74 - out.cableAngle = new Tween( 75 - Math.random() * $config.cable.angle.initial - 76 - $config.cable.angle.initial / 2, 77 - { 78 - duration: 400, 79 - easing: cubicInOut, 80 - } 81 - ); 82 - 83 - out.balloonX = 84 - out.cableX - 85 - $config.balloon.width / 2 + 86 - Math.sin((out.cableAngle.current * Math.PI) / 180) * out.cableLength; 87 - 88 - out.balloonY = 89 - Math.cos((out.cableAngle.current * Math.PI) / 180) * out.cableLength + 90 - $config.balloon.height; 91 - 92 - return { ...fallbackVal, ...out }; 93 - }); 94 - </script> 95 - 96 - <button 97 - style={`--width: ${$config?.balloon.width}px; 98 - --height: ${$config?.balloon.height}px;}; 99 - --x: ${balloonX}px; 100 - --y: ${balloonY}px; 101 - --rotate: ${balloonAngle}deg; 102 - --colour: ${colour};`} 103 - aria-label="pop balloon" 104 - onclick={() => 105 - pop( 106 - id, 107 - () => console.log("start"), 108 - () => console.log("climax") 109 - )} 110 - > 111 - <div class="tie"></div> 112 - </button> 113 - <div 114 - style={`--width: ${$config?.cable.width}px; 115 - --length: ${cableLength}px; 116 - --x: ${cableX}px; 117 - --post-height: ${boundingHeight}px; 118 - --rotate: ${cableAngle.current}deg;`} 119 - ></div> 120 - 121 - <style> 122 - /* general things */ 123 - button, 124 - div { 125 - position: absolute; 126 - } 127 - 128 - /* balloon */ 129 - button { 130 - /* looks */ 131 - background-color: var(--colour,); 132 - opacity: 0.75; 133 - background-image: url("../../assets/balloon-glint.svg"); 134 - box-shadow: 135 - inset 1.5rem 1.5rem 2.5rem rgba(255, 255, 255, 0.5), 136 - inset -1rem -1rem 2.5rem rgba(0, 0, 0, 0.5); 137 - 138 - width: var(--width); 139 - height: var(--height); 140 - border: none; 141 - border-radius: calc(var(--width) / 2); 142 - 143 - /* positions */ 144 - left: var(--x); 145 - top: calc(-1 * var(--y)); 146 - z-index: 1; 147 - 148 - /* tie */ 149 - overflow: visible; 150 - 151 - &::after { 152 - content: ""; 153 - display: block; 154 - z-index: -1; 155 - 156 - --width: 2rem; 157 - --height: calc(tan(60deg) * var(--width) / 2); 158 - width: var(--width); 159 - height: var(--height); 160 - 161 - background-color: var(--colour); 162 - 163 - position: absolute; 164 - left: calc(50% - 1rem); 165 - bottom: calc(var(--height) * -1 + 0.5rem); 166 - 167 - clip-path: polygon(25% 30%, 0% 100%, 100% 100%, 75% 30%); 168 - } 169 - } 170 - 171 - .hidden { 172 - display: none; 173 - } 174 - 175 - /* cable */ 176 - div { 177 - width: var(--width); 178 - height: var(--length); 179 - background-color: white; 180 - 181 - /* positions */ 182 - left: var(--x); 183 - bottom: var(--post-height); 184 - z-index: 0; 185 - 186 - /* rotate */ 187 - transform-origin: bottom center; 188 - rotate: var(--rotate); 189 - } 190 - </style>
-119
src/components/blog/background/cloud.astro
··· 1 - --- 2 - interface Props { 3 - id: number; 4 - center: { 5 - x: number; 6 - y: number; 7 - }; 8 - size: { 9 - width: number; 10 - height: number; 11 - }; 12 - steps: number; 13 - padding?: number; 14 - } 15 - 16 - const { 17 - id, 18 - center: { x, y }, 19 - size: { width, height }, 20 - steps, 21 - padding = 100, 22 - } = Astro.props; 23 - 24 - const arcLength: number = 25 - Math.PI * Math.sqrt(((width / 2) ** 2 + (height / 2) ** 2) / 2); 26 - 27 - const sizes: number[] = (() => { 28 - let items = new Array(steps).fill(0).map((_) => (Math.random() * 3 + 2) / 5); 29 - const total = items.reduce((a, b) => a + b, 0); 30 - return items.map((item) => (item / total) * arcLength); 31 - })(); 32 - 33 - const convert = { 34 - degToRad: (deg: number) => (deg * Math.PI) / 180, 35 - radToDeg: (rad: number) => (rad * 180) / Math.PI, 36 - }; 37 - 38 - const positions: { x: number; y: number }[] = (() => { 39 - const segmentSize = 180 / (sizes.length - 1); 40 - const output = sizes.map((_, i) => ({ 41 - x: ((Math.sin(convert.degToRad(segmentSize * i - 90)) + 1) * width) / 2, 42 - y: Math.cos(convert.degToRad(segmentSize * i - 90)) * height, 43 - })); 44 - 45 - return output; 46 - })(); 47 - --- 48 - 49 - <style slot="head"> 50 - .cloud { 51 - position: absolute; 52 - top: calc(var(--y) - var(--padding)); 53 - left: calc(var(--x) - var(--padding)); 54 - width: calc(var(--w) + var(--padding)); 55 - height: calc(var(--h) + var(--padding)); 56 - max-width: unset; 57 - } 58 - </style> 59 - 60 - <svg 61 - xmlns="http://www.w3.org/2000/svg" 62 - viewBox={`-${padding} -${padding} ${width + padding * 2} ${height + padding * 2}`} 63 - preserveAspectRatio="none" 64 - class="cloud" 65 - style={`--x: ${x}svw; --y: ${y}svh; --w: ${width}px; --h: ${height}px; --padding: ${padding}px;`} 66 - > 67 - <defs> 68 - <clipPath id={`i${id}lower-bounds`}> 69 - <rect 70 - x={`-${padding}px`} 71 - y={`-${padding}px`} 72 - width={width + padding * 2} 73 - height={height + padding}></rect> 74 - </clipPath> 75 - <linearGradient id={`i${id}background`} x1="0" x2="0" y1="0" y2="1"> 76 - <stop offset="0" stop-color="white"></stop> 77 - <stop offset={height} stop-color="white" stop-opacity="0"></stop> 78 - </linearGradient> 79 - <mask id={`i${id}fade-base`}> 80 - <rect 81 - x={-2 * padding} 82 - y={-1 * padding} 83 - width={width + 4 * padding} 84 - height={height + padding} 85 - fill={`url(#i${id}background)`}></rect> 86 - </mask> 87 - 88 - <!-- will render: --> 89 - <clipPath id={`i${id}-clouds`}> 90 - <ellipse 91 - cx={width / 2} 92 - cy={height} 93 - rx={width / 2} 94 - ry={height} 95 - fill="black" 96 - clip-path={`url(#i${id}lower-bounds)`}></ellipse> 97 - { 98 - sizes.map((size, i) => ( 99 - <circle 100 - cx={positions[i].x} 101 - cy={height - positions[i].y} 102 - r={size} 103 - clip-path={`url(#i${id}lower-bounds)`} 104 - fill="black" 105 - /> 106 - )) 107 - } 108 - </clipPath> 109 - </defs> 110 - 111 - <rect 112 - x={-2 * padding} 113 - y={-1 * padding} 114 - width={width + 4 * padding} 115 - height={height + 2 * padding} 116 - fill="white" 117 - clip-path={`url(#i${id}-clouds)`} 118 - mask={`url(#i${id}fade-base)`}></rect> 119 - </svg>
-103
src/components/blog/background/moon.astro
··· 1 - --- 2 - interface Props { 3 - center: { 4 - x: number; 5 - y: number; 6 - }; 7 - rad: number; 8 - } 9 - 10 - const { 11 - center: { x, y }, 12 - rad, 13 - } = Astro.props; 14 - --- 15 - 16 - <div 17 - id="moon-wrapper" 18 - style={`--rad: ${rad}px; --x: ${x - rad}px; --y: ${y - rad}px; display: none`} 19 - > 20 - <svg 21 - width={rad * 2} 22 - height={rad * 2} 23 - viewBox={`0 0 ${rad * 2} ${rad * 2}`} 24 - xmlns="http://www.w3.org/2000/svg" 25 - > 26 - <clipPath id="full-circle-clip"> 27 - <circle cx={rad} cy={rad} r={rad}></circle> 28 - </clipPath> 29 - 30 - <rect 31 - clip-path="url(#full-circle-clip)" 32 - id="l-rect" 33 - x="0" 34 - y="0" 35 - width={rad} 36 - height={rad * 2}></rect> 37 - <rect 38 - clip-path="url(#full-circle-clip)" 39 - id="r-rect" 40 - x={rad} 41 - y="0" 42 - width={rad} 43 - height={rad * 2}></rect> 44 - 45 - <ellipse 46 - clip-path="url(#full-circle-clip)" 47 - id="ellipse" 48 - cx={rad} 49 - cy={rad} 50 - rx={rad / 2} 51 - ry={rad}></ellipse> 52 - </svg> 53 - </div> 54 - 55 - <style> 56 - @keyframes moon-colour { 57 - 0%, 24.99% { 58 - --l-fill: var(--dark); 59 - --r-fill: var(--light); 60 - --e-fill: var(--dark); 61 - } 62 - 63 - 25%, 74.99% { 64 - --e-fill: var(--light); 65 - } 66 - 67 - 75%, 99.99% { 68 - --e-fill: var(--dark); 69 - } 70 - 71 - 100% { 72 - --l-fill: var(--light); 73 - --r-fill: var(--dark); 74 - --e-fill: var(--dark); 75 - } 76 - } 77 - 78 - #moon-wrapper { 79 - position: absolute; 80 - top: var(--y); 81 - left: var(--x); 82 - pointer-events: none; 83 - max-width: none; 84 - 85 - --light: #e0d3de; 86 - --dark: #372554; 87 - 88 - animation: 1s calc(var(--phase) * -1s) paused infinite moon-colour; 89 - 90 - & #l-rect { 91 - fill: var(--l-fill, var(--dark)); 92 - } 93 - & #r-rect { 94 - fill: var(--r-fill, var(--dark)); 95 - } 96 - & #ellipse { 97 - fill: var(--e-fill, var(--dark)); 98 - rx: calc( 99 - min(abs(-4 * var(--phase) + 1), abs(-4 * var(--phase) + 3)) * var(--rad) 100 - ); 101 - } 102 - } 103 - </style>
-51
src/components/blog/background/stars.astro
··· 1 - --- 2 - interface Props { 3 - area: { 4 - width: number; 5 - height: number; 6 - }; 7 - stars: number; 8 - } 9 - 10 - const { 11 - area: { width, height }, 12 - stars, 13 - } = Astro.props; 14 - 15 - const positions = new Array(stars).fill(0).map((_) => ({ 16 - x: Math.random() * width, 17 - y: Math.random() * height, 18 - })); 19 - --- 20 - 21 - <div id="star-wrapper" style="display: none"> 22 - <svg 23 - width={width + 30} 24 - height={height + 30} 25 - viewBox={`0 0 ${width + 30} ${height + 30}`} 26 - preserveAspectRatio="xMidYMid slice" 27 - xmlns="http://www.w3.org/2000/svg" 28 - > 29 - { 30 - positions.map((x) => ( 31 - <path 32 - transform={`translate(${x.x}, ${x.y})`} 33 - d="M13.4208 0.752502L15.4921 8.44522C15.9554 10.1659 17.2994 11.51 19.0202 11.9733L26.7129 14.0446L19.0202 16.1158C17.2994 16.5791 15.9554 17.9232 15.4921 19.6439L13.4208 27.3367L11.3496 19.6439C10.8863 17.9232 9.54216 16.5791 7.82144 16.1158L0.128723 14.0446L7.82144 11.9733C9.54216 11.51 10.8863 10.1659 11.3496 8.44522L13.4208 0.752502Z" 34 - fill="#FFF6D1" 35 - /> 36 - )) 37 - } 38 - </svg> 39 - </div> 40 - 41 - <style> 42 - #star-wrapper { 43 - position: absolute; 44 - top: 0; 45 - left: 0; 46 - overflow: clip; 47 - width: 100vw; 48 - height: var(--height); 49 - pointer-events: none; 50 - } 51 - </style>
-50
src/components/blog/background/sun.astro
··· 1 - --- 2 - interface Props { 3 - center: { 4 - x: number; 5 - y: number; 6 - }; 7 - rad: number; 8 - prongs: number; 9 - scale: number; 10 - } 11 - 12 - const { 13 - center: { x, y }, 14 - rad, 15 - prongs, 16 - scale, 17 - } = Astro.props; 18 - 19 - const positions = new Array(prongs).fill(0).map((_, i) => (360 / prongs) * i); 20 - --- 21 - 22 - <div id="sun-wrapper" style={`--x: ${x - rad + scale}px; --y: ${y - rad + scale}px; display: none`}> 23 - <svg 24 - width={(rad + scale) * 2} 25 - height={(rad + scale) * 2} 26 - viewBox={`-${rad + scale} -${rad + scale} ${(rad + scale) * 2} ${(rad + scale) * 2}`} 27 - xmlns="http://www.w3.org/2000/svg" 28 - > 29 - <circle cx="0" cy="0" r={rad-10} fill="#F7CB15"></circle> 30 - { 31 - positions.map((x) => ( 32 - <polygon 33 - points={`-${scale/2},${rad} ${scale/2},${rad} 0,${rad + scale}`} 34 - transform={`rotate(${x})`} 35 - fill="#F7CB15" 36 - /> 37 - )) 38 - } 39 - </svg> 40 - </div> 41 - 42 - <style> 43 - #sun-wrapper { 44 - position: absolute; 45 - top: var(--y); 46 - left: var(--x); 47 - pointer-events: none; 48 - max-width: none; 49 - } 50 - </style>
-175
src/components/blog/floater.svelte
··· 1 - <script lang="ts"> 2 - import { config, postPositions } from "@/stores/blog"; 3 - import Balloons from "./Balloons.svelte"; 4 - let { 5 - children, 6 - id, 7 - width, 8 - height, 9 - balloons, 10 - colour, 11 - }: { 12 - children: () => any; 13 - id: number; 14 - width: number; 15 - height: number; 16 - balloons: 0 | 1 | 2; 17 - colour: string; 18 - } = $props(); 19 - 20 - let x = $state(0), 21 - y = $state(0), 22 - thisWidth = $state(0), 23 - thisHeight = $state(0), 24 - elem: HTMLElement = null as unknown as HTMLElement; 25 - 26 - $effect(() => { 27 - if ($config === null || $postPositions === undefined) return; 28 - 29 - const { x: tX, y: tY } = $postPositions[id]; 30 - 31 - x = tX; 32 - y = tY; 33 - }); 34 - 35 - const pop = (id: number, start: () => void, climax: () => void): void => { 36 - console.log("popping " + id); 37 - 38 - if (!elem) return; 39 - 40 - start(); 41 - elem 42 - .animate( 43 - [ 44 - { 45 - transform: "translateY(0)", 46 - }, 47 - { 48 - transform: "translateY(100rem)", 49 - }, 50 - ], 51 - { 52 - duration: 2000, 53 - endDelay: 500, 54 - easing: "cubic-bezier(0.313, 0.079, 0.554, 0.972)", 55 - fill: "forwards", 56 - iterations: 1, 57 - } 58 - ) 59 - .finished.then(() => { 60 - climax(); 61 - 62 - elem.animate( 63 - [ 64 - { 65 - transform: "translateY(100rem)", 66 - }, 67 - { 68 - transform: "translateY(0)", 69 - }, 70 - ], 71 - { 72 - duration: 10000, 73 - iterations: 1, 74 - fill: "forwards", 75 - easing: "ease-in-out", 76 - } 77 - ); 78 - }); 79 - }; 80 - </script> 81 - 82 - <section 83 - bind:clientWidth={thisWidth} 84 - bind:clientHeight={thisHeight} 85 - bind:this={elem} 86 - style={` 87 - --w: ${width}; 88 - --h: ${height}; 89 - --x: ${x}; 90 - --y: ${y}; 91 - 92 - --__anim-y-speed: ${Math.random() * 20 + 10}s; 93 - --__anim-y-0: ${Math.random() * 20 - 10}rem; 94 - --__anim-y-25: ${Math.random() * 20 - 10}rem; 95 - --__anim-y-50: ${Math.random() * 20 - 10}rem; 96 - --__anim-y-75: ${Math.random() * 20 - 10}rem; 97 - 98 - --__anim-x-speed: ${Math.random() * 20 + 20}s; 99 - --__anim-x-0: ${Math.random() * 8 - 4}rem; 100 - --__anim-x-25: ${Math.random() * 8 - 4}rem; 101 - --__anim-x-50: ${Math.random() * 8 - 4}rem; 102 - --__anim-x-75: ${Math.random() * 8 - 4}rem; 103 - `} 104 - > 105 - {#each new Array(balloons) as _, i} 106 - <Balloons 107 - {colour} 108 - id={i as 0 | 1} 109 - {...balloons === 1 ? { single: true } : {}} 110 - boundingWidth={thisWidth} 111 - boundingHeight={thisHeight} 112 - {pop} 113 - /> 114 - {/each} 115 - {@render children()} 116 - </section> 117 - 118 - <style> 119 - /* bobbing animation */ 120 - @keyframes bob-y { 121 - 0%, 122 - 100% { 123 - top: calc(var(--y) * 0.1rem + var(--__anim-y-0)); 124 - } 125 - 126 - 25% { 127 - top: calc(var(--y) * 0.1rem + var(--__anim-y-25)); 128 - } 129 - 130 - 50% { 131 - top: calc(var(--y) * 0.1rem + var(--__anim-y-50)); 132 - } 133 - 134 - 75% { 135 - top: calc(var(--y) * 0.1rem + var(--__anim-y-75)); 136 - } 137 - } 138 - 139 - @keyframes bob-x { 140 - 0%, 141 - 100% { 142 - left: calc(var(--x) * 0.1rem + var(--__anim-x-0, 0)); 143 - } 144 - 145 - 25% { 146 - left: calc(var(--x) * 0.1rem + var(--__anim-x-25, 0)); 147 - } 148 - 149 - 50% { 150 - left: calc(var(--x) * 0.1rem + var(--__anim-x-50, 0)); 151 - } 152 - 153 - 75% { 154 - left: calc(var(--x) * 0.1rem + var(--__anim-x-75, 0)); 155 - } 156 - } 157 - 158 - section { 159 - background-color: white; 160 - box-shadow: 0 0 2rem rgba(0, 0, 0, 0.25); 161 - border-radius: 2rem; 162 - padding: 1rem; 163 - 164 - width: calc(var(--w) * 0.1rem); 165 - height: calc(var(--h) * 0.1rem); 166 - 167 - position: absolute; 168 - left: calc(var(--x) * 0.1rem); 169 - top: calc(var(--y) * 0.1rem); 170 - 171 - animation: 172 - bob-y var(--__anim-y-speed) ease-in-out infinite, 173 - bob-x var(--__anim-x-speed) ease-in-out infinite; 174 - } 175 - </style>
-63
src/components/blog/post.astro
··· 1 - --- 2 - import { Image } from "astro:assets"; 3 - 4 - export interface Props { 5 - post: { 6 - data: { 7 - image: { 8 - src: string; 9 - alt: string; 10 - }; 11 - title: string; 12 - date: Date; 13 - }; 14 - }; 15 - } 16 - 17 - const { 18 - post: { 19 - data: { image: img, title, date }, 20 - }, 21 - } = Astro.props; 22 - 23 - let imageSrc: RegExpMatchArray | null | string[] = img.src.match(/.*(?=\.png)/gm); 24 - if (imageSrc === null) { 25 - imageSrc = ["404"]; 26 - } 27 - const { default: image } = await import(`../../posts/assets/${imageSrc[0]}.png`); 28 - --- 29 - 30 - <style slot="head"> 31 - div { 32 - overflow: hidden; 33 - height: 100%; 34 - } 35 - 36 - img { 37 - object-fit: cover; 38 - border-radius: 1rem; 39 - } 40 - 41 - h2 { 42 - width: 100%; 43 - white-space: nowrap; 44 - overflow: hidden; 45 - text-overflow: ellipsis; 46 - font-size: 2rem; 47 - } 48 - 49 - p { 50 - font-size: 1.2rem; 51 - } 52 - </style> 53 - 54 - <div> 55 - <Image src={image} alt={img.alt} width={180} height={135} /> 56 - <h2>{title}</h2> 57 - <p> 58 - {String(date.getDate()).padStart(2, "0")} 59 - -{String(date.getMonth() + 1).padStart(2, "0")} 60 - -{String(date.getFullYear()).padStart(2, "0").substring(2)} 61 - </p> 62 - <p>{date.toLocaleDateString()}</p> 63 - </div>
+5 -400
src/pages/blog.astro
··· 1 1 --- 2 2 import Base from "@/layouts/base.astro"; 3 - import Floater from "@/components/blog/floater.svelte"; 4 - import Post from "@/components/blog/post.astro"; 5 - import { Image } from "astro:assets"; 6 - import { getCollection } from "astro:content"; 7 - 8 - import rss from "@/assets/rss.svg"; 9 - import Cloud from "@/components/blog/background/cloud.astro"; 10 - import Stars from "@/components/blog/background/stars.astro"; 11 - import Sun from "@/components/blog/background/sun.astro"; 12 - import Moon from "@/components/blog/background/moon.astro"; 13 - 14 - const posts = await getCollection("blog"); 3 + import Rss from "@/assets/rss.svg"; 4 + console.log(Rss); 15 5 --- 16 6 17 - <Base title="blog"> 18 - <Fragment slot="head"> 19 - <script> 20 - import { isOverlapping, config, postPositions } from "@/stores/blog"; 21 - import { Moon } from "lunarphase-js"; 22 - 23 - /////////////////// 24 - // CONFIGURATION // 25 - /////////////////// 26 - config.set({ 27 - // general 28 - general: { 29 - maxReccurs: 1000, 30 - }, 31 - 32 - // post body 33 - post: { 34 - drift: 40, 35 - gap: { 36 - initial: -80, 37 - min: 100, 38 - max: 250, 39 - }, 40 - }, 41 - 42 - // cable 43 - cable: { 44 - padding: 0.1, 45 - range: 0.2, 46 - width: 5, 47 - angle: { 48 - initial: 20, 49 - drift: 70, 50 - }, 51 - length: { 52 - min: 100, 53 - max: 150, 54 - }, 55 - }, 56 - 57 - // balloons 58 - balloon: { 59 - width: 80, 60 - height: 120, 61 - rotation: 40, 62 - }, 63 - }); 64 - 65 - /////////// 66 - // UTILS // 67 - /////////// 68 - 69 - const getAbsRect = (el: Element) => { 70 - const rect = el.getBoundingClientRect(); 71 - return new DOMRect( 72 - rect.x - window.scrollX, 73 - rect.y - window.scrollY, 74 - rect.width, 75 - rect.height 76 - ); 77 - }; 78 - 79 - const padRect = (rect: DOMRect) => { 80 - const conf = config.get(); 81 - if (conf === null) throw new Error("config is null"); 82 - 83 - return new DOMRect( 84 - rect.x - conf.post.drift, 85 - rect.y - 86 - conf.post.drift - 87 - conf.cable.length.max - 88 - conf.balloon.height, 89 - rect.width + conf.post.drift * 2, 90 - rect.height + 91 - conf.post.drift * 2 + 92 - conf.cable.length.max + 93 - conf.balloon.height 94 - ); 95 - }; 96 - 97 - const unpadRect = (rect: DOMRect) => { 98 - const conf = config.get(); 99 - if (conf === null) throw new Error("config is null"); 100 - return new DOMRect( 101 - rect.x + conf.post.drift, 102 - rect.y + 103 - conf.post.drift + 104 - conf.cable.length.max + 105 - conf.balloon.height, 106 - rect.width - conf.post.drift * 2, 107 - rect.height - 108 - conf.post.drift * 2 - 109 - conf.cable.length.max - 110 - conf.balloon.height 111 - ); 112 - }; 113 - 114 - const renderRect = ( 115 - rect: DOMRect, 116 - colour: string | [number, number, number] = "orange", 117 - solid: boolean = true 118 - ) => { 119 - const el = document.createElement("div"); 120 - 121 - el.style.position = "absolute"; 122 - 123 - el.style.top = `${rect.top}px`; 124 - el.style.left = `${rect.left}px`; 125 - el.style.width = `${rect.width}px`; 126 - el.style.height = `${rect.height}px`; 127 - 128 - el.style.outline = "1px solid transparent"; 129 - el.style[solid ? "backgroundColor" : "outlineColor"] = 130 - typeof colour === "string" ? colour : `rgb(${colour.join(", ")})`; 131 - 132 - el.style.opacity = solid ? "0.25" : "1"; 133 - // el.style.zIndex = "-1"; 134 - 135 - document.body.appendChild(el); 136 - }; 137 - 138 - /////////////// 139 - // RECT GENS // 140 - /////////////// 141 - 142 - function* getElms(parent: string) { 143 - // get config (if non existent exit early) 144 - const conf = config.get(); 145 - if (conf === null) return null; 146 - // get container (if non existent exit early) 147 - const postContainer = document.getElementById(parent); 148 - if (postContainer === null) return null; 149 - const children = postContainer.children; 150 - 151 - // define one step in the loop 152 - const step = (el: Element): Element | null => { 153 - const styles = window.getComputedStyle(el); 154 - 155 - // test if el is display: none and if it is, skip 156 - if (styles.display === "none") { 157 - return null; 158 - } 159 - // if element is display content, retry step of loop with its child 160 - if (styles.display === "contents") { 161 - const children = el.children; 162 - for (const child of children) { 163 - const result = step(child); 164 - if (result !== null) { 165 - return result; 166 - } 167 - } 168 - return null; 169 - } 170 - 171 - return el; 172 - }; 173 - 174 - for (const el of children) { 175 - const result = step(el); 176 - if (result !== null) { 177 - yield result; 178 - } 179 - } 180 - } 181 - 182 - function* getRects(parent: string) { 183 - for (const el of getElms(parent)) { 184 - yield getAbsRect(el); 185 - } 186 - } 187 - 188 - function* getPadRects(parent: string) { 189 - for (const rect of getRects(parent)) { 190 - yield padRect(rect); 191 - } 192 - } 193 - 194 - ////////// 195 - // BODY // 196 - ////////// 197 - 198 - const newPos = () => { 199 - const conf = config.get(); 200 - if (conf === null) throw new Error("config is null"); 201 - 202 - const positions: DOMRect[] = []; 203 - let y = conf.post.gap.initial - conf.post.gap.min; 204 - 205 - let lowestHeight = 0; 206 - 207 - const step = (rect: DOMRect, y: number, steps: number = 0) => { 208 - if (steps > conf.general.maxReccurs) { 209 - console.warn("too many steps, quitting for recursion"); 210 - return rect; 211 - } 212 - 213 - rect.y = y; 214 - rect.x = Math.random() * (window.innerWidth - rect.width); 215 - 216 - if ( 217 - isOverlapping( 218 - rect, 219 - padRect(positions.at(-1) ?? new DOMRect(0, 0, 0, 0)) 220 - ) 221 - ) { 222 - return step(rect, y, steps + 1); 223 - } 224 - 225 - if (rect.bottom > lowestHeight) lowestHeight = rect.y + rect.height; 226 - 227 - return rect; 228 - }; 229 - 230 - for (let rect of getPadRects("post-container")) { 231 - y += 232 - Math.random() * (conf.post.gap.max - conf.post.gap.min) + 233 - conf.post.gap.min; 234 - positions.push(unpadRect(step(rect, y))); 235 - } 236 - 237 - postPositions.set(positions); 238 - 239 - document.body.style.setProperty( 240 - "--height", 241 - `max(100lvh, ${(lowestHeight + 50) / 10}rem)` 242 - ); 243 - }; 244 - 245 - newPos(); 246 - 247 - let timeout: number | false = false; 248 - window.addEventListener("resize", () => { 249 - if (timeout) clearTimeout(timeout); 250 - timeout = setTimeout(newPos, 100) as unknown as number; 251 - }); 252 - 253 - const handleDayTime = (overrides?: { 254 - isDayTime?: boolean; 255 - phase?: number; 256 - date?: Date; 257 - }) => { 258 - const elements = { 259 - body: document.body, 260 - clouds: 261 - document.getElementById("cloud-wrapper") ?? new HTMLDivElement(), 262 - stars: 263 - document.getElementById("star-wrapper") ?? new HTMLDivElement(), 264 - sun: document.getElementById("sun-wrapper") ?? new HTMLDivElement(), 265 - moon: document.getElementById("moon-wrapper") ?? new HTMLDivElement(), 266 - }; 267 - const hours = 268 - overrides && overrides.date 269 - ? overrides.date.getHours() 270 - : new Date().getHours(); 271 - const isDayTime = overrides?.isDayTime ?? (hours > 6 && hours < 20); 272 - const phase = 273 - (overrides?.phase ?? (overrides && overrides.date)) 274 - ? Moon.lunarAge(overrides.date) 275 - : Moon.lunarAgePercent(); 276 - 277 - if (overrides) console.log(overrides, hours, isDayTime, phase); 278 - 279 - elements.body.classList.toggle("day", isDayTime); 280 - elements.body.classList.toggle("night", !isDayTime); 281 - elements.moon.style.setProperty("--phase", `${phase}`); 282 - 283 - if (isDayTime) { 284 - elements.clouds.style.display = "block"; 285 - elements.sun.style.display = "block"; 286 - elements.stars.style.display = "none"; 287 - elements.moon.style.display = "none"; 288 - } else { 289 - elements.clouds.style.display = "none"; 290 - elements.sun.style.display = "none"; 291 - elements.stars.style.display = "block"; 292 - elements.moon.style.display = "block"; 293 - } 294 - }; 295 - 296 - handleDayTime({ 297 - isDayTime: true, 298 - }); 299 - </script> 300 - <style> 301 - body { 302 - background-color: var(--bg-colour, #87ceeb); 303 - width: 100vw; 304 - height: var(--height, 100vh); 305 - overflow-x: clip; 306 - 307 - &.night { 308 - --bg-colour: #372554; 309 - } 310 - 311 - &.day { 312 - --bg-colour: #87ceeb; 313 - } 314 - } 315 - 316 - #post-container { 317 - height: 100%; 318 - overflow: clip; 319 - /* contain: content; */ 320 - position: relative; 321 - } 322 - 323 - #cloud-wrapper { 324 - width: 100%; 325 - height: var(--height, 100%); 326 - overflow: clip; 327 - pointer-events: none; 328 - position: absolute; 329 - } 330 - </style> 331 - </Fragment> 332 - <div id="cloud-wrapper"> 333 - { 334 - new Array(40).fill(0).map((_, i) => ( 335 - <Cloud 336 - id={i} 337 - center={{ 338 - x: Math.random() * 100 - 10, 339 - y: i * 30 + Math.random() * 10, 340 - }} 341 - padding={200} 342 - size={{ 343 - width: Math.random() * 300 + 200, 344 - height: Math.random() * 100 + 100, 345 - }} 346 - steps={Math.floor(Math.random() * 4) + 6} 347 - /> 348 - )) 349 - } 350 - </div> 351 - <Stars 352 - area={{ width: 3000, height: 3000 }} 353 - stars={Math.floor(Math.random() * 40 + 60)} 354 - /> 355 - <Sun 356 - center={{ x: Math.random() * 80 + 120, y: Math.random() * 180 + 120 }} 357 - rad={80} 358 - prongs={12} 359 - scale={20} 360 - /> 361 - <Moon 362 - center={{ x: Math.random() * 80 + 120, y: Math.random() * 180 + 120 }} 363 - rad={80} 364 - /> 365 - <div id="post-container"> 366 - { 367 - [ 368 - // define each entry as an object with width, height, balloons (optional) and floater contents 369 - { 370 - width: 100, 371 - height: 100, 372 - balloons: 1, 373 - colour: "orange", 374 - content: ( 375 - <div style="border-radius: 1rem; background-color: orange; width: 100%; height: 100%; padding: 1rem;"> 376 - <Image src={rss} alt="rss" width={60} height={60} /> 377 - </div> 378 - ), 379 - }, 380 - // then splat out the post entries with width and height, as well as the templated contents 381 - ...posts.map((x) => { 382 - return { 383 - width: 200, 384 - height: 200, 385 - colour: x.data.colour, 386 - content: <Post post={x} />, 387 - }; 388 - }), 389 - // things are done this way so that the floaters know what ID they are 390 - // this cannot be done in the frontmatter as it doesnt support JSX unfortunately 391 - ].map(async (x, i) => ( 392 - <Floater 393 - client:load 394 - width={x.width} 395 - height={x.height} 396 - id={i} 397 - balloons={"balloons" in x ? x.balloons : 2} 398 - colour={x.colour} 399 - > 400 - {x.content} 401 - </Floater> 402 - )) 403 - } 404 - </div> 7 + <Base title="Blog"> 8 + <h1>Blog</h1> 9 + <a href="/rss.xml" aria-label="Rss Feed"><Rss /></a> 405 10 </Base>