Verified Commit b8660d32 authored by Jakob Moser's avatar Jakob Moser
Browse files

Add remaining files (with AI taint)

parent a611362a
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
   I should do this at some point in the future and change things I don't like. */
import search from "../../model/search.js"

export default {
    view: function (vnode) {
        return ["Möchtest du ", m(
            "a.clear-search-link",
            {
                onclick: e => {
                    e.preventDefault()
                    search.query = null
                },
                href: "#",
                role: "button",
                tabindex: 0,
                onkeypress: e => {
                    if ((e.key === "Enter" || e.key === " ")) {
                        e.preventDefault()
                        search.query = null
                    }
                },
            },
            `alle ${vnode.attrs.totalFavoriteCount} Favoriten anzeigen`,
        ), "?"]
    },
}
+122 −0
Original line number Diff line number Diff line
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
   I should do this at some point in the future and change things I don't like. */
const PaginationControls = {
    view: function (vnode) {
        const { currentPage, totalPages, onPageChange } = vnode.attrs

        if (totalPages <= 1) {
            return null // Keine Steuerelemente anzeigen, wenn nicht benötigt
        }

        const pages = []
        const delta = 2 // Anzahl der Seiten, die um die aktuelle Seite herum angezeigt werden

        // Hilfsfunktion zum Hinzufügen einer Seitenzahl
        const addPage = pageNumber => {
            pages.push(
                m(
                    "span.page-number",
                    {
                        class:
                            pageNumber === currentPage ? "active" : "clickable",
                        onclick: () =>
                            pageNumber !== currentPage
                                ? onPageChange(pageNumber)
                                : null,
                        role: "button",
                        tabindex: 0, // Für Tastaturnavigation
                        onkeypress: e => {
                            // Für Tastaturnavigation (Enter/Space)
                            if (
                                (e.key === "Enter" || e.key === " ") &&
                                pageNumber !== currentPage
                            ) {
                                e.preventDefault()
                                onPageChange(pageNumber)
                            }
                        },
                        "aria-label": `Gehe zu Seite ${pageNumber}`,
                        "aria-current":
                            pageNumber === currentPage ? "page" : undefined,
                    },
                    pageNumber,
                ),
            )
        }

        // "Zurück"-Button
        const prevButton = m(
            "button.pagination-button",
            {
                onclick: () => onPageChange(currentPage - 1),
                disabled: currentPage === 1,
                "aria-label": "Vorherige Seite",
            },
            "Zurück",
        )

        // "Weiter"-Button
        const nextButton = m(
            "button.pagination-button",
            {
                onclick: () => onPageChange(currentPage + 1),
                disabled: currentPage === totalPages,
                "aria-label": "Nächste Seite",
            },
            "Weiter",
        )

        // Logik zur Seitenzahlanzeige
        if (totalPages <= 7) {
            // Alle Seiten anzeigen, wenn es 7 oder weniger sind
            for (let i = 1; i <= totalPages; i++) {
                addPage(i)
            }
        } else {
            addPage(1) // Immer die erste Seite anzeigen

            if (currentPage > delta + 2) {
                // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Anfang entfernt ist
                pages.push(m("span.dots", "..."))
            }

            // Bereich der Seiten um die aktuelle Seite bestimmen
            let start = Math.max(2, currentPage - delta)
            let end = Math.min(totalPages - 1, currentPage + delta)

            if (currentPage <= delta + 1) {
                // Aktuelle Seite ist nahe am Anfang
                start = 2
                end = Math.min(totalPages - 1, 2 * delta + 1)
            } else if (currentPage >= totalPages - delta) {
                // Aktuelle Seite ist nahe am Ende
                start = Math.max(2, totalPages - 2 * delta)
                end = totalPages - 1
            }

            // Korrektur für den Fall, dass start und end sich überschneiden oder ungültig werden
            if (start > end && currentPage < totalPages / 2) {
                // Wenn start > end und wir sind eher am Anfang
                end = start // Zeige zumindest die Startseite
            } else if (start > end && currentPage > totalPages / 2) {
                // Wenn start > end und wir sind eher am Ende
                start = end // Zeige zumindest die Endseite
            }

            for (let i = start; i <= end; i++) {
                if (i > 0) addPage(i) // Sicherstellen, dass i positiv ist
            }

            if (currentPage < totalPages - delta - 1 && end < totalPages - 1) {
                // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Ende entfernt ist
                pages.push(m("span.dots", "..."))
            }

            if (totalPages > 1) addPage(totalPages) // Immer die letzte Seite anzeigen (wenn nicht 1)
        }

        return m("div.pagination-controls", [prevButton, ...pages, nextButton])
    },
}

