diff --git a/assets/wallpapers/ascii-nixos.png b/assets/wallpapers/ascii-nixos.png
new file mode 100644
index 0000000..2cd684f
Binary files /dev/null and b/assets/wallpapers/ascii-nixos.png differ
diff --git a/assets/wallpapers/pixel-setup.jpeg b/assets/wallpapers/pixel-setup.jpeg
new file mode 100644
index 0000000..3734f93
Binary files /dev/null and b/assets/wallpapers/pixel-setup.jpeg differ
diff --git a/assets/wallpapers/ultrawide-nixos-default.png b/assets/wallpapers/ultrawide-nixos-default.png
new file mode 100644
index 0000000..289e905
Binary files /dev/null and b/assets/wallpapers/ultrawide-nixos-default.png differ
diff --git a/assets/wallpapers/ultrawide-tree-shadow.png b/assets/wallpapers/ultrawide-tree-shadow.png
new file mode 100644
index 0000000..5638179
Binary files /dev/null and b/assets/wallpapers/ultrawide-tree-shadow.png differ
diff --git a/modules/desktop.nix b/modules/desktop.nix
index 18e055d..580e4fc 100644
--- a/modules/desktop.nix
+++ b/modules/desktop.nix
@@ -12,6 +12,7 @@
../packages/matugen/default.nix
../packages/swww/default.nix
../packages/quickshell/default.nix
+ ../packages/ags/default.nix
]
++ lib.optionals (myConfig.linux.gaming == true) [
../packages/mangohud/default.nix
diff --git a/packages/ags/biscuit/.gitignore b/packages/ags/biscuit/.gitignore
new file mode 100644
index 0000000..298eb4d
--- /dev/null
+++ b/packages/ags/biscuit/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+@girs/
diff --git a/packages/ags/biscuit/app.ts b/packages/ags/biscuit/app.ts
new file mode 100644
index 0000000..8952ecf
--- /dev/null
+++ b/packages/ags/biscuit/app.ts
@@ -0,0 +1,17 @@
+import { App } from "astal/gtk3"
+import style from "./style.scss"
+import Bar from "./widget/Bar"
+
+App.start({
+ css: style,
+ instanceName: "js",
+ requestHandler(request, res) {
+ print(request)
+ res("ok")
+ },
+ main: () => {
+ const monitors = App.get_monitors()
+ const primary = monitors.find(m => m.primary) || monitors[0]
+ return Bar(primary)
+ }
+})
diff --git a/packages/ags/biscuit/colors.scss b/packages/ags/biscuit/colors.scss
new file mode 100644
index 0000000..96044bb
--- /dev/null
+++ b/packages/ags/biscuit/colors.scss
@@ -0,0 +1,3 @@
+$background: #131318;
+$foreground: #e4e1e9;
+$primary: #bec2ff;
diff --git a/packages/ags/biscuit/env.d.ts b/packages/ags/biscuit/env.d.ts
new file mode 100644
index 0000000..467c0a4
--- /dev/null
+++ b/packages/ags/biscuit/env.d.ts
@@ -0,0 +1,21 @@
+declare const SRC: string
+
+declare module "inline:*" {
+ const content: string
+ export default content
+}
+
+declare module "*.scss" {
+ const content: string
+ export default content
+}
+
+declare module "*.blp" {
+ const content: string
+ export default content
+}
+
+declare module "*.css" {
+ const content: string
+ export default content
+}
diff --git a/packages/ags/biscuit/package.json b/packages/ags/biscuit/package.json
new file mode 100644
index 0000000..23b6342
--- /dev/null
+++ b/packages/ags/biscuit/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "astal-shell",
+ "dependencies": {
+ "astal": "/home/biscuit/.local/share/ags"
+ }
+}
diff --git a/packages/ags/biscuit/style.scss b/packages/ags/biscuit/style.scss
new file mode 100644
index 0000000..88d42f9
--- /dev/null
+++ b/packages/ags/biscuit/style.scss
@@ -0,0 +1,143 @@
+@use "sass:color";
+@use "./colors" as *;
+
+// default
+// $bg: #212223;
+// $fg: #f1f1f1;
+// $accent: #378DF7;
+// $radius: 7px;
+
+// Kanagawa Theme
+// $bg: #1F1F28;
+// $fg: #DCD7BA;
+// $accent: #C0A36E;
+// $radius: 7px;
+
+// mstcl
+// $bg: #121212;
+// $fg: #f1f1f1;
+// $accent: #C0A36E;
+// $radius: 7px;
+
+$bg: $background;
+$fg: $foreground;
+$accent: $primary;
+$radius: 7px;
+
+window.Bar {
+ border: none;
+ box-shadow: none;
+ background-color: $bg;
+ color: $fg;
+ font-size: 1.1em;
+ font-weight: bold;
+ font-family: "JetBrainsMono Nerd Font";
+
+ label {
+ margin: 0 8px;
+ }
+
+ .Workspaces {
+ button {
+ all: unset;
+ background-color: transparent;
+
+ &:hover label {
+ background-color: color.adjust($fg, $alpha: -0.84);
+ border-color: color.adjust($accent, $alpha: -0.8);
+ }
+
+ &:active label {
+ background-color: color.adjust($fg, $alpha: -0.8)
+ }
+ }
+
+ label {
+ transition: 200ms;
+ padding: 0 8px;
+ margin: 2px;
+ border-radius: $radius;
+ border: 1pt solid transparent;
+ }
+
+ .focused label {
+ color: $accent;
+ border-color: $accent;
+ }
+ }
+
+ .SysTray {
+ margin-right: 8px;
+
+ button {
+ padding: 0 4px;
+ }
+ }
+
+ .Time {
+ .TimeHM {
+ font-weight: bold;
+ color: $accent;
+ }
+
+ .TimeDate {
+ // color: color.adjust($fg, $lightness: -10%);
+ color: $fg;
+ opacity: 0.85;
+ font-weight: normal;
+ }
+ }
+
+
+ .FocusedClient {
+ color: color.adjust($fg, $lightness: -30%);
+ opacity: 0.7;
+ }
+
+ .Media .Cover {
+ min-height: 1.2em;
+ min-width: 1.2em;
+ border-radius: $radius;
+ background-position: center;
+ background-size: contain;
+ }
+
+ .Battery label {
+ padding-left: 0;
+ margin-left: 0;
+ }
+
+ .AudioSlider {
+ * {
+ all: unset;
+ }
+
+ icon {
+ margin-right: .6em;
+ }
+
+ & {
+ margin: 0 1em;
+ }
+
+ trough {
+ background-color: color.adjust($fg, $alpha: -0.8);
+ border-radius: $radius;
+ }
+
+ highlight {
+ background-color: $accent;
+ min-height: .8em;
+ border-radius: $radius;
+ }
+
+ slider {
+ background-color: $fg;
+ border-radius: $radius;
+ min-height: 1em;
+ min-width: 1em;
+ margin: -.2em;
+ }
+ }
+}
+
diff --git a/packages/ags/biscuit/tsconfig.json b/packages/ags/biscuit/tsconfig.json
new file mode 100644
index 0000000..9471e35
--- /dev/null
+++ b/packages/ags/biscuit/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "strict": true,
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "Bundler",
+ // "checkJs": true,
+ // "allowJs": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "astal/gtk3",
+ }
+}
diff --git a/packages/ags/biscuit/widget/Bar.tsx b/packages/ags/biscuit/widget/Bar.tsx
new file mode 100644
index 0000000..9941dc4
--- /dev/null
+++ b/packages/ags/biscuit/widget/Bar.tsx
@@ -0,0 +1,189 @@
+import { App } from "astal/gtk3"
+import { Variable, GLib, bind } from "astal"
+import { Astal, Gtk, Gdk } from "astal/gtk3"
+import Hyprland from "gi://AstalHyprland"
+import Mpris from "gi://AstalMpris"
+import Battery from "gi://AstalBattery"
+import Wp from "gi://AstalWp"
+import Network from "gi://AstalNetwork"
+import Tray from "gi://AstalTray"
+
+function SysTray() {
+ const tray = Tray.get_default()
+
+ return
+ {bind(tray, "items").as(items => items.map(item => (
+ ["dbusmenu", ag])}
+ menuModel={bind(item, "menuModel")}>
+
+
+ )))}
+
+}
+
+function Wifi() {
+ const network = Network.get_default()
+ const wifi = bind(network, "wifi")
+
+ return
+ {wifi.as(wifi => wifi && (
+
+ ))}
+
+
+}
+
+function AudioSlider() {
+ const speaker = Wp.get_default()?.audio.defaultSpeaker!
+
+ return
+
+ speaker.volume = value}
+ value={bind(speaker, "volume")}
+ />
+
+}
+
+function BatteryLevel() {
+ const bat = Battery.get_default()
+
+ return
+
+
+}
+
+function Media() {
+ const mpris = Mpris.get_default()
+
+ return
+ {bind(mpris, "players").as(ps => ps[0] ? (
+
+
+ `background-image: url('${cover}');`
+ )}
+ />
+
+ ) : (
+
+ ))}
+
+}
+
+
+function Workspaces() {
+ const hypr = Hyprland.get_default();
+
+ return (
+
+ {bind(hypr, "focusedWorkspace").as((fw) => {
+ if (!fw) return null;
+
+ // Determine the current chunk of 5 visible workspace buttons
+ const currentChunkStart = Math.floor((fw.id - 1) / 5) * 5 + 1;
+ const visibleIds = Array.from({ length: 5 }, (_, i) => currentChunkStart + i);
+
+ return visibleIds.map((id) => {
+ // Try to get the real workspace, fall back to a dummy one if it doesn't exist
+ const ws =
+ hypr.workspaces.find((w) => w.id === id) ??
+ Hyprland.Workspace.dummy(id, null);
+
+ return (
+
+ );
+ });
+ })}
+
+ );
+}
+
+function FocusedClient() {
+ const hypr = Hyprland.get_default();
+ const focused = bind(hypr, "focusedClient");
+
+ return (
+
+ {focused.as(client => {
+ if (!client) return null;
+
+ return (
+
+ );
+}
+function Time({ format = "%H:%M|%a %b %d" }) {
+ const time = Variable("").poll(1000, () =>
+ GLib.DateTime.new_now_local().format(format)!
+ );
+
+ return bind(time).as(str => {
+ const [hm, date] = str.split("|");
+
+ return (
+
+
+
+
+ );
+ });
+}
+
+export default function Bar(monitor: Gdk.Monitor) {
+ const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
+
+ return
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/packages/ags/default.nix b/packages/ags/default.nix
new file mode 100644
index 0000000..60f4e69
--- /dev/null
+++ b/packages/ags/default.nix
@@ -0,0 +1,28 @@
+{
+ inputs,
+ pkgs,
+ system,
+ ...
+}: {
+ imports = [inputs.ags.homeManagerModules.default];
+
+ programs.ags = {
+ enable = true;
+ configDir = ./biscuit;
+
+ extraPackages = let
+ agsPkgs = inputs.ags.packages.${system};
+ in
+ with pkgs; [
+ agsPkgs.battery
+ agsPkgs.hyprland
+ agsPkgs.mpris
+ agsPkgs.wireplumber
+ agsPkgs.notifd
+ agsPkgs.apps
+ agsPkgs.network
+ agsPkgs.tray
+ fzf
+ ];
+ };
+}