Skip to content

Commit

Permalink
Dynamic backgrounds
Browse files Browse the repository at this point in the history
  • Loading branch information
iamawatermelo committed Oct 27, 2024
1 parent f3222f8 commit dd4bd23
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 8 deletions.
4 changes: 0 additions & 4 deletions jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,4 @@
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
3 changes: 2 additions & 1 deletion src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const handle: Handle = async ({ event, resolve }) => {
}

response.headers.set('link', link.join(','));

response.headers.set('Accept-CH', 'Width, Viewport-Width, Sec-CH-Width, Sec-CH-Viewport-Width, Sec-CH-Viewport-Height, Sec-CH-UA-Mobile')

return response;
};
77 changes: 77 additions & 0 deletions src/lib/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { render } from 'svelte/server';
import Background from '$lib/components/Background.svelte';

let WIDTH_HEADERS = ["Width", "Viewport-Width", "Sec-CH-Width", "Sec-CH-Viewport-Width"];
let HEIGHT_HEADERS = ["Sec-CH-Viewport-Height"];
let MOBILE_INDICATORS = ["mobile", "android", "iphone"]

export default async ({ request }: { request: Request }) => {
// Try detect viewport height
let width = 1920;

for (const header of WIDTH_HEADERS) {
const headerContent = request.headers.get(header)
if (headerContent !== null) {
const newWidth = parseInt(headerContent);

if (isFinite(newWidth) && newWidth > 200) {
width = parseInt(headerContent)
console.log(`Detected width: ${width}`)
break
}
}
}

// Now try detect viewport width
let height = null;

for (const header of HEIGHT_HEADERS) {
const headerContent = request.headers.get(header)
if (headerContent !== null) {
const newHeight = parseInt(headerContent);

if (isFinite(newHeight) && newHeight > 200) {
height = parseInt(headerContent)
console.log(`Detected height: ${height}`)
break
}
}
}

// If that fails, try detect whether it is mobile and derive an aspect ratio from that
if (height == null) {
let aspectRatio = 0.5625;

const userAgent = request.headers.get("User-Agent")
if (userAgent !== null) {
for (const indicator of MOBILE_INDICATORS) {
if (userAgent.toLowerCase().includes(indicator)) {
aspectRatio ^= -1
break
}
}
}

height = width * aspectRatio
console.log(`Derived height: ${height}`)
}

// Now render the background and return it
// Create an insecure random seed from the request headers
const seed = Array.from([...request.headers].join(''))
.reduce((hash, char) => 0 | (31 * hash + char.charCodeAt(0)), 0)
const svg = render(Background, {
props: { width, height, seed }
})

// Evil
let body = svg.body.replaceAll(/<!--[\[\]]?!?\??-->/g, "")

return new Response(body, {
status: 200,
headers: {
"Vary": "Width, Viewport-Width, Sec-CH-Width, Sec-CH-Viewport-Width, Sec-CH-Viewport-Height, Sec-CH-UA-Mobile",
"Content-Type": "image/svg+xml"
}
})
}
69 changes: 69 additions & 0 deletions src/lib/components/Background.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script lang="ts">
export let width: number;
export let height: number;
export let seed: number;
let boxWidth = Math.ceil(width / 32);
let boxHeight = Math.ceil(height / 16);
// Perlin code derived from https://github.com/joeiddon/perlin/blob/master/perlin.js
// Generate a somewhat random vector with the SplitMix32 algorithm
function random() {
seed |= 0;
seed = seed + 0x9e3779b9 | 0;
let t = seed ^ seed >>> 16;
t = Math.imul(t, 0x21f0aaad);
t = t ^ t >>> 15;
t = Math.imul(t, 0x735a2d97);
let value = (((t = t ^ t >>> 15) >>> 0) / 4294967296) * 2 * Math.PI
return {x: Math.cos(value), y: Math.sin(value)}
}
function dotProductGrid(x: number, y: number, vx: number, vy: number) {
let gVector = random();
let dVector = {x: x - vx, y: y - vy};
return dVector.x * gVector.x + dVector.y * gVector.y
}
function interpolate(x: number, a: number, b: number) {
return a + (6*x**5 - 15*x**4 + 10*x**3) * (b-a)
}
// This function assumes it will be called once per pair.
// As such, it does no memoization at all.
function evaluateAt(x: number, y: number) {
let xf = Math.floor(x);
let yf = Math.floor(y);
let tl = dotProductGrid(x, y, xf, yf);
let tr = dotProductGrid(x, y, xf+1, yf);
let bl = dotProductGrid(x, y, xf, yf+1);
let br = dotProductGrid(x, y, xf+1, yf+1);
let xt = interpolate(x-xf, tl, tr);
let xb = interpolate(x-xf, bl, br);
let v = interpolate(y-yf, xt, xb);
return v;
}
</script>

<svelte:options namespace="svg" />

<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 {width} {height}">
<metadata>
<seed>{seed}</seed>
<width>{width}</width>
<height>{height}</height>
</metadata>
<defs>
<rect id="r" fill="black" width="31" height="15" />
</defs>
{#each Array(boxHeight) as _, y}
{#each Array(boxWidth) as _, x}
{@const opacity = (evaluateAt(x/64, y/32) / 4) - 0.01}
{#if opacity > 0}
<use href="#r" y={y * 16} x={x * 32 - ((y % 2) * 16)} fill-opacity={opacity} />
{/if}
{/each}
{/each}
</svg>
6 changes: 3 additions & 3 deletions src/lib/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ $size_lg: 64rem;
& {
background:
$additional_backgrounds,
repeat url('$lib/textures/brick.svg') left/64px;
repeat url('$lib/textures/brick.svg') top left;
}

@media (max-width: $size_sm) {
@media (min-width: $size_sm) {
background:
$additional_backgrounds,
repeat url('$lib/textures/brick.svg');
repeat url('$lib/textures/brick.svg') top left/64px;
}
}

Expand Down
19 changes: 19 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,30 @@
// Main element brick texture
:global(main) {
position: relative;
min-height: calc(100vh - 4em);
overflow: clip;
z-index: -2;
@include style.brick-texture(var(--overlay-brick));
}
:global(main::before) {
content: '';
position: absolute;
display: block;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeat url('/api/background.svg') top left;
@media (min-width: style.$size_sm) {
transform-origin: top left;
transform: scale(2);
}
}
// Footer styles
footer {
Expand Down
3 changes: 3 additions & 0 deletions src/routes/api/background.svg/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import handler from '$lib/background';

export const GET = handler;

0 comments on commit dd4bd23

Please sign in to comment.