feat(qbz-qt): phase 2 — app shell with 1:1 Slint visual parity

HeaderBar (42px, sidebar cycle, back/forward, centered search replica,
offline badge + flyout, app menu), three-state Sidebar (open 240 / mini
64 / closed 0, 160ms animation, nav rows with exact msgids), NowPlaying
bar SMALL (3px three-color seekbar, art+title+time left, transport
center, quality/volume/queue-toggle right, exact empty state), Queue
panel (300px animated column, Queue/History tabs, exact empty states).
Nav history stack + NowPlayingModel with empty-state defaults behind
new bridge properties/invokables (transport log-and-noop until
playback lands). Icons as pre-baked tinted SVG variants (ColorOverlay
is broken on the software path); Inter fonts bundled; full dark theme
tokens ported from the Slint foundation files.

km
This commit is contained in:
vicrodh committed 2026-07-27 12:56:38 -06:00
1 parent df7eb3588e
commit 744bce3819
189 files changed
+3206 -135

No files matched your search

+32 -5
View File
@@ -1,6 +1,30 @@
use std::path::Path;
use cxx_qt_build::{CxxQtBuilder, QmlModule};
/// Collect every file under `dir` (recursive), crate-root-relative — the
/// baked icon variants (qml/assets/icons/<tint>/<name>.svg) are too many to
/// list by hand.
fn collect_qrc_files(dir: &Path, out: &mut Vec<String>) {
for entry in std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read_dir {}: {e}", dir.display())) {
let path = entry.expect("dir entry").path();
if path.is_dir() {
collect_qrc_files(&path, out);
} else {
out.push(path.to_string_lossy().into_owned());
}
}
}
fn main() {
let mut qrc_files: Vec<String> = vec!["qml/assets/qbz-logo.png".to_string()];
collect_qrc_files(Path::new("qml/assets/icons"), &mut qrc_files);
collect_qrc_files(Path::new("qml/assets/fonts"), &mut qrc_files);
// Non-QML resources land at qrc:/qt/qml/com/blitzfc/qbz/<path> — QML
// files in qml/ reference them relatively (e.g.
// "assets/icons/primary/plus.svg").
let qrc_refs: Vec<&str> = qrc_files.iter().map(String::as_str).collect();
CxxQtBuilder::new()
// Qt modules the bridge links against (Qt6 CMake in /usr/lib64/cmake).
.qt_module("Qml")
@@ -12,13 +36,16 @@ fn main() {
qml_files: &[
"qml/Main.qml",
"qml/LoginScreen.qml",
"qml/ShellPlaceholder.qml",
"qml/AppShell.qml",
"qml/Sidebar.qml",
"qml/HeaderBar.qml",
"qml/NowPlayingBarSmall.qml",
"qml/QueuePanel.qml",
"qml/HomeView.qml",
"qml/QbzTheme.qml",
"qml/QbzIcon.qml",
],
// Non-QML resources. The path below lands at
// qrc:/qt/qml/com/blitzfc/qbz/qml/assets/qbz-logo.png — QML files
// in qml/ reference it relatively as "assets/qbz-logo.png".
qrc_files: &["qml/assets/qbz-logo.png"],
qrc_files: &qrc_refs,
..Default::default()
})
.build();
+76
View File
@@ -0,0 +1,76 @@
// App shell — the QML port of crates/qbz-ui/ui/shell/AppShell.slint's
// chrome: HeaderBar (top, 42px) / { Sidebar | content | queue column } /
// NowPlayingBarSmall (bottom). The recovery affordance and logout live in
// the header (offline badge flyout + app menu), like the Slint shell.
import QtQuick
import com.blitzfc.qbz
Rectangle {
id: root
color: theme.surfaceMain
QbzTheme { id: theme }
HeaderBar {
id: header
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: theme.headerHeight
}
NowPlayingBarSmall {
id: npb
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: theme.npbSmallHeight
}
Sidebar {
id: sidebar
anchors.left: parent.left
anchors.top: header.bottom
anchors.bottom: npb.top
// Animated 3-state width lives inside the component.
}
// Right-side queue column — a layout sibling of the content (it shrinks
// the content, no overlay), animated 0 <-> 300 like the Slint AppShell
// column (160ms ease-in-out).
Rectangle {
id: queueColumn
anchors.right: parent.right
anchors.top: header.bottom
anchors.bottom: npb.top
width: QbzBridge.queueOpen ? theme.queuePanelWidth : 0
clip: true
color: theme.surfaceCard
Behavior on width {
NumberAnimation { duration: 160; easing.type: Easing.InOutQuad }
}
QueuePanel {
anchors.fill: parent
visible: QbzBridge.queueOpen
}
}
// Content frame — the view area between sidebar and queue column.
Rectangle {
id: contentFrame
anchors.left: sidebar.right
anchors.right: queueColumn.left
anchors.top: header.bottom
anchors.bottom: npb.top
color: theme.surfaceMain
clip: true
Loader {
anchors.fill: parent
source: QbzBridge.currentView === "home" ? "HomeView.qml" : ""
}
}
}
+441
View File
@@ -0,0 +1,441 @@
// Top header bar — QML port of crates/qbz-ui/ui/shell/HeaderBar.slint.
//
// Left: the three "sacred" nav buttons (sidebar cycle, back, forward) and
// — while the sidebar is fully closed — the compact icon-only section nav
// (nav-in-sidebar is ON in this POC, so the full text tabs never mount).
// Center: the search field, absolutely centered on the window (VISUAL
// replica — the cortinilla/live-search is out of scope; POC-NOTE).
// Right: the tri-state offline status badge with its flyout (recovery
// "Sign in" wired to QbzBridge.recoveryLogin) and the app menu (user block
// + Log Out + Close; the other Slint menu items are POC-NOTE stubs).
//
// POC-NOTE: the custom window-chrome parts (drag surface, drawn
// min/max/close WindowControls) are skipped — the POC keeps NATIVE window
// decorations.
import QtQuick
import QtQuick.Controls
import com.blitzfc.qbz
Rectangle {
id: root
color: theme.surfaceCard
QbzTheme { id: theme }
// Slint OfflineState.badge-state: 0 hidden / 1 hard offline / 2 manual /
// 3 logged out (wins over the others).
readonly property int badgeState: QbzBridge.offlineSession ? 3
: QbzBridge.offlineMode === 2 ? 2
: QbzBridge.offlineMode === 1 ? 1 : 0
// Circular history / collapse button (HeaderNavBtn).
component HeaderNavBtn: Rectangle {
property string name: ""
property bool btnEnabled: true
signal clicked()
width: 28
height: 28
radius: 14
opacity: btnEnabled ? 1.0 : 0.4
color: (hnArea.containsMouse && btnEnabled) ? theme.surfaceHover : theme.surfaceElevated
QbzIcon {
name: parent.name
width: 15
height: 15
anchors.centerIn: parent
tintName: parent.btnEnabled ? "primary" : "muted"
}
MouseArea {
id: hnArea
anchors.fill: parent
enabled: parent.btnEnabled
hoverEnabled: true
cursorShape: parent.btnEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: parent.clicked()
}
}
// Square icon button (HeaderIconButton, 36px) / compact nav (30px).
component HeaderIconBtn: Rectangle {
property string name: ""
property int btnSize: 36
property int iconSize: 19
property bool active: false
signal clicked()
width: btnSize
height: btnSize
radius: theme.radiusSm
color: active ? theme.surfaceElevated
: hiArea.containsMouse ? theme.surfaceHover : "transparent"
QbzIcon {
name: parent.name
width: parent.iconSize
height: parent.iconSize
anchors.centerIn: parent
tintName: parent.active ? "accent"
: hiArea.containsMouse ? "primary" : "secondary"
}
MouseArea {
id: hiArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: parent.clicked()
}
}
// --- Left controls ---------------------------------------------------
Row {
id: leftControls
x: theme.spacingMd
y: (root.height - height) / 2
height: 36
spacing: 6
HeaderNavBtn {
name: "panel-left"
anchors.verticalCenter: parent.verticalCenter
onClicked: QbzBridge.cycleSidebar()
}
HeaderNavBtn {
name: "chevron-left"
anchors.verticalCenter: parent.verticalCenter
btnEnabled: QbzBridge.canBack
onClicked: QbzBridge.navigateBack()
}
HeaderNavBtn {
name: "chevron-right"
anchors.verticalCenter: parent.verticalCenter
btnEnabled: QbzBridge.canForward
onClicked: QbzBridge.navigateForward()
}
// Compact section nav — only while the sidebar is fully closed
// (Cerrado), so the sections stay reachable. POC-NOTE: these open
// dropdown menus in Slint; here they are inert visual replicas.
Row {
visible: QbzBridge.sidebarState === 2
height: parent.height
spacing: 2
Item { width: 6; height: 1 }
HeaderIconBtn {
name: "compass"
btnSize: 30; iconSize: 16
anchors.verticalCenter: parent.verticalCenter
visible: !QbzBridge.offline
}
HeaderIconBtn {
name: "music-library-2"
btnSize: 30; iconSize: 16
anchors.verticalCenter: parent.verticalCenter
visible: !QbzBridge.offline
}
HeaderIconBtn {
name: "hard-drive"
btnSize: 30; iconSize: 16
anchors.verticalCenter: parent.verticalCenter
}
HeaderIconBtn {
name: "qbz-symbolic"
btnSize: 30; iconSize: 16
anchors.verticalCenter: parent.verticalCenter
}
// Thin separator + the playlists flyout button.
Rectangle {
width: 1
height: 18
anchors.verticalCenter: parent.verticalCenter
color: theme.borderSubtle
}
HeaderIconBtn {
name: "list-music"
btnSize: 30; iconSize: 16
anchors.verticalCenter: parent.verticalCenter
}
}
}
// --- Search (absolutely centered; VISUAL replica only) ---------------
// POC-NOTE: the field is inert — the live-search cortinilla is out of
// scope; metrics/colors/placeholder match the Slint field.
Rectangle {
x: (root.width - width) / 2
y: (root.height - height) / 2
width: root.width < 960 ? 179 : 256
height: 32
radius: 6
border.width: 1
border.color: theme.borderSubtle
color: theme.surfaceElevated
QbzIcon {
name: "search"
width: 14
height: 14
x: 10
anchors.verticalCenter: parent.verticalCenter
tintName: "muted"
}
Text {
anchors.fill: parent
text: QbzBridge.tr("Search")
color: theme.textMuted
font.pixelSize: 13
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
// --- Right controls: status badge + app menu --------------------------
Row {
id: rightControls
x: root.width - width - theme.spacingMd + 2
y: (root.height - height) / 2
height: 36
spacing: 4
// Offline status badge (OfflineStatusBadge) — ghost chrome like
// NavTab: transparent, hover -> surface-hover, radius sm.
Rectangle {
visible: root.badgeState !== 0
height: 30
width: badgeRow.implicitWidth
anchors.verticalCenter: parent.verticalCenter
radius: theme.radiusSm
color: badgeArea.containsMouse ? theme.surfaceHover : "transparent"
readonly property string stateTintName: root.badgeState === 1 ? "warning"
: root.badgeState === 2 ? "accent" : "muted"
Row {
id: badgeRow
height: parent.height
leftPadding: 9
rightPadding: 11
spacing: 6
QbzIcon {
name: root.badgeState === 3 ? "user" : "cloud-off"
width: 14
height: 14
anchors.verticalCenter: parent.verticalCenter
tintName: parent.parent.stateTintName
}
Text {
height: parent.height
text: root.badgeState === 3 ? QbzBridge.tr("Logged out")
: root.badgeState === 2 ? QbzBridge.tr("Offline (manual)")
: QbzBridge.tr("Offline (hard)")
color: theme.textSecondary
font.pixelSize: 11
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
id: badgeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: badgeFlyout.open()
}
// Status flyout — the former banner texts + actions.
Popup {
id: badgeFlyout
x: parent.width - 320
y: parent.height + 6
width: 320
padding: 14
closePolicy: Popup.CloseOnPressOutside
background: Rectangle {
color: theme.surfaceMain
radius: theme.radiusSm
border.width: 1
border.color: theme.borderMuted
}
contentItem: Column {
spacing: 12
Row {
spacing: 8
width: parent.width
QbzIcon {
name: root.badgeState === 3 ? "user" : "cloud-off"
width: 15
height: 15
tintName: "muted"
}
Text {
width: parent.width - 23
text: root.badgeState === 3
? (QbzBridge.connectivity === 2
? QbzBridge.tr("You're signed out. Sign-in needs a connection.")
: QbzBridge.connectivity === 1
? QbzBridge.tr("Connection available — sign back in to Qobuz.")
: QbzBridge.tr("You're signed out — sign back in to Qobuz."))
: root.badgeState === 2
? QbzBridge.tr("Offline mode is enabled. Disable it in Settings to use Qobuz.")
: QbzBridge.tr("No internet connection — your local library and downloads keep working.")
color: theme.textPrimary
font.pixelSize: 12
wrapMode: Text.WordWrap
}
}
// Sign in — logged-out state only; disabled only when the
// link is CONFIRMED down.
Item {
visible: root.badgeState === 3
width: parent.width
height: visible ? 32 : 0
Rectangle {
anchors.right: parent.right
width: signInText.implicitWidth + 28
height: 32
radius: theme.radiusSm
border.width: 1
border.color: theme.borderSubtle
opacity: QbzBridge.connectivity === 2 ? 0.4 : 1.0
color: signInArea.containsMouse ? theme.surfaceHover : theme.surfaceElevated
Text {
id: signInText
anchors.centerIn: parent
text: QbzBridge.tr("Sign in")
color: theme.textSecondary
font.pixelSize: 13
}
MouseArea {
id: signInArea
anchors.fill: parent
enabled: QbzBridge.connectivity !== 2
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
QbzBridge.recoveryLogin()
badgeFlyout.close()
}
}
}
}
// POC-NOTE: the manual-offline quick toggle (state 2)
// needs OfflineModeActions.set-offline — not wired; the
// status text above still renders.
}
}
}
HeaderIconBtn {
name: "menu"
anchors.verticalCenter: parent.verticalCenter
onClicked: appMenu.open()
}
}
// --- App menu (stub: user block + Log Out + Close) --------------------
// POC-NOTE: the Slint menu also carries Settings / Open Music Link /
// Keyboard Shortcuts / Documentation / Report an Issue / What's New /
// About QBZ — omitted here (no views/dialogs to open yet).
Popup {
id: appMenu
x: root.width - 234 - theme.spacingMd
y: theme.headerHeight - 4
width: 234
padding: 0
closePolicy: Popup.CloseOnPressOutside
background: Rectangle {
color: theme.surfaceMain
radius: theme.radiusSm
border.width: 1
border.color: theme.borderMuted
}
contentItem: Column {
width: parent.width
topPadding: 6
bottomPadding: 6
// Signed-in user — name and subscription tier.
Column {
width: parent.width
leftPadding: 14
rightPadding: 14
topPadding: 6
bottomPadding: 10
spacing: 2
Text {
text: QbzBridge.sessionUserName === ""
? QbzBridge.tr("Guest") : QbzBridge.sessionUserName
color: theme.textPrimary
font.pixelSize: theme.fontBody
font.weight: theme.weightSemibold
}
Text {
text: QbzBridge.sessionSubscription === ""
? QbzBridge.tr("Not signed in") : QbzBridge.sessionSubscription
color: theme.textMuted
font.pixelSize: 12
}
}
Rectangle { width: parent.width; height: 1; color: theme.borderSubtle }
Item { width: 1; height: 4 }
// One menu row (MenuItem).
component AppMenuItem: Rectangle {
property string name: ""
property string label: ""
signal clicked()
width: parent ? parent.width : 0
height: 34
color: miArea.containsMouse ? theme.surfaceHover : "transparent"
Row {
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 18
spacing: 10
QbzIcon {
name: parent.parent.name
width: 15
height: 15
anchors.verticalCenter: parent.verticalCenter
tintName: "secondary"
}
Text {
height: parent.height
text: parent.parent.label
color: theme.textSecondary
font.pixelSize: 13
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
id: miArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: parent.clicked()
}
}
AppMenuItem {
name: "log-out"
label: QbzBridge.tr("Log Out")
onClicked: {
appMenu.close()
QbzBridge.logout()
}
}
AppMenuItem {
name: "x"
label: QbzBridge.tr("Close")
// POC-NOTE: the Slint app hides to tray / follows the
// platform close behavior; the POC just quits.
onClicked: Qt.quit()
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
// Content view stub for the only mounted route ("home" = Discover > Home).
// Phase 3 fills this with the real Discover view; for now it proves the
// content frame, theme and i18n wiring.
import QtQuick
import com.blitzfc.qbz
Rectangle {
color: theme.surfaceMain
QbzTheme { id: theme }
Column {
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: theme.spacingXl
spacing: theme.spacingSm
Text {
text: QbzBridge.tr("Discover") + " / " + QbzBridge.tr("Home")
color: theme.textPrimary
font.pixelSize: theme.fontSection
font.weight: theme.weightSemibold
}
Text {
text: "QBZ Qt POC — phase 3 fills this view"
color: theme.textMuted
font.pixelSize: theme.fontLegal
}
}
}
+11 -1
View File
@@ -2,6 +2,10 @@
// property ("splash" | "login" | "shell"). Kicks off the Rust boot
// sequence once the QML tree is complete (the bridge registers its Qt
// thread handle in that first invokable, so every async UI hop lands).
//
// Font: the Slint app bundles the Inter 18pt faces (app.slint); the same
// TTFs are embedded here via qrc and applied app-wide through the
// ApplicationWindow font (children inherit).
import QtQuick
import QtQuick.Controls
@@ -17,6 +21,12 @@ ApplicationWindow {
title: "QBZ"
color: "#0f0f0f"
FontLoader { id: interRegular; source: "assets/fonts/Inter_18pt-Regular.ttf" }
FontLoader { id: interMedium; source: "assets/fonts/Inter_18pt-Medium.ttf" }
FontLoader { id: interSemiBold; source: "assets/fonts/Inter_18pt-SemiBold.ttf" }
FontLoader { id: interBold; source: "assets/fonts/Inter_18pt-Bold.ttf" }
font.family: interRegular.status === FontLoader.Ready ? interRegular.name : "Sans Serif"
Component.onCompleted: QbzBridge.boot()
Loader {
@@ -25,7 +35,7 @@ ApplicationWindow {
active: QbzBridge.screen !== "splash"
source: QbzBridge.screen === "login"
? "LoginScreen.qml"
: (QbzBridge.screen === "shell" ? "ShellPlaceholder.qml" : "")
: (QbzBridge.screen === "shell" ? "AppShell.qml" : "")
}
// Splash: shown until the silent session restore resolves.
+464
View File
@@ -0,0 +1,464 @@
// Bottom now-playing / transport bar — QML port of
// crates/qbz-ui/ui/shell/PlayerBarSmall.slint (Mode C "Small", the only
// variant in this POC).
//
// 3px full-width top seekbar (3-color: background / cache / progress),
// then the symmetric 3-column controls row (~39px):
// LEFT — 37px album art + 2-line title/artist + fixed time column
// CENTER — transport cluster (info · shuffle · prev · play · next ·
// repeat · +)
// RIGHT — AudioStamp · Connect · Cast · Settings · ViewMode · Lyrics ·
// Volume · Queue
// Total height 42px (ShellState.npb-small-height; npb-small-extra is 0 on
// Linux).
//
// POC-NOTE: playback is not wired (phase 4) — the bar renders its EMPTY
// state (NowPlayingState.has-track == false): idle music-glyph art,
// "Nothing playing" title, transport disabled (opacity 0.3), no time
// column, no AudioStamp. Connect / Cast / Settings / ViewMode / Lyrics are
// inert visual replicas (their flyouts are out of scope); Volume, Queue,
// Shuffle, Repeat, Mute are live against the POC NowPlayingModel.
import QtQuick
import com.blitzfc.qbz
Rectangle {
id: root
color: theme.surfaceCard
QbzTheme { id: theme }
// Responsive side fraction — IDENTICAL to PlayerBarSmall's mechanism:
// LEFT and RIGHT columns share this fraction so the CENTER transport
// column lands PLAY on the window centre.
// width < 1366 -> 39% | 22% | 39%
// 1366..<1920 -> 30% | 40% | 30%
// width >= 1920 -> 25% | 50% | 25%
property real sideFrac: root.width >= 1920 ? 0.25 : (root.width >= 1366 ? 0.30 : 0.39)
property real colCentre: 1.0 - 2.0 * root.sideFrac
// WIDE = inline horizontal volume slider; NARROW = button-only.
property bool volumeWide: root.width >= 1366
function fmt(secs) {
var m = Math.floor(secs / 60)
var s = Math.floor(secs % 60)
return m + ":" + (s < 10 ? "0" : "") + s
}
// Square compact icon button (BarControls.IconButton): disabled = dim
// 0.3 + non-clickable; active = accent tint.
component BarIconBtn: Rectangle {
property string name: ""
property int iconSize: 16
property bool active: false
property bool btnEnabled: true
signal clicked()
width: 32
height: 32
radius: theme.radiusSm
opacity: btnEnabled ? 1.0 : 0.3
color: (biArea.containsMouse && btnEnabled) ? theme.surfaceHover : "transparent"
QbzIcon {
name: parent.name
width: parent.iconSize
height: parent.iconSize
anchors.centerIn: parent
tintName: parent.active ? "accent"
: (biArea.containsMouse && parent.btnEnabled)
? "primary" : "secondary"
}
MouseArea {
id: biArea
anchors.fill: parent
hoverEnabled: true
cursorShape: parent.btnEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: if (parent.btnEnabled) parent.clicked()
}
}
// Thin vertical separator (PlayerBarSmall's Sep): a 1px line,
// border-subtle, ~60% of the row height, vertically centred; the
// horizontal gaps are the caller's.
component Sep: Item {
property int gapLeft: 6
property int gapRight: 6
width: gapLeft + 1 + gapRight
height: parent ? parent.height : 0
Rectangle {
x: gapLeft
width: 1
height: 24
anchors.verticalCenter: parent.verticalCenter
color: theme.borderSubtle
}
}
Column {
anchors.fill: parent
spacing: 0
// === A. TOP full-width seekbar (the content/bar divider) ========
Item {
width: parent.width
height: 3
Rectangle {
id: seekTrack
width: parent.width
height: 3
radius: 1.5
color: theme.surfaceElevated
// Buffered / cache line.
Rectangle {
width: parent.width * Math.min(Math.max(QbzBridge.npCacheProgress, 0), 1)
height: parent.height
radius: 1.5
color: theme.textMuted
opacity: 0.35
}
// Playback progress line.
Rectangle {
width: parent.width * Math.min(Math.max(QbzBridge.npProgress, 0), 1)
height: parent.height
radius: 1.5
color: theme.accent
}
// Hover thumb.
Rectangle {
width: 12
height: 12
radius: 6
color: theme.textPrimary
x: parent.width * Math.min(Math.max(QbzBridge.npProgress, 0), 1) - width / 2
anchors.verticalCenter: parent.verticalCenter
opacity: seekArea.containsMouse ? 1.0 : 0.0
Behavior on opacity { NumberAnimation { duration: 100 } }
}
}
// Tall, easy-to-grab hit area over the thin line.
MouseArea {
id: seekArea
width: parent.width
height: 18
anchors.verticalCenter: seekTrack.verticalCenter
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
enabled: QbzBridge.npHasTrack
onClicked: QbzBridge.seek(Math.min(Math.max(mouseX / width, 0), 1))
}
}
// === B. CONTROLS ROW — symmetric 3 columns =======================
Item {
width: parent.width
height: parent.height - 3
// ---- LEFT column: art + title/artist + time column ---------
Item {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: (root.width - 6) * root.sideFrac
clip: true
Row {
anchors.fill: parent
spacing: 0
// Song card: 37px art (3px surface-card border, flush to
// the window's left edge) + tight 2-line text block.
// Width = the column minus the Sep + time column, so the
// title elides at exactly the free space.
Item {
height: parent.height
width: parent.width - (QbzBridge.npHasTrack ? 59 : 0)
Rectangle {
id: artBox
width: 37
height: 37
anchors.verticalCenter: parent.verticalCenter
radius: 6
border.width: 3
border.color: theme.surfaceCard
color: theme.surfaceElevated
clip: true
Image {
visible: QbzBridge.npHasTrack && QbzBridge.npArtworkPath !== ""
anchors.fill: parent
source: QbzBridge.npArtworkPath
fillMode: Image.PreserveAspectCrop
}
// Idle (no track): a STATIC album glyph
// placeholder (50% of the art size, muted).
QbzIcon {
visible: !QbzBridge.npHasTrack
name: "music"
width: 18.5
height: 18.5
anchors.centerIn: parent
tintName: "muted"
}
}
// Title + artist — constrained to the art's inner
// height (37 − 2×3 = 31px) and vertically centred
// (SongCard text-center).
Column {
id: metaBlock
width: Math.max(0, parent.width - 37 - 9)
height: 31
anchors.left: artBox.right
anchors.leftMargin: 9
anchors.verticalCenter: parent.verticalCenter
spacing: 1
Text {
width: parent.width
text: QbzBridge.npHasTrack
? QbzBridge.npTitle : QbzBridge.tr("Nothing playing")
color: theme.textPrimary
font.pixelSize: 13
font.weight: theme.weightMedium
elide: Text.ElideRight
}
Text {
visible: QbzBridge.npHasTrack
width: parent.width
text: QbzBridge.npArtist
color: theme.textMuted
font.pixelSize: 11
elide: Text.ElideRight
}
}
}
// Separator: song-card | time column (8px toward the
// card, 10px toward the timer).
Sep {
visible: QbzBridge.npHasTrack
gapLeft: 8
gapRight: 10
}
// Time column — fixed 40px 2-row block (elapsed /
// total), monospace for stable digit positions.
Column {
visible: QbzBridge.npHasTrack
width: 40
anchors.verticalCenter: parent.verticalCenter
spacing: 1
Text {
text: root.fmt(QbzBridge.npElapsedSecs)
font.family: "monospace"
font.pixelSize: 10
color: theme.textMuted
}
Text {
text: root.fmt(QbzBridge.npDurationSecs)
font.family: "monospace"
font.pixelSize: 10
color: theme.textMuted
}
}
}
}
// ---- CENTER column: transport, window-centred --------------
Item {
anchors.left: parent.left
anchors.leftMargin: (root.width - 6) * root.sideFrac
anchors.top: parent.top
anchors.bottom: parent.bottom
width: (root.width - 6) * root.colCentre
clip: true
Row {
anchors.centerIn: parent
spacing: 2
BarIconBtn {
name: "info"
btnEnabled: QbzBridge.npHasTrack
// POC-NOTE: track-info panel is out of scope.
}
BarIconBtn {
name: "shuffle"
active: QbzBridge.npShuffle
btnEnabled: QbzBridge.npHasTrack
onClicked: QbzBridge.toggleShuffle()
}
BarIconBtn {
name: "skip-back"
btnEnabled: QbzBridge.npHasTrack
onClicked: QbzBridge.previous()
}
// Play/pause — the Classic plain glyph (play-circle:
// false in Small): 34px, 20px glyph, text-primary,
// accent on hover.
Rectangle {
width: 34
height: 34
radius: theme.radiusSm
opacity: QbzBridge.npHasTrack ? 1.0 : 0.3
color: (playArea.containsMouse && QbzBridge.npHasTrack)
? theme.surfaceHover : "transparent"
QbzIcon {
name: QbzBridge.npPlaying ? "pause" : "play-fill"
width: 20
height: 20
anchors.centerIn: parent
tintName: playArea.containsMouse ? "accent" : "primary"
}
MouseArea {
id: playArea
anchors.fill: parent
hoverEnabled: true
cursorShape: QbzBridge.npHasTrack ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: if (QbzBridge.npHasTrack) QbzBridge.togglePlay()
}
}
BarIconBtn {
name: "skip-forward"
btnEnabled: QbzBridge.npHasTrack
onClicked: QbzBridge.next()
}
BarIconBtn {
name: QbzBridge.npRepeatMode === 2 ? "repeat-1" : "repeat"
active: QbzBridge.npRepeatMode !== 0
btnEnabled: QbzBridge.npHasTrack
onClicked: QbzBridge.cycleRepeat()
}
BarIconBtn {
name: "plus"
btnEnabled: QbzBridge.npHasTrack
// POC-NOTE: the grouped "Add to…" flyout is out of
// scope.
}
}
}
// ---- RIGHT column: the control SET, right-aligned ----------
// Order (PlayerBarSmall mount order): AudioStamp · Connect ·
// Cast · Settings · ViewMode · Lyrics · Volume · Queue.
Item {
anchors.right: parent.right
anchors.rightMargin: 6
anchors.top: parent.top
anchors.bottom: parent.bottom
width: (root.width - 6) * root.sideFrac
clip: true
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: parent.height
spacing: 1
// AudioStamp — tier + detail line, width-clamped.
// POC-NOTE: row 2 (backend/mode LEDs) is not rendered —
// the POC model carries no output-backend state.
Column {
visible: QbzBridge.npHasTrack
anchors.verticalCenter: parent.verticalCenter
spacing: 1
Row {
spacing: 5
layoutDirection: Qt.RightToLeft
Text {
text: QbzBridge.npQualityLabel
font.pixelSize: 9
font.weight: theme.weightSemibold
color: theme.textPrimary
elide: Text.ElideRight
}
Text {
text: QbzBridge.npQualityTier === "hires" ? "HI-RES"
: QbzBridge.npQualityTier === "mp3" ? "MP3"
: QbzBridge.npQualityTier === "lossless" ? "LOSSLESS" : "CD"
font.pixelSize: 9
font.weight: theme.weightBold
font.letterSpacing: 0.3
color: theme.textSecondary
}
}
}
// Separator: AudioStamp | icon cluster (10px toward the
// stamp, 8px toward the buttons).
Sep {
visible: QbzBridge.npHasTrack
gapLeft: 10
gapRight: 8
}
// POC-NOTE: Connect / Cast / Settings / ViewMode /
// Lyrics are inert visual replicas — their flyouts
// (device pickers, audio toggles, NPB-mode menu) are
// out of scope for the POC.
BarIconBtn { name: "monitor-speaker" }
BarIconBtn { name: "cast" }
BarIconBtn { name: "settings-2" }
BarIconBtn { name: "layout-grid" }
BarIconBtn { name: "mic-vocal" }
Item { width: 4; height: 1 }
// Volume — mute icon + (WIDE) inline horizontal slider.
BarIconBtn {
name: QbzBridge.npMuted ? "volume-x" : "volume-2"
active: QbzBridge.npMuted
onClicked: QbzBridge.toggleMute()
}
Item {
visible: root.volumeWide
width: 72
height: 32
Rectangle {
id: volTrack
width: parent.width
height: 3
radius: 1.5
anchors.verticalCenter: parent.verticalCenter
color: theme.surfaceElevated
Rectangle {
width: parent.width * QbzBridge.npVolume
height: parent.height
radius: 1.5
color: theme.accent
}
Rectangle {
width: 12
height: 12
radius: 6
color: theme.textPrimary
x: parent.width * QbzBridge.npVolume - width / 2
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: QbzBridge.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
onPositionChanged: if (pressed)
QbzBridge.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
}
}
Item { width: 4; height: 1 }
// Queue panel toggle.
BarIconBtn {
name: "list-ordered"
active: QbzBridge.queueOpen
onClicked: QbzBridge.toggleQueue()
}
}
}
}
}
}
+27
View File
@@ -0,0 +1,27 @@
// Tinted SVG icon — the QML equivalent of Slint's QbzIcon (glyphs recolored
// to a theme tint).
//
// POC-NOTE (icon tinting): the QBZ icon set is Feather-style SVGs with a
// hardcoded `stroke="#ffffff"`; Slint recolors at render time, QML's Image
// cannot. A ColorOverlay (Qt5Compat.GraphicalEffects) was tried first and
// renders NOTHING on the offscreen/software path (verified with a probe —
// effects need the GL path), so tinting is done with PRE-BAKED SVG
// variants: assets/icons/<tint>/<name>.svg for tint in {primary,
// secondary, muted, accent, warning} (script-generated from the originals,
// also rewriting fill="currentColor"). `tintName` switches variants
// dynamically (hover = secondary -> primary), served from qrc.
import QtQuick
Image {
id: root
// Icon file name, e.g. "compass" (".svg" appended).
property string name: ""
// One of: "primary" | "secondary" | "muted" | "accent" | "warning".
property string tintName: "secondary"
source: "assets/icons/" + tintName + "/" + name + ".svg"
sourceSize: Qt.size(Math.max(1, Math.round(width * 2)), Math.max(1, Math.round(height * 2)))
fillMode: Image.PreserveAspectFit
asynchronous: false
}
+34 -2
View File
@@ -1,7 +1,7 @@
// Shared dark-theme constants for the POC — the values the Slint app
// pushes into its ThemeColors struct for the dark theme
// (crates/qbz-ui/ui/foundation/theme.slint, dark block), plus the spacing /
// radius / typography tokens the login screen uses.
// radius / typography / layout tokens the shell uses (foundation/*.slint).
//
// Kept as a plain instantiable QtObject (NOT a pragma Singleton — the
// cxx-qt-build generated qmldir cannot mark singletons).
@@ -9,30 +9,62 @@
import QtQuick
QtObject {
// --- Theme colors (dark) ------------------------------------------
readonly property color surfaceMain: "#0f0f0f"
readonly property color surfaceCard: "#1a1a1a"
readonly property color surfaceElevated: "#2a2a2a"
readonly property color surfaceHover: "#10ffffff"
readonly property color textPrimary: "#ffffff"
readonly property color textSecondary: "#cccccc"
readonly property color textMuted: "#888888"
readonly property color textDisabled: "#555555"
readonly property color accent: "#4285f4"
// #ffffff14 / #00000066 in the Slint ARGB notation.
readonly property color accentHover: "#5a9bf4"
readonly property color accentPressed: "#3275e4"
readonly property color accentText: "#ffffff"
readonly property color warning: "#fbbf24"
readonly property color favorite: "#ef4444"
// #ffffff14 / #ffffff38 / #00000066 in the Slint ARGB notation.
readonly property color borderSubtle: "#14ffffff"
readonly property color borderMuted: "#38ffffff"
readonly property color cardShadow: "#66000000"
// --- Spacing (spacing.slint) --------------------------------------
readonly property int spacingXs: 4
readonly property int spacingSm: 8
readonly property int spacingMd: 16
readonly property int spacingLg: 20
readonly property int spacingXl: 32
readonly property int cardPadding: 52
// --- Radius (radius.slint) ----------------------------------------
readonly property int radiusSm: 8
readonly property int radiusMd: 12
readonly property int radiusLg: 16
// --- Typography (typography.slint, boost = 1.0) -------------------
readonly property int fontLegal: 13
readonly property int fontLink: 14
readonly property int fontBody: 15
readonly property int fontSubtitle: 15
readonly property int fontButton: 17
readonly property int fontSection: 18
readonly property int fontHeading: 19
readonly property int fontTitle: 25
readonly property int fontWordmark: 29
readonly property int weightRegular: Font.Normal
readonly property int weightMedium: Font.Medium
readonly property int weightSemibold: Font.DemiBold
readonly property int weightBold: Font.Bold
// --- Shell layout (layout.slint + state.slint) --------------------
readonly property int headerHeight: 42
// Three-state sidebar rendered widths (ShellState.sidebar-rendered-width).
readonly property int sidebarOpenWidth: 240
readonly property int sidebarMiniWidth: 64
// Right queue/lyrics column width (AppShell).
readonly property int queuePanelWidth: 300
// Small now-playing bar total height (ShellState.npb-small-height,
// npb-small-extra is 0 on Linux).
readonly property int npbSmallHeight: 42
}
+206
View File
@@ -0,0 +1,206 @@
// Right-side Queue panel — QML port of
// crates/qbz-ui/ui/shell/QueueSidebar.slint.
//
// Header with the two tabs ("Queue" / "History"), the NOW PLAYING card
// area, the UP NEXT list region, and the EMPTY states (the queue is empty
// until phase 4 — QbzBridge.queueModel is an empty QVariantList).
//
// POC-NOTE: the footer (count line + clear / save-as-playlist / infinite /
// sleep-timer actions + inline queue search) is omitted — in the Slint
// panel it only mounts when the queue is non-empty, which is never until
// phase 4. Drag-reorder, pagination, the now-playing favorite toggle and
// the sleep-timer popover are likewise phase-4 scope.
import QtQuick
import com.blitzfc.qbz
Rectangle {
id: root
color: "transparent"
clip: true
// Queue (0) / History (1) — Slint QueueState.tab.
property int tab: 0
readonly property bool queueEmpty: QbzBridge.queueModel.length === 0
QbzTheme { id: theme }
// One tab — plain text, no pill (QBZ design rule): active brightest,
// inactive dim, brighten on hover.
component QueueTab: Text {
property bool active: false
signal clicked()
font.pixelSize: theme.fontBody
font.weight: theme.weightSemibold
color: active ? theme.textPrimary
: tabArea.containsMouse ? theme.textSecondary : theme.textMuted
verticalAlignment: Text.AlignVCenter
height: parent ? parent.height : 0
MouseArea {
id: tabArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: parent.clicked()
}
}
Column {
anchors.fill: parent
spacing: 0
// --- Header: tabs + close button -------------------------------
Item {
width: parent.width
height: 44
Row {
anchors.left: parent.left
anchors.leftMargin: theme.spacingMd
anchors.right: closeBtn.left
anchors.rightMargin: theme.spacingSm
anchors.verticalCenter: parent.verticalCenter
height: 28
spacing: 14
QueueTab {
text: QbzBridge.tr("Queue")
active: root.tab === 0
onClicked: root.tab = 0
}
Text {
text: "|"
color: theme.textDisabled
font.pixelSize: theme.fontBody
height: parent.height
verticalAlignment: Text.AlignVCenter
}
QueueTab {
text: QbzBridge.tr("History")
active: root.tab === 1
onClicked: root.tab = 1
}
}
// Close button (mirrors the LyricsSidebar X).
Rectangle {
id: closeBtn
anchors.right: parent.right
anchors.rightMargin: theme.spacingSm
anchors.verticalCenter: parent.verticalCenter
width: 28
height: 28
radius: theme.radiusSm
color: closeArea.containsMouse ? theme.surfaceHover : "transparent"
QbzIcon {
name: "x"
width: 15
height: 15
anchors.centerIn: parent
tintName: closeArea.containsMouse ? "primary" : "secondary"
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: QbzBridge.toggleQueue()
}
}
}
Rectangle {
width: parent.width
height: 1
color: theme.borderSubtle
}
// --- Body --------------------------------------------------------
Item {
width: parent.width
height: parent.height - 45
// ===== Queue tab =====
Column {
visible: root.tab === 0
anchors.fill: parent
anchors.margins: 10
spacing: 12
// NOW PLAYING section — mounts only with a current track.
Text {
visible: !root.queueEmpty
text: QbzBridge.tr("NOW PLAYING")
color: theme.textMuted
font.pixelSize: 11
font.weight: theme.weightSemibold
font.letterSpacing: 0.5
}
// UP NEXT section — mounts only with upcoming tracks.
Text {
visible: !root.queueEmpty
text: QbzBridge.tr("UP NEXT")
color: theme.textMuted
font.pixelSize: 11
font.weight: theme.weightSemibold
font.letterSpacing: 0.5
}
Repeater {
model: QbzBridge.queueModel
// Phase 4 renders real rows (44px QueueRow); unreachable
// while the model is empty.
delegate: Item {}
}
// Empty state — no current track and no upcoming.
Column {
visible: root.queueEmpty
width: parent.width
topPadding: 48 - 10
spacing: 6
Text {
width: parent.width
text: QbzBridge.tr("Your queue is empty")
color: theme.textSecondary
font.pixelSize: theme.fontBody
font.weight: theme.weightMedium
horizontalAlignment: Text.AlignHCenter
}
Text {
width: parent.width
text: QbzBridge.tr("Play an album or track to get started")
color: theme.textMuted
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
}
}
// ===== History tab =====
Column {
visible: root.tab === 1
anchors.fill: parent
anchors.margins: 10
spacing: 8
Text {
text: QbzBridge.tr("RECENTLY PLAYED")
color: theme.textMuted
font.pixelSize: 11
font.weight: theme.weightSemibold
font.letterSpacing: 0.5
}
// POC-NOTE: history is always empty until phase 4.
Text {
width: parent.width
topPadding: 48 - 10
text: QbzBridge.tr("Nothing played yet")
color: theme.textMuted
font.pixelSize: theme.fontBody
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
-115
View File
@@ -1,115 +0,0 @@
// Shell placeholder for phase 1 — proves the post-login state (session
// header, D2 recovery banner, offline-session badge, logout) without
// porting the real shell (phase 2).
import QtQuick
import com.blitzfc.qbz
Rectangle {
id: root
color: theme.surfaceMain
QbzTheme { id: theme }
Column {
anchors.centerIn: parent
spacing: theme.spacingMd
width: Math.min(parent.width - 64, 560)
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "QBZ Qt POC — shell (phase 2 pending)"
color: theme.textPrimary
font.pixelSize: 22
font.weight: theme.weightSemibold
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: QbzBridge.sessionUserName
+ (QbzBridge.sessionSubscription !== ""
? " — " + QbzBridge.sessionSubscription : "")
color: theme.textSecondary
font.pixelSize: theme.fontBody
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
visible: QbzBridge.offlineSession
text: "Offline session — Qobuz™ services unavailable"
color: theme.textMuted
font.pixelSize: theme.fontLegal
}
// D2 recovery banner: a saved session exists but the app is
// running unauthenticated while connectivity is UP.
Rectangle {
visible: QbzBridge.showRecoveryBanner
width: parent.width
height: recoveryColumn.implicitHeight + 24
color: theme.surfaceElevated
radius: theme.radiusSm
border.width: 1
border.color: theme.borderSubtle
Column {
id: recoveryColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 12
spacing: 8
Text {
width: parent.width
text: "Your session could not be restored — sign in again to regain access to Qobuz™."
color: theme.textSecondary
font.pixelSize: theme.fontBody
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: recoveryButtonText.implicitWidth + 32
height: 36
radius: theme.radiusSm
color: recoveryArea.containsMouse ? "#5a95f5" : theme.accent
Text {
id: recoveryButtonText
anchors.centerIn: parent
text: "Sign in again"
color: theme.textPrimary
font.pixelSize: theme.fontBody
font.weight: theme.weightMedium
}
MouseArea {
id: recoveryArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: QbzBridge.recoveryLogin()
}
}
}
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: logoutText.implicitWidth + 32
height: 36
radius: theme.radiusSm
color: theme.surfaceElevated
border.width: 1
border.color: theme.borderSubtle
Text {
id: logoutText
anchors.centerIn: parent
text: "Log out"
color: theme.textPrimary
font.pixelSize: theme.fontBody
font.weight: theme.weightMedium
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: QbzBridge.logout()
}
}
}
}
+210
View File
@@ -0,0 +1,210 @@
// Left navigation sidebar — QML port of crates/qbz-ui/ui/shell/Sidebar.slint.
//
// Three states (ShellState.sidebar-state): 0 = open 240px (icon + label),
// 1 = mini 64px (icons only), 2 = closed 0px. Width animates 160ms
// ease-in-out; the header's panel-left button cycles (QbzBridge.cycleSidebar).
//
// Top-level section nav rows (Discover / Library / Local Library / My QBZ)
// replicate SidebarNavRow: 34px rows, radius 6, 16px icons, 13px/w500
// labels, surface-hover on hover, Discover + Library HIDDEN while offline
// (ADR-010 mount-site gating).
//
// POC-NOTE: in the Slint app these rows open dropdown flyout menus; the
// flyouts are out of scope — rows here navigate (only "home" exists, so
// only Discover is live) and carry the SidebarDirectRow active treatment
// (surface-hover bg + primary text/icon) for the current section.
// POC-NOTE: the playlist/folder tree below the nav is out of scope; the
// "PLAYLISTS" header + toolbar render for parity and the Slint empty state
// shows ("No playlists yet.").
import QtQuick
import com.blitzfc.qbz
Rectangle {
id: root
property bool mini: QbzBridge.sidebarState === 1
// The current section — Discover (home) is the only real view.
property string activeNav: "discover"
QbzTheme { id: theme }
width: QbzBridge.sidebarState === 2 ? 0
: QbzBridge.sidebarState === 1 ? theme.sidebarMiniWidth
: theme.sidebarOpenWidth
color: theme.surfaceCard
// Square edges; clip keeps content from spilling while the width
// animates (same as the Slint root).
clip: true
Behavior on width {
NumberAnimation { duration: 160; easing.type: Easing.InOutQuad }
}
// One section-nav row (SidebarNavRow / SidebarDirectRow metrics).
component NavRow: Rectangle {
id: navRow
property string navId: ""
property string name: ""
property string label: ""
property bool active: root.activeNav === navId
signal clicked()
width: parent ? parent.width : 0
height: 34
radius: 6
color: (navArea.containsMouse || active) ? theme.surfaceHover : "transparent"
Row {
anchors.fill: parent
anchors.leftMargin: root.mini ? 0 : 8
anchors.rightMargin: root.mini ? 0 : 8
spacing: 10
Item {
width: root.mini ? parent.width : 16
height: parent.height
QbzIcon {
name: navRow.name
width: 16
height: 16
anchors.centerIn: parent
tintName: (navArea.containsMouse || navRow.active)
? "primary" : "secondary"
}
}
Text {
visible: !root.mini
height: parent.height
width: parent.width - (root.mini ? 0 : 26)
text: navRow.label
color: (navArea.containsMouse || navRow.active)
? theme.textPrimary : theme.textSecondary
font.pixelSize: 13
font.weight: theme.weightMedium
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
MouseArea {
id: navArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: navRow.clicked()
}
}
Column {
anchors.fill: parent
anchors.leftMargin: root.mini ? 8 : theme.spacingMd
anchors.rightMargin: root.mini ? 8 : theme.spacingMd
anchors.topMargin: root.mini ? 8 : theme.spacingMd
anchors.bottomMargin: root.mini ? 8 : theme.spacingMd
spacing: theme.spacingMd
// ---- Section nav -------------------------------------------
Column {
id: navColumn
width: parent.width
spacing: 2
// Qobuz-only sections — HIDDEN entirely while offline (ADR-010).
NavRow {
navId: "discover"
name: "compass"
label: QbzBridge.tr("Discover")
visible: !QbzBridge.offline
onClicked: root.activeNav = "discover"
}
NavRow {
navId: "library"
name: "music-library-2"
label: QbzBridge.tr("Library")
visible: !QbzBridge.offline
// Inert — the view lands in phase 3.
onClicked: root.activeNav = "library"
}
NavRow {
navId: "local"
name: "hard-drive"
label: QbzBridge.tr("Local Library")
onClicked: root.activeNav = "local"
}
NavRow {
navId: "myqbz"
name: "qbz-symbolic"
// Slint: MyQbzBrandingState.label, default "My QBZ".
label: QbzBridge.tr("My QBZ")
onClicked: root.activeNav = "myqbz"
}
}
Rectangle {
width: parent.width
height: 1
color: theme.borderSubtle
}
// ---- Playlists header toolbar (hidden in the mini state) -----
Row {
visible: !root.mini
width: parent.width
height: 22
spacing: 4
Text {
width: parent.width - 4 * 26
height: parent.height
text: QbzBridge.tr("PLAYLISTS")
color: theme.textMuted
font.pixelSize: 10
font.letterSpacing: 1
verticalAlignment: Text.AlignVCenter
}
Repeater {
// POC-NOTE: search / new-playlist / sort / collapse are
// visual stubs (the playlist tree is out of scope).
model: [
{ "icon": "search", "size": 15 },
{ "icon": "plus", "size": 16 },
{ "icon": "ellipsis", "size": 15 },
{ "icon": "chevron-down", "size": 15 },
]
delegate: Rectangle {
required property var modelData
width: 22
height: 22
radius: 4
color: hdrArea.containsMouse ? theme.surfaceHover : "transparent"
QbzIcon {
name: modelData.icon
width: modelData.size
height: modelData.size
anchors.centerIn: parent
tintName: hdrArea.containsMouse ? "primary" : "muted"
}
MouseArea {
id: hdrArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
}
}
}
}
// ---- Playlist list (out of scope) — the Slint empty state ----
Text {
visible: !root.mini
width: parent.width
text: QbzBridge.tr("No playlists yet.")
color: theme.textMuted
font.pixelSize: 12
wrapMode: Text.WordWrap
}
// Fill the rest.
Item { width: 1; height: 0; }
}
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6" />
<path d="M2 12a9 9 0 0 1 8 8" />
<path d="M2 16a5 5 0 0 1 4 4" />
<line x1="2" x2="2.01" y1="20" y2="20" />
</svg>

After

Width:  |  Height:  |  Size: 371 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 212 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m15 18-6-6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 214 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m9 18 6-6-6-6" />
</svg>

After

Width:  |  Height:  |  Size: 213 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m2 2 20 20" />
<path d="M5.782 5.782A7 7 0 0 0 9 19h8.5a4.5 4.5 0 0 0 1.307-.193" />
<path d="M21.532 16.5A4.5 4.5 0 0 0 17.5 10h-1.79A7.008 7.008 0 0 0 10 5.07" />
</svg>

After

Width:  |  Height:  |  Size: 364 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z" />
</svg>

After

Width:  |  Height:  |  Size: 327 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="1" />
<circle cx="19" cy="12" r="1" />
<circle cx="5" cy="12" r="1" />
</svg>

After

Width:  |  Height:  |  Size: 288 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 16h.01" />
<path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
<path d="M21.946 12.013H2.054" />
<path d="M6 16h.01" />
</svg>

After

Width:  |  Height:  |  Size: 435 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>

After

Width:  |  Height:  |  Size: 270 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="7" x="3" y="3" rx="1" />
<rect width="7" height="7" x="14" y="3" rx="1" />
<rect width="7" height="7" x="14" y="14" rx="1" />
<rect width="7" height="7" x="3" y="14" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 392 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15V6" />
<path d="M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" />
<path d="M12 12H3" />
<path d="M16 6H3" />
<path d="M12 18H3" />
</svg>

After

Width:  |  Height:  |  Size: 340 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 12h11" />
<path d="M10 18h11" />
<path d="M10 6h11" />
<path d="M4 10h2" />
<path d="M4 6h1v4" />
<path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" />
</svg>

After

Width:  |  Height:  |  Size: 351 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m16 17 5-5-5-5" />
<path d="M21 12H9" />
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
</svg>

After

Width:  |  Height:  |  Size: 293 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5h16" />
<path d="M4 12h16" />
<path d="M4 19h16" />
</svg>

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1,17 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-mic-vocal"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#4285f4"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m11 7.601-5.994 8.19a1 1 0 0 0 .1 1.298l.817.818a1 1 0 0 0 1.314.087L15.09 12" />
<path d="M16.5 21.174C15.5 20.5 14.372 20 13 20c-2.058 0-3.928 2.356-6 2-2.072-.356-2.775-3.369-1.5-4.5" />
<circle cx="16" cy="7" r="5" />
</svg>

After

Width:  |  Height:  |  Size: 520 B

@@ -0,0 +1,19 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-monitor-speaker"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#4285f4"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5.5 20H8" />
<path d="M17 9h.01" />
<rect width="10" height="16" x="12" y="4" rx="2" />
<path d="M8 6H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h4" />
<circle cx="17" cy="15" r="1" />
</svg>

After

Width:  |  Height:  |  Size: 482 B

@@ -0,0 +1,12 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M8.50989 2.00001H15.49C15.7225 1.99995 15.9007 1.99991 16.0565 2.01515C17.1643 2.12352 18.0711 2.78958 18.4556 3.68678H5.54428C5.92879 2.78958 6.83555 2.12352 7.94337 2.01515C8.09917 1.99991 8.27741 1.99995 8.50989 2.00001Z" fill="#4285f4"/> <path d="M6.31052 4.72312C4.91989 4.72312 3.77963 5.56287 3.3991 6.67691C3.39117 6.70013 3.38356 6.72348 3.37629 6.74693C3.77444 6.62636 4.18881 6.54759 4.60827 6.49382C5.68865 6.35531 7.05399 6.35538 8.64002 6.35547H15.5321C17.1181 6.35538 18.4835 6.35531 19.5639 6.49382C19.9833 6.54759 20.3977 6.62636 20.7958 6.74693C20.7886 6.72348 20.781 6.70013 20.773 6.67691C20.3925 5.56287 19.2522 4.72312 17.8616 4.72312H6.31052Z" fill="#4285f4"/> <path d="M11.25 17C11.25 16.5858 10.9142 16.25 10.5 16.25C10.0858 16.25 9.75 16.5858 9.75 17C9.75 17.4142 10.0858 17.75 10.5 17.75C10.9142 17.75 11.25 17.4142 11.25 17Z" fill="#4285f4"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M8.67239 7.54204H15.3276C18.7024 7.54204 20.3898 7.54204 21.3377 8.52887C22.2855 9.51569 22.0625 11.0404 21.6165 14.0896L21.1935 16.9811C20.8437 19.3724 20.6689 20.568 19.7717 21.284C18.8745 22 17.5512 22 14.9046 22H9.09534C6.4488 22 5.12553 22 4.22834 21.284C3.33115 20.568 3.15626 19.3724 2.80648 16.9811L2.38351 14.0896C1.93748 11.0403 1.71447 9.5157 2.66232 8.52887C3.61017 7.54204 5.29758 7.54204 8.67239 7.54204ZM12.75 10.5C12.75 10.0858 12.4142 9.75 12 9.75C11.5858 9.75 11.25 10.0858 11.25 10.5V14.878C11.0154 14.7951 10.763 14.75 10.5 14.75C9.25736 14.75 8.25 15.7574 8.25 17C8.25 18.2426 9.25736 19.25 10.5 19.25C11.7426 19.25 12.75 18.2426 12.75 17V13.3197C13.4202 13.8634 14.2617 14.25 15 14.25C15.4142 14.25 15.75 13.9142 15.75 13.5C15.75 13.0858 15.4142 12.75 15 12.75C14.6946 12.75 14.1145 12.5314 13.5835 12.0603C13.0654 11.6006 12.75 11.0386 12.75 10.5Z" fill="#4285f4"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18V5l12-2v13" />
<circle cx="6" cy="18" r="3" />
<circle cx="18" cy="16" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 284 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M9 3v18" />
</svg>

After

Width:  |  Height:  |  Size: 260 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="14" y="3" width="5" height="18" rx="1" />
<rect x="5" y="3" width="5" height="18" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 289 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#4285f4" stroke="none">
<path d="M8 5v14l11-7z" />
</svg>

After

Width:  |  Height:  |  Size: 149 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>

After

Width:  |  Height:  |  Size: 232 B

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024 1024"
width="1024"
height="1024">
<g transform="translate(0,1024) scale(0.1,-0.1)" fill="#4285f4">
<path d="M4760 10144 c-19 -2 -91 -9 -160 -14 -847 -72 -1725 -399 -2431 -906
-1164 -835 -1890 -2083 -2056 -3536 -25 -220 -25 -916 0 -1136 154 -1342 780
-2504 1802 -3342 742 -609 1640 -983 2637 -1097 220 -25 915 -25 1138 0 1649
187 3040 1107 3839 2540 319 572 519 1205 598 1899 25 220 25 916 0 1136 -272
2372 -2067 4171 -4427 4438 -136 15 -842 29 -940 18z m670 -825 c683 -62 1338
-282 1910 -642 l85 -54 6 -79 c3 -46 16 -103 29 -136 22 -57 22 -59 5 -103
-59 -155 -443 -516 -731 -688 -138 -83 -405 -168 -570 -182 -84 -8 -196 6
-394 46 -257 53 -610 73 -870 50 -1126 -100 -2011 -942 -2187 -2081 -25 -163
-25 -601 1 -785 108 -784 631 -1513 1331 -1855 223 -109 407 -170 655 -216
143 -26 698 -32 859 -9 248 36 473 106 720 226 90 43 165 79 168 79 2 0 89
-83 193 -184 284 -276 310 -290 530 -284 110 3 146 8 193 27 81 32 168 114
209 199 l33 67 0 160 c0 223 -5 234 -235 471 -95 97 -176 187 -182 199 -9 19
-1 37 49 116 261 411 377 802 390 1304 11 452 -61 826 -228 1183 -116 247
-206 320 -408 329 -228 11 -348 -48 -425 -207 -30 -64 -31 -68 -31 -215 l0
-150 61 -130 c136 -287 178 -458 178 -730 1 -177 -1 -198 -28 -305 -40 -160
-79 -268 -142 -390 -83 -161 -73 -155 -147 -84 -364 351 -377 363 -440 394
l-65 32 -173 -4 c-199 -3 -235 -15 -327 -108 -88 -87 -107 -149 -107 -340 0
-223 -4 -216 251 -478 108 -112 195 -204 193 -206 -31 -27 -207 -88 -332 -115
-223 -48 -367 -48 -595 -1 -578 120 -1041 544 -1237 1134 -318 959 385 1998
1426 2106 127 13 267 -2 544 -57 l230 -46 275 -4 c218 -4 299 -2 390 11 469
66 863 279 1305 706 397 384 395 383 529 315 57 -29 81 -35 131 -35 72 0 66 5
155 -128 578 -864 822 -1900 680 -2878 -163 -1115 -762 -2124 -1663 -2802
-878 -660 -1994 -961 -3032 -816 -1884 261 -3370 1736 -3645 3618 -80 550 -42
1102 116 1656 477 1672 1877 2869 3609 3085 212 26 554 33 755 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m17 2 4 4-4 4" />
<path d="M3 11v-1a4 4 0 0 1 4-4h14" />
<path d="m7 22-4-4 4-4" />
<path d="M21 13v1a4 4 0 0 1-4 4H3" />
<path d="M11 10h1v4" />
</svg>

After

Width:  |  Height:  |  Size: 349 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m17 2 4 4-4 4" />
<path d="M3 11v-1a4 4 0 0 1 4-4h14" />
<path d="m7 22-4-4 4-4" />
<path d="M21 13v1a4 4 0 0 1-4 4H3" />
</svg>

After

Width:  |  Height:  |  Size: 323 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" />
</svg>

After

Width:  |  Height:  |  Size: 251 B

@@ -0,0 +1,18 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-settings-2"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#4285f4"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14 17H5" />
<path d="M19 7h-9" />
<circle cx="17" cy="17" r="3" />
<circle cx="7" cy="7" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 400 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m18 14 4 4-4 4" />
<path d="m18 2 4 4-4 4" />
<path d="M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22" />
<path d="M2 6h1.972a4 4 0 0 1 3.6 2.2" />
<path d="M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45" />
</svg>

After

Width:  |  Height:  |  Size: 420 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.971 4.285A2 2 0 0 1 21 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432z" />
<path d="M3 20V4" />
</svg>

After

Width:  |  Height:  |  Size: 311 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 4v16" />
<path d="M6.029 4.285A2 2 0 0 0 3 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z" />
</svg>

After

Width:  |  Height:  |  Size: 309 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="5" />
<path d="M20 21a8 8 0 0 0-16 0" />
</svg>

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z" />
<path d="M16 9a5 5 0 0 1 0 6" />
<path d="M19.364 18.364a9 9 0 0 0 0-12.728" />
</svg>

After

Width:  |  Height:  |  Size: 452 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 5 6 9H2v6h4l5 4z" />
<line x1="22" x2="16" y1="9" y2="15" />
<line x1="16" x2="22" y1="9" y2="15" />
</svg>

After

Width:  |  Height:  |  Size: 304 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4285f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>

After

Width:  |  Height:  |  Size: 236 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6" />
<path d="M2 12a9 9 0 0 1 8 8" />
<path d="M2 16a5 5 0 0 1 4 4" />
<line x1="2" x2="2.01" y1="20" y2="20" />
</svg>

After

Width:  |  Height:  |  Size: 371 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 212 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m15 18-6-6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 214 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m9 18 6-6-6-6" />
</svg>

After

Width:  |  Height:  |  Size: 213 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m2 2 20 20" />
<path d="M5.782 5.782A7 7 0 0 0 9 19h8.5a4.5 4.5 0 0 0 1.307-.193" />
<path d="M21.532 16.5A4.5 4.5 0 0 0 17.5 10h-1.79A7.008 7.008 0 0 0 10 5.07" />
</svg>

After

Width:  |  Height:  |  Size: 364 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z" />
</svg>

After

Width:  |  Height:  |  Size: 327 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="1" />
<circle cx="19" cy="12" r="1" />
<circle cx="5" cy="12" r="1" />
</svg>

After

Width:  |  Height:  |  Size: 288 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 16h.01" />
<path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
<path d="M21.946 12.013H2.054" />
<path d="M6 16h.01" />
</svg>

After

Width:  |  Height:  |  Size: 435 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>

After

Width:  |  Height:  |  Size: 270 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="7" x="3" y="3" rx="1" />
<rect width="7" height="7" x="14" y="3" rx="1" />
<rect width="7" height="7" x="14" y="14" rx="1" />
<rect width="7" height="7" x="3" y="14" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 392 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15V6" />
<path d="M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" />
<path d="M12 12H3" />
<path d="M16 6H3" />
<path d="M12 18H3" />
</svg>

After

Width:  |  Height:  |  Size: 340 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 12h11" />
<path d="M10 18h11" />
<path d="M10 6h11" />
<path d="M4 10h2" />
<path d="M4 6h1v4" />
<path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" />
</svg>

After

Width:  |  Height:  |  Size: 351 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m16 17 5-5-5-5" />
<path d="M21 12H9" />
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
</svg>

After

Width:  |  Height:  |  Size: 293 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5h16" />
<path d="M4 12h16" />
<path d="M4 19h16" />
</svg>

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1,17 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-mic-vocal"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#888888"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m11 7.601-5.994 8.19a1 1 0 0 0 .1 1.298l.817.818a1 1 0 0 0 1.314.087L15.09 12" />
<path d="M16.5 21.174C15.5 20.5 14.372 20 13 20c-2.058 0-3.928 2.356-6 2-2.072-.356-2.775-3.369-1.5-4.5" />
<circle cx="16" cy="7" r="5" />
</svg>

After

Width:  |  Height:  |  Size: 520 B

@@ -0,0 +1,19 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-monitor-speaker"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#888888"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5.5 20H8" />
<path d="M17 9h.01" />
<rect width="10" height="16" x="12" y="4" rx="2" />
<path d="M8 6H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h4" />
<circle cx="17" cy="15" r="1" />
</svg>

After

Width:  |  Height:  |  Size: 482 B

@@ -0,0 +1,12 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M8.50989 2.00001H15.49C15.7225 1.99995 15.9007 1.99991 16.0565 2.01515C17.1643 2.12352 18.0711 2.78958 18.4556 3.68678H5.54428C5.92879 2.78958 6.83555 2.12352 7.94337 2.01515C8.09917 1.99991 8.27741 1.99995 8.50989 2.00001Z" fill="#888888"/> <path d="M6.31052 4.72312C4.91989 4.72312 3.77963 5.56287 3.3991 6.67691C3.39117 6.70013 3.38356 6.72348 3.37629 6.74693C3.77444 6.62636 4.18881 6.54759 4.60827 6.49382C5.68865 6.35531 7.05399 6.35538 8.64002 6.35547H15.5321C17.1181 6.35538 18.4835 6.35531 19.5639 6.49382C19.9833 6.54759 20.3977 6.62636 20.7958 6.74693C20.7886 6.72348 20.781 6.70013 20.773 6.67691C20.3925 5.56287 19.2522 4.72312 17.8616 4.72312H6.31052Z" fill="#888888"/> <path d="M11.25 17C11.25 16.5858 10.9142 16.25 10.5 16.25C10.0858 16.25 9.75 16.5858 9.75 17C9.75 17.4142 10.0858 17.75 10.5 17.75C10.9142 17.75 11.25 17.4142 11.25 17Z" fill="#888888"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M8.67239 7.54204H15.3276C18.7024 7.54204 20.3898 7.54204 21.3377 8.52887C22.2855 9.51569 22.0625 11.0404 21.6165 14.0896L21.1935 16.9811C20.8437 19.3724 20.6689 20.568 19.7717 21.284C18.8745 22 17.5512 22 14.9046 22H9.09534C6.4488 22 5.12553 22 4.22834 21.284C3.33115 20.568 3.15626 19.3724 2.80648 16.9811L2.38351 14.0896C1.93748 11.0403 1.71447 9.5157 2.66232 8.52887C3.61017 7.54204 5.29758 7.54204 8.67239 7.54204ZM12.75 10.5C12.75 10.0858 12.4142 9.75 12 9.75C11.5858 9.75 11.25 10.0858 11.25 10.5V14.878C11.0154 14.7951 10.763 14.75 10.5 14.75C9.25736 14.75 8.25 15.7574 8.25 17C8.25 18.2426 9.25736 19.25 10.5 19.25C11.7426 19.25 12.75 18.2426 12.75 17V13.3197C13.4202 13.8634 14.2617 14.25 15 14.25C15.4142 14.25 15.75 13.9142 15.75 13.5C15.75 13.0858 15.4142 12.75 15 12.75C14.6946 12.75 14.1145 12.5314 13.5835 12.0603C13.0654 11.6006 12.75 11.0386 12.75 10.5Z" fill="#888888"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18V5l12-2v13" />
<circle cx="6" cy="18" r="3" />
<circle cx="18" cy="16" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 284 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M9 3v18" />
</svg>

After

Width:  |  Height:  |  Size: 260 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="14" y="3" width="5" height="18" rx="1" />
<rect x="5" y="3" width="5" height="18" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 289 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#888888" stroke="none">
<path d="M8 5v14l11-7z" />
</svg>

After

Width:  |  Height:  |  Size: 149 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>

After

Width:  |  Height:  |  Size: 232 B

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024 1024"
width="1024"
height="1024">
<g transform="translate(0,1024) scale(0.1,-0.1)" fill="#888888">
<path d="M4760 10144 c-19 -2 -91 -9 -160 -14 -847 -72 -1725 -399 -2431 -906
-1164 -835 -1890 -2083 -2056 -3536 -25 -220 -25 -916 0 -1136 154 -1342 780
-2504 1802 -3342 742 -609 1640 -983 2637 -1097 220 -25 915 -25 1138 0 1649
187 3040 1107 3839 2540 319 572 519 1205 598 1899 25 220 25 916 0 1136 -272
2372 -2067 4171 -4427 4438 -136 15 -842 29 -940 18z m670 -825 c683 -62 1338
-282 1910 -642 l85 -54 6 -79 c3 -46 16 -103 29 -136 22 -57 22 -59 5 -103
-59 -155 -443 -516 -731 -688 -138 -83 -405 -168 -570 -182 -84 -8 -196 6
-394 46 -257 53 -610 73 -870 50 -1126 -100 -2011 -942 -2187 -2081 -25 -163
-25 -601 1 -785 108 -784 631 -1513 1331 -1855 223 -109 407 -170 655 -216
143 -26 698 -32 859 -9 248 36 473 106 720 226 90 43 165 79 168 79 2 0 89
-83 193 -184 284 -276 310 -290 530 -284 110 3 146 8 193 27 81 32 168 114
209 199 l33 67 0 160 c0 223 -5 234 -235 471 -95 97 -176 187 -182 199 -9 19
-1 37 49 116 261 411 377 802 390 1304 11 452 -61 826 -228 1183 -116 247
-206 320 -408 329 -228 11 -348 -48 -425 -207 -30 -64 -31 -68 -31 -215 l0
-150 61 -130 c136 -287 178 -458 178 -730 1 -177 -1 -198 -28 -305 -40 -160
-79 -268 -142 -390 -83 -161 -73 -155 -147 -84 -364 351 -377 363 -440 394
l-65 32 -173 -4 c-199 -3 -235 -15 -327 -108 -88 -87 -107 -149 -107 -340 0
-223 -4 -216 251 -478 108 -112 195 -204 193 -206 -31 -27 -207 -88 -332 -115
-223 -48 -367 -48 -595 -1 -578 120 -1041 544 -1237 1134 -318 959 385 1998
1426 2106 127 13 267 -2 544 -57 l230 -46 275 -4 c218 -4 299 -2 390 11 469
66 863 279 1305 706 397 384 395 383 529 315 57 -29 81 -35 131 -35 72 0 66 5
155 -128 578 -864 822 -1900 680 -2878 -163 -1115 -762 -2124 -1663 -2802
-878 -660 -1994 -961 -3032 -816 -1884 261 -3370 1736 -3645 3618 -80 550 -42
1102 116 1656 477 1672 1877 2869 3609 3085 212 26 554 33 755 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m17 2 4 4-4 4" />
<path d="M3 11v-1a4 4 0 0 1 4-4h14" />
<path d="m7 22-4-4 4-4" />
<path d="M21 13v1a4 4 0 0 1-4 4H3" />
<path d="M11 10h1v4" />
</svg>

After

Width:  |  Height:  |  Size: 349 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m17 2 4 4-4 4" />
<path d="M3 11v-1a4 4 0 0 1 4-4h14" />
<path d="m7 22-4-4 4-4" />
<path d="M21 13v1a4 4 0 0 1-4 4H3" />
</svg>

After

Width:  |  Height:  |  Size: 323 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m21 21-4.34-4.34" />
<circle cx="11" cy="11" r="8" />
</svg>

After

Width:  |  Height:  |  Size: 251 B

@@ -0,0 +1,18 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-settings-2"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#888888"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14 17H5" />
<path d="M19 7h-9" />
<circle cx="17" cy="17" r="3" />
<circle cx="7" cy="7" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 400 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m18 14 4 4-4 4" />
<path d="m18 2 4 4-4 4" />
<path d="M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22" />
<path d="M2 6h1.972a4 4 0 0 1 3.6 2.2" />
<path d="M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45" />
</svg>

After

Width:  |  Height:  |  Size: 420 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.971 4.285A2 2 0 0 1 21 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432z" />
<path d="M3 20V4" />
</svg>

After

Width:  |  Height:  |  Size: 311 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 4v16" />
<path d="M6.029 4.285A2 2 0 0 0 3 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z" />
</svg>

After

Width:  |  Height:  |  Size: 309 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="5" />
<path d="M20 21a8 8 0 0 0-16 0" />
</svg>

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z" />
<path d="M16 9a5 5 0 0 1 0 6" />
<path d="M19.364 18.364a9 9 0 0 0 0-12.728" />
</svg>

After

Width:  |  Height:  |  Size: 452 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 5 6 9H2v6h4l5 4z" />
<line x1="22" x2="16" y1="9" y2="15" />
<line x1="16" x2="22" y1="9" y2="15" />
</svg>

After

Width:  |  Height:  |  Size: 304 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#888888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>

After

Width:  |  Height:  |  Size: 236 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6" />
<path d="M2 12a9 9 0 0 1 8 8" />
<path d="M2 16a5 5 0 0 1 4 4" />
<line x1="2" x2="2.01" y1="20" y2="20" />
</svg>

After

Width:  |  Height:  |  Size: 371 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 212 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m15 18-6-6 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 214 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m9 18 6-6-6-6" />
</svg>

After

Width:  |  Height:  |  Size: 213 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m2 2 20 20" />
<path d="M5.782 5.782A7 7 0 0 0 9 19h8.5a4.5 4.5 0 0 0 1.307-.193" />
<path d="M21.532 16.5A4.5 4.5 0 0 0 17.5 10h-1.79A7.008 7.008 0 0 0 10 5.07" />
</svg>

After

Width:  |  Height:  |  Size: 364 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z" />
</svg>

After

Width:  |  Height:  |  Size: 327 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="1" />
<circle cx="19" cy="12" r="1" />
<circle cx="5" cy="12" r="1" />
</svg>

After

Width:  |  Height:  |  Size: 288 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 16h.01" />
<path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
<path d="M21.946 12.013H2.054" />
<path d="M6 16h.01" />
</svg>

After

Width:  |  Height:  |  Size: 435 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>

After

Width:  |  Height:  |  Size: 270 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="7" x="3" y="3" rx="1" />
<rect width="7" height="7" x="14" y="3" rx="1" />
<rect width="7" height="7" x="14" y="14" rx="1" />
<rect width="7" height="7" x="3" y="14" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 392 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15V6" />
<path d="M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" />
<path d="M12 12H3" />
<path d="M16 6H3" />
<path d="M12 18H3" />
</svg>

After

Width:  |  Height:  |  Size: 340 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 12h11" />
<path d="M10 18h11" />
<path d="M10 6h11" />
<path d="M4 10h2" />
<path d="M4 6h1v4" />
<path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" />
</svg>

After

Width:  |  Height:  |  Size: 351 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m16 17 5-5-5-5" />
<path d="M21 12H9" />
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
</svg>

After

Width:  |  Height:  |  Size: 293 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5h16" />
<path d="M4 12h16" />
<path d="M4 19h16" />
</svg>

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1,17 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-mic-vocal"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m11 7.601-5.994 8.19a1 1 0 0 0 .1 1.298l.817.818a1 1 0 0 0 1.314.087L15.09 12" />
<path d="M16.5 21.174C15.5 20.5 14.372 20 13 20c-2.058 0-3.928 2.356-6 2-2.072-.356-2.775-3.369-1.5-4.5" />
<circle cx="16" cy="7" r="5" />
</svg>

After

Width:  |  Height:  |  Size: 520 B

@@ -0,0 +1,19 @@
<!-- @license lucide-static v1.17.0 - ISC -->
<svg
class="lucide lucide-monitor-speaker"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5.5 20H8" />
<path d="M17 9h.01" />
<rect width="10" height="16" x="12" y="4" rx="2" />
<path d="M8 6H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h4" />
<circle cx="17" cy="15" r="1" />
</svg>

After

Width:  |  Height:  |  Size: 482 B

@@ -0,0 +1,12 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M8.50989 2.00001H15.49C15.7225 1.99995 15.9007 1.99991 16.0565 2.01515C17.1643 2.12352 18.0711 2.78958 18.4556 3.68678H5.54428C5.92879 2.78958 6.83555 2.12352 7.94337 2.01515C8.09917 1.99991 8.27741 1.99995 8.50989 2.00001Z" fill="#ffffff"/> <path d="M6.31052 4.72312C4.91989 4.72312 3.77963 5.56287 3.3991 6.67691C3.39117 6.70013 3.38356 6.72348 3.37629 6.74693C3.77444 6.62636 4.18881 6.54759 4.60827 6.49382C5.68865 6.35531 7.05399 6.35538 8.64002 6.35547H15.5321C17.1181 6.35538 18.4835 6.35531 19.5639 6.49382C19.9833 6.54759 20.3977 6.62636 20.7958 6.74693C20.7886 6.72348 20.781 6.70013 20.773 6.67691C20.3925 5.56287 19.2522 4.72312 17.8616 4.72312H6.31052Z" fill="#ffffff"/> <path d="M11.25 17C11.25 16.5858 10.9142 16.25 10.5 16.25C10.0858 16.25 9.75 16.5858 9.75 17C9.75 17.4142 10.0858 17.75 10.5 17.75C10.9142 17.75 11.25 17.4142 11.25 17Z" fill="#ffffff"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M8.67239 7.54204H15.3276C18.7024 7.54204 20.3898 7.54204 21.3377 8.52887C22.2855 9.51569 22.0625 11.0404 21.6165 14.0896L21.1935 16.9811C20.8437 19.3724 20.6689 20.568 19.7717 21.284C18.8745 22 17.5512 22 14.9046 22H9.09534C6.4488 22 5.12553 22 4.22834 21.284C3.33115 20.568 3.15626 19.3724 2.80648 16.9811L2.38351 14.0896C1.93748 11.0403 1.71447 9.5157 2.66232 8.52887C3.61017 7.54204 5.29758 7.54204 8.67239 7.54204ZM12.75 10.5C12.75 10.0858 12.4142 9.75 12 9.75C11.5858 9.75 11.25 10.0858 11.25 10.5V14.878C11.0154 14.7951 10.763 14.75 10.5 14.75C9.25736 14.75 8.25 15.7574 8.25 17C8.25 18.2426 9.25736 19.25 10.5 19.25C11.7426 19.25 12.75 18.2426 12.75 17V13.3197C13.4202 13.8634 14.2617 14.25 15 14.25C15.4142 14.25 15.75 13.9142 15.75 13.5C15.75 13.0858 15.4142 12.75 15 12.75C14.6946 12.75 14.1145 12.5314 13.5835 12.0603C13.0654 11.6006 12.75 11.0386 12.75 10.5Z" fill="#ffffff"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Loaded 100 of 189 files, more files were not shown because too many files have changed in this diff. Show more