~leah/basingstoke

0acf7aa4d4e507ed4c7f3fbb7a601f5f5493e12e — leah 4 months ago cd27c75
woag
M README.md => README.md +5 -2
@@ 1,7 1,10 @@
**basingstoke** is an exciting activitypub server for your computer.

[read the manual](https://goober.city/manual/) for build instructions and so 
on.
### links

* [build it](./bonus/build.md) (tl;dr `npm i && npm run dev`)
* [faq](./bonus/faq.md)
* [contribute](./bonus/contributing.md)

### attribution


A bonus/code_of_conduct.md => bonus/code_of_conduct.md +0 -0
A bonus/contributing.md => bonus/contributing.md +1 -0
@@ 0,0 1,1 @@
# 
\ No newline at end of file

A bonus/faq.md => bonus/faq.md +5 -0
@@ 0,0 1,5 @@
# frequently anticipated questions

i know many of basingstoke's design decisions are questionable. some of that is 
due to incompetence, but some of it was done that way on purpose.


M src/lib/Bonus/Menu.svelte => src/lib/Bonus/Menu.svelte +8 -6
@@ 6,7 6,7 @@
        stagger,
        type TimelineDefinition,
    } from "motion";
    import { afterUpdate, onMount } from "svelte";
    import { afterUpdate, onMount, setContext } from "svelte";

    const expo_out: Easing = [0.16, 1, 0.3, 1];
    const padding = 24;


@@ 16,6 16,8 @@
    export let trigger: Element | undefined = undefined;
    export let title: string;
    export let anchor: "left" | "right" = "left";
    export let mode: "list" | "freestyle" = "list";
    setContext("thecloser", () => close_menu());

    let menu: HTMLDialogElement | null = null;
    let bg: HTMLDivElement | null = null;


