{% extends "basis.html" %}{% block weich %} data-weich{% endblock %}{% block inhalt %}{% from "schalter.html" import schalter %} {# Quellen (UX-Paket 1, Teil 2): alles, wodurch Musik in die Library kommt – Onlinespeicher, Analyse unbekannter Dateien, seit 23.10.2026 eine Zeile zum letzten Lauf (Verlauf und Live-Status unter /analysen). Früher im Konto; /konto#speicher und /konto#analysen leiten hierher. Nicht verbunden zeigt der Abschnitt den Assistenten (Wahl des Speichers, Zugang, Testen), verbunden das Cockpit (Kennzahlen, Rückweg in die Library). #} {% set q = t.quellen %}
{# Navigation 16.10. (W7, E-N10): Solange keine Quelle eingerichtet ist, landet das Konto hier, und die Seite beginnt mit „Ein Schritt, dann geht’s los“ – der Fluss in drei Schritten, der jetzige Schritt hervorgehoben. Danach nie wieder. Ohne Speicher-Weg gibt es den einen Schritt nicht (Zeile 01 sagt das): dort bleibt die Überschrift „So geht’s los“. #} {% if start_marke %}{% set o = t.onboard %}

{{ o.eyebrow }}

{{ o.titel if speicher_weg else o.titel_ohne }}

  1. {{ t.quellen.nav }}{{ ((o.jetzt | ohne_desktop) if speicher_weg else t.store.leer_ohne_speicher) if store_app else (o.jetzt if speicher_weg else o.jetzt_ohne) }}
  2. {{ t.nav.explore.name }}{{ o.library }}
  3. {{ t.nav.review.name }}{{ o.playlists }}
{% if speicher_weg and speicher is defined and speicher and speicher.aktiv %}

{{ o.verbinden }}

{% endif %} {# 11.11.2026 (TP2 0529, „onboard.demo“): Wer noch keine eigene Musik verbunden hat, kommt von hier in die Live-Demo – in den Store-Fassungen und mit Early Bird (demo_web._demo_als_gast lässt ein Konto ohne Quelle dann hinein). #} {% if neutral and demo_an() %}

{{ t.earlybird.onboard_demo }} {{ t.earlybird.demo_knopf }}

{% endif %}
{% endif %}
{# Durchsicht S6: mit „So geht’s los“ steht die Eyebrow schon darüber – nicht zweimal „Musik rein“ #} {% if not start_marke %}

{{ q.eyebrow }}

{% endif %}
{% if start_marke %}

{{ q.titel }}

{% else %}

{{ q.titel }}

{% endif %}

{{ q.lead | ohne_desktop if store_app else q.lead }}

{% if not store_app %}

{{ t.tspeicher.upload.sprung }}

{% endif %}
{# Musik hinzufügen (29.10.2026): „Auf diesem Rechner“ – nur im App-Fenster, oben als erster Weg; static/desktop_extras.js füllt den Abschnitt (Stand aus lokal.uebersicht, Knöpfe je Fähigkeit einlesen.ordner / einlesen.xml) #} {% if desktop_app %}{% endif %} {# Onlinespeicher (N18): verbinden, testen, Abgleich, trennen. speicher.js übernimmt den Abschnitt; ohne JavaScript bleibt der Stand lesbar. Das Passwort steht nie in der Seite. Ist die Funktion aus (`speicher.aktiv` falsch), steht der Abschnitt GAR NICHT in der Seite – kein Formular, kein speicher.js; dann nur der Satz, dass der Onlinespeicher abgeschaltet ist. #} {% if speicher is defined and speicher and speicher.aktiv %}{% set s = t.speicher %}{% set a = speicher_anzeige %}
{# Kopf des Cockpits: Name und Zugang kurz; verbunden trägt die Karte die Akzentfarbe (CSS über data-zustand) #}

{{ s.titel }}

{{ speicher.benutzer or '' }} · {{ speicher.startordner or '' }}

{# Kennzahlen des Abgleichs (Cockpit): während des Laufs „geprüft / gesamt“, danach die Zahl der Dateien #}
{{ a.k_name }}
{{ a.k_dateien }}
{{ s.kennzahlen.zugeordnet }}
{{ a.k_zugeordnet }}
{{ s.kennzahlen.unbekannt }}
{{ a.k_unbekannt }}
{{ s.kennzahlen.fehler }}
{{ a.k_fehler }}

{{ a.fortschritt }}

{{ a.zustand }}

{# Restzeit des laufenden Abgleichs: dieselbe Zeile wie in der Kopfleiste (status.js, aus `rate`/`rest_s`) #}

{{ a.hinweis }}

{{ a.doppelt }}

{{ a.letzter }}

{# Rückweg in die Library: die Zahl zählt Tracks der Library aus dem Speicher (`library`), nicht zugeordnete Dateien #}
{{ a.library }} {# „Nach neuer Musik suchen“ (gleicher Begriff überall, 25.10.2026): diese Karte ist immer der EIGENE Onlinespeicher. #}

{{ s.fortschritt_hilfe_analyse if speicher_analyse_an else s.fortschritt_hilfe }}

{{ s.zugang }}
{{ s.adresse }}
{{ speicher.adresse or '' }}
{{ s.benutzer }}
{{ speicher.benutzer or '' }}
{{ s.startordner }}
{{ speicher.startordner or '' }}
{{ s.auflistung }}
{{ a.auflistung }}
{# Seit 25.10.2026 hat ein Konto zwei Plätze: diese Karte ist der eigene Onlinespeicher (trennbar). Der Timbre-Speicher hat seine eigene Karte darunter und lässt sich von Nutzenden nicht trennen. #}

{{ s.zugang_aendern }}

{# Unbekannte Dateien analysieren (N23). Nur wenn die Funktion an ist (Schalter TIMBRE_SPEICHER_ANALYSE und die Spalten dazu): sonst steht der Block gar nicht in der Seite. Kein Auslöser ohne Klick; der Klick merkt nur vor. #} {% if speicher_analyse_an %}{% set x = s.analyse %}{% set an = speicher_analyse %}

{{ x.titel }}

{{ an.zahl if an else '' }}

{{ an.reicht if an else '' }}

{# dieselbe Zeile wie in der Kopfleiste (status.js): „Analyse: 3 von 480 Tracks · noch etwa 4 Stunden“ #}

{{ an.stand if an else '' }}

{{ an.hinweis if an else '' }}

{{ x.fehler_titel }}

    {% if an %}{% for zeile in an.fehler %}
  • {{ zeile }}
  • {% endfor %}{% endif %}
{# „Erneut versuchen“: nimmt Gescheiterte wieder auf (außer „zu groß“ und „Format“). Alle Knöpfe des Blocks sind ohne JavaScript gesperrt – speicher.js gibt sie frei. #}

{{ x.einwilligung }}

{{ schalter(tasten.analyse_auto, x.auto, id="speicher-analyse-auto", an=(an and an.auto), aus=true) }}

{{ x.auto_hilfe }}

{% endif %}
{# Sicherheitsschritt (23.10.2026): Ory-Konten ohne Passkey und App sehen vor dem Verbinden „Erst dein Konto sichern“ (Schritt 1 von 2) statt des Formulars. Ehrlich und ohne Druck: Passkey empfohlen und hervorgehoben, App gleichwertig, „Weiter nur mit Mail-Code“ gleich gut erreichbar (POST, merkt sich die Wahl – danach keine Erinnerung). Alles geht ohne JavaScript; der Server sperrt das Verbinden nicht. Der Mail-Code bleibt allein gültig. #} {% set an_ = t.anm %}{% set konto_link %}{{ an_.konto_titel }}{% endset %} {% if sicherheitsschritt %}

{{ an_.q_schritt }}

{{ an_.q_titel }}

{{ an_.q_text }}

{{ (an_.q_nachholen | e).replace("{konto}", konto_link) }}

{% else %} {% if nur_code_ok and not speicher.verbunden %}

{{ (an_.q_nur_code_ok | e).replace("{konto}", konto_link) }}

{% endif %} {# Assistent (nicht verbunden): Schritte 1–4, Wahl des Speichers als Radiogruppe – sie setzt nur Platzhalter und Hilfe, die Adressprüfung auf dem Server bleibt dieselbe. #}

{{ s.einleitung }}{% if speicher_analyse_an %} {{ s.einleitung_analyse }}{% endif %}

    {% for name in s.schritte %}{{ loop.index }} {{ name }}{% endfor %}
{{ s.wo }}
{% for kennung, name, platz, platz_benutzer, hilfe in s.orte %} {% endfor %}

{{ s.orte[0][4] }}{% if hilfe_storagebox %} {{ t.freigabe.so_gehts }}{% endif %}

{{ t.earlybird.nur_lesen if mac_store_app else s.nur_lesen }}

{% endif %} {# Upload in den eigenen Speicher (09.11.2026, E1): nur mit Schalter/Konto-Freigabe und verbundenem Speicher (static/upload_eigen.js) #} {% if ue %}{% set ux = ue.texte %}

{{ ux.quellen_titel }}

{{ ux.schalter_an | replace("{ordner}", ue.stand.ordner or "") if ue.stand.ablegen else ux.schalter_aus }}

{{ schalter("AB", ux.schalter, id="ue-quellen-schalter", an=ue.stand.ablegen, hilfe_id="ue-quellen-klein") }}

{{ ux.probe_info }}

{{ ux.zustimmung_am | replace("{datum}", ue.stand.zugestimmt or "") if ue.stand.zustimmung else ux.zustimmung_noch }}

{{ ux.widerruf_text }}

{% endif %}
{% else %} {# Funktion aus: kein Formular, kein Stand, kein speicher.js – nur der Satz, dass der Onlinespeicher abgeschaltet ist #}

{{ t.speicher.aus }}

{% endif %} {# App-Store-Fassungen: kein Timbre-Speicher (Upload, Ablage) in der App #} {% if not store_app %}{% include "beta_speicher_karte.html" %}{% endif %} {# Rekordbox-Import, Stufe 1 (23.10.2026, Variante A + Bericht aus C): Assistent 01 XML wählen · 02 Prüfen · 03 Übernehmen. Der Browser liest die XML (static/rekordbox_worker.js) und schickt nur Werte je zugeordnetem Track. Der Server zeichnet den Stand (Bericht, Löschen ohne JavaScript); rekordbox_seite.js schaltet die Zustände um. Ohne Tabellen keine Karte. #} {% if rekordbox and rekordbox.an %}{% set r = t.rekordbox.karte %}{% set rs = rekordbox.stand %}{% set z = rs.zaehler or {} %}{% set an = rs.anzahl or {} %}

{{ r.titel }}

{{ r.quelle }}
{% if rekordbox_geloescht %}

{{ r.geloescht }}

{% endif %} {# Anleitung (31.10.2026, Wunsch Maintainer): exportieren, finden, einlesen, aktuell halten, was bei dir bleibt – aufklappbar, in jedem Zustand und ohne JavaScript; dieselben Texte als Strecke #rekordbox-xml in der Hilfe (texte_rekordbox_anleitung.py). #} {% set rka = t.rekordbox.anleitung %}
{{ rka.titel }}
    {% for kopf, text in rka.schritte %}
  1. {{ kopf }}

    {{ text | ohne_desktop if ios_app else text }}

  2. {% endfor %}
{# Bericht (aus Variante C) und Stand: steht ohne JavaScript da, wenn schon etwas übernommen ist #}

{% if rs.da %}{{ r.fertig_zeile.replace("{zeit}", rekordbox.uebernommen | ortszeit(sprache)) }}{% endif %}

{# Älter als 14 Tage (app._rekordbox_alt_tage): ruhiger Hinweis; nach einem neuen Import blendet rekordbox_seite.js ihn aus #} {% if rekordbox.alt_tage %}

{{ rka.alt.replace("{n}", rekordbox.alt_tage | zahl(sprache)) }}

{% endif %}

{{ r.bericht_titel }}

{% for k in ("xml", "erkannt", "unklar", "ohne", "uneinig") %}
{{ r.bericht[k] }}
{{ (z[k] | zahl(sprache)) if z[k] is defined else "–" }}
{% endfor %}

{% if rs.da %}{% set namen = [] %}{% for a in rs.arten %}{% set _ = namen.append(r.loeschen_arten[a]) %}{% endfor %}{{ r.uebernommen_arten.replace("{arten}", namen | join(", ")) }}{% endif %}

{{ r.loeschen_titel }}

{{ r.loeschen_text }}

{{ r.loeschen_was }} {% for art in ("cues", "vergleich", "gespielt", "bewertung", "kommentar") if art in (rs.arten or []) %}{% set zahl_ = an.cues if art == "cues" else an[art] %} {% endfor %}
{# Musikordner (25.10.2026, vorher im Konto): der Ordner, unter dem die Files des Onlinespeichers auf diesem Rechner liegen – für die Location je Track im Rekordbox-Export. Beim Einlesen (Schritt 02) fragt die Karte danach, vorbelegt mit dem gemerkten Wert, und schlägt den Ordner aus den Location-Pfaden der XML vor (TimbreRekordbox.ordnerVorschlag, nur im Browser). Sonst (Start, Bericht) steht der Block eingeklappt hier. Gemerkt nur im Browser (static/musikordner.js, localStorage); ohne JavaScript bleibt der Block verborgen. Das Feld hat keinen Namen: der Pfad landet nie in einer Adresse. #} {% set o = r.ordner %}
{% endif %} {# Cover „A+“ (24.10.2026): Bilder aus den Tags der eigenen Files – Kennzahlen, Nachlesen mit Restzeit, Rekordbox-Lage, was gespeichert wird (S2 nur im Browser, S4 Vorschauen im Konto), Löschen, Muster. Nur mit verbundenem Speicher (cover_zahlen). static/cover.js bedient Schalter und Browser-Löschen und zeichnet die Beispiel-Muster; ohne Skript bleiben Zahlen und Formular. #} {% if cover_zahlen %}{% set c = t.cover.quellen %}{% set cz = cover_zahlen %}{% set cl_ = cz.lauf %}

{{ c.titel }}

{{ c.lead }}

{% if cover_geloescht %}

{{ c.geloescht }}

{% endif %}
{{ c.k_mit }}
{{ cz.mit | zahl(sprache) }}
{{ c.k_ohne }}
{{ cz.ohne | zahl(sprache) }}
{% if cz.server %}
{{ c.k_server }}
{{ ('%.1f' | format(cz.bytes / 1048576)) | dezimal(sprache) }} MB
{% else %}
{{ c.k_offen }}
{{ cz.offen | zahl(sprache) }}
{% endif %}

{{ c.abgleich }}

{% if cl_ and not cl_.ende %}

{{ c.nachlesen | replace("{n}", cl_.n | zahl(sprache)) | replace("{m}", cl_.m | zahl(sprache)) }}{% if cl_.rest_s is defined %} · {{ c.nachlesen_rest | replace("{rest}", cl_.rest_s | rest(sprache)) }}{% endif %}

{% if cl_.pausiert %}

{{ c.nachlesen_pause }}

{% endif %} {% elif cz.offen %}

{{ c.nachlesen_offen | replace("{n}", cz.offen | zahl(sprache)) }}

{% elif cl_ %}

{{ c.nachlesen_fertig | replace("{m}", cl_.m | zahl(sprache)) }}

{% endif %}

{{ c.nur_tags }}

{% if cz.zu_gross %}

{{ c.zu_gross | replace("{n}", cz.zu_gross | zahl(sprache)) }}

{% endif %}

{{ c.rb_titel }}

{{ c.rb_text }}

{{ c.speicher_titel }}

{{ c.s4 if cz.server else c.s2 | replace("{mb}", "100") }}

{{ schalter(tasten.cover_zeigen, c.zeigen, id="cover-zeigen", an=true, aus=true) }}

{{ c.zeigen_hilfe }}

{% if cz.server %}
{% endif %}

{{ c.loeschen_hinweis }}

{{ c.muster_titel }}

{{ c.muster_text }}

    {% for ini, g, key, ok in [("Intro", [["a", 0.6], ["b", 0.3], ["c", 0.1]], "8A", "8m"), ("Peak", [["a", 0.8], ["b", 0.2]], "11B", "5d"), ("Dub", [["a", 0.45], ["b", 0.35], ["c", 0.2]], "1A", "6m"), ("Edit", [["a", 1.0]], "4B", "10d"), ("Loop", [["a", 0.4], ["b", 0.3], ["c", 0.2], ["d", 0.1]], "6A", "11m")] %}
  • {{ ok }}
  • {% endfor %}
{% endif %} {# Kontingent kurz: die Analyse wird hier ausgelöst – wie viel noch geht, steht deshalb auch hier (Einzelheiten im Konto). #}

{{ t.kontingent }}

{{ t.kontingent_satz.replace("{g}", k.genutzt | zahl(sprache)).replace("{n}", k.grenze | zahl(sprache)).replace("{f}", k.frei | zahl(sprache)) }}

{# Kontingent aufgebraucht (11.11.2026): ruhige Meldung hier, wo Musik hineinkommt; alles andere läuft weiter. Store-Fassungen ohne Kontakt (TP2 0529, Befund 55); im Browser bleibt der Kontakt, für Early-Bird-Konten mit der Zahl. #} {% if k.frei <= 0 %}

{% if store_app %}{{ t.earlybird.kontingent_leer }}{% elif earlybird and nutzer.get('kontingent_grenze') is not none %}{{ t.earlybird.kontingent_voll_web | replace("{n}", k.grenze | zahl(sprache)) }}{% else %}{{ t.kontingent_leer }}{% endif %}

{% endif %}

{{ q.mehr_im_konto }}

{# Analysen (23.10.2026): Live-Status und Verlauf stehen auf der eigenen Seite /analysen; hier bleibt eine Zeile mit dem letzten Lauf. Der Anker #analysen bleibt für alte Links – static/konto.js leitet /quellen#analysen nach /analysen weiter. #} {% set ax = t.analysen %}

{% if letzter_lauf %}{{ (ax.quellen_zeile_eins if letzter_lauf.gemessen == 1 else ax.quellen_zeile) | replace("{datum}", letzter_lauf.ende | kurzdatum(sprache)) | replace("{n}", letzter_lauf.gemessen | zahl(sprache)) }}{% else %}{{ ax.quellen_keiner }}{% endif %} {{ ax.quellen_link }}

{# alte Anker: /quellen#analysen -> /analysen (der Anker kommt nie beim Server an) #} {# Ablegen irgendwo auf der Seite (Live-Befund 25.10.2026): sofort sichtbar, was mit gezogenen Dateien passiert – je Zustand eigener Text; der Browser öffnet die Datei nie selbst. static/quellen_ablage.js, Texte als JSON. #} {% if not store_app %}{% set ab = t.tspeicher.ablage %} {% if speicher is defined and speicher and speicher.aktiv %}{% endif %} {% if beta and beta.zustand == 'aktiv' %}{% endif %} {% endif %} {% if rekordbox and rekordbox.an %}{% endif %} {% endblock %}