feat: add mapix logo assets

This commit is contained in:
2026-06-13 08:46:37 +02:00
parent 5e10be463e
commit 4dd9e5e487
6 changed files with 135 additions and 7 deletions

21
src/assets/favicon.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title">
<title id="title">mapix favicon</title>
<defs>
<linearGradient id="pin" x1="18" y1="5" x2="46" y2="58" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#1f6a8c"/>
<stop offset="1" stop-color="#124d6b"/>
</linearGradient>
</defs>
<path d="M22 8h20c7 0 13 6 13 13v12c0 13-18 29-23 33-5-4-23-20-23-33V21C9 14 15 8 22 8Z" fill="url(#pin)"/>
<path d="M25 8l3-5h12l3 5" fill="#124d6b"/>
<circle cx="32" cy="29" r="16" fill="#f8fafc"/>
<circle cx="32" cy="29" r="12" fill="#174f6e"/>
<g fill="#f8fafc">
<path d="M32 17a12 12 0 0 1 9 4H30l-3-4h5Z"/>
<path d="M44 25a12 12 0 0 1-1 9l-6-10 4-3 3 4Z"/>
<path d="M40 39a12 12 0 0 1-9 2l6-10 4 1-1 7Z"/>
<path d="M27 40a12 12 0 0 1-7-8h11l2 4-6 4Z"/>
<path d="M20 27a12 12 0 0 1 5-8l6 10-3 3-8-5Z"/>
</g>
<circle cx="43" cy="19" r="3" fill="#f8fafc"/>
</svg>

After

Width:  |  Height:  |  Size: 964 B

42
src/assets/logo-dark.svg Normal file
View File