@@ 24,7 26,7 @@
    // TODO: this makes the items invisible!
    let reduced_motion = true;

    function outside_click(event: MouseEvent) {        
    function outside_click(event: MouseEvent) {
        if (
            open &&
            event.target &&


@@ 57,7 59,7 @@
            ],
        ];

        if (!$$slots.notamenu) {
        if (mode == "list") {
            timeline_def.push([
                //@ts-ignore this is fine. it works
                content.children,


@@ 85,7 87,7 @@
                },
            ]);
        }
        

        if (anchor == "right") {
            xy[0] -= menu!.offsetWidth;
        }


@@ 146,9 148,9 @@
        {open}
    >
        <div bind:this={bg} class="bg" aria-hidden></div>
        {#if $$slots.notamenu}
        {#if mode == "freestyle"}
            <div bind:this={content}>
                <slot name="notamenu" />
                <slot />
            </div>
        {:else}
            <ul role="menu" bind:this={content} class="content">

M src/lib/Bonus/MenuItem.svelte => src/lib/Bonus/MenuItem.svelte +10 -2
@@ 1,12 1,20 @@
<script lang="ts">
    import Icon from "$lib/Icons/Icon.svelte";
    import { getContext } from "svelte";

    export let icon: string | undefined = undefined,
        shortcut: string[] = [];
        shortcut: string[] = [],
        autoclose = true;

    const the_closer = getContext<() => void | undefined>("thecloser");
</script>

<li>
    <button on:click role="menuitem">
    <button
        on:click
        on:click={() => (autoclose && the_closer ? the_closer() : null)}
        role="menuitem"
    >
        {#if icon}
            <Icon source={icon}></Icon>
        {/if}

A src/lib/Bonus/PrefsFooter.svelte => src/lib/Bonus/PrefsFooter.svelte +94 -0
@@ 0,0 1,94 @@
<script lang="ts">
    import { animate } from "motion";
    import { onDestroy, onMount } from "svelte";

    let scrolled = false,
        footer: HTMLElement,
        bg: HTMLDivElement,
        observer: IntersectionObserver;

    onMount(() => {
        observer = new IntersectionObserver(
            ([e]) => {
                if (e.intersectionRatio == 1) {
                    enbiggen();
                } else {
                    ensmallen();
                }
            },
            {
                threshold: [1],
                rootMargin: "0px 0px -1px 0px",
            },
        );

        observer.observe(footer);
    });

    function enbiggen() {
        if (bg) {
            animate(
                bg,
                {
                    opacity: 0,
                },
                {
                    duration: 0.1,
                    easing: "ease-out",
                },
            ).finished.then(() => (bg.style.display = "none"));
        }
        scrolled = false;
    }

    function ensmallen() {
        if (bg) {
            bg.style.display = "block";
            animate(
                bg,
                {
                    opacity: 1,
                },
                {
                    duration: 0.1,
                    easing: "ease-out",
                },
            );
        }
        scrolled = true;
    }

    onDestroy(() => observer.disconnect());
</script>

<div bind:this={bg} class="bg" class:scrolled></div>

<footer bind:this={footer} class:scrolled {...$$restProps}>
    <slot {scrolled} />
</footer>

<style>
    footer {
        position: sticky;
        bottom: 0px;
        width: 100%;
        height: 3.5rem;
        display: flex;
        align-items: end;
        padding: 1rem 0;
    }

    .bg {
        position: fixed;
        bottom: 0;
        left: 0;
        width: 100vw;
        height: 3.5rem;
        pointer-events: none;
        background: color-mix(in srgb, var(--background), var(--foreground) 7.5%);

        &.scrolled {
            opacity: 1;
        }
    }
</style>

M src/lib/Bonus/SensibleMenu.svelte => src/lib/Bonus/SensibleMenu.svelte +4 -5
@@ 3,7 3,8 @@

    export let open = false,
        title: string,
        anchor: "left" | "right" = "left";
        anchor: "left" | "right" = "left",
        mode: "list" | "freestyle" = "list";

    let xy: [number, number] = [0, 0],
        trigger: HTMLButtonElement;


@@ 17,8 18,6 @@

<slot name="button" open={open_menu} />

<Menu bind:open bind:trigger bind:xy {title} {anchor}>
    <svelte:fragment slot="notamenu">
        <slot />
    </svelte:fragment>
<Menu bind:open bind:trigger bind:xy bind:mode {title} {anchor}>
    <slot />
</Menu>

M src/lib/Colours/colours.json => src/lib/Colours/colours.json +14 -114
@@ 1,116 1,16 @@
[
  {
    "background": "#fafafa",
    "foreground": "#0a0a0a",
    "backgroundTone": "#f6f6f6",
    "foregroundTone": "#161616"
  },
  {
    "background": "#faf972",
    "foreground": "#3a2e0c",
    "backgroundMix": "black",
    "foregroundMix": "black",
    "backgroundTone": "#E4E35E",
    "foregroundTone": "#574921"
  },
  {
    "background": "#e7dbf4",
    "foreground": "#71213b",
    "backgroundMix": "white",
    "foregroundMix": "black",
    "backgroundTone": "#e7dbf4",
    "foregroundTone": "#71213b"
  },
  {
    "background": "#E7F6DA",
    "foreground": "#7C5DB4",
    "backgroundMix": "white",
    "foregroundMix": "black",
    "backgroundTone": "#dbe2d5",
    "foregroundTone": "#7658ad"
  },
  {
    "background": "#2f024c",
    "foreground": "#00ff7f",
    "backgroundMix": "white",
    "foregroundMix": "white",
    "backgroundTone": "#4F1276",
    "foregroundTone": "#1BDA7A"
  },
  {
    "background": "#287b61",
    "foreground": "#effaec",
    "backgroundMix": "black",
    "foregroundMix": "white",
    "backgroundTone": "#349979",
    "foregroundTone": "#DAEBD5"
  },
  {
    "background": "#731919",
    "foreground": "#f6bc06",
    "backgroundMix": "black",
    "foregroundMix": "white",
    "backgroundTone": "#5C1010",
    "foregroundTone": "#DAAA16"
  },
  {
    "background": "#f9f3fe",
    "foreground": "#8929ea",
    "backgroundTone": "#f0e5fb",
    "foregroundTone": "#a65df0"
  },
  {
    "background": "#8A1C22",
    "foreground": "#F5C79E",
    "backgroundTone": "#7f181d",
    "foregroundTone": "#eab98f"
  },
  {
    "background": "#0000FF",
    "foreground": "#FFFFFF",
    "backgroundTone": "#0000D0",
    "foregroundTone": "#DADADA"
  },
  {
    "background": "#02635F",
    "foreground": "#26EBCE",
    "backgroundTone": "#025451",
    "foregroundTone": "#1edbbf"
  },
  {
    "background": "#E173CC",
    "foreground": "#570B3E",
    "backgroundTone": "#d365bf",
    "foregroundTone": "#490834"
  },
  {
    "background": "#030A0D",
    "foreground": "#B8604C",
    "backgroundTone": "#0a161c",
    "foregroundTone": "#9e4c3a"
  },
  {
    "background": "#0D2714",
    "foreground": "#94AB5B",
    "backgroundTone": "#14381e",
    "foregroundTone": "#859b4f"
  },
  {
    "background": "#addcd3",
    "foreground": "#6712ba",
    "backgroundTone": "#98cec4",
    "foregroundTone": "#5d0faa"
  },
  {
    "background": "#f4edf3",
    "foreground": "#a53554",
    "backgroundTone": "#eadae8",
    "foregroundTone": "#9e314f"
  },
  {
    "background": "#DAD5FD",
    "foreground": "#7111F9",
    "backgroundTone": "#cec8f4",
    "foregroundTone": "#7b24f4"
  }
  ["b&w", "#fafafa", "#0a0a0a"],
  ["high contrast", "#FFFFFF", "#0000FF"],
  ["sandy", "#FEF3C4", "#6B4336"],
  ["wes anderson", "#872720", "#F9C600"],
  ["red", "#F4EDF3", "#A53554"],
  ["joe", "#F8D1AE", "#9E2C2C"],
  ["halloween", "#341606", "#F25C21"],
  ["rust", "#040C10", "#C6755E"],
  ["forestry", "#81CE8B", "#144D31"],
  ["swampy", "#0F3219", "#A4B76E"],
  ["foliage-y", "#F2FAF0", "#2F8C74"],
  ["quiet", "#E7F6DA", "#7C5DB4"],
  ["parma violet", "#E7E4FB", "#490DF4"],
  ["charcoal", "#021F2C", "#808FA6"]
]

M src/lib/Colours/index.ts => src/lib/Colours/index.ts +8 -24
@@ 1,36 1,26 @@
import { set, z } from 'zod';
import coloursJson from './colours.json';
import { writable, type Writable } from 'svelte/store';
import { notify_colours_changed } from '$lib/switching';

const colour_set_schema = z.object({
	background: z.string(),
	foreground: z.string(),
	backgroundTone: z.string(),
	foregroundTone: z.string(),
});
export type ColourSet = [name: string, bg: string, fg: string]
export const colours: ColourSet[] = coloursJson as ColourSet[];

export type ColourSet = z.infer<typeof colour_set_schema>;

export const colours: ColourSet[] = coloursJson;

export const colour_store: Writable<ColourSet> = writable();
export const colour_store: Writable<ColourSet> = writable(colours[0]);

const local_storage_key = 'colours';

export function get_current_colours() {
	const json = localStorage.getItem(local_storage_key);
	if (!json) return colours[0];
	const value = localStorage.getItem(local_storage_key);
	if (!value) return colours[0];

	const parsed = colour_set_schema.parse(JSON.parse(json));
	return parsed;
	return JSON.parse(value);
}

export function update_colours(
	colours: ColourSet,
	[name , bg, fg]: ColourSet,
	opt?: { reverse?: boolean },
) {
	const value = opt?.reverse ? reverse_colours(colours) : colours;
	const value: ColourSet = opt?.reverse ? [name, fg, bg] : [name, bg, fg]
	notify_colours_changed(value);
	set_colours(value);
}


@@ 40,9 30,3 @@ export function set_colours(set: ColourSet) {
	localStorage.setItem(local_storage_key, JSON.stringify(set));
}

export const reverse_colours = (colours: ColourSet): ColourSet => ({
	background: colours.foreground,
	foreground: colours.background,
	backgroundTone: colours.foregroundTone,
	foregroundTone: colours.backgroundTone,
});

M src/lib/Composer/CwMenu.svelte => src/lib/Composer/CwMenu.svelte +1 -1
@@ 4,7 4,7 @@
    import Action from "./Action.svelte";
</script>

<SensibleMenu title="Content Warnings">
<SensibleMenu title="Content Warnings" mode="freestyle">
    <Action
        let:open
        slot="button"

M src/lib/Composer/EmojiPicker.svelte => src/lib/Composer/EmojiPicker.svelte +1 -1
@@ 14,7 14,7 @@
    const dispatch = createEventDispatcher();
</script>

<SensibleMenu title="Emoji Picket">
<SensibleMenu title="Emoji Picket" mode="freestyle">
    <Action let:open slot="button" title="Emoji" label="Emoji" on:click={open}>
        :)
    </Action>

M src/lib/Composer/MoreMenu.svelte => src/lib/Composer/MoreMenu.svelte +1 -1
@@ 4,7 4,7 @@
    import Action from "./Action.svelte";
</script>

<SensibleMenu title="More">
<SensibleMenu title="More" mode="freestyle">
    <Action
        let:open
        slot="button"

M src/lib/Navigation/Navigation.svelte => src/lib/Navigation/Navigation.svelte +12 -3
@@ 4,8 4,11 @@
	import ComposerDialog from "$lib/Composer/ComposerDialog.svelte";
	import CollapsedNav from "./CollapsedNav.svelte";
	import { getContext } from "svelte";
	import PrefsMenu from "./PrefsMenu.svelte";

	export let username: string | undefined, admin: boolean | undefined;
	export let username: string | undefined,
		admin: boolean | undefined,
		email: string | undefined;

	const available_pages = getContext(
		"available_pages",


@@ 15,7 18,7 @@

<nav bind:this={nav}>
	<h1>Basingstoke <span class="alpha">the alpha!!</span></h1>
	{#if username}
	{#if username && email}
		<a aria-label="Home" href="/">Timeline</a>
		<a href="#content">Skip Navigation</a>
		<ComposerDialog let:open>


@@ 29,7 32,9 @@
			<a href="/its/{username}">@{username}</a>
		</UserMenu>
		<a href="/its/{username}?filter=drafts">Drafts</a>
		<a href="/prefs" aria-label="Preferences">Prefs</a>
		<PrefsMenu {username} {email}>
			<a href="/prefs" aria-label="Preferences">Prefs</a>
		</PrefsMenu>
		{#if admin}
			<a href="/admin">Admin</a>
		{/if}


@@ 56,8 61,12 @@

		z-index: 100;

		max-width: 20rem;
		width: 20rem;
		min-width: 20rem;

		height: max-content;
		margin: 0;

		padding: 5rem 0;
	}

A src/lib/Navigation/PrefsMenu.svelte => src/lib/Navigation/PrefsMenu.svelte +100 -0
@@ 0,0 1,100 @@
<script lang="ts">
    import { goto } from "$app/navigation";
    import ContextMenu from "$lib/Bonus/ContextMenu.svelte";
    import Item from "$lib/Bonus/MenuItem.svelte";
    import { colours } from "$lib/Colours";
    import {
        Accessibility,
        Bell,
        CircleUser,
        Cog,
        Cube,
        Hand,
        HeadSide,
        List,
        PencilSquare,
        Rss,
        Swatches,
        User,
    } from "$lib/Icons";

    export let username: string, email: string;
</script>

<ContextMenu anchor="right">
    <slot />
    <svelte:fragment slot="menu">
        <ul class="colours">
            {#each colours.slice(0, 6) as colour}
                <li class="colour">
                    <div style="--colour: {colour[1]}" />
                    <div style="--colour: {colour[2]}" />
                </li>
            {/each}
            <li class="colour more">-></li>
        </ul>
        <h3>@{username}</h3>
        <Item on:click={() => goto("/prefs/profile")} icon={CircleUser}>
            Profile
        </Item>
        <Item on:click={() => goto("/prefs/stationery")} icon={PencilSquare}>
            Stationery
        </Item>
        <Item on:click={() => goto("/prefs/syndication")} icon={Rss}>
            Syndication
        </Item>
        <h3>{email}</h3>
        <Item on:click={() => goto("/prefs/account")} icon={List}>Details</Item>
        <Item on:click={() => goto("/prefs/appearance")} icon={Swatches}>
            Appearance
        </Item>
        <Item
            on:click={() => goto("/prefs/accessibility")}
            icon={Accessibility}
        >
            Accessibility
        </Item>
        <Item on:click={() => goto("/prefs/wellbeing")} icon={HeadSide}>
            Wellbeing
        </Item>
        <Item on:click={() => goto("/prefs/pings")} icon={Bell}>Pings</Item>
        <Item on:click={() => goto("/prefs/apps")} icon={Cube}>Applications</Item>
        <Item on:click={() => goto("/prefs/safety")} icon={Hand}>Safety</Item>
    </svelte:fragment>
</ContextMenu>

<style>
    ul {
        list-style: none;
        padding: 0;
        display: flex;
        justify-content: space-between;
        max-width: 16rem;
        padding: 0.25rem 0.5rem;
    }

    li {
        min-width: 1.5rem;
        height: 1.5rem;
        aspect-ratio: 1 / 1;
        display: grid;
        border-radius: 2px;
        overflow: hidden;
        grid-template-columns: 1fr 1fr;
    }

    div {
        background-color: var(--colour);
    }

    .more {
        display: flex;
        align-items: center;
        justify-content: center;
        background-color: color-mix(
            in srgb,
            var(--foreground) 15%,
            transparent
        );
    }
</style>

M src/routes/(guarded)/prefs/appearance/ColourPicker.svelte => src/routes/(guarded)/prefs/appearance/ColourPicker.svelte +33 -26
@@ 2,34 2,41 @@
	import {
		colour_store,
		colours,
		reverse_colours,
		update_colours,
	} from '$lib/Colours';
		type ColourSet,
	} from "$lib/Colours";

	const is_selected = (colour_set: ColourSet) =>
		$colour_store.includes(colour_set[1]) &&
		$colour_store.includes(colour_set[2]);

	const is_selected_strict = (colour_set: ColourSet) =>
		$colour_store[1] == colour_set[1] && $colour_store[2] == colour_set[2];
</script>

<ul>
	{#each colours as colour_set}
		<li
			style="
				--set-background: {colour_set.background};
				--set-foreground: {colour_set.foreground};
				--set-background-tone: {colour_set.backgroundTone};
				--set-foreground-tone: {colour_set.foregroundTone};
        "
		>
			<button
				class:selected={$colour_store == colour_set ||
					$colour_store == reverse_colours(colour_set)}
				on:click={() =>
					update_colours(colour_set, {
						reverse: $colour_store == colour_set,
					})}
	{#key $colour_store}
		{#each colours as colour_set}
			<li
				title={colour_set[0]}
				style="--set-background: {colour_set[1]};
				--set-foreground: {colour_set[2]};
				--set-background-tone: color-mix(in srgb, {colour_set[1]}, {colour_set[2]} 10%);
				--set-foreground-tone: color-mix(in srgb, {colour_set[2]}, {colour_set[1]} 10%);"
			>
				<div class="foreground" />
				<div class="background" />
			</button>
		</li>
	{/each}
				<button
					class:selected={is_selected(colour_set)}
					on:click={() =>
						update_colours(colour_set, {
							reverse: is_selected_strict(colour_set),
						})}
				>
					<div class="foreground" />
					<div class="background" />
				</button>
			</li>
		{/each}
	{/key}
</ul>

<style>


@@ 71,11 78,11 @@
	}

	button:hover > .background {
		background-color: var(--set-background-tone)
		background-color: var(--set-background-tone);
	}

	button:hover > .foreground {
		background-color: var(--set-foreground-tone)
		background-color: var(--set-foreground-tone);
	}

	.selected {


@@ 99,6 106,6 @@
		place-items: center;
		justify-content: center;

		content: '✓';
		content: "✓";
	}
</style>

M src/routes/(guarded)/prefs/profile/+page.svelte => src/routes/(guarded)/prefs/profile/+page.svelte +2 -1
@@ 15,7 15,8 @@
<Header parents={[["preferences", "/prefs"]]}>profile</Header>

<p>
	put some flavour text here lol <br />
	put some words about yourself here and they'll show up in various places
	<br />
</p>

{#if browser}

M src/routes/(guarded)/prefs/profile/FancyForm.svelte => src/routes/(guarded)/prefs/profile/FancyForm.svelte +63 -48
@@ 1,7 1,12 @@
<script lang="ts">
    import PrefsFooter from "$lib/Bonus/PrefsFooter.svelte";
    import Button from "$lib/Buttons/Button.svelte";
    import FormInput from "$lib/Forms/FormInput.svelte";
    import { MAX_DISPLAY_NAME_LEN, MAX_HOMEPAGE_LEN, MAX_PRONOUNS_LEN } from "$lib/constants";
    import {
        MAX_DISPLAY_NAME_LEN,
        MAX_HOMEPAGE_LEN,
        MAX_PRONOUNS_LEN,
    } from "$lib/constants";
    import type { UserPageData } from "$lib/server/Model/userpage";
    import ImageControls from "./ImageControls.svelte";



@@ 12,57 17,67 @@

<form method="post" class="grid">
    <div class="text">
            <FormInput
                label="Display Name"
                flavour_text="a bit of text that gets put next to your username in various places"
            >
                <input
                    bind:value={display_name}
                    maxlength={MAX_DISPLAY_NAME_LEN}
                    type="text"
                    name="display_name"
                />
            </FormInput>
            <FormInput
                label="Pronouns"
                flavour_text="how people should address you (e.g., they/them)"
            >
                <input
                    bind:value={pronouns}
                    maxlength={MAX_PRONOUNS_LEN}
                    name="pronouns"
                    type="text"
                />
            </FormInput>
            <FormInput
                label="Homepage"
                flavour_text="a link to your webbed site, if you've got one"
            >
                <input
                    bind:value={homepage}
                    maxlength={MAX_HOMEPAGE_LEN}
                    type="url"
                    name="homepage"
                />
            </FormInput>
            <FormInput
                label="Bio"
                flavour_text="some words to let the world know who you are
        <FormInput
            label="Display Name"
            flavour_text="a bit of text that gets put next to your username sometimes"
        >
            <input
                bind:value={display_name}
                maxlength={MAX_DISPLAY_NAME_LEN}
                type="text"
                name="display_name"
            />
        </FormInput>
        <FormInput
            label="Pronouns"
            flavour_text="how people should address you (e.g., they/them)"
        >
            <input
                bind:value={pronouns}
                maxlength={MAX_PRONOUNS_LEN}
                name="pronouns"
                type="text"
            />
        </FormInput>
        <FormInput
            label="Homepage"
            flavour_text="a link to your webbed site, if you've got one"
        >
            <input
                bind:value={homepage}
                maxlength={MAX_HOMEPAGE_LEN}
                type="url"
                name="homepage"
            />
        </FormInput>
        <FormInput
            label="Bio"
            flavour_text="some words to let the world know who you are
                and what your deal is. markdown works here."
            >
                <textarea
                    bind:value={bio}
                    rows="12"
                    name="bio"
                    style="resize: none"
                />
            </FormInput>
        >
            <textarea
                bind:value={bio}
                rows="12"
                name="bio"
                style="resize: none"
            />
        </FormInput>
    </div>
    <ImageControls {page} />
    <PrefsFooter
        style="grid-column: 1 / span 2; justify-self: end;"
        let:scrolled
    >
        <Button size="medium">
            ↶ Revert
        </Button>

    <Button style="grid-column: 2; justify-self: end;" size="big" type="submit">
        Save ✓
    </Button>
        <Button
            size={scrolled ? "medium" : "big"}
            style="margin-left: auto;"
            type="submit">Save ✓</Button
        >
    </PrefsFooter>
</form>

<style>

M src/routes/(guarded)/prefs/profile/ProfileForm.svelte => src/routes/(guarded)/prefs/profile/ProfileForm.svelte +3 -3
@@ 1,8 1,8 @@
<script lang="ts">
	import {
		MAX_DISPLAY_NAME_LEN,
		MAX_HOMEPAGE_LEN,
		MAX_PRONOUNS_LEN,
	    MAX_DISPLAY_NAME_LEN,
	    MAX_HOMEPAGE_LEN,
	    MAX_PRONOUNS_LEN,
	} from "$lib/constants";

	export let data,

M src/routes/+layout.svelte => src/routes/+layout.svelte +8 -11
@@ 16,7 16,7 @@
	import { themed_favicon } from "$lib/Bonus/favicon";
	import { setContext } from "svelte";
	import HeadsUpses from "$lib/Pings/HeadsUpses.svelte";
    import Footer from "./Footer.svelte";
	import Footer from "./Footer.svelte";

	export let data;



@@ 25,11 25,11 @@
	}

	$: style = `
			--foreground-color: ${$colour_store?.foreground ?? "black"};
			--background-color: ${$colour_store?.background ?? "white"};
			--foreground-tone:  ${$colour_store?.foregroundTone};
			--background-tone:  ${$colour_store?.backgroundTone};
		`;
	--foreground-color: ${$colour_store[2] ?? "black"};
	--background-color: ${$colour_store[1] ?? "white"};
	--foreground-tone: color-mix(in srgb, ${$colour_store[2]}, ${$colour_store[1]} 10%);
	--background-tone: color-mix(in srgb, ${$colour_store[1]}, ${$colour_store[2]} 10%);
	`;

	$: meta = $page.url.pathname.startsWith("/meta");



@@ 43,10 43,7 @@
	{#if browser}
		<link
			rel="icon"
			href={themed_favicon(
				$colour_store?.foreground,
				$colour_store?.background,
			)}
			href={themed_favicon($colour_store[2], $colour_store[1])}
		/>
	{/if}
</svelte:head>


@@ 62,6 59,7 @@
		<Navigation
			admin={data.user?.administrator}
			username={data.page?.username}
			email={data.user?.email}
		/>
	{/if}
	<main class:meta id="content"><slot /></main>


@@ 69,7 67,6 @@
	<Footer wisdom={data.wisdom} />
</div>


<style>
	div {
		--background: var(--background-color);

M src/routes/Footer.svelte => src/routes/Footer.svelte +9 -6
@@ 1,7 1,5 @@
<script lang="ts">
    import Button from "$lib/Buttons/Button.svelte";
    import { ArrowUp } from "$lib/Icons";
    import Icon from "$lib/Icons/Icon.svelte";

    export let wisdom: string;
</script>


@@ 9,10 7,15 @@
<footer>
    <div class="inner">
        <section>
            <h1>Basingstoke A1</h1>
            <h1>Basingstoke</h1>
            <p>
                Basingstoke is licensed under the GNU Affero General Public
                License
                Basingstoke is licensed under the
                <a
                    target="_blank"
                    href="https://git.sr.ht/~leah/basingstoke/tree/main/item/LICENSE"
                >
                    GNU Affero General Public License
                </a>
            </p>
            <ul>
                <li>


@@ 64,7 67,7 @@
        background: linear-gradient(
            to bottom,
            var(--background),
            color-mix(in srgb, var(--foreground) 20%, transparent)
            color-mix(in srgb, var(--foreground) 15%, transparent)
        );
        margin-left: -1rem;
        margin-right: -1rem;

M src/routes/its/[username]/Filters.svelte => src/routes/its/[username]/Filters.svelte +19 -10
@@ 17,14 17,16 @@

    let trigger: HTMLButtonElement, open: boolean, xy: [number, number];

    const filters = <{ id: FilterType; label: string }[]>[
        { id: "everything", label: own_page ? "Public" : "All Posts" },
        own_page ? { id: "drafts", label: "Drafts" } : undefined,
        own_page ? { id: "bookmarks", label: "Bookmarks" } : undefined,
        { id: "pinned", label: "Pinned" },
        own_page ? { id: "yeahd", label: "Yeah'd" } : undefined,
        { id: "media", label: "Media" },
    ].filter((o) => o);
    const filters = <{ id: FilterType; label: string }[]>(
        [
            { id: "everything", label: own_page ? "Public" : "All Posts" },
            own_page ? { id: "drafts", label: "Drafts" } : undefined,
            own_page ? { id: "bookmarks", label: "Bookmarks" } : undefined,
            { id: "pinned", label: "Pinned" },
            own_page ? { id: "yeahd", label: "Yeah'd" } : undefined,
            { id: "media", label: "Media" },
        ].filter((o) => o)
    );

    function onclick(e: unknown) {
        let event = e as MouseEvent & {


@@ 55,8 57,15 @@
    <button bind:this={trigger} on:click={onclick}>
        <Icon source={Sliders} label="Settings" />
    </button>
    <Menu anchor="right" title="Filter Settings" bind:trigger bind:xy bind:open>
        <MenuProse class="" slot="notamenu" style="height:max-content">
    <Menu
        anchor="right"
        mode="freestyle"
        title="Filter Settings"
        bind:trigger
        bind:xy
        bind:open
    >
        <MenuProse style="height:max-content">
            <h1>Filters</h1>
            <div class="boxes">
                <Checkbox checked bigness="maybe">Show Reposts</Checkbox>

A src/routes/its/[username]/MoreButton.svelte => src/routes/its/[username]/MoreButton.svelte +25 -0
@@ 0,0 1,25 @@
<script lang="ts">
    import MenuItem from "$lib/Bonus/MenuItem.svelte";
    import SensibleMenu from "$lib/Bonus/SensibleMenu.svelte";
    import Button from "$lib/Buttons/Button.svelte";
    import type { UserPageData } from "$lib/server/Model/userpage";
    import Icon from "$lib/Icons/Icon.svelte";
    import { ThreeDotsHorizontal, At, DoorOpen } from "$lib/Icons";

    export let page: UserPageData;
</script>

<SensibleMenu title="Page Actions">
    <svelte:fragment slot="button" let:open>
        <Button on:click={(e) => open(e)}>
            More
            <Icon source={ThreeDotsHorizontal} />
        </Button>
    </svelte:fragment>
    <MenuItem icon={At}>Mention</MenuItem>
    <hr />
    <MenuItem>Mute</MenuItem>
    <MenuItem>Block</MenuItem>
    <MenuItem>Block Instance</MenuItem>
    <MenuItem>Report</MenuItem>
</SensibleMenu>

M src/routes/its/[username]/ProfileHeader.svelte => src/routes/its/[username]/ProfileHeader.svelte +4 -0
@@ 6,6 6,7 @@
    import { Pencil } from "$lib/Icons";
    import Icon from "$lib/Icons/Icon.svelte";
    import type { UserPageData } from "$lib/server/Model/userpage";
    import MoreButton from "./MoreButton.svelte";

    export let page: UserPageData;
    export let own_page = false;


@@ 40,6 41,7 @@
            {:else}
                <UrlHandlerRedirectDialog {page} />
            {/if}
            <MoreButton {page} />
        </div>
    </div>



@@ 92,6 94,8 @@
    }

    div.buttons {
        display: flex;
        gap: 1rem;
        margin-top: auto;
        margin-bottom: 0.5rem;
    }

M tests/test.ts => tests/test.ts +1 -4
@@ 1,6 1,3 @@
import { expect, test } from '@playwright/test';

test('index page has expected h1', async ({ page }) => {
	await page.goto('/');
	await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
});
// todo :3
\ No newline at end of file