feat: Wischgeste wechselt Titel, Designs Violet und Crimson, Mischen-Anzeige gefixt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
549bec85ce
commit
899c98977a
9 files changed
+203
-23
No files matched your search
+28
-1
@@ -80,9 +80,26 @@ Die Android-App zählt eigenständig weiter (mehrere APKs pro Release). Welche d
|
||||
|
||||
## [3.1] — 2026-09-22
|
||||
|
||||
**Android:** 1.15 (versionCode 18) → 1.21 (versionCode 24). 1.16 brachte den Rollbalken, 1.17 bis 1.21 machten ihn bedienbar, 1.19 bringt das Bearbeiten der Warteschlange.
|
||||
**Android:** 1.15 (versionCode 18) → 1.22 (versionCode 25). 1.16 brachte den Rollbalken, 1.17 bis 1.21 machten ihn bedienbar, 1.19 das Bearbeiten der Warteschlange, 1.22 die Wischgeste, den Mischen-Fehler und die zwei neuen Designs.
|
||||
|
||||
### Added
|
||||
- **Zwei neue Designs: Violet und Crimson (Web + Android)** — ein helles Lila auf tiefem
|
||||
Violettschwarz und ein gedämpftes Rot auf Weinrot-Schwarz. Beide mit demselben Variablensatz wie
|
||||
die vorhandenen, geprüft: kein Design vermisst eine Variable.
|
||||
- Crimson trägt bewusst **kein reines Rot** als Akzent — das läge zu nah an der Warnfarbe, und
|
||||
Löschknöpfe sähen dann aus wie gewöhnliche Akzente. Stattdessen ein gedämpftes Rosé-Rot, und
|
||||
die Warnfarbe wurde dort heller gesetzt, damit sie weiterhin absticht.
|
||||
- Beide Plattformen lesen ihre Designliste durch, die Auswahl in den Einstellungen füllt sich
|
||||
also von selbst.
|
||||
- **Wischen wechselt den Titel (Android)** — seitwärts wischen blätterte im Now-Playing zwischen
|
||||
Player, Warteschlange und Text; den Titel wechselte nur ein Wisch **genau über dem Cover**. Das
|
||||
ist verdreht: der Titelwechsel ist die Geste, die man dort ständig braucht. Jetzt nimmt die ganze
|
||||
Player-Seite den Wisch entgegen und springt zum nächsten oder vorherigen Titel.
|
||||
- **Umstellbar** in den Einstellungen unter *Wiedergabe* → „Wischen wechselt den Titel". Aus
|
||||
heißt: wie bisher. Die Reiter oben schalten in beiden Fällen weiter, es geht also nie eine
|
||||
Ansicht verloren.
|
||||
- Auf Warteschlange und Text liegt die Geste **nicht** — dort würde sie mit dem Ziehen in der
|
||||
Liste streiten.
|
||||
- **Die Warteschlange lässt sich bearbeiten (Web + Android)** — bisher war sie eine reine Anzeige:
|
||||
reinlegen ging, umsortieren oder wieder herausnehmen nicht. Jetzt trägt jede Zeile einen Griff
|
||||
zum Ziehen und ein ✕ zum Entfernen, auf beiden Plattformen und in beiden Web-Listen (Seitenleiste
|
||||
@@ -177,6 +194,16 @@ Die Android-App zählt eigenständig weiter (mehrere APKs pro Release). Welche d
|
||||
erreichbar war) und zählt für die Seitenleiste jetzt zur Bibliothek statt zu Entdecken.
|
||||
|
||||
### Fixed
|
||||
- **Das Mischen-Symbol zeigte nie an, dass es an ist (Android)** — und der Grund lag nicht in der
|
||||
Anzeige. `toggleShuffle` baute den Rest der Warteschlange als Schleife aus `removeMediaItem` +
|
||||
`addMediaItem` um; dazwischen hatte der Controller vorübergehend weniger Titel als unsere Liste.
|
||||
Genau darauf reagiert `onTimelineChanged` mit „die Titel kamen von außen", baute die
|
||||
Warteschlange neu auf — und setzte dabei `isManuallyShuffled` **zurück**. Gemischt wurde also,
|
||||
aber der Zustand war eine Zehntelsekunde später wieder weg. Jetzt wird der Rest in **einem** Zug
|
||||
ersetzt (`replaceMediaItems`), der Zwischenzustand entfällt.
|
||||
- **Mischen und Wiederholen sind jetzt auch zu sehen (Android)** — ein eingefärbtes Symbol allein
|
||||
reicht auf einem dunklen Cover nicht, das liest sich wie eine Schattierung. Ist der Schalter an,
|
||||
liegt ein getönter Kreis darunter und ein Punkt darunter.
|
||||
- **Der Rollbalken lief über die Player-Leiste (Web)** — sofort nach dem Verbreitern gemeldet. Der
|
||||
Balken gehört zum Scrollbereich und läuft über dessen ganze Höhe, und `.main` reichte per
|
||||
`padding-bottom` bis an den Fensterboden, also bis **unter** die Leiste. Bei 4 px fiel das nie
|
||||
|
||||
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.novasonic.app"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 24
|
||||
versionName = "1.21"
|
||||
versionCode = 25
|
||||
versionName = "1.22"
|
||||
// Host der eigenen NovaSonic-Web-App — Ziel der geteilten Links. Einmal in
|
||||
// gradle.properties setzen (novasonicHost=music.example.com); ohne Angabe bleibt der
|
||||
// Platzhalter stehen und der App-Link greift schlicht nicht.
|
||||
|
||||
@@ -28,6 +28,7 @@ class PrefsStore(private val context: Context) {
|
||||
val KEY_ABS_API_KEY = stringPreferencesKey("abs_api_key")
|
||||
val KEY_ABS_CHAPTER_TRACK = booleanPreferencesKey("abs_chapter_track")
|
||||
val KEY_DL_WIFI_ONLY = booleanPreferencesKey("download_wifi_only")
|
||||
val KEY_WISCH_TITEL = booleanPreferencesKey("swipe_changes_track")
|
||||
val KEY_LAST_SONG_ID = stringPreferencesKey("last_song_id")
|
||||
val KEY_LAST_SONG_TITLE = stringPreferencesKey("last_song_title")
|
||||
val KEY_LAST_SONG_ARTIST = stringPreferencesKey("last_song_artist")
|
||||
@@ -60,6 +61,9 @@ class PrefsStore(private val context: Context) {
|
||||
/** Offline-Downloads nur ueber WLAN. Standard an: eine grosse Playlist sind schnell
|
||||
* mehrere Gigabyte, und die Wiederholkette laeuft ohne Zutun weiter. */
|
||||
val downloadWifiOnly: Boolean = true,
|
||||
/** Waagerechtes Wischen im Now-Playing wechselt den Titel statt der Ansicht.
|
||||
* Standard an — Titelwechsel ist die Geste, die man dort staendig braucht. */
|
||||
val wischenWechseltTitel: Boolean = true,
|
||||
)
|
||||
|
||||
data class LastPlayedSong(
|
||||
@@ -99,6 +103,7 @@ class PrefsStore(private val context: Context) {
|
||||
absApiKey = prefs[KEY_ABS_API_KEY] ?: "",
|
||||
absChapterTrackMode = prefs[KEY_ABS_CHAPTER_TRACK] ?: false,
|
||||
downloadWifiOnly = prefs[KEY_DL_WIFI_ONLY] ?: true,
|
||||
wischenWechseltTitel = prefs[KEY_WISCH_TITEL] ?: true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -135,6 +140,7 @@ class PrefsStore(private val context: Context) {
|
||||
prefs[KEY_ABS_API_KEY] = settings.absApiKey
|
||||
prefs[KEY_ABS_CHAPTER_TRACK] = settings.absChapterTrackMode
|
||||
prefs[KEY_DL_WIFI_ONLY] = settings.downloadWifiOnly
|
||||
prefs[KEY_WISCH_TITEL] = settings.wischenWechseltTitel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -251,22 +251,22 @@ class PlayerController(private val context: Context, private val repo: MusicRepo
|
||||
val ctrl = controller ?: return
|
||||
val currentIdx = ctrl.currentMediaItemIndex
|
||||
|
||||
if (!isManuallyShuffled) {
|
||||
// Der Rest hinter dem laufenden Titel wird in EINEM Zug ersetzt. Vorher lief das als
|
||||
// Schleife aus removeMediaItem + addMediaItem, und dazwischen hatte der Controller
|
||||
// voruebergehend weniger Titel als unsere Liste. Genau darauf reagiert onTimelineChanged
|
||||
// mit "die Titel kamen von aussen" — es baute die Warteschlange neu auf und setzte dabei
|
||||
// isManuallyShuffled ZURUECK. Das Mischen lief, aber das Symbol leuchtete nie.
|
||||
val neuerRest = if (!isManuallyShuffled) {
|
||||
isManuallyShuffled = true
|
||||
val played = queue.take(currentIdx + 1)
|
||||
val remaining = queue.drop(currentIdx + 1).shuffled()
|
||||
queue = played + remaining
|
||||
for (i in ctrl.mediaItemCount - 1 downTo currentIdx + 1) ctrl.removeMediaItem(i)
|
||||
remaining.forEach { s -> ctrl.addMediaItem(s.toMediaItem(repo)) }
|
||||
queue.drop(currentIdx + 1).shuffled()
|
||||
} else {
|
||||
isManuallyShuffled = false
|
||||
val currentSong = queue.getOrNull(currentIdx)
|
||||
val origIdx = originalQueue.indexOfFirst { it.id == currentSong?.id }.coerceAtLeast(0)
|
||||
val restOfOriginal = originalQueue.drop(origIdx + 1)
|
||||
queue = originalQueue.take(origIdx + 1) + restOfOriginal
|
||||
for (i in ctrl.mediaItemCount - 1 downTo currentIdx + 1) ctrl.removeMediaItem(i)
|
||||
restOfOriginal.forEach { s -> ctrl.addMediaItem(s.toMediaItem(repo)) }
|
||||
originalQueue.drop(origIdx + 1)
|
||||
}
|
||||
queue = queue.take(currentIdx + 1) + neuerRest
|
||||
ctrl.replaceMediaItems(currentIdx + 1, ctrl.mediaItemCount, neuerRest.map { it.toMediaItem(repo) })
|
||||
syncState()
|
||||
}
|
||||
|
||||
|
||||
@@ -83,9 +83,14 @@ fun NowPlayingScreen(
|
||||
val song = state.currentSong
|
||||
var tab by remember { mutableStateOf(NpTab.PLAYER) }
|
||||
|
||||
// Swipe left/right between Player / Lyrics / Queue, kept in sync with the tab pills (like
|
||||
// Erkunden). A horizontal drag that starts on the cover still skips tracks — that gesture is a
|
||||
// child pointerInput and consumes the drag before the pager sees it.
|
||||
// Waagerechtes Wischen: standardmaessig wechselt es den TITEL — das ist die Geste, die man hier
|
||||
// staendig braucht. Wer lieber zwischen Player, Warteschlange und Text blaettert, stellt es in
|
||||
// den Einstellungen um; dann uebernimmt wieder der Pager. Die Reiter oben schalten in beiden
|
||||
// Faellen weiter, es geht also nie eine Ansicht verloren.
|
||||
val prefsCtx = LocalContext.current
|
||||
val wischTitel by produceState(initialValue = true, prefsCtx) {
|
||||
com.novasonic.app.data.PrefsStore(prefsCtx).appSettings.collect { value = it.wischenWechseltTitel }
|
||||
}
|
||||
val pagerState = rememberPagerState(initialPage = tab.ordinal) { NpTab.entries.size }
|
||||
LaunchedEffect(tab) {
|
||||
if (pagerState.currentPage != tab.ordinal) pagerState.animateScrollToPage(tab.ordinal)
|
||||
@@ -232,11 +237,31 @@ fun NowPlayingScreen(
|
||||
// ── Swipeable tab content (Player / Lyrics / Queue) ────────────
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
userScrollEnabled = !wischTitel,
|
||||
modifier = Modifier.weight(1f).fillMaxWidth(),
|
||||
) { page ->
|
||||
// Die ganze Player-Seite nimmt die Wischgeste an, nicht nur das Cover: sonst muesste
|
||||
// man genau das Bild treffen, und darunter liegt die halbe Seite. Auf Warteschlange
|
||||
// und Text liegt sie NICHT — dort wuerde sie mit dem Ziehen in der Liste streiten.
|
||||
var seitenDrag by remember { mutableStateOf(0f) }
|
||||
val wischbar = wischTitel && NpTab.entries[page] == NpTab.PLAYER
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.then(
|
||||
if (!wischbar) Modifier else Modifier.pointerInput(Unit) {
|
||||
detectHorizontalDragGestures(
|
||||
onDragEnd = {
|
||||
if (seitenDrag < -80) controller.skipNext()
|
||||
else if (seitenDrag > 80) controller.skipPrev()
|
||||
seitenDrag = 0f
|
||||
},
|
||||
onDragCancel = { seitenDrag = 0f },
|
||||
onHorizontalDrag = { _, d -> seitenDrag += d },
|
||||
)
|
||||
}
|
||||
),
|
||||
) {
|
||||
when (NpTab.entries[page]) {
|
||||
// ── PLAYER TAB ─────────────────────────────────────────────────
|
||||
@@ -391,7 +416,7 @@ fun NowPlayingScreen(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
) {
|
||||
NpCircleBtn(onClick = { controller.toggleShuffle() }) {
|
||||
NpCircleBtn(onClick = { controller.toggleShuffle() }, aktiv = state.shuffled) {
|
||||
Icon(Icons.Default.Shuffle, null, modifier = Modifier.size(22.dp),
|
||||
tint = if (state.shuffled) NsAccent else Color.White.copy(0.65f))
|
||||
}
|
||||
@@ -417,7 +442,8 @@ fun NowPlayingScreen(
|
||||
NpCircleBtn(onClick = { controller.skipNext() }) {
|
||||
Icon(Icons.Default.SkipNext, null, modifier = Modifier.size(34.dp), tint = Color.White)
|
||||
}
|
||||
NpCircleBtn(onClick = { controller.toggleRepeat() }) {
|
||||
val wiederholt = state.repeatMode != androidx.media3.common.Player.REPEAT_MODE_OFF
|
||||
NpCircleBtn(onClick = { controller.toggleRepeat() }, aktiv = wiederholt) {
|
||||
val (icon, tint) = when (state.repeatMode) {
|
||||
androidx.media3.common.Player.REPEAT_MODE_ONE -> Icons.Default.RepeatOne to NsAccent
|
||||
androidx.media3.common.Player.REPEAT_MODE_ALL -> Icons.Default.Repeat to NsAccent
|
||||
@@ -768,11 +794,34 @@ private fun NpSeekBar(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NpCircleBtn(onClick: () -> Unit, content: @Composable () -> Unit) {
|
||||
private fun NpCircleBtn(
|
||||
onClick: () -> Unit,
|
||||
aktiv: Boolean = false,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
// Ein eingefaerbtes Symbol allein ist als "an" schwer zu erkennen — auf dem dunklen Cover
|
||||
// sieht Akzentfarbe wie eine Schattierung aus. Ist der Schalter an, liegt deshalb zusaetzlich
|
||||
// ein getoenter Kreis darunter und ein Punkt darunter, wie in gaengigen Playern.
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.size(44.dp).clip(CircleShape).clickable(onClick = onClick),
|
||||
) { content() }
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (aktiv) NsAccent.copy(alpha = 0.22f) else Color.Transparent)
|
||||
.clickable(onClick = onClick),
|
||||
) {
|
||||
content()
|
||||
if (aktiv) {
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 5.dp)
|
||||
.size(4.dp)
|
||||
.clip(CircleShape)
|
||||
.background(NsAccent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatMs(ms: Long): String {
|
||||
|
||||
@@ -122,6 +122,30 @@ fun SettingsScreen(onOpenStats: () -> Unit = {}, onOpenCompare: () -> Unit = {})
|
||||
)
|
||||
}
|
||||
|
||||
// ── ▶ Wiedergabe ────────────────────────────────────────────────────
|
||||
SectionHeader("▶ Wiedergabe")
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("Wischen wechselt den Titel", fontFamily = Outfit, fontWeight = FontWeight.Medium, fontSize = 14.sp, color = NsOnSurface)
|
||||
Text(
|
||||
if (state.wischenWechseltTitel)
|
||||
"Im Now-Playing: seitwärts wischen springt zum nächsten oder vorherigen Titel"
|
||||
else
|
||||
"Im Now-Playing: seitwärts wischen blättert zwischen Player, Warteschlange und Text",
|
||||
fontFamily = Outfit, fontSize = 12.sp, color = NsOnSurface2,
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = state.wischenWechseltTitel,
|
||||
onCheckedChange = { vm.onWischenWechseltTitel(it) },
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = NsBackground, checkedTrackColor = NsAccent,
|
||||
uncheckedThumbColor = NsOnSurface2,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// ── 📥 Downloads ─────────────────────────────────────────────────────
|
||||
SectionHeader("📥 Offline-Downloads")
|
||||
|
||||
|
||||
@@ -85,6 +85,20 @@ val NS_THEMES = listOf(
|
||||
Color(0xFF162A21), Color(0xFF1E3A2D),
|
||||
Color(0xFFE8FFF1), Color(0xFF8FBFA4), Color(0xFF557A63),
|
||||
),
|
||||
NsTheme(
|
||||
"violet", "Violet",
|
||||
Color(0xFFA78BFA), Color(0xFF7C5CF0),
|
||||
Color(0xFF0D0916), Color(0xFF140E20),
|
||||
Color(0xFF221835), Color(0xFF2D2145),
|
||||
Color(0xFFF2EDFF), Color(0xFF9B8BBD), Color(0xFF655780),
|
||||
),
|
||||
NsTheme(
|
||||
"crimson", "Crimson",
|
||||
Color(0xFFE05263), Color(0xFFB03445),
|
||||
Color(0xFF120708), Color(0xFF1A0B0D),
|
||||
Color(0xFF2B1316), Color(0xFF3A1A1E),
|
||||
Color(0xFFFFEEF0), Color(0xFFBF8D94), Color(0xFF7D545C),
|
||||
),
|
||||
)
|
||||
|
||||
fun applyNsTheme(themeId: String) {
|
||||
|
||||
@@ -37,6 +37,7 @@ data class SettingsUiState(
|
||||
val absApiKey: String = "",
|
||||
val absChapterTrackMode: Boolean = false,
|
||||
val downloadWifiOnly: Boolean = true,
|
||||
val wischenWechseltTitel: Boolean = true,
|
||||
val isSaving: Boolean = false,
|
||||
val savedOk: Boolean = false,
|
||||
val error: String? = null,
|
||||
@@ -71,6 +72,7 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
|
||||
absApiKey = s.absApiKey,
|
||||
absChapterTrackMode = s.absChapterTrackMode,
|
||||
downloadWifiOnly = s.downloadWifiOnly,
|
||||
wischenWechseltTitel = s.wischenWechseltTitel,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -88,6 +90,7 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
|
||||
fun onAbsApiKey(v: String) = update { it.copy(absApiKey = v, savedOk = false, absStatus = ConnStatus.Idle) }
|
||||
fun onAbsChapterTrackMode(v: Boolean) = update { it.copy(absChapterTrackMode = v, savedOk = false) }
|
||||
fun onDownloadWifiOnly(v: Boolean) = update { it.copy(downloadWifiOnly = v, savedOk = false) }
|
||||
fun onWischenWechseltTitel(v: Boolean) = update { it.copy(wischenWechseltTitel = v, savedOk = false) }
|
||||
|
||||
fun onTheme(id: String) {
|
||||
update { it.copy(theme = id) }
|
||||
@@ -182,6 +185,7 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
|
||||
absApiKey = s.absApiKey.trim(),
|
||||
absChapterTrackMode = s.absChapterTrackMode,
|
||||
downloadWifiOnly = s.downloadWifiOnly,
|
||||
wischenWechseltTitel = s.wischenWechseltTitel,
|
||||
))
|
||||
// Die Netzbedingung steckt in der bereits eingereihten Arbeit — ohne Neueinreihen
|
||||
// wuerde der WLAN-Schalter erst beim naechsten App-Start greifen.
|
||||
|
||||
+57
-1
@@ -124,8 +124,64 @@ export const THEMES = [
|
||||
|
||||
'--danger': '#e05555',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'violet',
|
||||
name: 'Violet',
|
||||
accent: '#a78bfa',
|
||||
bg: '#0d0916',
|
||||
vars: {
|
||||
'--bg': '#0d0916', // Tiefes Violettschwarz
|
||||
'--bg2': '#140e20',
|
||||
'--bg3': '#1a1229',
|
||||
'--surface': '#221835',
|
||||
'--surface2': '#2d2145',
|
||||
'--border': 'rgba(255,255,255,0.07)',
|
||||
'--border2': 'rgba(255,255,255,0.13)',
|
||||
|
||||
'--accent': '#a78bfa', // Helles Lila — auf dunklem Grund gut lesbar
|
||||
'--accent2': '#c4b5fd',
|
||||
'--accent-dark':'#7c5cf0',
|
||||
'--accent-dim': 'rgba(167,139,250,0.15)',
|
||||
'--accent-glow':'rgba(167,139,250,0.32)',
|
||||
|
||||
'--text': '#f2edff',
|
||||
'--text2': '#9b8bbd',
|
||||
'--text3': '#655780',
|
||||
|
||||
'--danger': '#e05555',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'crimson',
|
||||
name: 'Crimson',
|
||||
accent: '#e05263',
|
||||
bg: '#120708',
|
||||
vars: {
|
||||
'--bg': '#120708', // Dunkles Weinrot-Schwarz
|
||||
'--bg2': '#1a0b0d',
|
||||
'--bg3': '#210e11',
|
||||
'--surface': '#2b1316',
|
||||
'--surface2': '#3a1a1e',
|
||||
'--border': 'rgba(255,255,255,0.07)',
|
||||
'--border2': 'rgba(255,255,255,0.13)',
|
||||
|
||||
// Bewusst kein reines Rot: das laege zu nah an --danger, und Loeschknoepfe saehen
|
||||
// dann aus wie gewoehnliche Akzente.
|
||||
'--accent': '#e05263',
|
||||
'--accent2': '#f2788a',
|
||||
'--accent-dark':'#b03445',
|
||||
'--accent-dim': 'rgba(224,82,99,0.16)',
|
||||
'--accent-glow':'rgba(224,82,99,0.32)',
|
||||
|
||||
'--text': '#ffeef0',
|
||||
'--text2': '#bf8d94',
|
||||
'--text3': '#7d545c',
|
||||
|
||||
'--danger': '#ff7a6b', // heller als der Akzent, damit Warnungen abstechen
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user