export default PaginationControls
+16 −0
Original line number Diff line number Diff line
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
   I should do this at some point in the future and change things I don't like. */
export const Tab = {
    view(vnode) {
        return m(
            m.route.Link,
            {
                class: m.route.get() === vnode.attrs.href ? "active" : "",
                href: vnode.attrs.href,
                role: "button", // Für Barrierefreiheit
                tabindex: 0, // Für Tastaturnavigation
            },
            vnode.attrs.label,
        )
    },
}
+275 −0
Original line number Diff line number Diff line
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
   I should do this at some point in the future and change things I don't like. */

/* Globale Resets und Basis-Styling (wie vorher) */
body,
html {
    margin: 0;
    padding: 0;
    font-family: "Roboto", sans-serif;
    background-color: #f4f4f9;
    color: #333;
    line-height: 1.6;
}

main > p {
    text-align: center;
    padding: 1em;
    font-size: 1.1em;
    color: #757575;
}

/* Suchleiste (Höhe ca. 60px) */
search {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background-color: #3f51b5; /* Indigo - Material Primary */
    padding: 12px 16px;
    z-index: 1000;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
    box-sizing: border-box; /* Stellt sicher, dass Padding die Höhe nicht sprengt */
    height: 60px; /* Feste Höhe für genaue Positionierung */
}

input[type="search"] {
    width: 100%;
    padding: 10px 12px;
    border-radius: 4px;
    border: none;
    font-size: 16px;
    box-sizing: border-box;
    background-color: #fff;
    color: #333;
}

input[type="search"]::placeholder {
    color: #888;
}

/* Tab Bar (Höhe ca. 48px) */
nav {
    display: flex;
    position: fixed;
    top: 60px; /* Direkt unter der Suchleiste */
    left: 0;
    right: 0;
    background-color: #3f51b5; /* Gleiche Farbe wie Suchleiste für einen einheitlichen Header-Block */
    z-index: 999; /* Unter der Suchleiste, falls sie überlappen könnten */
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); /* Dezenter Schatten */
    height: 48px; /* Feste Höhe */
    box-sizing: border-box;
}

nav a {
    flex-grow: 1;
    display: flex; /* Für vertikales Zentrieren des Texts */
    align-items: center;
    justify-content: center;
    padding: 0 8px; /* Horizontal Padding */
    text-align: center;
    color: rgba(
        255,
        255,
        255,
        0.75
    ); /* Etwas helleres, aber gedämpftes Weiß für inaktive Tabs */
    cursor: pointer;
    font-weight: 500;
    font-size: 0.9em; /* Leicht kleiner als Haupttext */
    text-transform: uppercase;
    border-bottom: 3px solid transparent; /* Platzhalter für den aktiven Indikator */
    transition:
        color 0.2s ease-in-out,
        border-bottom-color 0.2s ease-in-out;
    user-select: none;
    text-decoration: none;
}

nav a:hover {
    background-color: rgba(255, 255, 255, 0.1); /* Leichter Hover-Effekt */
}

nav a.active {
    color: #ffffff; /* Reines Weiß für aktiven Tab */
    border-bottom-color: #ff4081; /* Material Accent Pink für den Indikator */
}

/* song .Liste Container */
main {
    padding-top: calc(60px + 48px); /* Höhe Suchleiste + Höhe TabBar */
    padding-bottom: 20px; /* Platz für Paginierung, falls am Ende */
}

