/* okto-motion.css — zentrale Bewegungs-Ebene für OktoWise
 * ---------------------------------------------------------------------------
 * WOZU: Vieles in der Oberfläche erscheint und verschwindet hart (99 Stellen
 * schalten Elemente über style.display um). Das wirkt ruckartig und billig,
 * obwohl die Software gut ist. Diese Datei legt EINE gemeinsame Bewegungs-
 * sprache darüber - ohne dass an den 99 Stellen etwas geändert werden muss.
 *
 * GRUNDSÄTZE
 *  1. Kurz. 150-260 ms. Alles darüber fühlt sich zäh an, nicht "edel".
 *  2. Nur zwei Eigenschaften bewegen: opacity und transform. Beide laufen auf
 *     der Grafikkarte; Höhe/Breite/top/left zu animieren ruckelt auf Tablets.
 *  3. Nichts Springendes: Bewegungen sind klein (4-10 px), nie verspielt.
 *  4. Bedienung geht vor Schönheit: Ein Klick darf nie auf eine Animation
 *     warten müssen.
 *
 * BARRIEREFREIHEIT: Ganz unten steht der Block prefers-reduced-motion. Wer im
 * Betriebssystem "Bewegung reduzieren" eingestellt hat - Menschen mit
 * vestibulärer Störung, Migräne, Epilepsie - bekommt ALLE Animationen aus.
 * Das ist keine Höflichkeit, sondern WCAG 2.3.3 / BITV. Deshalb steht diese
 * Datei bewusst als Ganzes unter diesem Vorbehalt.
 *
 * EINGEBUNDEN: über includes/components/page_shell.php (okto_page_head) auf
 * allen Seiten mit gemeinsamem Seitenrahmen, zusätzlich in
 * public/operator/_boot.php für den Betriebsbereich. Früher lud
 * assets/js/oktowise-ux.js diese Datei per JavaScript nach - ein Behelf aus
 * der Zeit vor dem gemeinsamen Seitenkopf, inzwischen entfernt.
 * =========================================================================== */

:root {
    --okto-t-schnell: 140ms;
    --okto-t-normal:  220ms;
    --okto-t-ruhig:   320ms;
    /* Sanft beschleunigen, weich abbremsen - wirkt "physikalisch", nicht mechanisch */
    --okto-kurve:     cubic-bezier(0.22, 0.61, 0.36, 1);
    --okto-kurve-aus: cubic-bezier(0.4, 0, 1, 1);
}

/* ---------------------------------------------------------------------------
 * 1. ERSCHEINEN
 * Eine CSS-Animation startet auch dann, wenn ein Element von display:none auf
 * sichtbar wechselt. Genau darüber bekommen die vielen display-Umschaltungen
 * eine Bewegung, ohne dass ihr JavaScript angefasst werden muss.
 * ------------------------------------------------------------------------- */
@keyframes okto-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}
@keyframes okto-pop {
    from { opacity: 0; transform: scale(0.97); }
    to   { opacity: 1; transform: none; }
}
@keyframes okto-in-rechts {
    from { opacity: 0; transform: translateX(-8px); }
    to   { opacity: 1; transform: none; }
}

/* Reiter-Wechsel: der neue Inhalt blendet sanft ein, statt zu springen.
 *
 * .profil-inhalt > .tab-content steht daneben, weil die Profilseite seit dem
 * 04.10.2026 nur noch EINEN Bereich liefert und ihre Abschnitte deshalb keine
 * Klasse „active" mehr brauchen (sieben von zehn trugen sie ohnehin nie). */
.tab-content.active,
.profil-inhalt > .tab-content,
.s-content.active {
    animation: okto-in var(--okto-t-normal) var(--okto-kurve) both;
}