@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 220" role="img" aria-labelledby="title desc">
<title id="title">mapix logo for dark backgrounds</title>
<desc id="desc">A mint map pin shaped camera icon above the mapix wordmark.</desc>
<defs>
<linearGradient id="pin-dark" x1="116" y1="24" x2="202" y2="150" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#86d2c2"/>
<stop offset="1" stop-color="#66b79c"/>
</linearGradient>
<linearGradient id="accent-dark" x1="68" y1="72" x2="246" y2="130" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#a5df91"/>
<stop offset="1" stop-color="#7bd3c9"/>
</linearGradient>
</defs>
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M86 124c-32 5-50 14-49 25 2 14 38 19 82 8 31-8 58-23 69-39" stroke="url(#accent-dark)" stroke-width="9"/>
<path d="M84 122c-20 11-30 21-27 28 4 9 31 8 63-2" stroke="#86d2c2" stroke-width="5"/>
<path d="M206 75c20 12 27 26 18 42-8 14-26 23-47 32" stroke="#86d2c2" stroke-width="7"/>
<path d="M215 86c12 8 15 17 9 27" stroke="#7bd3c9" stroke-width="6"/>
</g>
<path d="M126 46h63c12 0 22 10 22 22v45c0 29-45 75-54 84-9-9-54-55-54-84V69c0-13 10-23 23-23Z" fill="url(#pin-dark)"/>
<path d="M143 46l8-14h35l9 14" fill="#86d2c2"/>
<circle cx="157" cy="96" r="34" fill="#172025"/>
<circle cx="157" cy="96" r="25" fill="#11181d"/>
<g fill="#a5df91">
<path d="M157 71a25 25 0 0 1 19 9h-24l-6-9h11Z"/>
<path d="M181 88a25 25 0 0 1-1 19l-12-21 6-6 7 8Z"/>
<path d="M174 117a25 25 0 0 1-18 4l12-21 8 2-2 15Z"/>
<path d="M145 119a25 25 0 0 1-13-15h24l4 8-15 7Z"/>
<path d="M132 93a25 25 0 0 1 10-17l12 21-5 7-17-11Z"/>
</g>
<circle cx="192" cy="71" r="7" fill="#172025"/>
<g fill="#a5df91">
<path d="M69 94c0 13-17 28-17 28S35 107 35 94a17 17 0 1 1 34 0Z"/>
<circle cx="52" cy="93" r="5" fill="#172025"/>
</g>
<g fill="#7bd3c9">
<path d="M230 50c0 10-13 22-13 22s-13-12-13-22a13 13 0 1 1 26 0Z"/>
<circle cx="217" cy="49" r="4" fill="#172025"/>
</g>
<text x="42" y="184" fill="#f8fafc" stroke="#11181d" stroke-width="2" paint-order="stroke" font-family="Avenir Next, Montserrat, Verdana, sans-serif" font-size="78" font-weight="800" letter-spacing="0">mapix</text>
<text x="45" y="213" fill="#aeb6bf" font-family="Avenir Next, Montserrat, Verdana, sans-serif" font-size="24" font-weight="400">Visualize Photos on Maps</text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

42
src/assets/logo-light.svg Normal file
View File

@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 220" role="img" aria-labelledby="title desc">
<title id="title">mapix logo for light backgrounds</title>
<desc id="desc">A map pin shaped camera icon above the mapix wordmark.</desc>
<defs>
<linearGradient id="pin-light" x1="116" y1="24" x2="202" y2="150" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#1f6a8c"/>
<stop offset="1" stop-color="#124d6b"/>
</linearGradient>
<linearGradient id="accent-light" x1="68" y1="72" x2="246" y2="130" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#a8d889"/>
<stop offset="1" stop-color="#73c7bd"/>
</linearGradient>
</defs>
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M86 124c-32 5-50 14-49 25 2 14 38 19 82 8 31-8 58-23 69-39" stroke="url(#accent-light)" stroke-width="9"/>
<path d="M84 122c-20 11-30 21-27 28 4 9 31 8 63-2" stroke="#124d6b" stroke-width="5"/>
<path d="M206 75c20 12 27 26 18 42-8 14-26 23-47 32" stroke="#124d6b" stroke-width="7"/>
<path d="M215 86c12 8 15 17 9 27" stroke="#73c7bd" stroke-width="6"/>
</g>
<path d="M126 46h63c12 0 22 10 22 22v45c0 29-45 75-54 84-9-9-54-55-54-84V69c0-13 10-23 23-23Z" fill="url(#pin-light)"/>
<path d="M143 46l8-14h35l9 14" fill="#124d6b"/>
<circle cx="157" cy="96" r="34" fill="#f7fbfd"/>
<circle cx="157" cy="96" r="25" fill="#174f6e"/>
<g fill="#f7fbfd">
<path d="M157 71a25 25 0 0 1 19 9h-24l-6-9h11Z"/>
<path d="M181 88a25 25 0 0 1-1 19l-12-21 6-6 7 8Z"/>
<path d="M174 117a25 25 0 0 1-18 4l12-21 8 2-2 15Z"/>
<path d="M145 119a25 25 0 0 1-13-15h24l4 8-15 7Z"/>
<path d="M132 93a25 25 0 0 1 10-17l12 21-5 7-17-11Z"/>
</g>
<circle cx="192" cy="71" r="7" fill="#f7fbfd"/>
<g fill="#83c47d">
<path d="M69 94c0 13-17 28-17 28S35 107 35 94a17 17 0 1 1 34 0Z"/>
<circle cx="52" cy="93" r="5" fill="#f7fbfd"/>
</g>
<g fill="#73c7bd">
<path d="M230 50c0 10-13 22-13 22s-13-12-13-22a13 13 0 1 1 26 0Z"/>
<circle cx="217" cy="49" r="4" fill="#f7fbfd"/>
</g>
<text x="42" y="184" fill="#124d6b" font-family="Avenir Next, Montserrat, Verdana, sans-serif" font-size="78" font-weight="800" letter-spacing="0">mapix</text>
<text x="45" y="213" fill="#6b7280" font-family="Avenir Next, Montserrat, Verdana, sans-serif" font-size="24" font-weight="400">Visualize Photos on Maps</text>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -3,24 +3,31 @@ import type { ServerResponse } from "node:http";
const CLIENT_ASSET_PREFIX = "/client/";
const VENDOR_ASSET_PREFIX = "/vendor/";
const APP_ASSET_PREFIX = "/assets/";
export function isClientAssetPath(pathname: string): boolean {
return pathname.startsWith(CLIENT_ASSET_PREFIX) || pathname.startsWith(VENDOR_ASSET_PREFIX);
return (
pathname.startsWith(CLIENT_ASSET_PREFIX) ||
pathname.startsWith(VENDOR_ASSET_PREFIX) ||
pathname.startsWith(APP_ASSET_PREFIX)
);
}
export async function handleClientAssetRequest(url: URL, res: ServerResponse): Promise<void> {
const isVendorAsset = url.pathname.startsWith(VENDOR_ASSET_PREFIX);
const assetPrefix = isVendorAsset ? VENDOR_ASSET_PREFIX : CLIENT_ASSET_PREFIX;
const isAppAsset = url.pathname.startsWith(APP_ASSET_PREFIX);
const assetPrefix = isVendorAsset ? VENDOR_ASSET_PREFIX : isAppAsset ? APP_ASSET_PREFIX : CLIENT_ASSET_PREFIX;
const assetName = url.pathname.slice(assetPrefix.length);
if (!isAllowedAssetPath(assetName, isVendorAsset)) {
if (!isAllowedAssetPath(assetName, isVendorAsset, isAppAsset)) {
res.statusCode = 404;
res.end("Not found");
return;
}
try {
const assetUrl = new URL(`../${isVendorAsset ? "vendor" : "client"}/${assetName}`, import.meta.url);
const assetDirectory = isVendorAsset ? "vendor" : isAppAsset ? "assets" : "client";
const assetUrl = new URL(`../${assetDirectory}/${assetName}`, import.meta.url);
const source = await readFile(assetUrl);
res.statusCode = 200;
res.setHeader("content-type", getContentType(assetName));
@@ -32,11 +39,15 @@ export async function handleClientAssetRequest(url: URL, res: ServerResponse): P
}
}
function isAllowedAssetPath(assetName: string, isVendorAsset: boolean): boolean {
function isAllowedAssetPath(assetName: string, isVendorAsset: boolean, isAppAsset: boolean): boolean {
if (assetName.split("/").includes("..")) {
return false;
}
if (isAppAsset) {
return /^[a-z0-9_.-]+\.svg$/i.test(assetName);
}
if (isVendorAsset) {
return /^[a-z0-9/_.-]+\.(css|js|mjs|png)$/i.test(assetName);
}
@@ -53,5 +64,9 @@ function getContentType(assetName: string): string {
return "image/png";
}
if (assetName.endsWith(".svg")) {
return "image/svg+xml; charset=utf-8";
}
return "text/javascript; charset=utf-8";
}

View File

@@ -7,6 +7,7 @@ export function htmlPage(): string {
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>mapix</title>
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />
<link rel="stylesheet" href="/vendor/leaflet/leaflet.css" />
<style>
${APP_STYLES}

View File

@@ -3,14 +3,16 @@ import { join } from "node:path";
const projectRoot = process.cwd();
const vendorRoot = join(projectRoot, "dist", "vendor");
const assetsRoot = join(projectRoot, "dist", "assets");
async function copyFile(source: string, target: string): Promise<void> {
await mkdir(join(target, ".."), { recursive: true });
await cp(source, target);
}
async function copyVendorAssets(): Promise<void> {
async function copyStaticAssets(): Promise<void> {
await rm(vendorRoot, { recursive: true, force: true });
await rm(assetsRoot, { recursive: true, force: true });
await copyFile(
join(projectRoot, "node_modules", "leaflet", "dist", "leaflet.css"),
@@ -29,6 +31,11 @@ async function copyVendorAssets(): Promise<void> {
join(projectRoot, "node_modules", "exifr", "dist", "full.esm.mjs"),
join(vendorRoot, "exifr", "full.esm.mjs")
);
await cp(
join(projectRoot, "src", "assets"),
assetsRoot,
{ recursive: true }
);
}
await copyVendorAssets();
await copyStaticAssets();