···11+export { default as StatusCard } from './StatusCard.svelte';
22+export { default as CodeBlock } from './CodeBlock.svelte';
33+export { default as Link } from './Link.svelte';
44+export { default as ShortLinkItem } from './ShortLinkItem.svelte';
55+export { default as ApiEndpoint } from './ApiEndpoint.svelte';
66+export { default as Section } from './Section.svelte';
77+export { default as ThemeToggle } from './ThemeToggle.svelte';
88+export { default as Spinner } from './Spinner.svelte';
99+export { default as CopyButton } from './CopyButton.svelte';
···11-import { SHORTCODE } from '$lib/constants'; // Import constants for shortcode configuration
22-import { parse, getDomain } from 'tldts'; // Import utility functions for domain parsing
11+import { SHORTCODE } from '$lib/constants'; // Import constants for shortcode configuration
22+import { parse, getDomain } from 'tldts'; // Import utility functions for domain parsing
3344// Constants related to the character set for encoding
55-const BASE_CHARS = SHORTCODE.CHARS; // Charset for the shortcode encoding
66-const BASE = BASE_CHARS.length; // Base (the number of unique characters in the charset)
55+const BASE_CHARS = SHORTCODE.CHARS; // Charset for the shortcode encoding
66+const BASE = BASE_CHARS.length; // Base (the number of unique characters in the charset)
7788// Hashes a given string into a bigint value
99function hashString(text: string): bigint {
1010- let hash = 1469598103934665603n; // FNV-1a hash initialisation value
1010+ let hash = 1469598103934665603n; // FNV-1a hash initialisation value
1111 for (let i = 0; i < text.length; i++) {
1212- const char = BigInt(text.charCodeAt(i)); // Convert each character to a bigint
1313- hash = (hash ^ char) * 1099511628211n; // FNV-1a hashing algorithm
1212+ const char = BigInt(text.charCodeAt(i)); // Convert each character to a bigint
1313+ hash = (hash ^ char) * 1099511628211n; // FNV-1a hashing algorithm
1414 }
1515- return hash < 0n ? -hash : hash; // Ensure the hash is positive
1515+ return hash < 0n ? -hash : hash; // Ensure the hash is positive
1616}
17171818// Converts a number (bigint) into a base encoded string with a given length
1919function toBase(num: bigint, length: number, seed = ''): string {
2020- let encoded = ''; // The resulting encoded string
2020+ let encoded = ''; // The resulting encoded string
2121 let n = num;
2222 for (let i = 0; i < length; i++) {
2323 let rem: bigint;
···3030 const fallback = hashString(num.toString() + '::' + seed + '::' + i.toString());
3131 rem = fallback % BigInt(BASE);
3232 }
3333- encoded = BASE_CHARS[Number(rem)] + encoded; // Prepend the character for this base value
3333+ encoded = BASE_CHARS[Number(rem)] + encoded; // Prepend the character for this base value
3434 }
3535 return encoded;
3636}
···4040 try {
4141 // Ensure the URL starts with 'https://' and parse it
4242 const parsed = new URL(url.startsWith('http') ? url : `https://${url}`);
4343- parsed.hash = ''; // Remove hash fragment
4343+ parsed.hash = ''; // Remove hash fragment
44444545 // Sort URL query parameters alphabetically
4646 const sortedParams = [...parsed.searchParams.entries()].sort((a, b) =>
4747 a[0].localeCompare(b[0])
4848 );
4949- parsed.search = ''; // Clear existing search parameters
5050- for (const [key, value] of sortedParams) parsed.searchParams.append(key, value); // Rebuild query string
4949+ parsed.search = ''; // Clear existing search parameters
5050+ for (const [key, value] of sortedParams) parsed.searchParams.append(key, value); // Rebuild query string
51515252- parsed.hostname = parsed.hostname.toLowerCase(); // Convert hostname to lowercase
5353- parsed.protocol = 'https:'; // Ensure HTTPS protocol is used
5454- return parsed.toString(); // Return the normalised URL as a string
5252+ parsed.hostname = parsed.hostname.toLowerCase(); // Convert hostname to lowercase
5353+ parsed.protocol = 'https:'; // Ensure HTTPS protocol is used
5454+ return parsed.toString(); // Return the normalised URL as a string
5555 } catch (e) {
5656 // If URL parsing fails, return the original URL (trimmed)
5757 return url.trim();
···7979 // Validate and adjust the length of the shortcode
8080 if (!Number.isInteger(length) || length < 3) length = SHORTCODE.DEFAULT_LENGTH;
81818282- const DOMAIN_PREFIX_LENGTH = 2; // Number of characters used for the domain prefix
8282+ const DOMAIN_PREFIX_LENGTH = 2; // Number of characters used for the domain prefix
83838484 // Normalise the URL and extract the base domain
8585 const normalised = normaliseUrl(url);
···9292 // Calculate the remaining length for the URL core and tail
9393 const remaining = Math.max(1, length - DOMAIN_PREFIX_LENGTH);
94949595- let hostname = ''; // The hostname portion of the URL
9595+ let hostname = ''; // The hostname portion of the URL
9696 try {
9797- hostname = new URL(normalised).hostname.toLowerCase(); // Try to extract hostname from normalised URL
9797+ hostname = new URL(normalised).hostname.toLowerCase(); // Try to extract hostname from normalised URL
9898 } catch (e) {
9999 // Fallback if URL parsing fails
100100 try {
101101 hostname = new URL(url.startsWith('http') ? url : `https://${url}`).hostname.toLowerCase();
102102 } catch {
103103- hostname = ''; // If both parsing attempts fail, leave hostname empty
103103+ hostname = ''; // If both parsing attempts fail, leave hostname empty
104104 }
105105 }
106106107107 let subLevels: string[] = [];
108108 // If there is a subdomain, split it into separate levels
109109 if (apex && hostname && hostname !== apex) {
110110- const sub = hostname.replace(new RegExp(`\.${apex}$`), ''); // Remove the apex domain
111111- subLevels = sub.split('.'); // Split subdomains by '.'
110110+ const sub = hostname.replace(new RegExp(`\.${apex}$`), ''); // Remove the apex domain
111111+ subLevels = sub.split('.'); // Split subdomains by '.'
112112 }
113113114114 // URL core length is determined based on the remaining space after the domain prefix
115115 const MIN_URL_CORE = 1;
116116 const MIN_TAIL = 1;
117117- const tailLength = remaining; // Length allocated to the tail portion of the shortcode
117117+ const tailLength = remaining; // Length allocated to the tail portion of the shortcode
118118119119 // Hash the normalised URL for the URL core portion of the shortcode
120120 const urlHash = hashString(normalised + '::url');
121121- const urlCoreLength = remaining - subLevels.length; // Account for subdomain levels
121121+ const urlCoreLength = remaining - subLevels.length; // Account for subdomain levels
122122 const urlCore = toBase(urlHash, Math.max(MIN_URL_CORE, urlCoreLength), 'url');
123123124124 // Generate subdomain-based tail (if applicable)
125125 const subTail: string[] = [];
126126- const reversedSubLevels = subLevels.slice().reverse(); // Reverse the subdomain levels for encoding
126126+ const reversedSubLevels = subLevels.slice().reverse(); // Reverse the subdomain levels for encoding
127127 for (let i = 0; i < reversedSubLevels.length; i++) {
128128- const h = hashString(reversedSubLevels[i] + '::sub'); // Hash the subdomain level
129129- subTail.push(toBase(h, 1, 'sub' + i)); // Add to subTail
128128+ const h = hashString(reversedSubLevels[i] + '::sub'); // Hash the subdomain level
129129+ subTail.push(toBase(h, 1, 'sub' + i)); // Add to subTail
130130 }
131131132132 // If no subdomain tail is generated, use a fallback hash for the tail
···138138139139 // Combine domain prefix, URL core, and tail to form the final shortcode
140140 let out = domainPrefix + urlCore + tail;
141141- if (out.length > length) out = out.slice(0, length); // Trim to the desired length
141141+ if (out.length > length) out = out.slice(0, length); // Trim to the desired length
142142 if (out.length < length) {
143143 // Pad the shortcode if it is too short
144144 let pad = '';
···148148 pad += toBase(h, Math.min(4, length - out.length - pad.length), 'pad2' + i);
149149 i++;
150150 }
151151- out += pad.slice(0, length - out.length); // Append padding to reach the correct length
151151+ out += pad.slice(0, length - out.length); // Append padding to reach the correct length
152152 }
153153154154 // --- LOGGING MAX COMBINATIONS --- (for debugging purposes)
155155- const maxCombinations = BigInt(BASE) ** BigInt(length); // Calculate the max possible combinations for the shortcode
155155+ const maxCombinations = BigInt(BASE) ** BigInt(length); // Calculate the max possible combinations for the shortcode
156156 console.log(`[Shortcode Info] URL: ${url}`);
157157 console.log(`[Shortcode Info] Length: ${length}, Charset: ${BASE} chars`);
158158 console.log(`[Shortcode Info] Max possible combinations: ${maxCombinations.toString()}`);
···160160 `[Shortcode Info] Domain prefix: ${domainPrefix}, URL core: ${urlCore}, Subdomain tail: ${tail}`
161161 );
162162163163- return out; // Return the final encoded shortcode
163163+ return out; // Return the final encoded shortcode
164164}
165165166166// Function to validate if a given shortcode is valid (contains only alphanumeric characters)
···170170171171// Function to calculate the maximum number of possible combinations for a shortcode of a given length
172172export function getMaxCombinations(length: number): number {
173173- return Math.pow(BASE, length); // BASE raised to the power of length
173173+ return Math.pow(BASE, length); // BASE raised to the power of length
174174}
+7-1
src/routes/+layout.svelte
···11<script lang="ts">
22 import '../app.css';
33+ import { ThemeToggle } from '$lib/components';
3445 let { children } = $props();
56</script>
···89 <script>
910 // Prevent flash of unstyled content (FOUC) by applying theme before page renders
1011 (function () {
1212+ const stored = localStorage.getItem('theme');
1113 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1214 const htmlElement = document.documentElement;
13151414- if (prefersDark) {
1616+ // Use stored theme if available, otherwise use system preference
1717+ const shouldBeDark = stored === 'dark' || (!stored && prefersDark);
1818+1919+ if (shouldBeDark) {
1520 htmlElement.classList.add('dark');
1621 } else {
1722 htmlElement.classList.remove('dark');
···2429 style="min-height: 100vh; background-color: rgb(var(--color-background)); color: rgb(var(--color-text-primary))"
2530>
2631 {@render children()}
3232+ <ThemeToggle />
2733</div>