/* Link zum Zurücksetzen der Suche in der main > p */
main > p a.clear-search-link,
main > p .clear-search-link {
    /* Zweite Regel für den Fall, dass Mithril das 'a' nicht direkt rendert */
    color: #3f51b5; /* Material Indigo, passend zum Header */
    text-decoration: underline;
    cursor: pointer;
    font-weight: 500; /* Etwas hervorheben */
    background: none; /* Sicherstellen, dass keine Button-Styles übernommen werden */
    border: none;
    padding: 0;
    display: inline; /* Damit es sich wie ein normaler Link im Text verhält */
}

main > p a.clear-search-link:hover,
main > p a.clear-search-link:focus,
main > p .clear-search-link:hover,
main > p .clear-search-link:focus {
    color: #303f9f; /* Dunkleres Indigo für Hover/Focus */
    text-decoration: none; /* Optional: Unterstreichung bei Hover entfernen */
}

/* Restliche Styles für Artist-Group, song .Item etc. bleiben wie zuvor */
section.artist {
    margin: 12px 8px;
    background-color: #ffffff;
    border-radius: 4px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    overflow: hidden;
}

section.artist > h2 {
    margin: 0;
    padding: 12px 16px;
    background-color: #e8eaf6;
    color: #303f9f;
    font-size: 1.1em;
    font-weight: 500;
    border-bottom: 1px solid #c5cae9;
}

.song {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    border-bottom: 1px solid #f0f0f0;
    transition: background-color 0.2s ease-in-out;
}

.song:last-child {
    border-bottom: none;
}

.song:hover {
    background-color: #f9f9f9;
}

.song .info {
    flex-grow: 1;
    margin-right: 8px; /* Abstand zum Icon */
    overflow: hidden; /* Verhindert Textüberlauf */
}

.song .title {
    font-size: 1em;
    font-weight: 400;
    color: #212121;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis; /* Text abschneiden bei Überlauf */
}

.song .artist {
    font-size: 0.85em;
    color: #757575;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.song .actions {
    margin-left: auto;
    flex-shrink: 0;
}

.heart-icon {
    cursor: pointer;
    color: #bdbdbd;
    font-size: 24px;
    user-select: none;
    transition:
        color 0.2s ease-in-out,
        transform 0.1s ease-out;
    vertical-align: middle;
}

.heart-icon:hover {
    transform: scale(1.1);
}

.heart-icon.is-favorite {
    color: #ff4081;
    font-variation-settings: "FILL" 1;
}

.material-symbols-outlined {
    font-variation-settings:
        "FILL" 0,
        "wght" 400,
        "GRAD" 0,
        "opsz" 24;
}

/* NEU: Pagination Controls Styles */
.pagination-controls {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px 0; /* Mehr Padding für bessere Sichtbarkeit */
    margin-top: 10px;
    user-select: none;
    flex-wrap: wrap; /* Erlaubt Umbruch bei vielen Seitenzahlen auf kleinen Bildschirmen */
}

.pagination-controls button.pagination-button, /* Klasse für Buttons hinzugefügt */
.pagination-controls span.page-number {
    margin: 4px; /* Etwas mehr Margin für Umbruch */
    padding: 8px 12px;
    border: 1px solid #ddd;
    background-color: #fff;
    color: #3f51b5; /* Material Indigo */
    cursor: pointer;
    border-radius: 4px;
    transition:
        background-color 0.2s,
        color 0.2s,
        border-color 0.2s;
    font-size: 0.9em;
    min-width: 36px; /* Mindestbreite für bessere Klickbarkeit */
    text-align: center;
    box-sizing: border-box;
}

.pagination-controls button.pagination-button:hover:not(:disabled),
.pagination-controls span.page-number.clickable:hover {
    background-color: #e8eaf6; /* Heller Indigo-Ton für Hover */
    border-color: #c5cae9;
}

.pagination-controls button.pagination-button:disabled {
    color: #aaa;
    cursor: not-allowed;
    background-color: #f9f9f9;
    border-color: #eee;
}

.pagination-controls span.page-number.active {
    background-color: #3f51b5;
    color: #fff;
    border-color: #3f51b5;
    font-weight: bold;
    cursor: default;
}

.pagination-controls span.dots {
    padding: 8px 6px;
    color: #777;
    margin: 4px;
    font-size: 0.9em;
}