/* Dialoge/Überlagerungen, die per display sichtbar werden.
 *
 * WARUM HIER MEHR STEHT, ALS MAN ERWARTET:
 * Diese Liste traf zuerst .modal-body - die Klasse kommt im Markup aber nur
 * 7-mal vor, während .modal 41-mal und .modal-content im Chat jeden Dialog
 * umschließt. Die Bewegung war deshalb fast nirgends zu sehen.
 *
 * .modal ist BEWUSST NICHT dabei: Die Klasse bedeutet in den beiden
 * Dialogsystemen Verschiedenes. In glass-theme.css ist .modal der Kasten
 * INNERHALB von .modal-overlay und trägt bereits ein eigenes
 * "transform: scale(.95)" mit Übergang - eine Animation mit "transform: none"
 * würde genau das überschreiben und den Dialog springen lassen. Im Chat
 * dagegen ist .modal die ganzflächige Überlagerung. Eine gemeinsame Regel
 * wäre in einem der beiden Fälle immer falsch.
 * .modal-content gibt es NUR im Chat (Markup und CSS) - dort ist es der
 * Kasten, und dort ist die Regel eindeutig richtig.
 */
.modal-body,
.modal-content,
.ow-dlg,
.notif-panel.ow-open,
.search-box,
.okto-dialog {
    animation: okto-pop var(--okto-t-normal) var(--okto-kurve) both;
}

/* .okto-dialog ist die Klasse für Dialoge OHNE gemeinsame Struktur - im Chat
 * werden drei Fenster per JavaScript mit reinen Inline-Stilen erzeugt. Ihnen
 * .modal-content zu geben wäre falsch: Die Klasse bringt eigene Maße und
 * Ränder mit und würde die vorhandene Gestaltung stören. .okto-dialog macht
 * NICHTS außer der Bewegung - deshalb an jeden Dialogkasten anhängbar. */

/* ---------------------------------------------------------------------------
 * 1a. DER DIALOG GEHT AUS DEM GEDRÜCKTEN KNOPF AUF (04.10.2026)
 *
 * Ein Fenster, das in der Bildschirmmitte erscheint, kommt von nirgendwo. Geht
 * es dagegen als Kreis aus der Stelle auf, die man gerade berührt hat, ist der
 * Zusammenhang zwischen Handlung und Folge sichtbar - man hat den Dialog
 * GEÖFFNET, er ist nicht einfach aufgetaucht. Dieselbe Geste benutzen native
 * Apps seit Jahren.
 *
 * DER PUNKT KOMMT AUS --okto-ox/--okto-oy, die okto_dialog_ursprung.js bei
 * jedem Klick setzt. Ohne JavaScript oder vor dem ersten Klick stehen sie auf
 * 50 % - dann geht der Kreis aus der Mitte auf, was immer noch besser aussieht
 * als ein hartes Erscheinen.
 *
 * NUR AUF DEM RECHNER: Auf Handy und Tablet fahren Dialoge als Bottom-Sheet
 * von unten herein (Abschnitt 1b). Beides zusammen wäre eine Bewegung zu viel,
 * und das Sheet ist dort das vertrautere Muster.
 *
 * clip-path ist bewusst gewählt: Es läuft auf der Grafikkarte und lässt den
 * Inhalt unangetastet. Eine Alternative über width/height würde bei jedem
 * Bild das Layout neu rechnen - und genau davor warnt der Kopf dieser Datei.
 */
@media (min-width: 1025px), (min-width: 1367px) and (pointer: coarse) {
    @supports (clip-path: circle(0% at 50% 50%)) {
        @keyframes okto-kreis-auf {
            from { clip-path: circle(0% at var(--okto-ox, 50%) var(--okto-oy, 50%)); opacity: 0; }
            to   { clip-path: circle(150% at var(--okto-ox, 50%) var(--okto-oy, 50%)); opacity: 1; }
        }
        .modal-overlay.active > .modal,
        .modal > .modal-content,
        .ow-dlg-ov.show > .ow-dlg,
        .oks-ov.show > .oks-box {
            animation: okto-kreis-auf var(--okto-t-ruhig) var(--okto-kurve) both;
        }
    }
}

/* Inhaltsbereiche, die per display umgeschaltet werden (Reiter innerhalb von
 * Dialogen, umschaltbare Ansichten). Sanfter als ein Dialog: Sie wechseln
 * häufiger, ein Aufpoppen wäre bei jedem Klick zu viel. */
