diff --git a/shell/ReloadPopup.qml b/shell/ReloadPopup.qml
new file mode 100644
index 0000000..1822aad
--- /dev/null
+++ b/shell/ReloadPopup.qml
@@ -0,0 +1,128 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+
+Scope {
+ id: root
+ property bool failed
+ property string errorString
+
+ // Connect to the Quickshell global to listen for the reload signals.
+ Connections {
+ target: Quickshell
+
+ function onReloadCompleted() {
+ Quickshell.inhibitReloadPopup();
+ root.failed = false;
+ popupLoader.loading = true;
+ }
+
+ function onReloadFailed(error: string) {
+ Quickshell.inhibitReloadPopup();
+ // Close any existing popup before making a new one.
+ popupLoader.active = false;
+
+ root.failed = true;
+ root.errorString = error;
+ popupLoader.loading = true;
+ }
+ }
+
+ // Keep the popup in a loader because it isn't needed most of the timeand will take up
+ // memory that could be used for something else.
+ LazyLoader {
+ id: popupLoader
+
+ PanelWindow {
+ id: popup
+
+ anchors {
+ top: true
+ left: true
+ }
+
+ margins {
+ top: 25
+ left: 25
+ }
+
+ implicitWidth: rect.width
+ implicitHeight: rect.height
+
+ // color blending is a bit odd as detailed in the type reference.
+ color: "transparent"
+
+ Rectangle {
+ id: rect
+ color: failed ? "#40802020" : "#40009020"
+
+ implicitHeight: layout.implicitHeight + 50
+ implicitWidth: layout.implicitWidth + 30
+
+ // Fills the whole area of the rectangle, making any clicks go to it,
+ // which dismiss the popup.
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onClicked: popupLoader.active = false
+
+ // makes the mouse area track mouse hovering, so the hide animation
+ // can be paused when hovering.
+ hoverEnabled: true
+ }
+
+ ColumnLayout {
+ id: layout
+ anchors {
+ top: parent.top
+ topMargin: 20
+ horizontalCenter: parent.horizontalCenter
+ }
+
+ Text {
+ text: root.failed ? "Reload failed." : "Reloaded completed!"
+ color: "white"
+ }
+
+ Text {
+ text: root.errorString
+ color: "white"
+ // When visible is false, it also takes up no space.
+ visible: root.errorString != ""
+ }
+ }
+
+ // A progress bar on the bottom of the screen, showing how long until the
+ // popup is removed.
+ Rectangle {
+ id: bar
+ color: "#20ffffff"
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ height: 20
+
+ PropertyAnimation {
+ id: anim
+ target: bar
+ property: "width"
+ from: rect.width
+ to: 0
+ duration: failed ? 10000 : 2000
+ onFinished: popupLoader.active = false
+
+ // Pause the animation when the mouse is hovering over the popup,
+ // so it stays onscreen while reading. This updates reactively
+ // when the mouse moves on and off the popup.
+ paused: mouseArea.containsMouse
+ }
+ }
+
+ // We could set `running: true` inside the animation, but the width of the
+ // rectangle might not be calculated yet, due to the layout.
+ // In the `Component.onCompleted` event handler, all of the component's
+ // properties and children have been initialized.
+ Component.onCompleted: anim.start()
+ }
+ }
+ }
+}
diff --git a/shell/assets/triangle-alert.svg b/shell/assets/triangle-alert.svg
new file mode 100644
index 0000000..1a5b698
--- /dev/null
+++ b/shell/assets/triangle-alert.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/shell/assets/triangle-dashed.svg b/shell/assets/triangle-dashed.svg
new file mode 100644
index 0000000..664abcf
--- /dev/null
+++ b/shell/assets/triangle-dashed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/shell/assets/triangle.svg b/shell/assets/triangle.svg
new file mode 100644
index 0000000..9f7c478
--- /dev/null
+++ b/shell/assets/triangle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/shell/modules/background/Background.qml b/shell/modules/background/Background.qml
new file mode 100644
index 0000000..343ff38
--- /dev/null
+++ b/shell/modules/background/Background.qml
@@ -0,0 +1,35 @@
+import QtQuick
+import Quickshell
+import Quickshell.Wayland
+import "../../widgets"
+
+Variants {
+ model: Quickshell.screens
+
+ StyledWindow {
+ id: background
+
+ required property ShellScreen modelData
+
+ screen: modelData
+ name: "background"
+ WlrLayershell.exclusionMode: ExclusionMode.Ignore
+ WlrLayershell.layer: WlrLayer.Background
+ color: "black"
+
+ anchors.top: true
+ anchors.bottom: true
+ anchors.left: true
+ anchors.right: true
+
+ Image {
+ id: wallpaper
+
+ anchors.fill: parent
+
+ opacity: 1
+
+ source: "/home/baobeld/Wallpapers/bailey-zindel-NRQV-hBF10M-unsplash.jpg"
+ }
+ }
+}
diff --git a/shell/modules/bar/Bar.qml b/shell/modules/bar/Bar.qml
new file mode 100644
index 0000000..cbc1717
--- /dev/null
+++ b/shell/modules/bar/Bar.qml
@@ -0,0 +1,58 @@
+import QtQuick
+import Quickshell
+import "components"
+import "../../config"
+
+Scope {
+ PanelWindow {
+ id: root
+
+ anchors {
+ top: true
+ left: true
+ right: true
+ }
+
+ implicitHeight: 30
+
+ color: 'transparent'
+
+ Rectangle {
+ anchors.fill: parent
+ color: Colours.palette.base100
+ }
+
+ Row {
+ id: leftbar
+
+ spacing: 6
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.margins: 2
+
+ Workspaces {
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Row {
+ id: centerbar
+
+ spacing: 6
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+
+ Clock {}
+ }
+
+ Row {
+ id: rightbar
+
+ anchors.top: parent.top
+ anchors.right: parent.left
+ anchors.bottom: parent.bottom
+ }
+ }
+}
diff --git a/shell/modules/bar/components/Clock.qml b/shell/modules/bar/components/Clock.qml
new file mode 100644
index 0000000..d5df94b
--- /dev/null
+++ b/shell/modules/bar/components/Clock.qml
@@ -0,0 +1,53 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+
+Item {
+ id: clock
+
+ implicitWidth: 200
+ implicitHeight: 30
+
+ Rectangle {
+ anchors.fill: parent
+ color: "#333"
+ opacity: 0.5
+ radius: 5
+ }
+
+ Text {
+ id: text
+ anchors.centerIn: parent
+
+ color: "white"
+
+ Process {
+ // give the process object an id so we can talk
+ // about it from the timer
+ id: dateProc
+
+ command: ["date"]
+ running: true
+
+ stdout: StdioCollector {
+ onStreamFinished: text.text = this.text
+ }
+ }
+
+ // use a timer to rerun the process at an interval
+ Timer {
+ // 1000 milliseconds is 1 second
+ interval: 1000
+
+ // start the timer immediately
+ running: true
+
+ // run the timer again when it ends
+ repeat: true
+
+ // when the timer is triggered, set the running property of the
+ // process to true, which reruns it if stopped.
+ onTriggered: dateProc.running = true
+ }
+ }
+}
diff --git a/shell/modules/bar/components/Workspaces.qml b/shell/modules/bar/components/Workspaces.qml
new file mode 100644
index 0000000..4a69735
--- /dev/null
+++ b/shell/modules/bar/components/Workspaces.qml
@@ -0,0 +1,51 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.VectorImage
+import Quickshell.Hyprland
+import "../../../config"
+
+Row {
+ id: root
+
+ spacing: 4
+
+ Repeater {
+
+ model: Hyprland.workspaces
+
+ Item {
+ id: workspace
+
+ required property HyprlandWorkspace modelData
+
+ visible: modelData.id > 0
+
+ width: 25
+ height: 25
+
+ Rectangle {
+ id: rectangle
+
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.fill: parent
+ color: "#161212"
+ radius: 8
+ }
+
+ Button {
+ id: button
+ anchors.centerIn: parent
+ anchors.fill: parent
+
+ rotation: workspace.modelData.active ? 0 : 180
+
+ icon.source: "/home/baobeld/dotfiles/quickshell/shell/assets/triangle.svg"
+ icon.color: "#1fb854"
+
+ // palette.button: QtColor.
+
+ onClicked: workspace.modelData.activate()
+ }
+ }
+ }
+}
diff --git a/shell/shell.qml b/shell/shell.qml
new file mode 100644
index 0000000..37db2c9
--- /dev/null
+++ b/shell/shell.qml
@@ -0,0 +1,10 @@
+import Quickshell
+import "modules/background"
+import "modules/bar"
+
+ShellRoot {
+ ReloadPopup {}
+
+ // Background {}
+ Bar {}
+}
diff --git a/shell/widgets/StyledWindow.qml b/shell/widgets/StyledWindow.qml
new file mode 100644
index 0000000..f1275ba
--- /dev/null
+++ b/shell/widgets/StyledWindow.qml
@@ -0,0 +1,9 @@
+import Quickshell
+import Quickshell.Wayland
+
+PanelWindow {
+ required property string name
+
+ WlrLayershell.namespace: `lux-${name}`
+ color: "transparent"
+}