Zum Inhalt

Themes

Ein Theme gestaltet die Oberfläche von Evey neu: ein Paket mit kind: theme ohne eigenen Serverprozess, bestehend aus einem Stylesheet und optional einem Skript, das isoliert in einem unsichtbaren Frame läuft. Diese Seite richtet sich an Theme-Autoren. Sie sagt, was die beiden Dateien enthalten dürfen und was der Installer umschreibt oder ablehnt. Themes werden wie Plugins installiert, unter Verwaltung → Plugins. Jeder Benutzer wählt eines unter Einstellungen → Darstellung → Design.

Manifest

plugin.yaml
name: aurora               # Pflicht; lesbarer Name
version: 1.0.0             # Pflicht; semver
kind: theme                # Pflicht

description: "..."         # optional
author: "..."              # optional

# kein language, entrypoint, permissions oder capabilities: sonst wird die Installation abgelehnt

Das ZIP enthält theme.css und/oder theme.js auf oberster Ebene, neben dem Manifest. Ein Paket ohne beide wird angenommen und bewirkt sichtbar nichts. examples/plugins/theme-dark-blue (eine kurze theme.css, die die Farbvariablen überschreibt) und examples/plugins/open-workshop (eine vollständige Neugestaltung) bestehen beide den Sanitizer und sind gute Ausgangspunkte.

theme.css

Das Stylesheet wird als <link rel="stylesheet"> in den Kopf jeder Seite eingefügt und gilt für das ganze Dokument: Custom Properties, @keyframes, @media, @font-face mit data:-Quellen, @layer und Container Queries funktionieren alle. Es wird ohne Authentifizierung unter /api/v1/themes/<id>/theme.css ausgeliefert. Ein Theme, das sich der Browser merkt, gilt also schon vor der Anmeldung.

Mit den Custom Properties beginnen

Die Oberfläche nimmt ihre Flächen, Texte, Akzentfarbe, Rahmen und Radien aus einer Reihe von Custom Properties auf :root. examples/plugins/theme-dark-blue/theme.css listet sie auf, jede mit einem Kommentar. Wer sie überschreibt, gestaltet jeden Bildschirm auf einmal um:

theme.css
:root {
  --bg: #0b1120;
  --surface: #162032;
  --text: #e2e8f0;
  --accent: #3b82f6;
  --accent-hover: #60a5fa;
  --radius: 8px;
}

Stabile Ansatzpunkte für Themes

Einige gestalterische Elemente tragen stabile Klassen. Sie können sie ansprechen, ohne von den Utility-Klassen abzuhängen, die sich von Version zu Version ändern. Diese Klassen sind verbindlich: Sie werden nicht umbenannt.