.media-tab-content,
.course-data-view {
    animation: okto-in var(--okto-t-schnell, .18s) var(--okto-kurve) both;
}

/* ---------------------------------------------------------------------------
 * 1b. DIALOGE ALS BOTTOM-SHEETS (Handy und Tablet)
 *
 * Auf kleinen Geräten fahren Dialoge von unten herein statt in der Mitte zu
 * erscheinen - so wie man es von Apps kennt, und in Reichweite des Daumens.
 * Der Griff-Balken oben ist kein Zierrat: Er zeigt, dass man den Dialog nach
 * unten wegwischen kann. Die Geste selbst steckt in assets/js/okto_sheets.js.
 *
 * DIE SELEKTOREN SIND BEWUSST PAARE. ".modal" heißt im Projekt zweierlei:
 * in glass-theme.css der Kasten INNERHALB von .modal-overlay, in chat.css die
 * ganzflächige Überlagerung. Eine Regel auf ".modal" allein träfe je nach
 * Seite das Falsche - deshalb immer "Hintergrund > Kasten".
 *
 * Dieselbe Grenze wie bei der Navigationsleiste, damit Aussehen und Geste
 * zusammenpassen.
 * ------------------------------------------------------------------------- */
@media (max-width: 1024px), (max-width: 1366px) and (pointer: coarse) {

    /* Hintergrund: Inhalt nach unten schieben statt zentrieren. */
    .modal-overlay,
    .oks-ov,
    .rub-ov {
        align-items: flex-end !important;
        padding: 0 !important;
    }
    /* :has() steht BEWUSST in einer eigenen Regel: Kennt ein Browser den
     * Selektor nicht, verwirft er die GANZE Regel, in der er steht - die
     * Dialoge oben hätten dann ebenfalls nichts abbekommen. */
    .modal:has(> .modal-content) {
        align-items: flex-end !important;
        padding: 0 !important;
    }

    /* Kasten: volle Breite, oben abgerundet, unten bündig. */
    .modal-overlay > .modal,
    .modal > .modal-content,
    .oks-ov > .oks-box,
    .rub-ov > .rub-box {
        width: 100% !important;
        max-width: 100% !important;
        margin: 0 !important;
        /* Rundung aus okto-popup.css (07.10.2026). Hier stand fest 22px, und
           weil die Zeile ein !important trägt, schlug sie die gemeinsame
           Gestalt: Ein Bottom-Sheet hatte 22px, ein mittiger Dialog 24px —
           derselbe Dialog, je nach Bildschirmbreite anders gerundet. */
        border-radius: var(--okto-pop-radius, 22px) var(--okto-pop-radius, 22px) 0 0 !important;
        max-height: 92vh;
        /* Platz für die Systemleiste am unteren Rand (iPhone-Balken). */
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }

    /* Das Einfahren hängt daran, WIE ein Dialog sichtbar wird - und das ist in
     * den beiden Systemen verschieden:
     *
     *   .modal-overlay wird über die Klasse "active" sichtbar (visibility).
     *     Eine Animation ohne diese Klasse liefe schon beim Laden der Seite ab -
     *     unsichtbar - und beim Öffnen wäre nichts mehr zu sehen.
     *   .modal (Chat) wird über display umgeschaltet. Dort startet die
     *     Animation von selbst im richtigen Moment.
     */
    .modal-overlay.active > .modal,
    .modal > .modal-content,
    .oks-ov.show > .oks-box,
    .rub-ov.show > .rub-box {
        animation: okto-sheet-rein var(--okto-t-ruhig) var(--okto-kurve) both;
    }

    /* Griff-Balken. ::before, damit kein Markup geändert werden muss - die
     * Dialoge stammen aus einem Dutzend Dateien. */
    .modal-overlay > .modal::before,
    .modal > .modal-content::before,
    .oks-ov > .oks-box::before,
    .rub-ov > .rub-box::before {
        content: '';
        display: block;
        flex-shrink: 0;
        width: 44px;
        height: 5px;
        border-radius: 99px;
        background: rgba(255, 255, 255, 0.22);
        margin: 10px auto 2px;
    }
}

