[Archived] Archived WIP of vielle.dev

Add clouds as procedurally generated SVGs

- Replace <img> tag in Clouds.astro with a Cloud component
- Clouds are generated by creating an array of random lengths then using it to aproximate the perimiter of a circle. These lengths are then used as the diameter of a circle. The final length always closes it to the end of the circle
- values are configurable

TODO: ensure last entry closes the circle if missing

Changed files
+166 -15
src
components
blog
+154
src/components/blog/background/Cloud.astro
··· 1 + --- 2 + import { utils } from "@/config"; 3 + import { blog } from "@/config"; 4 + 5 + // utils 6 + 7 + const toRad = (n: number) => (n * Math.PI) / 180; 8 + const toDeg = (n: number) => (n * 180) / Math.PI; 9 + 10 + /* y 11 + 90 ____ 90-n 12 + | / 13 + | / 14 + x | / l 15 + |/ 16 + n 17 + 18 + y 19 + L ____ X 20 + | / 21 + | / 22 + x | / l 23 + |/ 24 + Y 25 + */ 26 + 27 + /** 28 + * generate the vector [x, y] from the angle and length 29 + * @param n angle (degrees) from north 30 + * @param l length (unit as output) 31 + */ 32 + const vector = (n: number, l: number): [number, number] => [ 33 + (l / Math.sin(toRad(90))) * Math.sin(toRad(n)), 34 + (l / Math.sin(toRad(90))) * Math.sin(toRad(90 - n)), 35 + ]; 36 + 37 + const vectorOffset = ( 38 + v: [number, number], 39 + o: [number, number] 40 + ): [number, number] => { 41 + return [o[0] - v[0], o[1] - v[1]]; 42 + }; 43 + 44 + // constants 45 + const r = 100; 46 + --- 47 + 48 + <svg 49 + viewBox={`-${blog.background.clouds.bumpRadius[1]} -${blog.background.clouds.bumpRadius[1]} ${2 * (r + blog.background.clouds.bumpRadius[1])} ${r + blog.background.clouds.bumpRadius[1]}`} 50 + preserveAspectRatio="none" 51 + {...Astro.props} 52 + > 53 + <defs> 54 + <clipPath id={`cloud-clip-${Astro.props.id}`}> 55 + { 56 + new Array(10) 57 + .fill(0) 58 + .map((_) => utils.getRandom(blog.background.clouds.bumpRadius)) 59 + .reduce( 60 + (p, c) => { 61 + if (p.complete) return p; 62 + /* C 63 + /\ 64 + r / \ r 65 + /___\ 66 + R c R 67 + r² + r² - c² 68 + cos C = ──────────── 69 + 2 x r x r 70 + (r² + r² - c²) 71 + C = cos⁻¹(────────────) 72 + ( 2 x r x r ) 73 + R = (180 - C) / 2 */ 74 + // angle C (opposite of chord, between 2 radii) in radians 75 + const C = Math.acos((r ** 2 + r ** 2 - c ** 2) / (2 * r * r)); 76 + // angle R (opposite of radius) in radians 77 + const R = (Math.PI - C) / 2; 78 + const ang = -(180 - (90 - p.prev) - toDeg(R)); 79 + const center = vectorOffset(vector(ang, c / 2), p.origin); 80 + const hex = () => { 81 + const hex = Math.floor(Math.random() * 256).toString(16); 82 + return "0".repeat(2 - hex.length) + hex; 83 + }; 84 + const nextOrigin = vectorOffset(vector(ang, c), p.origin); 85 + if (nextOrigin[1] > 100) { 86 + const newCenter: [number, number] = [ 87 + (2 * r - p.origin[0]) / 2 + p.origin[0], 88 + (r - p.origin[1]) / 2 + p.origin[1], 89 + ]; 90 + const newDistance = Math.sqrt( 91 + (newCenter[0] - p.origin[0]) ** 2 + 92 + (newCenter[1] - p.origin[1]) ** 2 93 + ); 94 + return { 95 + origin: vectorOffset(vector(ang, c), p.origin), 96 + prev: p.prev + toDeg(C), 97 + complete: true, 98 + output: [ 99 + ...p.output, 100 + <circle 101 + cx={newCenter[0]} 102 + cy={newCenter[1]} 103 + r={newDistance} 104 + />, 105 + ], 106 + }; 107 + } 108 + return { 109 + origin: vectorOffset(vector(ang, c), p.origin), 110 + prev: p.prev + toDeg(C), 111 + complete: false, 112 + output: [ 113 + ...p.output, 114 + <circle cx={center[0]} cy={center[1]} r={c / 2} />, 115 + ], 116 + }; 117 + }, 118 + { 119 + origin: [0, r] as [number, number], 120 + prev: 0, 121 + output: [] as any[], 122 + complete: false, 123 + } 124 + ).output 125 + } 126 + <circle cx={r} cy={r} r={r}></circle> 127 + </clipPath> 128 + 129 + <linearGradient 130 + id={`cloud-gradient-${Astro.props.id}`} 131 + x1="0" 132 + x2="0" 133 + y1="0" 134 + y2="1" 135 + > 136 + <stop offset="0%" stop-color="white"></stop> 137 + <stop 138 + offset={`${blog.background.clouds.gradientStops[0]}%`} 139 + stop-color="white"></stop> 140 + <stop 141 + offset={`${blog.background.clouds.gradientStops[1]}%`} 142 + stop-color="transparent"></stop> 143 + <stop offset="100%" stop-color="transparent"></stop> 144 + </linearGradient> 145 + </defs> 146 + 147 + <rect 148 + width={2 * (r + blog.background.clouds.bumpRadius[1])} 149 + height={r + blog.background.clouds.bumpRadius[1]} 150 + x={blog.background.clouds.bumpRadius[1] * -1} 151 + y={blog.background.clouds.bumpRadius[1] * -1} 152 + clip-path={`url(#cloud-clip-${Astro.props.id})`} 153 + fill={`url(#cloud-gradient-${Astro.props.id})`}></rect> 154 + </svg>
+9 -15
src/components/blog/background/Clouds.astro
··· 1 1 --- 2 2 import { blog } from "@/config"; 3 + import Cloud from "./Cloud.astro"; 3 4 --- 4 5 5 - <style> 6 - img { 7 - position: absolute; 8 - } 9 - </style> 10 - 11 6 <div id="clouds"> 12 7 { 13 8 new Array(blog.background.clouds.count).fill(0).reduce( 14 - (prev) => { 9 + (prev, _, i) => { 15 10 const width = 16 11 blog.background.clouds.width[0] + 17 12 (blog.background.clouds.width[1] - ··· 33 28 y: y, 34 29 output: [ 35 30 ...prev.output, 36 - <img 37 - src="https://cdn.zmescience.com/wp-content/uploads/2017/07/8690313402_5f76f736b3_k-1-1024x683.webp" 38 - alt="" 31 + <Cloud 39 32 style={`--parallax-speed: ${blog.background.parallax.clouds}; 40 - 41 - width: ${width}rem; 42 - height: ${height}rem; 43 - top: ${y}rem; 44 - left: calc(${x} * 200lvw - 100lvw);`} 33 + width: ${width}rem; 34 + height: ${height}rem; 35 + top: ${y}rem; 36 + left: calc(${x} * 200lvw - 100lvw); 37 + position: absolute;`} 38 + id={"cloud-" + i} 45 39 data-parallax 46 40 />, 47 41 ],
+3
src/config.ts
··· 20 20 width: [40, 80], 21 21 height: [15, 30], 22 22 yGap: [15, 25], 23 + 24 + bumpRadius: [20, 60], 25 + gradientStops: [35, 80], 23 26 }, 24 27 25 28 stars: {