feat: add mapix logo assets
This commit is contained in:
21
src/assets/favicon.svg
Normal file
21
src/assets/favicon.svg
Normal 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
42
src/assets/logo-dark.svg
Normal 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
42
src/assets/logo-light.svg
Normal 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 |
@@ -3,24 +3,31 @@ import type { ServerResponse } from "node:http";
|
|||||||
|
|
||||||
const CLIENT_ASSET_PREFIX = "/client/";
|
const CLIENT_ASSET_PREFIX = "/client/";
|
||||||
const VENDOR_ASSET_PREFIX = "/vendor/";
|
const VENDOR_ASSET_PREFIX = "/vendor/";
|
||||||
|
const APP_ASSET_PREFIX = "/assets/";
|
||||||
|
|
||||||
export function isClientAssetPath(pathname: string): boolean {
|
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> {
|
export async function handleClientAssetRequest(url: URL, res: ServerResponse): Promise<void> {
|
||||||
const isVendorAsset = url.pathname.startsWith(VENDOR_ASSET_PREFIX);
|
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);
|
const assetName = url.pathname.slice(assetPrefix.length);
|
||||||
|
|
||||||
if (!isAllowedAssetPath(assetName, isVendorAsset)) {
|
if (!isAllowedAssetPath(assetName, isVendorAsset, isAppAsset)) {
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
res.end("Not found");
|
res.end("Not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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);
|
const source = await readFile(assetUrl);
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
res.setHeader("content-type", getContentType(assetName));
|
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("..")) {
|
if (assetName.split("/").includes("..")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAppAsset) {
|
||||||
|
return /^[a-z0-9_.-]+\.svg$/i.test(assetName);
|
||||||
|
}
|
||||||
|
|
||||||
if (isVendorAsset) {
|
if (isVendorAsset) {
|
||||||
return /^[a-z0-9/_.-]+\.(css|js|mjs|png)$/i.test(assetName);
|
return /^[a-z0-9/_.-]+\.(css|js|mjs|png)$/i.test(assetName);
|
||||||
}
|
}
|
||||||
@@ -53,5 +64,9 @@ function getContentType(assetName: string): string {
|
|||||||
return "image/png";
|
return "image/png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (assetName.endsWith(".svg")) {
|
||||||
|
return "image/svg+xml; charset=utf-8";
|
||||||
|
}
|
||||||
|
|
||||||
return "text/javascript; charset=utf-8";
|
return "text/javascript; charset=utf-8";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export function htmlPage(): string {
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>mapix</title>
|
<title>mapix</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />
|
||||||
<link rel="stylesheet" href="/vendor/leaflet/leaflet.css" />
|
<link rel="stylesheet" href="/vendor/leaflet/leaflet.css" />
|
||||||
<style>
|
<style>
|
||||||
${APP_STYLES}
|
${APP_STYLES}
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ import { join } from "node:path";
|
|||||||
|
|
||||||
const projectRoot = process.cwd();
|
const projectRoot = process.cwd();
|
||||||
const vendorRoot = join(projectRoot, "dist", "vendor");
|
const vendorRoot = join(projectRoot, "dist", "vendor");
|
||||||
|
const assetsRoot = join(projectRoot, "dist", "assets");
|
||||||
|
|
||||||
async function copyFile(source: string, target: string): Promise<void> {
|
async function copyFile(source: string, target: string): Promise<void> {
|
||||||
await mkdir(join(target, ".."), { recursive: true });
|
await mkdir(join(target, ".."), { recursive: true });
|
||||||
await cp(source, target);
|
await cp(source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyVendorAssets(): Promise<void> {
|
async function copyStaticAssets(): Promise<void> {
|
||||||
await rm(vendorRoot, { recursive: true, force: true });
|
await rm(vendorRoot, { recursive: true, force: true });
|
||||||
|
await rm(assetsRoot, { recursive: true, force: true });
|
||||||
|
|
||||||
await copyFile(
|
await copyFile(
|
||||||
join(projectRoot, "node_modules", "leaflet", "dist", "leaflet.css"),
|
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(projectRoot, "node_modules", "exifr", "dist", "full.esm.mjs"),
|
||||||
join(vendorRoot, "exifr", "full.esm.mjs")
|
join(vendorRoot, "exifr", "full.esm.mjs")
|
||||||
);
|
);
|
||||||
|
await cp(
|
||||||
|
join(projectRoot, "src", "assets"),
|
||||||
|
assetsRoot,
|
||||||
|
{ recursive: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await copyVendorAssets();
|
await copyStaticAssets();
|
||||||
|
|||||||
Reference in New Issue
Block a user