@keyframes okto-sheet-rein {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}

/* Kacheln und Karten: dezent gestaffelt, damit eine Seite "aufgeht" statt
 * schlagartig dazustehen. Bewusst nur die ersten sechs - danach wartet man
 * sonst spürbar auf Inhalt. */
.admin-card,
.child-card,
.op-card,
.course-card,
.quest-item,
.steam-panel {
    animation: okto-in var(--okto-t-ruhig) var(--okto-kurve) both;
}

/* Fortschrittsbalken wachsen, statt zu springen. Betrifft die XP-Leiste auf
 * der Startseite (Breite wird per JavaScript gesetzt) und das Klassen-
 * Wochenziel. Ohne den Übergang steht der Balken einfach da - mit ihm sieht
 * man, dass sich etwas bewegt hat. */
.xp-bar-fill,
.streak-bar > span,
.fc-pbar > span {
    transition: width .6s var(--okto-kurve);
}

/* Zahlen, die sich ändern (XP, Münzen), bekommen einen kurzen Impuls, wenn
 * ihnen die Klasse .okto-zaehlt gegeben wird. Bewusst dezent - eine Zahl,
 * die hüpft, lenkt vom Lesen ab. */
@keyframes okto-zahl {
    0%   { transform: none; }
    35%  { transform: scale(1.14); }
    100% { transform: none; }
}
.okto-zaehlt {
    display: inline-block;
    animation: okto-zahl var(--okto-t-ruhig) var(--okto-kurve) both;
}
.admin-card:nth-of-type(2), .child-card:nth-of-type(2), .op-card:nth-of-type(2),
.course-card:nth-of-type(2), .quest-item:nth-of-type(2), .steam-panel:nth-of-type(2) { animation-delay: 40ms; }
.admin-card:nth-of-type(3), .child-card:nth-of-type(3), .op-card:nth-of-type(3),
.course-card:nth-of-type(3), .quest-item:nth-of-type(3), .steam-panel:nth-of-type(3) { animation-delay: 80ms; }
.admin-card:nth-of-type(4), .child-card:nth-of-type(4), .op-card:nth-of-type(4),
.course-card:nth-of-type(4), .quest-item:nth-of-type(4), .steam-panel:nth-of-type(4) { animation-delay: 120ms; }
.admin-card:nth-of-type(5), .child-card:nth-of-type(5), .op-card:nth-of-type(5),
.course-card:nth-of-type(5), .quest-item:nth-of-type(5), .steam-panel:nth-of-type(5) { animation-delay: 160ms; }
.admin-card:nth-of-type(6), .child-card:nth-of-type(6), .op-card:nth-of-type(6),
.course-card:nth-of-type(6), .quest-item:nth-of-type(6), .steam-panel:nth-of-type(6) { animation-delay: 200ms; }

/* ---------------------------------------------------------------------------
 * 2. REAKTION AUF BERÜHRUNG
 * Jede anklickbare Fläche soll sofort spürbar antworten. Das ist der größte
 * Unterschied zwischen "Webseite" und "App": nicht die Dauer, sondern dass
 * überhaupt etwas passiert - und zwar SOFORT.
 * ------------------------------------------------------------------------- */
/* DIE LISTE WURDE AM 04.10.2026 NACHGEZOGEN.
 *
 * Sie traf vorher .btn-primary, .skbtn und .op-btn - alles Klassen, die im
 * Markup kaum vorkommen. Die tatsächlich benutzten Knöpfe standen nicht darin:
 *
 *     .steam-btn   40×      .ex-knopf   27×      .back-btn   22×
 *     .btn         21×      .ax-btn     16×      .filter-btn  9×
 *
 * Die Regeln gab es also, sie zielten nur daneben. Wer im Profil oder im
 * Dateiexplorer einen Knopf drückte, bekam keinerlei Rückmeldung - und genau
 * dieses Ausbleiben ist der Unterschied zwischen "Webseite" und "App". Nicht
 * die Dauer einer Bewegung, sondern dass auf eine Berührung überhaupt etwas
 * passiert.
 *
 * .okto-tippbar steht mit in der Liste: eine Klasse, die NICHTS tut außer
 * dieser Rückmeldung. Damit lässt sich jedes künftige Bedienelement anhängen,
 * ohne diese Liste wieder zu erweitern - und ohne dass sie erneut veraltet.
 */
