feat(native): TREK's own app icon and launch screen, named just TREK

The shells still carried Capacitor's placeholder icon and splash, the blue
X on a grid. They now show the white TREK mark on a dark, slightly glassy
tile: a soft light from the top left and a sheen over the upper half, in
the app's launch colour #09090b. Android 12+ draws its launch screen on the
same colour instead of light grey.

scripts/generate-app-icons.mjs renders the sources into assets/ from
public/icons/icon-white.svg, and `npm run icons:app` turns them into every
size both platforms ask for through @capacitor/assets. The Android
foreground mark is sized for the adaptive icon's round mask.

The home screen label is TREK: the old "TREK - Plan your trips!" was cut
off under the icon, and the longer title belongs to the store listings.
This commit is contained in:
Maurice
2026-09-24 14:50:25 +02:00
parent f567e15a99
commit 7572c610a6
76 changed files with 184 additions and 32 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon> </adaptive-icon>
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon> </adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#FFFFFF</color> <color name="ic_launcher_background">#09090B</color>
</resources> <color name="launch_background">#09090B</color>
</resources>
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<resources> <resources>
<string name="app_name">TREK - Plan your trips!</string> <string name="app_name">TREK</string>
<string name="title_activity_main">TREK - Plan your trips!</string> <string name="title_activity_main">TREK</string>
<string name="package_name">com.liketrek.trek</string> <string name="package_name">com.liketrek.trek</string>
<string name="custom_url_scheme">com.liketrek.trek</string> <string name="custom_url_scheme">com.liketrek.trek</string>
</resources> </resources>
@@ -18,5 +18,8 @@
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="android:background">@drawable/splash</item>
<!-- Android 12+ draws its own launch screen: the app icon on this colour.
The same near-black as the icon tile and the first painted frame. -->
<item name="windowSplashScreenBackground">@color/launch_background</item>
</style> </style>
</resources> </resources>
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