Selektor Element Gefahrlos änderbar
.evey-app-header die obere Leiste Farben, Höhe, Abstände, Unschärfe; die Bedienelemente darin nie ausblenden oder verschieben
.evey-media-grid die Raster der Medienkarten (Bibliothek, Reihen) Spalten, gap, Ausrichtung
.evey-media-card eine Medienkarte (Poster und Angaben) Breite, Abstände, Hover
.evey-poster das Poster oder Artwork (auch auf der Detailseite) aspect-ratio, border-radius, Schatten, Einpassung
.evey-media-detail das Wurzelelement der Detailseite eines Mediums Layout des Detailbereichs
theme.css - größere Poster, dichteres Raster, schärfere Ecken
.evey-media-card { width: 12rem; }
.evey-poster     { border-radius: 2px; }
.evey-media-grid { gap: 0.75rem; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.evey-media-card:hover { transform: scale(1.03); transition: transform 120ms ease; }

Was der Sanitizer umschreibt

Bei der Installation durchläuft theme.css einen Sanitizer. Er schreibt um oder entfernt, was ein Theme nicht darf, hält für jede Änderung eine Warnung fest und speichert das Ergebnis zurück: Ausgeliefert wird die bereinigte Datei, und die kann von dem abweichen, was Sie geschrieben haben. Das Paket lehnt er nie ab.

Was Sie geschrieben haben Was passiert Warum
@import ... Entfernt Ein Theme ist in sich geschlossen; ein Import würde Daten über das Netz preisgeben.
url(http://...), url(https://...), url(//...), ein externer Kandidat in image-set(), eine Custom Property mit einer solchen URL Durch none ersetzt oder unschädlich gemacht Derselbe Grund. data:-URIs bleiben unverändert.
url(javascript:...), expression(...), -moz-binding, behavior Entfernt Alte Wege, Code auszuführen.
Eine Regel, deren Selektor [type=password], [type=hidden], .admin-*, .token-* oder .secret-* trifft Der ganze Regelblock entfernt Ein Theme darf Passwortfelder und privilegierte Elemente nicht umgestalten.
Eine Regel mit position: fixed oder sticky, die den Bildschirm bedeckt: inset: 0, die vier Seiten auf 0 oder mindestens zwei von top/right/bottom/left: 0, width: 100vw, height: 100vh, darunter eine Größe in Viewport-Einheiten Der ganze Regelblock entfernt Die klassische Phishing-Überlagerung. Eine Kopfzeile, die mit top: 0; left: 0 angeheftet ist und sich nach ihrem Inhalt richtet, bleibt erhalten. Deklarationen für denselben Selektor werden über alle Regeln zusammengezählt.
pointer-events: none in einer Regel, die button, [role=button], a[href*=logout] oder [data-critical] anspricht Diese Deklaration entfernt, der Rest bleibt Ein kritisches Bedienelement unklickbar zu machen, ist eine Täuschung der Oberfläche (UI Redressing).
z-index über 999 Auf 999 begrenzt Ein Theme bleibt unter der Ebene der Dialoge der Anwendung.

Maskierte Zeichen werden vor diesen Prüfungen entschlüsselt. Eine Escape-Sequenz umgeht sie also nicht.

theme.js

Ein Skript ist optional. Es wird angemeldeten Benutzern unter /api/v1/themes/<id>/theme.js ausgeliefert und läuft in einem unsichtbaren <iframe sandbox="allow-scripts">, dessen Richtlinie connect-src 'none' setzt: kein Zugriff auf die Hostseite, ihren Speicher oder das Netz, auch nicht für ein Skript, das die Prüfung bei der Installation bestanden hat. Die einzige Schnittstelle ist window.evey.theme, das vor dem Skript eingefügt wird.

evey.theme.css(declarations)

Schickt Deklarationen von Custom Properties an die Hostseite, die sie auf :root anwendet. Nur diese Form wird angenommen: eine Liste --name: value, höchstens 200 Deklarationen, jeder Wert höchstens 500 Zeichen, ohne url(), image-set, expression(), @import, javascript:, data:, http(s):, //, Kommentar oder geschweifte Klammer. Alles andere wird verworfen und in der Konsole gemeldet. Eine Property wirkt nur dort, wo das bereinigte Stylesheet sie verwendet.

theme.js
evey.theme.css("--accent: #e8a33d; --accent-hover: #f0b95c");

evey.theme.log(level, message)

Gibt eine Zeile an die Browserkonsole weiter. level ist "debug", "info", "warn" oder "error"; die Nachricht wird nach 500 Zeichen abgeschnitten. Dem Benutzer wird nichts angezeigt.

evey.theme.registerComponent und evey.theme.observe gibt es auf dem Objekt, sie sind aber nicht umgesetzt: Der Host beantwortet sie mit einem Fehler.

Was der Scanner ablehnt

Bei der Installation wird theme.js als Text geprüft. Ein einziger Treffer lehnt das Paket ab, mit Angabe des Grundes. Der Scanner ist absichtlich streng: Ein Theme hat keinen legitimen Grund, diese Namen zu erwähnen, nicht einmal in einem Kommentar.

Jede dieser Zeichenfolgen, egal wo im Quelltext:

document.cookie, localStorage, sessionStorage, indexedDB, XMLHttpRequest, navigator.sendBeacon, navigator.serviceWorker, WebSocket, EventSource, importScripts, document.write, Element.prototype, Object.defineProperty, globalThis, window.parent, window.top, window.opener, Reflect.get, Reflect.apply, Reflect.construct, window.frames, frameElement, postMessage.

Jedes dieser Muster:

Muster Was es erkennt
\b(fetch\|eval\|atob\|btoa\|Function\|setTimeout\|setInterval)\s*\( Direkte Aufrufe verbotener globaler Funktionen
\bimport\s*\( Dynamischer Import
(?:window\|self\|globalThis\|top\|parent\|document\|navigator\|location)\s*\[ Zugriff mit eckigen Klammern auf globale Objekte des Hosts, etwa window["fetch"]
\.cookie\b Jeder Zugriff auf .cookie
\.constructor\s*\.\s*constructor Der Ausbruch über [].constructor.constructor
new\s+Function\b Code zur Laufzeit erzeugen
(?:window\|self\|top\|parent\|globalThis)\s*\.\s*(?:Function\|eval\|fetch\|atob\|btoa\|XMLHttpRequest\|WebSocket\|EventSource)\b Lesen einer verbotenen globalen Eigenschaft
(?:window\|top\|parent\|document\|location\|globalThis\|self)\s*\.\s*[A-Za-z_$][\w$]*\s*=[^=] Zuweisung an ein globales Objekt des Hosts
Object\s*\.\s*prototype Prototype Pollution
\.register\s*\(\s*['"]\S+\.js Registrierung eines Service Workers