.btn, .btn-primary, .btn-outline, .btn-secondary, .skbtn, .op-btn,
.steam-btn, .ex-knopf, .back-btn, .ax-btn, .filter-btn, .ac-btn, .rb-btn, .fc-btn,
.action-btn, .profil-punkt, .okto-tippbar,
button.admin-nav-item, .admin-nav-item, .s-tab,
.color-swatch, .icon-box, .ow-color-btn {
    transition: transform var(--okto-t-schnell) var(--okto-kurve),
                background-color var(--okto-t-schnell) linear,
                border-color var(--okto-t-schnell) linear,
                box-shadow var(--okto-t-normal) var(--okto-kurve),
                opacity var(--okto-t-schnell) linear;
}
.btn:active, .btn-primary:active, .btn-outline:active, .btn-secondary:active,
.skbtn:active, .op-btn:active,
.steam-btn:active, .ex-knopf:active, .back-btn:active, .ax-btn:active,
.filter-btn:active, .ac-btn:active, .rb-btn:active, .fc-btn:active,
.action-btn:active:not([disabled]), .profil-punkt:active, .okto-tippbar:active {
    /* Kurz "eingedrückt" - der Finger bekommt eine Rückmeldung. */
    transform: scale(0.97);
    transition-duration: 60ms;
}
.admin-nav-item:hover { transform: translateX(2px); }

/* Karten heben sich beim Überfahren leicht - NUR dort, wo es auch klickbar ist.
 *
 * Bewusst NICHT .card: Die Klasse kommt 133-mal vor, aber nur 43-mal an etwas
 * Anklickbarem. Eine Karte, die sich beim Überfahren hebt, verspricht, dass
 * dahinter etwas passiert. Bei 90 Karten wäre dieses Versprechen falsch - und
 * ein falsches Versprechen ist schlimmer als gar keine Bewegung.
 * .tool-card und .tpl-card dagegen sind an JEDER Fundstelle in einem <a> oder
 * tragen ein onclick; dort stimmt es. */
a.ok-card, .course-card, .clickable-card, .study-set-card,
.tool-card, .tpl-card {
    transition: transform var(--okto-t-normal) var(--okto-kurve),
                box-shadow var(--okto-t-normal) var(--okto-kurve);
}
a.ok-card:hover, .course-card:hover, .clickable-card:hover, .study-set-card:hover,
.tool-card:hover, .tpl-card:hover {
    transform: translateY(-3px);
}

/* ---------------------------------------------------------------------------
 * 3. TASTATUR-BEDIENUNG SICHTBAR MACHEN
 * Gehört zur Barrierefreiheit: Wer mit der Tastatur arbeitet, muss jederzeit
 * sehen, wo er steht. :focus-visible zeigt den Ring NUR bei Tastatur, nicht
 * bei jedem Mausklick - deshalb stört er niemanden.
 * ------------------------------------------------------------------------- */
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
    outline: 2px solid #fdc58a;
    outline-offset: 2px;
    border-radius: 6px;
}

/* ---------------------------------------------------------------------------
 * 4. HELFER FÜR JAVASCRIPT
 * Für Stellen, die bewusst mit Bewegung ein- und ausblenden sollen. Benutzt
 * von OW.reveal()/OW.hide() in oktowise-ux.js - siehe dort.
 * ------------------------------------------------------------------------- */
.okto-anim-in  { animation: okto-in var(--okto-t-normal) var(--okto-kurve) both; }
.okto-anim-out { animation: okto-in var(--okto-t-schnell) var(--okto-kurve-aus) reverse both; }

/* Sanftes Aufklappen (Akkordeon, Detailbereiche) ohne Höhen-Animation:
 * grid-template-rows von 0fr auf 1fr ist der einzige Weg, der ohne feste
 * Pixelhöhe auskommt und trotzdem flüssig läuft. */