+3 -1
View File
@@ -5,7 +5,9 @@ import { KeyboardResize } from '@capacitor/keyboard';
const config: CapacitorConfig = { const config: CapacitorConfig = {
appId: 'com.liketrek.trek', appId: 'com.liketrek.trek',
appName: 'TREK - Plan your trips!', // What the home screen shows under the icon; the longer title belongs to
// the store listings.
appName: 'TREK',
// The app ships only the address screen. Once a server is picked, the native // The app ships only the address screen. Once a server is picked, the native
// side points the WebView at it (TrekBridgeViewController.swift, // side points the WebView at it (TrekBridgeViewController.swift,
// MainActivity.java) and the UI comes from that server, always in the version // MainActivity.java) and the UI comes from that server, always in the version
Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 104 KiB

@@ -1,14 +1,14 @@
{ {
"images" : [ "images": [
{ {
"filename" : "AppIcon-512@2x.png", "idiom": "universal",
"idiom" : "universal", "size": "1024x1024",
"platform" : "ios", "filename": "AppIcon-512@2x.png",
"size" : "1024x1024" "platform": "ios"
} }
], ],
"info" : { "info": {
"author" : "xcode", "author": "xcode",
"version" : 1 "version": 1
} }
} }
@@ -1,23 +1,56 @@
{ {
"images" : [ "images": [
{ {
"idiom" : "universal", "idiom": "universal",
"filename" : "splash-2732x2732-2.png", "filename": "Default@1x~universal~anyany.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"filename" : "splash-2732x2732-1.png", "filename": "Default@2x~universal~anyany.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"idiom" : "universal", "idiom": "universal",
"filename" : "splash-2732x2732.png", "filename": "Default@3x~universal~anyany.png",
"scale" : "3x" "scale": "3x"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"scale": "1x",
"filename": "Default@1x~universal~anyany-dark.png"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"scale": "2x",
"filename": "Default@2x~universal~anyany-dark.png"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"idiom": "universal",
"scale": "3x",
"filename": "Default@3x~universal~anyany-dark.png"
} }
], ],
"info" : { "info": {
"version" : 1, "version": 1,
"author" : "xcode" "author": "xcode"
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

+1 -1
View File
@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>en</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>TREK - Plan your trips!</string> <string>TREK</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
+1
View File
@@ -7,6 +7,7 @@
"dev": "vite", "dev": "vite",
"prebuild": "node scripts/patch-maplibre.mjs && node scripts/generate-icons.mjs", "prebuild": "node scripts/patch-maplibre.mjs && node scripts/generate-icons.mjs",
"icons:native": "node scripts/generate-native-icons.mjs", "icons:native": "node scripts/generate-native-icons.mjs",
"icons:app": "node scripts/generate-app-icons.mjs && npx @capacitor/assets generate --android --ios --iconBackgroundColor #09090b --iconBackgroundColorDark #09090b --splashBackgroundColor #09090b --splashBackgroundColorDark #09090b",
"build:native": "vite build --config vite.native.config.js", "build:native": "vite build --config vite.native.config.js",
"cap:sync": "npm run build:native && cap sync", "cap:sync": "npm run build:native && cap sync",
"build": "vite build", "build": "vite build",
+104
View File
@@ -0,0 +1,104 @@
/**
* Renders the native apps' icon and splash sources into assets/, from the
* white TREK mark in public/icons/icon-white.svg. `npx @capacitor/assets
* generate --android --ios` then turns them into every size both platforms
* ask for (see the icons:app script).
*
* The look: a dark, slightly glassy tile (a soft light from the top left and a
* sheen over the upper half) under the white mark. Dark because that is the
* app's launch background (#09090b) and its default scheme.
*
* Android's adaptive icon masks the foreground to a circle inside the
* canvas, so the mark there sits smaller than on the iOS tile.
*/
import { mkdirSync, readFileSync } from 'fs'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
import sharp from 'sharp'
const clientDir = join(dirname(fileURLToPath(import.meta.url)), '..')
const outDir = join(clientDir, 'assets')
mkdirSync(outDir, { recursive: true })
const source = readFileSync(join(clientDir, 'public', 'icons', 'icon-white.svg'), 'utf8')
const markPath = [...source.matchAll(/<path d="([^"]+)"/g)].map((m) => m[1]).find((d) => d.length > 500)
if (!markPath) throw new Error('No mark path found in icon-white.svg')
// The mark's extent inside its 1500 x 1500 viewBox.
const MARK = { x: 40, y: 5, w: 1420, h: 1490 }
const LAUNCH_BG = '#09090b'
function tile(size) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="base" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#2a2a31"/>
<stop offset="0.55" stop-color="#141418"/>
<stop offset="1" stop-color="#08080a"/>
</linearGradient>
<radialGradient id="light" cx="0.22" cy="0.12" r="0.75">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.20"/>
<stop offset="0.45" stop-color="#ffffff" stop-opacity="0.05"/>
<stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
</radialGradient>
<linearGradient id="sheen" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffffff" stop-opacity="0.10"/>
<stop offset="0.48" stop-color="#ffffff" stop-opacity="0.03"/>
<stop offset="0.5" stop-color="#ffffff" stop-opacity="0"/>
</linearGradient>
<radialGradient id="depth" cx="0.8" cy="0.95" r="0.7">
<stop offset="0" stop-color="#000000" stop-opacity="0.35"/>
<stop offset="1" stop-color="#000000" stop-opacity="0"/>
</radialGradient>
</defs>
<rect width="1024" height="1024" fill="url(#base)"/>
<rect width="1024" height="1024" fill="url(#depth)"/>
<rect width="1024" height="1024" fill="url(#light)"/>
<rect width="1024" height="1024" fill="url(#sheen)"/>
</svg>`
}
/** The white mark, `share` of the canvas tall, centred, with a soft shadow for depth. */
function mark(size, share, shadow = true) {
const scale = (1024 * share) / MARK.h
const w = MARK.w * scale
const h = MARK.h * scale
const tx = (1024 - w) / 2 - MARK.x * scale
const ty = (1024 - h) / 2 - MARK.y * scale
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="ink" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="1" stop-color="#e7e7ec"/>
</linearGradient>
<filter id="lift" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="14" stdDeviation="22" flood-color="#000000" flood-opacity="0.45"/>
</filter>
</defs>
<g ${shadow ? 'filter="url(#lift)"' : ''}>
<path transform="translate(${tx} ${ty}) scale(${scale})" fill="url(#ink)" d="${markPath}"/>
</g>
</svg>`
}
async function render(svg, file) {
await sharp(Buffer.from(svg)).png().toFile(join(outDir, file))
}
async function composite(background, overlay, file, size) {
const base = await sharp(Buffer.from(background)).resize(size, size).png().toBuffer()
const top = await sharp(Buffer.from(overlay)).resize(size, size).png().toBuffer()
await sharp(base).composite([{ input: top }]).flatten({ background: LAUNCH_BG }).png().toFile(join(outDir, file))
}
// iOS and store listings: one opaque tile, the mark at about half its height.
await composite(tile(1024), mark(1024, 0.5), 'icon-only.png', 1024)
// Android adaptive icon: the two layers apart, the mark inside the safe circle.
await render(tile(1024), 'icon-background.png')
await render(mark(1024, 0.58), 'icon-foreground.png')
// Splash: the launch background with the mark, light and dark alike.
const splashBg = `<svg xmlns="http://www.w3.org/2000/svg" width="2732" height="2732"><rect width="2732" height="2732" fill="${LAUNCH_BG}"/></svg>`
await composite(splashBg, mark(2732, 0.2, false), 'splash.png', 2732)
await composite(splashBg, mark(2732, 0.2, false), 'splash-dark.png', 2732)
console.log(`[app-icons] wrote icon and splash sources to ${outDir}`)