Improvements to Icons for the Website service (#21)
Reviewed-on: rock-n-code/loud-amsterdam#21 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
@@ -213,6 +213,27 @@ Preview the optimized output locally — requires only Docker and writes to the
|
|||||||
make ast-minify
|
make ast-minify
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Icons
|
||||||
|
All icons are renditions of the star mark in `icon.svg`, which is the canonical source — there is no external design file to regenerate from.
|
||||||
|
|
||||||
|
| File | Size | Used by | Dark mode |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `icon.svg` | vector | Tab icon in modern browsers (preferred over the ICO) | Adapts: an embedded `prefers-color-scheme` style flips the accent paths from `#000` to `#f3ecf5`. |
|
||||||
|
| `favicon.ico` | 32×32 | Tab icon in browsers without SVG favicon support (Safari) | Theme-neutral **by design**: it is the orange star *without* the accent paths, so one raster reads on both themes. Keep it accent-free when regenerating. |
|
||||||
|
| `icon-192.png`, `icon-512.png` | 192/512 | `site.webmanifest` install icons and splash screens | None (no platform mechanism); full artwork, light rendering, on a transparent background. |
|
||||||
|
| `apple-touch-icon.png` | 180×180 | iOS home-screen bookmarks | None (fetched once, outside any page context); full artwork, deliberately opaque — iOS fills transparent regions with black. |
|
||||||
|
|
||||||
|
The landing page pairs the icons with two `theme-color` metas: `#0c0710` (media-qualified for dark, listed first — the first matching entry wins) and `#fafafa` as the fallback.
|
||||||
|
|
||||||
|
The raster icons are committed binaries, regenerated from `icon.svg` on demand via a throwaway container (no local toolchain needed) — e.g. for the 512px rendition:
|
||||||
|
```sh
|
||||||
|
docker run --rm -v "$PWD/Resources/Static:/work" alpine sh -c '
|
||||||
|
apk add --no-cache imagemagick librsvg oxipng &&
|
||||||
|
magick -background none -density 256 /work/icon.svg -depth 8 PNG32:/work/icon-512.png &&
|
||||||
|
oxipng --opt max --strip safe /work/icon-512.png'
|
||||||
|
```
|
||||||
|
(`-density` scales the 192px viewBox: `96 × target ÷ 192`. For `favicon.ico`, rasterize a star-only copy of the SVG at 32px and pack it with `icotool -c --raw`.)
|
||||||
|
|
||||||
### Required variables
|
### Required variables
|
||||||
The Makefile and Compose files read these from a `.env` file (or the environment). Provide your own values — do **not** commit secrets.
|
The Makefile and Compose files read these from a `.env` file (or the environment). Provide your own values — do **not** commit secrets.
|
||||||
| Variable | Used for |
|
| Variable | Used for |
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 700 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 192 192"><path fill="#e08524" d="M75.3 73.4H18.4l45.3 34.3L48.3 163l46.1-32.3 48.2 34.6-16.9-58.3 44.9-33.6H115l-20.5-55-19.2 55z"/><path d="m96.7 18.8 18.2 8.2 16.5 44.3h-15.1L96.7 18.8zm-47 146 18.7 9.9 42.6-29.9-16.5-11.4-44.8 31.4zm79.1-56.8 17.4 9.4 18.6 60.1-19.7-11.3-16.3-58.2z"/><path d="m173.1 74.3 17.8 9.2-44.7 34-17.4-9.4 44.3-33.8z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 192 192"><style>.accent{fill:#000}@media (prefers-color-scheme:dark){.accent{fill:#f3ecf5}}</style><path fill="#e08524" d="M75.3 73.4H18.4l45.3 34.3L48.3 163l46.1-32.3 48.2 34.6-16.9-58.3 44.9-33.6H115l-20.5-55-19.2 55z"/><path class="accent" d="m96.7 18.8 18.2 8.2 16.5 44.3h-15.1L96.7 18.8zm-47 146 18.7 9.9 42.6-29.9-16.5-11.4-44.8 31.4zm79.1-56.8 17.4 9.4 18.6 60.1-19.7-11.3-16.3-58.2z"/><path class="accent" d="m173.1 74.3 17.8 9.2-44.7 34-17.4-9.4 44.3-33.8z"/></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 549 B |
@@ -2,9 +2,13 @@
|
|||||||
"short_name": "",
|
"short_name": "",
|
||||||
"name": "",
|
"name": "",
|
||||||
"icons": [{
|
"icons": [{
|
||||||
"src": "icon.png",
|
"src": "icon-192.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "192x192"
|
"sizes": "192x192"
|
||||||
|
}, {
|
||||||
|
"src": "icon-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
}],
|
}],
|
||||||
"start_url": "/?utm_source=homescreen",
|
"start_url": "/?utm_source=homescreen",
|
||||||
"background_color": "#fafafa",
|
"background_color": "#fafafa",
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ enum StaticFile: CaseIterable, Sendable {
|
|||||||
case error
|
case error
|
||||||
/// The `favicon.ico` icon.
|
/// The `favicon.ico` icon.
|
||||||
case favicon
|
case favicon
|
||||||
/// The `icon.png` and `icon.svg` icons.
|
/// The `icon.svg` icon.
|
||||||
case icon
|
case icon
|
||||||
|
/// The `icon-192.png` icon for the web manifest.
|
||||||
|
case icon192
|
||||||
|
/// The `icon-512.png` icon for the web manifest.
|
||||||
|
case icon512
|
||||||
/// The `css/index.css` stylesheet and `js/index.js` script for the landing page.
|
/// The `css/index.css` stylesheet and `js/index.js` script for the landing page.
|
||||||
case index
|
case index
|
||||||
/// The `robots.txt` crawler directives.
|
/// The `robots.txt` crawler directives.
|
||||||
@@ -57,12 +61,14 @@ extension StaticFile {
|
|||||||
/// The file extensions the file is available with.
|
/// The file extensions the file is available with.
|
||||||
var fileExtensions: [Extension] {
|
var fileExtensions: [Extension] {
|
||||||
switch self {
|
switch self {
|
||||||
case .appleTouchIcon: [.png]
|
case .appleTouchIcon,
|
||||||
|
.icon192,
|
||||||
|
.icon512: [.png]
|
||||||
case .error,
|
case .error,
|
||||||
.index,
|
.index,
|
||||||
.shared: [.css, .js]
|
.shared: [.css, .js]
|
||||||
case .favicon: [.ico]
|
case .favicon: [.ico]
|
||||||
case .icon: [.png, .svg]
|
case .icon: [.svg]
|
||||||
case .robots: [.txt]
|
case .robots: [.txt]
|
||||||
case .site: [.webmanifest]
|
case .site: [.webmanifest]
|
||||||
case .sitemap: [.xml]
|
case .sitemap: [.xml]
|
||||||
@@ -76,6 +82,8 @@ extension StaticFile {
|
|||||||
case .error: "error"
|
case .error: "error"
|
||||||
case .favicon: "favicon"
|
case .favicon: "favicon"
|
||||||
case .icon: "icon"
|
case .icon: "icon"
|
||||||
|
case .icon192: "icon-192"
|
||||||
|
case .icon512: "icon-512"
|
||||||
case .index: "index"
|
case .index: "index"
|
||||||
case .robots: "robots"
|
case .robots: "robots"
|
||||||
case .shared: "shared"
|
case .shared: "shared"
|
||||||
|
|||||||
@@ -74,6 +74,14 @@ struct IndexPage: HTMLDocument, Sendable {
|
|||||||
.rel("manifest"),
|
.rel("manifest"),
|
||||||
.href(StaticFile.site.urlPath(for: .webmanifest))
|
.href(StaticFile.site.urlPath(for: .webmanifest))
|
||||||
)
|
)
|
||||||
|
meta(
|
||||||
|
.name("theme-color"),
|
||||||
|
.content("#0c0710"),
|
||||||
|
.custom(
|
||||||
|
name: "media",
|
||||||
|
value: "(prefers-color-scheme: dark)"
|
||||||
|
)
|
||||||
|
)
|
||||||
meta(
|
meta(
|
||||||
.name("theme-color"),
|
.name("theme-color"),
|
||||||
.content("#fafafa")
|
.content("#fafafa")
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ struct StaticFileTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func `all cases`() {
|
func `all cases`() {
|
||||||
#expect(File.allCases.count == 9)
|
#expect(File.allCases.count == 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,9 @@ private extension StaticFileTests {
|
|||||||
[.png],
|
[.png],
|
||||||
[.css, .js],
|
[.css, .js],
|
||||||
[.ico],
|
[.ico],
|
||||||
[.png, .svg],
|
[.svg],
|
||||||
|
[.png],
|
||||||
|
[.png],
|
||||||
[.css, .js],
|
[.css, .js],
|
||||||
[.txt],
|
[.txt],
|
||||||
[.css, .js],
|
[.css, .js],
|
||||||
@@ -171,6 +173,8 @@ private extension StaticFileTests {
|
|||||||
"error",
|
"error",
|
||||||
"favicon",
|
"favicon",
|
||||||
"icon",
|
"icon",
|
||||||
|
"icon-192",
|
||||||
|
"icon-512",
|
||||||
"index",
|
"index",
|
||||||
"robots",
|
"robots",
|
||||||
"shared",
|
"shared",
|
||||||
@@ -181,7 +185,9 @@ private extension StaticFileTests {
|
|||||||
["apple-touch-icon.png"],
|
["apple-touch-icon.png"],
|
||||||
["css/error.css", "js/error.js"],
|
["css/error.css", "js/error.js"],
|
||||||
["favicon.ico"],
|
["favicon.ico"],
|
||||||
["icon.png", "icon.svg"],
|
["icon.svg"],
|
||||||
|
["icon-192.png"],
|
||||||
|
["icon-512.png"],
|
||||||
["css/index.css", "js/index.js"],
|
["css/index.css", "js/index.js"],
|
||||||
["robots.txt"],
|
["robots.txt"],
|
||||||
["css/shared.css", "js/shared.js"],
|
["css/shared.css", "js/shared.js"],
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct IndexPageTests {
|
|||||||
#expect(html.contains("/icon.svg"))
|
#expect(html.contains("/icon.svg"))
|
||||||
#expect(html.contains("/apple-touch-icon.png"))
|
#expect(html.contains("/apple-touch-icon.png"))
|
||||||
#expect(html.contains("/site.webmanifest"))
|
#expect(html.contains("/site.webmanifest"))
|
||||||
|
#expect(html.contains(#"media="(prefers-color-scheme: dark)""#))
|
||||||
#expect(html.contains("Hello world!"))
|
#expect(html.contains("Hello world!"))
|
||||||
#expect(html.contains("/js/shared.js"))
|
#expect(html.contains("/js/shared.js"))
|
||||||
#expect(html.contains("/js/index.js"))
|
#expect(html.contains("/js/index.js"))
|
||||||
|
|||||||