.okto-collapse {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--okto-t-normal) var(--okto-kurve);
}
.okto-collapse > * { overflow: hidden; }
.okto-collapse.offen { grid-template-rows: 1fr; }

/* Ladezustand: ruhiges Pulsieren statt Zappeln. */
@keyframes okto-puls { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
.okto-laedt { animation: okto-puls 1.2s ease-in-out infinite; }

/* ---------------------------------------------------------------------------
 * LADE-PLATZHALTER ("Skelett")
 * Statt einer leeren Fläche mit Spinner steht schon die FORM des Inhalts da:
 * Zeilen, Kacheln, Kreise. Das hat zwei Wirkungen - die Wartezeit fühlt sich
 * kürzer an, und die Seite springt beim Eintreffen der Daten nicht mehr, weil
 * der Platz bereits eingenommen war. Springende Inhalte sind der häufigste
 * Grund für versehentliche Klicks.
 *
 * Verwendung:
 *   <div class="okto-skel okto-skel-zeile"></div>
 *   <div class="okto-skel okto-skel-karte"></div>
 * Beim Eintreffen der Daten das Element einfach ersetzen.
 * ------------------------------------------------------------------------- */
@keyframes okto-schimmer { from { background-position: -180% 0; } to { background-position: 180% 0; } }
.okto-skel {
    background: linear-gradient(90deg,
        rgba(255,255,255,0.05) 25%,
        rgba(255,255,255,0.12) 37%,
        rgba(255,255,255,0.05) 63%);
    background-size: 220% 100%;
    animation: okto-schimmer 1.4s linear infinite;
    border-radius: 10px;
}
.okto-skel-zeile  { height: 14px; margin-bottom: 10px; }
.okto-skel-zeile.kurz { width: 45%; }
.okto-skel-zeile.mittel { width: 70%; }
.okto-skel-titel  { height: 20px; width: 55%; margin-bottom: 14px; border-radius: 8px; }
.okto-skel-karte  { height: 84px; margin-bottom: 12px; border-radius: 14px; }
.okto-skel-kreis  { width: 42px; height: 42px; border-radius: 50%; flex: 0 0 auto; }
.okto-skel-reihe  { display: flex; align-items: center; gap: 14px; padding: 14px 16px;
                    border: 1px solid rgba(255,255,255,0.06); border-radius: 14px; margin-bottom: 10px; }
.okto-skel-reihe > .okto-skel-text { flex: 1; }

/* Bewegung aus: Der Platzhalter bleibt (die Form hilft weiter), nur das
 * Schimmern hört auf - eine dauerhaft laufende Animation ist genau das,
 * was bei vestibulären Beschwerden stört. */
@media (prefers-reduced-motion: reduce) {
    .okto-skel { animation: none; background: rgba(255,255,255,0.07); }
}

/* ---------------------------------------------------------------------------
 * 5. BEWEGUNG AUS (Systemeinstellung des Nutzers)
 * WCAG 2.3.3 / BITV. Für manche Menschen sind bewegte Oberflächen nicht
 * "hübsch", sondern lösen Schwindel oder Übelkeit aus. Ihre Einstellung im
 * Betriebssystem hat Vorrang vor jedem Gestaltungswunsch - deshalb wird hier
 * ALLES abgeschaltet, nicht nur das Auffälligste.
 * ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 1ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 1ms !important;
        scroll-behavior: auto !important;
    }
    .admin-nav-item:hover,
    a.ok-card:hover, .course-card:hover, .clickable-card:hover, .study-set-card:hover,
    .tool-card:hover, .tpl-card:hover,
    .btn:active, .btn-primary:active, .btn-outline:active, .btn-secondary:active,
    .skbtn:active, .op-btn:active,
    .steam-btn:active, .ex-knopf:active, .back-btn:active, .ax-btn:active,
    .filter-btn:active, .ac-btn:active, .rb-btn:active, .fc-btn:active,
    .action-btn:active, .profil-punkt:active, .okto-tippbar:active {
        transform: none;
    }
}
