Open image
+
diff --git a/src/routes/(public)/explore/users/+page.svelte b/src/routes/(public)/explore/users/+page.svelte
index daa93e53..eb5268a1 100644
--- a/src/routes/(public)/explore/users/+page.svelte
+++ b/src/routes/(public)/explore/users/+page.svelte
@@ -7,9 +7,10 @@
import Pagination from "$lib/components/misc/pagination.svelte";
import ItemsPerPageSelector from "$lib/components/misc/itemsPerPageSelector.svelte";
import SortSelector from "$lib/components/misc/sortSelector.svelte";
+ import type { DetailedProfile } from "$lib/queries/detailedProfiles";
- const { data } = $props();
- const { profiles } = data;
+ const { data } = $props<{ data: { profiles: DetailedProfile[] } }>();
+ const profiles: DetailedProfile[] = data.profiles;
let searchTerm: string = $state(""); // Text input for search bar
diff --git a/src/routes/(public)/explore/users/+page.ts b/src/routes/(public)/explore/users/+page.ts
index 4ef8f1f0..9611e9ee 100644
--- a/src/routes/(public)/explore/users/+page.ts
+++ b/src/routes/(public)/explore/users/+page.ts
@@ -1,15 +1,16 @@
import type { PageLoad } from "./$types";
import { supabase } from "$lib/supabaseClient";
+import { queryDetailedProfiles } from "$lib/queries/detailedProfiles";
import { error } from "@sveltejs/kit";
export const load = (async ({ setHeaders }) => {
- const { data: profiles, error: err } = await supabase
- .from("v_detailed_profiles")
- .select("*")
+ const { data, error: err } = await queryDetailedProfiles(supabase)
.order("id", { ascending: true });
if (err) throw error(500, err.message);
+ const profiles = data ?? [];
+
setHeaders({
"Cache-Control": "public, s-maxage=600, stale-while-revalidate=86400",
});
diff --git a/src/routes/(public)/manifest.json b/src/routes/(public)/manifest.json
new file mode 100644
index 00000000..61a52380
--- /dev/null
+++ b/src/routes/(public)/manifest.json
@@ -0,0 +1,68 @@
+{
+ "name": "CubeIndex{SUFFIX}",
+ "short_name": "CubeIndex{SUFFIX}",
+ "description": "Track and explore speedcubes",
+ "theme_color": "#044eb4",
+ "background_color": "#FFFFFF",
+ "orientation": "any",
+ "id": "/",
+ "start_url": "/",
+ "display_override": ["window-controls-overlay"],
+ "display": "standalone",
+ "icons": [
+ {
+ "src": "images/icons/manifest-icon-192.maskable.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "images/icons/manifest-icon-192.maskable.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable"
+ },
+ {
+ "src": "images/icons/manifest-icon-512.maskable.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "images/icons/manifest-icon-512.maskable.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ }
+ ],
+ "screenshots": [
+ {
+ "src": "/CI-desktop-home.webp",
+ "sizes": "1920x1080",
+ "type": "image/webp",
+ "form_factor": "wide",
+ "label": "Home screen showing catchline"
+ },
+ {
+ "src": "/CI-mobile-home.webp",
+ "sizes": "1440x3040",
+ "type": "image/webp",
+ "platform": "android",
+ "label": "Home screen showing catchline"
+ },
+ {
+ "src": "/CI-desktop-cubes.webp",
+ "sizes": "1920x1080",
+ "type": "image/webp",
+ "form_factor": "wide",
+ "label": "Page to explore the cubes database"
+ },
+ {
+ "src": "/CI-mobile-cubes.webp",
+ "sizes": "1440x3040",
+ "type": "image/webp",
+ "platform": "android",
+ "label": "Page to explore the cubes database"
+ }
+ ]
+}
diff --git a/src/routes/(public)/manifest.webmanifest/+server.ts b/src/routes/(public)/manifest.webmanifest/+server.ts
new file mode 100644
index 00000000..4df03bdd
--- /dev/null
+++ b/src/routes/(public)/manifest.webmanifest/+server.ts
@@ -0,0 +1,24 @@
+import { PUBLIC_DEPLOYMENT_CHANNEL } from '$env/static/public';
+import { readFile } from 'fs/promises';
+import path from 'path';
+import type { RequestHandler } from './$types';
+
+export const prerender = true;
+
+export const GET: RequestHandler = async () => {
+ const manifestPath = path.resolve('src/routes/(public)/manifest.json');
+ let raw = await readFile(manifestPath, { encoding: 'utf-8' });
+
+ const suffix = (PUBLIC_DEPLOYMENT_CHANNEL?.toLowerCase() === 'beta') ? ' Beta' : '';
+
+ raw = raw
+ .replace(/{SUFFIX}/g, suffix);
+
+ const manifest = JSON.parse(raw);
+
+ return new Response(JSON.stringify(manifest), {
+ headers: {
+ 'Content-Type': 'application/manifest+json'
+ }
+ });
+};
diff --git a/src/routes/(public)/pricing/+page.svelte b/src/routes/(public)/pricing/+page.svelte
index 9169f66b..0cfb3925 100644
--- a/src/routes/(public)/pricing/+page.svelte
+++ b/src/routes/(public)/pricing/+page.svelte
@@ -1,6 +1,5 @@
@@ -12,7 +11,7 @@ import { PUBLIC_KOFI_URL } from "$env/static/public";
/>
-
+
diff --git a/src/routes/(public)/user/[username]/achievements/+page.svelte b/src/routes/(public)/user/[username]/achievements/+page.svelte
index 9d734dfe..d7352774 100644
--- a/src/routes/(public)/user/[username]/achievements/+page.svelte
+++ b/src/routes/(public)/user/[username]/achievements/+page.svelte
@@ -2,7 +2,6 @@
import type { PageData } from "./$types";
import { formatDate } from "$lib/components/helper_functions/formatDate.svelte";
import SearchBar from "$lib/components/misc/searchBar.svelte";
- import FilterSidebar from "$lib/components/misc/filterSidebar.svelte";
import Pagination from "$lib/components/misc/pagination.svelte";
import SortSelector from "$lib/components/misc/sortSelector.svelte";
@@ -11,7 +10,6 @@
// Search, filters, sort, pagination
let searchTerm: string = $state("");
- let showFilters = $state(false);
let selectedRarity: string = $state("All");
let onlyWithDescription: boolean = $state(false);
@@ -29,16 +27,6 @@
// Derived
const total = $derived(user_achievements.length);
- const rarities: string[] = $derived(
- Array.from(new Set(user_achievements.map((a) => a.rarity).filter(Boolean))).sort()
- );
- const uniqueRaritiesCount = $derived(rarities.length);
- const lastEarned = $derived(
- user_achievements
- .map((a) => a.created_at)
- .filter(Boolean)
- .sort((a, b) => new Date(b).getTime() - new Date(a).getTime())[0]
- );
// Centralized rarity styles → maintainable & consistent
type RarityKey =
@@ -173,26 +161,6 @@
{/if}
-
- {#if total > 0}
-
-
-
-
-
Last Earned
-
- {lastEarned ? formatDate(lastEarned) : '—'}
-
-
-
-
- {/if}
-
(uc.status ?? "").toLowerCase() === "owned").length
- );
- const wishlistCount = $derived(
- user_cubes.filter((uc) => (uc.status ?? "").toLowerCase() === "wishlist").length
- );
- const uniqueTypesCount = $derived(
- new Set(user_cubes.map((c) => c.cube_model?.type).filter(Boolean)).size
- );
// Filtering and pagination
let searchTerm: string = $state("");
@@ -169,28 +160,6 @@
{/if}
-
-
-
-
-
-
Wishlist
-
{wishlistCount}
-
-
-
-
-
Unique Types
-
{uniqueTypesCount}
-
-
-
-
0
- ? Math.round(
- (user_cube_ratings.reduce((s, r) => s + (r.rating ?? 0), 0) / total) * 10
- ) / 10
- : 0
- );
- const withCommentsCount = $derived(
- user_cube_ratings.filter((r) => (r.comment ?? "").trim().length > 0).length
- );
const allTypes: string[] = $derived(
Array.from(
new Set(
@@ -143,30 +133,6 @@
{/if}
-
- {#if total > 0}
-
-
-
-
Total Ratings
-
{total}
-
-
-
-
-
Average
-
{averageRating}
-
-
-
-
-
With Comments
-
{withCommentsCount}
-
-
-
- {/if}
-
- {@html webManifest}
-
+ {siteTitle}