Compare commits
3 Commits
17db811bba
...
701e90153c
| Author | SHA1 | Date | |
|---|---|---|---|
| 701e90153c | |||
| e94e39addc | |||
| 8ddfafebcb |
@@ -154,8 +154,13 @@ service worker in `public/sw.js`. On iOS, Share → *Zum Home-Bildschirm*; it th
|
||||
full-screen with no Safari chrome, because iOS reads the `apple-mobile-web-app-*` meta
|
||||
tags in `index.html` rather than the manifest.
|
||||
|
||||
Two things worth knowing:
|
||||
A few things worth knowing:
|
||||
|
||||
- **Install from `?parentMode=1` to get a parents' app.** iOS starts an installed app at
|
||||
the manifest's `start_url`, not at the address it was added from, so the plain
|
||||
manifest's `/` would drop the flag. Opened with it, `index.html` swaps in
|
||||
`public/manifest-parents.webmanifest` (`start_url: /?parentMode=1`, named "Musik
|
||||
Eltern"). An app installed before this change has to be removed and added again.
|
||||
- **The service worker only registers over HTTPS or on `localhost`.** At
|
||||
`http://<mouse>:8080` on the LAN, iOS will still install the icon and run it
|
||||
standalone - that part is the meta tags - but there is no offline caching. Put the
|
||||
|
||||
@@ -15,10 +15,23 @@
|
||||
<meta name="theme-color" content="#0d2f3c" />
|
||||
<meta name="description" content="Musik und Hörbücher für die MusicMouse" />
|
||||
|
||||
<!-- iOS does not read the manifest for home-screen installs; these are what it uses. -->
|
||||
<!-- iOS takes start_url from the manifest, but the title, icon and status bar from
|
||||
these tags. -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="Musik" />
|
||||
<!-- A home-screen install starts at the manifest's start_url, not at the address it
|
||||
was added from, so `?parentMode=1` would be lost. Opened with it, the page offers
|
||||
a second manifest whose start_url keeps it - "Add to Home Screen" from a parent's
|
||||
URL installs the parents' app, under its own name. Inline, so the tags are
|
||||
switched before anything reads them. -->
|
||||
<script>
|
||||
if (new URLSearchParams(location.search).get("parentMode") === "1") {
|
||||
document.querySelector('link[rel="manifest"]').href = "/manifest-parents.webmanifest";
|
||||
document.querySelector('meta[name="apple-mobile-web-app-title"]').content = "Musik Eltern";
|
||||
document.title = "Musik Delfin Eltern";
|
||||
}
|
||||
</script>
|
||||
<!-- An opaque status bar, so the page starts below it - not black-translucent. That
|
||||
one ran the page under the clock, which put every top-anchored control behind
|
||||
it unless each remembered its inset, and since iOS 26 it also sizes the app one
|
||||
|
||||
33
web/public/manifest-parents.webmanifest
Normal file
33
web/public/manifest-parents.webmanifest
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "Musik Delfin Eltern",
|
||||
"short_name": "Musik Eltern",
|
||||
"description": "Musik und Hörbücher für die MusicMouse",
|
||||
"start_url": "/?parentMode=1",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "any",
|
||||
"background_color": "#0d2f3c",
|
||||
"theme_color": "#0d2f3c",
|
||||
"lang": "de",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"id": "/?parentMode=1"
|
||||
}
|
||||
@@ -19,13 +19,16 @@ import { SKIN } from "../lib/skins";
|
||||
import { SHOW_DECORATIVE_ANIMATIONS } from "../lib/theme";
|
||||
import { Drift, type DriftField } from "./Drift";
|
||||
|
||||
/** Beside the watch only, never behind it: on a 1080p kiosk the watch covers roughly
|
||||
* 28-72% of the width, and a bubble rising behind it would drag the watch into every
|
||||
* frame the Pi redraws. */
|
||||
const BUBBLES: DriftField = [
|
||||
[5, 14, 0.5, 9, 0],
|
||||
[12, 22, 0.5, 12, -3],
|
||||
[17, 16, 0.5, 11, -2],
|
||||
[22, 10, 0.5, 8, -6],
|
||||
[48, 16, 0.5, 11, -2],
|
||||
[63, 12, 0.5, 10, -7],
|
||||
[78, 24, 0.5, 14, -5],
|
||||
[83, 12, 0.5, 10, -7],
|
||||
[88, 12, 0.5, 9, -1],
|
||||
[95, 18, 0.5, 13, -9],
|
||||
];
|
||||
@@ -69,10 +72,14 @@ export function ClockOverlay({ visible, clock, onClose }: Props) {
|
||||
<div className="clock-face">
|
||||
<Dial />
|
||||
<div className="clock-hand clock-hand-hour" style={{ transform: `rotate(${angles.hour}deg)` }}>
|
||||
<div />
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<div className="clock-hand clock-hand-minute" style={{ transform: `rotate(${angles.minute}deg)` }}>
|
||||
<div />
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<div className="clock-hand clock-hand-second" style={{ transform: `rotate(${angles.second}deg)` }}>
|
||||
<div />
|
||||
|
||||
@@ -362,8 +362,12 @@
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.clock-hand-hour,
|
||||
.clock-hand-minute {
|
||||
/* The outline and shadow, on the hand's inner box rather than the hand: a filter on the
|
||||
layer itself would be recomputed on the GPU every frame anything on screen moves (the
|
||||
bubbles), while one inside it is drawn once into the layer's picture. The shape is
|
||||
one level further in, so the outline goes round the clipped arrow, not its box. */
|
||||
.clock-hand-hour > div,
|
||||
.clock-hand-minute > div {
|
||||
filter: drop-shadow(0.35vmin 0 0 var(--clock-ink)) drop-shadow(-0.35vmin 0 0 var(--clock-ink))
|
||||
drop-shadow(0 0.35vmin 0 var(--clock-ink)) drop-shadow(0 -0.35vmin 0 var(--clock-ink))
|
||||
drop-shadow(0 1vmin 1vmin oklch(15% 0.05 210 / 0.3));
|
||||
@@ -382,8 +386,18 @@
|
||||
margin-left: -2.7vmin;
|
||||
}
|
||||
|
||||
.clock-hand-hour > div {
|
||||
.clock-hand-hour > div,
|
||||
.clock-hand-minute > div {
|
||||
bottom: -3.5vmin;
|
||||
}
|
||||
|
||||
.clock-hand-hour > div > div,
|
||||
.clock-hand-minute > div > div {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.clock-hand-hour > div > div {
|
||||
background: oklch(92% 0.06 345);
|
||||
clip-path: polygon(50% 0%, 100% 22%, 66% 22%, 66% 100%, 34% 100%, 34% 22%, 0% 22%);
|
||||
}
|
||||
@@ -394,8 +408,7 @@
|
||||
margin-left: -2.5vmin;
|
||||
}
|
||||
|
||||
.clock-hand-minute > div {
|
||||
bottom: -3.5vmin;
|
||||
.clock-hand-minute > div > div {
|
||||
background: oklch(92% 0.05 200);
|
||||
clip-path: polygon(50% 0%, 100% 14%, 64% 14%, 64% 100%, 36% 100%, 36% 14%, 0% 14%);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user