Web-Apps paketieren, die in Studio und Player laufen.
ScreenWay-Apps sind versionierte Bundles aus Studio-Storage. Sie laufen im iFrame, erhalten ihre Konfiguration über window.APP_CONFIG und werden neben Designer-Projekten, Programmen und Notifications angezeigt.
Mit dem offiziellen Skeleton starten
Universal App Scaler, postMessage-verdrahtete config.html, deutsche und englische Locales sowie das Safe-Default-Boot-Pattern, zum Kopieren bereit.
Was eine App ist
Eine moderne App liefert mindestens index.html (die Runtime), config.html (das in-Studio-Settings-Formular), ein app.json-Schema, ein icon.svg und ein Pflicht-locales/de.json. Banner- und Featured-Bilder werden separat erzeugt und sind vor dem ersten Upload erforderlich.
Runtime
index.html rendert im Studio-Designer, im Player und in der Standalone-Vorschau. Designgröße: 1920×1080, automatisch auf jeden Viewport skaliert.Settings-UI
config.html rendert im Studio-iFrame und kommuniziert per postMessage mit Studio. Kein äußerer Card, kein App-Header. Das liefert Studio.Lokalisierung
locales/<code>.json. de.json ist der universelle Fallback und muss jeden Schlüssel enthalten.Bundle-Aufbau
my-app/
├── app.json # Pflicht: Schema, kein Anzeige-Text
├── index.html # Pflicht: die App selbst (1920×1080)
├── config.html # Pflicht: Konfigurations-UI im Studio-iFrame
├── icon.svg # Pflicht: 512×512 Brand-Symbol
├── locales/
│ ├── de.json # Pflicht: primärer Fallback-Locale
│ └── en.json # Optional: weiterer Locale
├── build.sh # im Skeleton enthalten: paketiert den Ordner
├── README.md # im Skeleton enthalten: wird aus dem Upload-ZIP exkludiert
├── banner.png # 1200×400, für App-Store-Sichtbarkeit erforderlich
├── banner.en.png # Optional: lokalisierter Banner pro Locale
├── featured.png # 1920×1080, für App-Store-Sichtbarkeit erforderlich
├── featured.en.png # Optional: lokalisiertes Featured-Bild
├── mobile.html # Optional: QR-Code-Controller-View
└── assets/ # Optional: Bilder, Videos, Fonts, Audio
├── images/
├── videos/
└── fonts/- index.html: Runtime-Einstieg
- config.html: in-Studio-Settings-Formular, kein äußerer Card
- app.json: technische Metadaten und config_schema
- icon.svg: 512×512 Brand-Symbol, ohne Wortmarke
- locales/de.json: primärer Fallback, enthält jeden sichtbaren String
- banner.png (1200×400) und featured.png (1920×1080) für App-Store-Sichtbarkeit
- build.sh + README.md liegen im Skeleton und werden aus dem Upload-ZIP exkludiert
- mobile.html und assets/ sind optional und fügen sich um die Pflichtdateien
Flaches Bundle, kein Eltern-Ordner
build.sh übernimmt das automatisch und exkludiert build.sh und README.md aus dem Upload.app.json
app.json ist das stabile technische Schema. Anzeige-Texte bleiben in den Locale-Files. Niemals Label, Beschreibung oder Option-Titel hier einsetzen.
{
"name": "Lobby Weather",
"slug": "lobby-weather",
"version": "1.0.0",
"category": "utility",
"is_free": true,
"price_monthly": 0,
"config_schema": {
"fields": [
{ "key": "title", "type": "text", "default": "Berlin", "required": true },
{ "key": "fontSize", "type": "number", "default": 120, "min": 24, "max": 320 },
{ "key": "accent", "type": "color", "default": "#0EA5E9" },
{
"key": "locale",
"type": "select",
"default": "de",
"options": [{ "value": "de" }, { "value": "en" }]
}
]
}
}Unterstützte Field-Typen
config_schema.fields[].type
textstringtextareastringnumbernumberurlstringpasswordstringcolorstringselectstringcheckboxbooleanlocale ist ein reservierter Key
key: "locale" steuert den Display-Locale. Sein Wert wird zu window.APP_CONFIG.locale.code und entscheidet, welche runtime.*-Strings Studio injiziert.Lokalisierung
Studio merged locales/<chosen>.json über locales/de.json. Fehlende Keys fallen automatisch auf Deutsch zurück. Apps müssen de.json vollständig halten.
{
"description": "Live-Wetter und Vorhersage für jede Stadt.",
"featured": {
"title": "Immer aktuelles Wetter auf jedem Screen",
"description": "Stadt wählen, Akzentfarbe setzen, auf jedem Display einsetzen."
},
"config_schema": {
"fields": {
"title": { "label": "Stadt", "placeholder": "Berlin" },
"fontSize": { "label": "Schriftgröße" },
"accent": { "label": "Akzentfarbe" }
}
},
"runtime": {
"today": "Heute",
"fallback": { "title": "Wetter", "subtitle": "Stadt in den App-Einstellungen festlegen." }
}
}Locale-Resolution-Chain
- Hat die Instance-Config
localegesetzt, nutzt Studio diesen Wert. - Sonst greift der App-Store-Locale des Studio-Users (die Sprache, in der er die Apps durchstöbert hat).
- Sonst gewinnt
de.
Runtime: window.APP_CONFIG
Studio injiziert window.APP_CONFIG am <script id="app-config"></script> -Platzhalter, bevor Ihr App-Script läuft. Read-only behandeln.
interface AppConfig {
instanceId: string // UUID dieser konfigurierten Instanz
instanceName: string // benutzerdefinierter Anzeigename
appId: string // App-UUID
appSlug: string // z. B. "lobby-weather"
appName: string // z. B. "Lobby Weather"
version: string // z. B. "v1.0.0"
config: Record<string, unknown> // Werte gemäß config_schema
locale: {
code: string // aufgelöster BCP-47 (z. B. "de", "en", "de-CH")
strings: Record<string, unknown> // runtime.* gemerged mit de.json runtime.*
}
mobileUrl?: string // nur gesetzt, wenn das Bundle mobile.html mitliefert
}- Immer defensiv lesen: var cfg = (window.APP_CONFIG && window.APP_CONFIG.config) || {}
- Auf Inline-Defaults zurückfallen, damit lokale Entwicklung ohne Studio funktioniert
- Runtime-Strings aus window.APP_CONFIG.locale.strings lesen
- Intl.DateTimeFormat mit locale.code für Datumsangaben und Zahlen nutzen
- document.body.style.opacity = "1" erst nach Anwenden der Config setzen, um Flicker zu vermeiden
- transparentBackground in der Config bedeutet: App rendert über transparentem Body
- mobileUrl ist nur gesetzt, wenn das Bundle mobile.html mitliefert. Vorher prüfen
Nur relative Asset-Pfade verwenden
<base>-Tag, damit relative Pfade auf den Storage-Pfad auflösen. <img src="assets/logo.png"> funktioniert; <img src="/assets/logo.png"> bricht in Produktion. Gleiches gilt für Stylesheets, Scripts und fetch-URLs für gebündelte Daten.Canvas-mode vs Fluid-mode
Das Skeleton liefert standardmäßig im Canvas-mode aus. Die Alternative ist sinnvoll, wenn Inhalte reflowen sollen.
Canvas-mode (Default)
Festes 1920×1080-Design, alle Größen in Pixeln. Der Universal App Scaler transformiert das Design per CSS auf den realen Viewport und hält das Seitenverhältnis bei.
Designer-Mockups sollen 1:1 mappen, das Layout besteht aus festen Regionen, Reflow auf engen Viewports ist kein Thema.
Fluid-mode (Alternative)
100vw / 100vh-Root, Größen über clamp() und Viewport-Units. Kein Scaler. Der Browser skaliert nativ.
Text soll reflowen, das Layout passt sich verschiedenen Seitenverhältnissen an, oder die App nutzt bereits ein fluides CSS-System.
Wechsel von Canvas zu Fluid
index.html nach BEGIN UNIVERSAL APP SCALER suchen und alles bis einschließlich END UNIVERSAL APP SCALER löschen. html, body width/height von 1920px / 1080px auf 100vw / 100vh ändern, Pixelwerte auf clamp() umstellen. Der app-config-Platzhalter, getConfig() und der postMessage-Handshake bleiben identisch.config.html (das Settings-Formular)
Studio rendert config.html in einem iFrame und übernimmt den umgebenden Card, den Titel und den Speichern-Button. Das Formular kommuniziert per postMessage mit Studio.
// config.html bootet im Studio-iFrame.
// postMessage zum Lesen und Schreiben der Config-Werte.
// 1) Studio mitteilen, dass wir bereit sind
window.parent.postMessage({ type: 'CONFIG_READY' }, '*')
// 2) Studio antwortet mit der gespeicherten Config
window.addEventListener('message', (event) => {
if (event.data?.type === 'LOAD_CONFIG') {
applyToForm(event.data.config)
}
})
// 3) Bei jeder Änderung Config zurückschicken
function emitChange(config) {
window.parent.postMessage({ type: 'CONFIG_CHANGED', config }, '*')
}Message-Typen
CONFIG_READYChild → ParentLOAD_CONFIGParent → ChildCONFIG_CHANGEDChild → ParentConfig-UI-Standard befolgen
config.html des Skeletons: kein äußerer Card, kein App-Header, kein Logo, keine eigenen Fonts. Pro App ändert sich nur die --accent-CSS-Custom-Property.Große Configs: über die API laden
URL-kodierte Configs sind durch den Browser begrenzt. Bei Apps mit langen Arrays (Quiz-Fragen, Ticker-Items, große Medien-Listen) holen Sie die gespeicherte Config direkt von /api/app-instances/{instanceId}/config statt sich auf URL-Parameter zu verlassen.
async function loadConfig() {
const urlParams = new URLSearchParams(window.location.search)
const instanceId = urlParams.get('instanceId') || window.APP_CONFIG?.instanceId
if (instanceId) {
const res = await fetch(`/api/app-instances/${instanceId}/config`)
if (res.ok) {
const { config } = await res.json()
return config
}
}
return window.APP_CONFIG?.config ?? {}
}Komplexe Felder in config.html serialisieren
CONFIG_CHANGED-Payload senden und beim Laden wieder parsen. So funktioniert jeder Transport-Pfad (URL, postMessage, API).Browser-Kompatibilität
Player-Geräte umfassen Android-Sticks, Smart-TVs und Embedded-Screens. Baseline: Chrome 66 / ES2017. Der Universal App Scaler ist bewusst in schlichtem ES5/ES2017 geschrieben, damit er überall läuft.
ES2017-Features nutzen
Keine Bundler nötig
Auf einem TV-Stick testen
Lokale Entwicklung
Beliebigen statischen File-Server vom Bundle-Root starten und index.html öffnen. Ohne APP_CONFIG greifen Ihre Inline-Defaults. Von Tag eins für diesen Pfad designen.
cd my-app
python3 -m http.server 8000
# index.html → http://localhost:8000/
# config.html → http://localhost:8000/config.htmlLokal die Config per URL-Parameter steuern. Das Skeleton parst sie zurück in dieselbe Shape wie APP_CONFIG.config:
http://localhost:8000/?title=Test&fontSize=200&transparentBackground=trueDebugging
console.log mit App-Kontext
[${APP_CONFIG?.appName ?? 'dev'}] präfixen. Screen-Logs bleiben durchsuchbar.Dev vs. Produktion erkennen
!!window.APP_CONFIG ist der sicherste Produktions-Check. So überspringen Sie Mock-Daten auf echten Screens.postMessage tracen
CONFIG_*-Message in config.html während der Entwicklung loggen. Studio verwirft fehlerhafte Payloads stillschweigend.Accent + Theme-Drift
Store-Assets
Studio zeigt für jede App zwei Bilder: im Katalog-Grid, auf der App-Detailseite und in der Instance-Vorschau. Beide Dateien liegen im Bundle-Root neben app.json.
Bilddateien
banner.pngrequired1200×400, PNGfeatured.pngrequired1920×1080, PNGbanner.<lang>.png1200×400, PNGbanner.en.png).featured.<lang>.png1920×1080, PNGLokalisierte Varianten fallen auf Default zurück
banner.<lang>.png nutzt automatisch banner.png. Lokalisierte Varianten nur dann mitliefern, wenn das Bild selbst sprachspezifische Inhalte hat (Text, Screenshots).Design-Regeln
Banner (1200×400, 3:1)
Horizontales Layout, stilisierter Marketing-Mockup (kein wörtlicher Screenshot). Safe-Area-Inset 72px / 40px. Linke Hälfte: Brand-Badge, Tagline, Hero-Element. Rechte Hälfte: Feature-Tiles, Varianten-Grid oder ein zentrierter Hero. Dunkle Gradient-Basis Richtung Akzentfarbe.
Featured (1920×1080, 16:9)
Reines Bild-Asset. Studio legt Brand, Headline, Description und CTAs als UI darüber, also draußen lassen. Center-stage-Hero 3–5× größer skaliert als auf dem Banner. Gleiche Palette wie der Banner. Safe-Area-Inset 140px / 100px. Großzügig Leerraum.
Palette aus der App ableiten
icon.svg (Gradient-Stops, Fills), dann aus config_schema-Defaults (backgroundColor, accentColor), erst zuletzt aus einer kategorie-basierten Palette. Generische Slate-Banner vermeiden, die Basis deutlich Richtung Akzentfarbe drücken.PNGs erzeugen
- HTML-Mockup + Chrome-Screenshot: in einem normalen Browser-Tab in exakter Größe rendern, DevTools öffnen, Device-Toolbar aktivieren, Custom-Size (1200×400 oder 1920×1080) bei DPR 1 setzen, dann
Capture full size screenshot. - Headless via Puppeteer:
page.setViewportmitdeviceScaleFactor: 1, lokale HTML-Datei laden,page.screenshot. - Design-Tool: Figma / Sketch / Affinity in exakter Größe als PNG exportieren, DPR 1× (nicht retina-doppelt).
Publish-Flow
Studio-Admins nehmen Apps in den Katalog auf. Self-Service-Uploads sind in Vorbereitung. Bis dahin geht jeder Katalog-Eintrag über das ScreenWay-Team mit kurzer Prüfung.
1. Paketieren
app.json validieren, sicherstellen, dass locales/de.json jeden Key abdeckt, Banner- und Featured-Bilder ins Bundle-Root legen.2. Version bumpen
./build.sh --bump-patch ausführen (oder --bump-minor / --bump-major). Das Skeleton liefert build.sh dafür mit.3. Einreichen
# Im App-Ordner. Das Skeleton liefert build.sh mit
./build.sh # build mit aktueller Version aus app.json
./build.sh --bump-patch # 1.0.0 → 1.0.1
./build.sh --bump-minor # 1.0.0 → 1.1.0
./build.sh --bump-major # 1.0.0 → 2.0.0
# Ergebnis: my-app-v1.0.1.zip im aktuellen Verzeichnis.
# Benötigt: bash, zip, jq (brew install jq / apt install jq).# Fallback ohne bash + jq
cd my-app
zip -r ../my-app-v1.0.0.zip . \
-x "*.DS_Store" -x "__MACOSX/*" \
-x "build.sh" -x "README.md" -x ".git/*"Versionierung
Semantische Versionierung
MAJOR1.0.0 → 2.0.0MINOR1.0.0 → 1.1.0PATCH1.0.0 → 1.0.1Upload-Validierung
- Abgelehnt: fehlende app.json, index.html, icon.svg oder locales/de.json im ZIP-Root
- Abgelehnt: ungültiges JSON in app.json oder einem Locale-File
- Abgelehnt: app.json ohne name oder slug oder mit ungültigem config_schema
- Abgelehnt: doppelter slug. Stattdessen als neue Version der bestehenden App hochladen
- Abgelehnt: kein ZIP, kaputtes ZIP oder verschachtelter Ordner
- Akzeptiert aber versteckt: fehlende banner.png oder featured.png. App wird hochgeladen, bleibt aber aus dem App-Store-Grid raus
- Akzeptiert mit Fallback: fehlende config.html. Studio generiert ein einfaches Formular (visuell inkonsistent zum Katalog)
Was in die Submission-Mail gehört
Nächste Schritte
Mit dem Skeleton starten
app-skeleton bringt vollständigen Scaler, postMessage-verdrahtete config.html, deutsche und englische Locales und das Safe-Default-Boot-Pattern mit. Kopieren, umbenennen, ausliefern.v1-API für Inhalte nutzen
config aktualisieren und sie an Programme hängen, ohne Studio zu öffnen.