services/config/config-schema.jsjavascript
/**
 * Declares every kiosk/device config value the app understands. Values come
 * from URL params (source "param", read live by useConfig in config.jsx and
 * kept alive across navigation), each with type, default and a German
 * description shown in admin/debug contexts.
 *
 * @type {Array<{key: string, source: string, name: string, type: string, default: *, min: number, description: string}>}
 */
export const CONFIG_SCHEMA = [
  // --- Stage (whole-app scale wrapper, src/components/Stage/Stage.jsx) ---
  // The authored reference resolution every page's CSS is designed against -
  // always the largest size for the CURRENT physical device/orientation
  // combination, so real screens only ever scale the whole app *down*,
  // never up (upscaling would blur the WebGL globe, see
  // InteractiveGlobe.jsx). Two independent axes, not one: `stageDevice`
  // picks which physical kiosk this is (each has its OWN landscape
  // width/height, since a 4K panel and an iPad's CSS point resolution are
  // very different - the iPad is a 2x Retina display, so its CSS points are
  // roughly half its physical pixel count), and orientation (from
  // PRESENTATION_MODES, see Stage.jsx) swaps that device's own pair for
  // portrait routes - i.e. "the same panel, physically rotated" is still
  // true WITHIN one device, just not across devices. Getting this wrong
  // (treating "portrait" as one single cross-device resolution) is exactly
  // what caused Rad Enablement to letterbox on the iPad but would upscale/
  // blur on a rotated 4K panel - see README.md's "Scaling (Stage)" section.
  // Exposed as params (not only hardcoded) so a different reference size
  // can be tested/tuned on a real kiosk without a rebuild.
  {
    key: "stageDevice",
    source: "param",
    name: "stageDevice",
    type: "string",
    default: "4k",
    description:
      "Welches physische Kiosk-Gerät das hier ist ('4k' oder 'ipad', siehe stageRefWidth/Height vs. stageIpadRefWidth/Height) - bestimmt, welches Referenzauflösungs-Paar Stage.jsx verwendet.",
  },
  {
    key: "stageRefWidth",
    source: "param",
    name: "stageRefWidth",
    type: "number",
    default: 3840,
    min: 1,
    description:
      "Referenzbreite in px, auf die die gesamte App im Landscape entworfen ist (stageDevice='4k', Stage.jsx).",
  },
  {
    key: "stageRefHeight",
    source: "param",
    name: "stageRefHeight",
    type: "number",
    default: 2160,
    min: 1,
    description:
      "Referenzhöhe in px, auf die die gesamte App im Landscape entworfen ist (stageDevice='4k', Stage.jsx).",
  },
  {
    key: "stageIpadRefWidth",
    source: "param",
    name: "stageIpadRefWidth",
    type: "number",
    default: 1366,
    min: 1,
    description:
      "Referenzbreite in px im Landscape für stageDevice='ipad' (Stage.jsx) - Standard entspricht der CSS-Punktbreite eines 13\" iPad.",
  },
  {
    key: "stageIpadRefHeight",
    source: "param",
    name: "stageIpadRefHeight",
    type: "number",
    default: 1024,
    min: 1,
    description:
      "Referenzhöhe in px im Landscape für stageDevice='ipad' (Stage.jsx) - Standard entspricht der CSS-Punkthöhe eines 13\" iPad.",
  },
  // --- Genereic Settings (src/services/config/config.jsx) ---
  {
    key: "screensaverTimeout",
    source: "param",
    name: "screensaverTimeout",
    type: "number",
    default: 360,
    min: 0,
    description: "Sekunden ohne Interaktion, bevor der Screensaver startet.",
  },
  {
    key: "screensaverFadeDuration",
    source: "param",
    name: "screensaverFadeDuration",
    type: "number",
    default: 1000,
    min: 0,
    description: "Dauer der Ein-/Ausblendanimation des Screensavers in Millisekunden.",
  },
  {
    key: "defaultMode",
    source: "param",
    name: "defaultMode",
    type: "string",
    default: "default",
    description:
      'Wählt, welcher Screensaver (content.screensavers, siehe Screensaver.jsx) beim Leerlauf gezeigt wird - einer der Modus-Keys aus src/services/presentationModes.js (z.B. "value-partnership").',
  },
  {
    key: "contentFile",
    source: "param",
    name: "contentFile",
    type: "string",
    default: null,
    description:
      "Erzwingt einen bestimmten Pfad zur content.json (statt der Datei, die ContentProvider anhand der aktuellen Route lädt, siehe src/services/presentationModes.js).",
  },
  {
    key: "disableAnimations",
    source: "param",
    name: "disableAnimations",
    type: "boolean",
    default: false,
    description: "Deaktiviert alle Background Animationen, wenn auf true gesetzt.",
  },

  // --- Value Partnership globe (InteractiveGlobe.jsx/GlobePage.jsx) -------
  // Nur einfache Werte (Zahl/String) - komplexe/strukturierte Defaults
  // (Filter-Startzustand, Marker-Icon-Pfade, Tab-Kategorien, Länder-Namens-
  // Aliase) liegen stattdessen in globeDefaults.js, da sie sich nicht
  // sinnvoll als URL-Parameter abbilden lassen.
  {
    key: "globeOceanColor",
    source: "param",
    name: "globeOceanColor",
    type: "string",
    default: "#000000",
    description: "Farbe der Basiskugel (Ozean-Fläche zwischen den Länder-Polygonen).",
  },
  {
    key: "globeLandStrokeColor",
    source: "param",
    name: "globeLandStrokeColor",
    type: "string",
    default: "rgba(255, 255, 255, 0.0)",
    description: "Farbe der Ländergrenz-Linien auf dem Globus.",
  },
  {
    key: "globePolygonSideColor",
    source: "param",
    name: "globePolygonSideColor",
    type: "string",
    default: "rgba(0, 0, 0, 0.6)",
    description: "Farbe der seitlichen 'Wände' der erhöhten Länder-Polygone.",
  },
  {
    key: "globeAtmosphereColor",
    source: "param",
    name: "globeAtmosphereColor",
    type: "string",
    default: "#ffffff",
    description: "Farbe des Atmosphären-Leuchtens am Kugelrand.",
  },
  {
    key: "globeFallbackCountryColor",
    source: "param",
    name: "globeFallbackCountryColor",
    type: "string",
    default: "#424242",
    description:
      "Länderfarbe, solange value-partnership-globe-countries.json noch nicht geladen ist (oder keine 'normal'-Farbe definiert).",
  },
  {
    key: "globeAtmosphereAltitude",
    source: "param",
    name: "globeAtmosphereAltitude",
    type: "number",
    default: 0.15,
    min: 0,
    description: "Höhe des Atmosphären-Leuchtens relativ zum Kugelradius.",
  },
  {
    key: "globePolygonAltitude",
    source: "param",
    name: "globePolygonAltitude",
    type: "number",
    default: 0.006,
    min: 0,
    description:
      "Höhe, in der die Länder-Polygone über der Basiskugel schweben (relativ zum Kugelradius).",
  },
  {
    key: "globeMinZoomAltitude",
    source: "param",
    name: "globeMinZoomAltitude",
    type: "number",
    default: 0.1,
    min: 0,
    description:
      "Nächste Kamera-Distanz beim Hineinzoomen (relativ zum Kugelradius) - verhindert, dass die Kamera zwischen die erhöhten Länder-Polygone gerät (siehe InteractiveGlobe.jsx).",
  },
  // not currently read anywhere (InteractiveGlobe.jsx sets no initial camera
  // altitude of its own, react-globe.gl's own default applies instead) -
  // kept here as a declared-but-inert value rather than removed, since the
  // description below still describes the intended behavior
  {
    key: "globeMaxZoomAltitude",
    source: "param",
    name: "globeMaxZoomAltitude",
    type: "number",
    default: 2.5,
    min: 0,
    description: "Kamera-Starthöhe beim Laden des Globus (relativ zum Kugelradius).",
  },
  {
    key: "globePolygonsTransitionDurationMs",
    source: "param",
    name: "globePolygonsTransitionDurationMs",
    type: "number",
    default: 200,
    min: 0,
    description:
      "Dauer der Farbüberblendung, wenn sich die Länder-Einfärbung ändert, in Millisekunden.",
  },
  {
    key: "globeZoomTransitionDurationMs",
    source: "param",
    name: "globeZoomTransitionDurationMs",
    type: "number",
    default: 1000,
    min: 0,
    description:
      "Dauer der Kamera-Zoomanimation beim Anklicken eines Markers oder Clusters, in Millisekunden.",
  },
  {
    key: "globeFocusZoomAltitude",
    source: "param",
    name: "globeFocusZoomAltitude",
    type: "number",
    default: 0.5,
    min: 0,
    description:
      "Kamerahöhe, auf die beim Anklicken eines einzelnen Markers gezoomt wird (relativ zum Kugelradius).",
  },
  {
    key: "globeAutoRotateSpeed",
    source: "param",
    name: "globeAutoRotateSpeed",
    type: "number",
    default: 1.0,
    min: 0,
    description: "Rotationsgeschwindigkeit des automatischen Drehens im Leerlauf.",
  },
  {
    key: "globeMarkerWidth",
    source: "param",
    name: "globeMarkerWidth",
    type: "number",
    default: 75,
    min: 1,
    description:
      "Breite der Marker-Pins in Pixeln (Höhe ergibt sich aus dem Seitenverhältnis der Pin-Grafik).",
  },
  {
    key: "globeAutoRotateIdleMs",
    source: "param",
    name: "globeAutoRotateIdleMs",
    type: "number",
    default: 120000,
    min: 0,
    description:
      "Leerlaufzeit ohne Interaktion mit dem Globus, bevor die automatische Rotation einsetzt, in Millisekunden.",
  },
];