Drei Auswahlfelder liessen sich gleichzeitig aufklappen
Jedes merkte sich nur seine eigene Liste. Nebeneinander geöffnet lagen sie übereinander, und keine wusste von den anderen. Ein Auswahlfeld ist ein Menü, und von denen ist höchstens eines offen — jetzt modulweit vermerkt, genau wie beim Kontextmenü. Escape schliesst zusätzlich von überall, nicht nur mit dem Fokus in der Liste. Dazu stand unter jeder Mediathek „0 Titel freigeben". Der Knopf wurde über `hidden` versteckt — das Attribut wirkt über `display: none` mit der Spezifität von Null, und `.btn` setzt `display: inline-flex`. Die Klasse `.hidden` trägt ein `!important` und setzt sich durch. Und das Favicon kommt jetzt aus `icon.png`, dem gerenderten App-Icon selbst. Meine nachgezeichnete Fassung war falsch: die Grafik füllt die Fläche, statt klein in der Mitte zu sitzen. Ein Icon, das dem echten nur ähnelt, driftet beim nächsten Entwurf wieder auseinander. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@@ -6,16 +6,18 @@
|
|||||||
<meta name="theme-color" content="#0B0B0D">
|
<meta name="theme-color" content="#0B0B0D">
|
||||||
<title>Flaro</title>
|
<title>Flaro</title>
|
||||||
|
|
||||||
<!-- **Dasselbe Zeichen wie das App-Icon**, erzeugt aus
|
<!-- **Das App-Icon selbst**, aus `icon.png` im Projektordner skaliert —
|
||||||
`Flaro/AppIcon.icon` — gleiche Grafik, gleiche Farben, von Display-P3
|
die Fassung, die Icon Composer rendert, mitsamt Glas und Bevel.
|
||||||
nach sRGB umgerechnet. Zwei Icons, die sich ähnlich sehen, driften
|
|
||||||
auseinander; eins, das aus demselben Ordner kommt, nicht.
|
|
||||||
|
|
||||||
SVG zuerst, weil es in jeder Grösse scharf bleibt. Das PNG daneben
|
Nachgezeichnet war es vorher, und das ging daneben: die Grafik füllt
|
||||||
für alles, was kein SVG-Favicon kann — und für den Startbildschirm
|
die Fläche, statt klein in der Mitte zu sitzen. Ein Icon, das dem
|
||||||
auf dem Telefon, der ohnehin nur PNG nimmt. -->
|
echten nur ähnelt, driftet beim nächsten Entwurf wieder auseinander.
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
||||||
|
Bei 16 und 32 Bildpunkten verliert sich die Glasoptik — das liegt am
|
||||||
|
Entwurf, nicht am Export: ein Icon für 1024 Punkte hat dort mehr
|
||||||
|
Feinheit, als ein Browsertab zeigen kann. -->
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/icon-32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="/icon-32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="/icon-192.png">
|
||||||
<link rel="apple-touch-icon" href="/icon-180.png">
|
<link rel="apple-touch-icon" href="/icon-180.png">
|
||||||
<link rel="manifest" href="/manifest.webmanifest">
|
<link rel="manifest" href="/manifest.webmanifest">
|
||||||
<!-- **Inter, mit der Systemschrift als Rückfall.** `display=swap`, damit der
|
<!-- **Inter, mit der Systemschrift als Rückfall.** `display=swap`, damit der
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Flaro">
|
|
||||||
<!--
|
|
||||||
Aus Flaro/AppIcon.icon erzeugt, nicht nachgezeichnet: dieselbe Grafik
|
|
||||||
(Assets/flare (1).svg) und dieselben Farben, von Display-P3 nach sRGB
|
|
||||||
umgerechnet. Ändert sich das App-Icon, wird diese Datei neu erzeugt.
|
|
||||||
|
|
||||||
#9860FF ist der helle Hintergrund des Icons, #261653 der dunkle.
|
|
||||||
Der Verlauf ahmt Icon Composers „automatic gradient" nach: oben heller.
|
|
||||||
-->
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="grund" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="0" stop-color="#B98CFF"/>
|
|
||||||
<stop offset="1" stop-color="#8243F5"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<rect width="64" height="64" rx="14" fill="url(#grund)"/>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Die Glyphe weiss statt im Originalton (#900AFF). Auf dem Plakat eines
|
|
||||||
App-Icons trägt sie das Glas; bei 16 Bildpunkten im Browsertab wäre
|
|
||||||
Violett auf Violett ein Fleck.
|
|
||||||
Skaliert: 24er-Raster auf 40 Punkte, zentriert.
|
|
||||||
-->
|
|
||||||
<g transform="translate(12 12) scale(1.6667)" fill="#FFFFFF">
|
|
||||||
<path clip-rule="evenodd" fill-rule="evenodd" d="m11.0721 5.13152c.3321-.84203 1.5237-.84203 1.8558 0l1.6803 4.26031 4.2603 1.68027c.842.3321.842 1.5237 0 1.8558l-4.2603 1.6803-1.6803 4.2603c-.3321.842-1.5237.842-1.8558 0l-1.68027-4.2603-4.26031-1.6803c-.84203-.3321-.84203-1.5237 0-1.8558l4.26031-1.68027zm.9279 1.73562-1.2928 3.27806c-.1014.2571-.3049.4606-.562.562l-3.27806 1.2928 3.27806 1.2928c.2571.1014.4606.3049.562.562l1.2928 3.2781 1.2928-3.2781c.1014-.2571.3049-.4606.562-.562l3.2781-1.2928-3.2781-1.2928c-.2571-.1014-.4606-.3049-.562-.562z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 213 KiB |
@@ -28,6 +28,20 @@ export type Auswahlfeld = {
|
|||||||
setzen: (optionen: Option[]) => void;
|
setzen: (optionen: Option[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Das eine offene Auswahlfeld — oder keines.
|
||||||
|
*
|
||||||
|
* **Modulweit, nicht je Feld.** Vorher merkte sich jedes Feld nur seine
|
||||||
|
* eigene Liste; drei Felder nebeneinander liessen sich alle drei aufklappen
|
||||||
|
* und überlagerten einander. Ein Auswahlfeld ist ein Menü, und von denen ist
|
||||||
|
* immer höchstens eines offen — genauso hält es das Kontextmenü.
|
||||||
|
*/
|
||||||
|
let offenesFeld: { schliessen: () => void } | null = null;
|
||||||
|
|
||||||
|
export function alleAuswahlenSchliessen(): void {
|
||||||
|
offenesFeld?.schliessen();
|
||||||
|
offenesFeld = null;
|
||||||
|
}
|
||||||
|
|
||||||
export function auswahl(optionen: Option[], vorgabe = ''): Auswahlfeld {
|
export function auswahl(optionen: Option[], vorgabe = ''): Auswahlfeld {
|
||||||
let liste = [...optionen];
|
let liste = [...optionen];
|
||||||
let wert = vorgabe;
|
let wert = vorgabe;
|
||||||
@@ -56,8 +70,11 @@ export function auswahl(optionen: Option[], vorgabe = ''): Auswahlfeld {
|
|||||||
offen?.remove();
|
offen?.remove();
|
||||||
offen = null;
|
offen = null;
|
||||||
knopf.setAttribute('aria-expanded', 'false');
|
knopf.setAttribute('aria-expanded', 'false');
|
||||||
|
if (offenesFeld === steuerung) offenesFeld = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const steuerung = { schliessen };
|
||||||
|
|
||||||
function waehlen(neu: string): void {
|
function waehlen(neu: string): void {
|
||||||
const geaendert = neu !== wert;
|
const geaendert = neu !== wert;
|
||||||
wert = neu;
|
wert = neu;
|
||||||
@@ -70,6 +87,10 @@ export function auswahl(optionen: Option[], vorgabe = ''): Auswahlfeld {
|
|||||||
function oeffnen(): void {
|
function oeffnen(): void {
|
||||||
if (offen) { schliessen(); return; }
|
if (offen) { schliessen(); return; }
|
||||||
|
|
||||||
|
// Erst das andere zu, dann dieses auf.
|
||||||
|
alleAuswahlenSchliessen();
|
||||||
|
offenesFeld = steuerung;
|
||||||
|
|
||||||
const box = document.createElement('div');
|
const box = document.createElement('div');
|
||||||
box.className = 'auswahl-liste';
|
box.className = 'auswahl-liste';
|
||||||
box.setAttribute('role', 'listbox');
|
box.setAttribute('role', 'listbox');
|
||||||
@@ -172,8 +193,11 @@ export function auswahl(optionen: Option[], vorgabe = ''): Auswahlfeld {
|
|||||||
// Kontextmenü. Je Feld registriert und nicht global: es gibt wenige davon,
|
// Kontextmenü. Je Feld registriert und nicht global: es gibt wenige davon,
|
||||||
// und beim Entfernen des Feldes gehen sie mit.
|
// und beim Entfernen des Feldes gehen sie mit.
|
||||||
document.addEventListener('click', e => {
|
document.addEventListener('click', e => {
|
||||||
if (offen && !offen.contains(e.target as Node) && e.target !== knopf) schliessen();
|
if (offen && !offen.contains(e.target as Node) && !knopf.contains(e.target as Node)) {
|
||||||
|
schliessen();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
document.addEventListener('keydown', e => { if (e.key === 'Escape' && offen) schliessen(); });
|
||||||
window.addEventListener('scroll', () => schliessen(), { passive: true, capture: true });
|
window.addEventListener('scroll', () => schliessen(), { passive: true, capture: true });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -999,7 +999,12 @@ function bibliotheksbereich(
|
|||||||
knoepfe.appendChild(freigeben);
|
knoepfe.appendChild(freigeben);
|
||||||
|
|
||||||
function freigabeSchreiben(): void {
|
function freigabeSchreiben(): void {
|
||||||
freigeben.hidden = aktuell.pendingCount === 0;
|
// **`hidden` reicht hier nicht.** Das Attribut wirkt über
|
||||||
|
// `display: none` mit der Spezifität von Null — `.btn` setzt
|
||||||
|
// `display: inline-flex` und gewinnt. Der Knopf stand deshalb als
|
||||||
|
// „0 Titel freigeben" da, obwohl nichts wartet. `.hidden` trägt ein
|
||||||
|
// `!important` und setzt sich durch.
|
||||||
|
freigeben.classList.toggle('hidden', aktuell.pendingCount === 0);
|
||||||
freigeben.textContent = `${aktuell.pendingCount} Titel freigeben`;
|
freigeben.textContent = `${aktuell.pendingCount} Titel freigeben`;
|
||||||
}
|
}
|
||||||
freigabeSchreiben();
|
freigabeSchreiben();
|
||||||
|
|||||||