;; ============================================================================
;;  eww config  —  niri top bar + slide-in panels + dock
;;  Monochrome / Fuji-accent theme.
;;  Icons: Material Design Icons (nf-md-*) from Iosevka Nerd Font.
;;  Repo lives in ~/eww and is symlinked to ~/.config/eww
;; ============================================================================

;; ---------------------------------------------------------------------------
;;  DATA SOURCES (poll / listen)
;; ---------------------------------------------------------------------------

(deflisten workspaces :initial "[]"
  "scripts/workspaces.sh")

(deflisten music
  :initial {'{"status":"Stopped","title":"","artist":"","icon":"","cover":""}'}
  "scripts/music.sh")

(defpoll volume :interval "1s"
  :initial {'{"percent":0,"muted":false,"icon":""}'}
  "scripts/volume.sh get")

;; audio output devices for the Control Center dropdown
(defpoll sinks :interval "3s" :initial "[]" "scripts/audio.sh sinks")
(defvar audio-menu false)

;; dock: pinned apps (real Papirus icons) + running state
;; 4s (not 2s): the poll re-renders the icon row, which briefly re-applies hover
;; state (a small flicker). 4s keeps running-dots fresh enough with less flicker.
(defpoll dock-apps :interval "4s" :initial "[]" "scripts/dock.sh apps")
;; dock visibility — drives the slide-in/out revealer (window stays mapped).
(defvar dock-open false)
;; panel + spotlight slide animation (one panel open at a time → one shared var).
(defvar panel-open false)
(defvar spot-open false)

;; live settings from TANINUX panel.json (pushed via eww update by panelcfg.sh)
(defvar cfg-icon-size 42)
(defvar cfg-modules {'{"music":true,"sys":true,"updates":true,"net":true,"bt":true,"vol":true}'})

;; GNOME light/dark — panels follow it (the bar stays dark). "light" = Shibui.
;; Polled (not a gsettings-monitor deflisten — that dies in eww's spawn env).
(defpoll colorscheme :interval "2s" :initial "dark" "scripts/colorscheme.sh get")
;; right-click context-menu target
(defvar dock-sel-class "")
(defvar dock-sel-exec "")
(defvar dock-sel-pinned false)
(defvar dock-sel-running false)
(defvar dock-sel-windows "[]")   ; open windows of the selected app (multi-instance)

(defpoll network :interval "5s"
  :initial {'{"icon":"","label":"…"}'}
  "scripts/network.sh status")

(defpoll bluetooth :interval "5s"
  :initial {'{"icon":"","label":"…","on":false}'}
  "scripts/bluetooth.sh status")

(defpoll updates-list :interval "900s" :initial "[]" "scripts/updates.sh list")
(defvar updates-open true)   ; dropdown state in the updates panel
(defvar upd-running false)   ; a system update is in progress (panel shows progress)
(defvar upd-progress "")     ; latest line of the running update
(defvar power-open false)    ; power dropdown in the control center
(defpoll nightlight :interval "5s" :initial "true" "scripts/night.sh status")
;; DnD is our own flag now (mako is gone — replaced by tiramisu + eww).
(defpoll dnd :interval "2s" :initial "false" "scripts/notifications.sh dnd-get")

;; Notifications: pushed in by scripts/notifications.sh via `eww update`.
;;   notifications — full history (newest first), rendered in the Control Center
;;   popup         — the single toast currently shown top-right
(defvar notifications "[]")
(defvar popup {'{"id":"0","app":"","summary":"","body":"","time":""}'})

;; Spotlight launcher — query text + active category + results
(defvar spot-q "")
(defvar spot-mode "apps")
(defvar spot-results "[]")

(defpoll sys :interval "2s"
  :initial {'{"cpu":0,"mem":0,"disk":0,"temp":0}'}
  "scripts/sysinfo.sh")

(defpoll time    :interval "10s"   :initial "" "scripts/clock.sh")

;; ---------------------------------------------------------------------------
;;  THE BAR
;; ---------------------------------------------------------------------------

(defwidget bar []
  (centerbox :class "bar" :vexpand true
    (left)
    (center)
    (right)))

(defwidget left []
  (box :halign "start" :valign "center" :space-evenly false
    (workspaces)))

(defwidget center []
  (box :halign "center" :valign "center" :space-evenly false
    (clock)))

(defwidget right []
  (box :halign "end" :valign "center" :space-evenly false :spacing 14
    (sys-mini)
    (updates-mod)
    (net)
    (bt)
    (vol)))

;; --- workspaces -------------------------------------------------------------
(defwidget workspaces []
  (eventbox
    :onscroll "test {} = up && scripts/ws.sh cycle up || scripts/ws.sh cycle down"
    (box :class "workspaces" :space-evenly false :spacing 6
      (for ws in workspaces
        (button
          :class {ws.active ? "ws active" : (ws.windows > 0 ? "ws occupied" : "ws")}
          :onclick "scripts/ws.sh switch ${ws.id}"
          "${ws.id}")))))

;; --- clock (center) ---------------------------------------------------------
(defwidget clock []
  (button :class "clock" :onclick "scripts/panel.sh toggle calendar"
    "${time}"))

;; --- right-hand modules -----------------------------------------------------
(defwidget music-mini []
  (revealer :transition "slideleft" :duration "200ms"
    :reveal {cfg-modules.music && music.status != "Stopped" && music.title != ""}
    (button :class "module music" :onclick "scripts/panel.sh toggle music"
      (label :limit-width 28 :text "${music.icon}  ${music.title}"))))

(defwidget sys-mini []
  (button :class "module sys" :visible {cfg-modules.sys} :onclick "scripts/panel.sh toggle sysmon"
    (label :text "󰓅 ${sys.cpu}%   󰍛 ${sys.mem}%")))

(defwidget updates-mod []
  (revealer :transition "slideleft" :duration "200ms"
            :reveal {cfg-modules.updates && (arraylength(updates-list) > 0 || upd-running)}
    (button :class {upd-running ? "module updates updating" : "module updates"}
            :onclick "scripts/panel.sh toggle updates"
            :tooltip {upd-running ? upd-progress : "${arraylength(updates-list)} updates"}
      (label :text {upd-running ? "󰚰 ⋯" : "󰚰 ${arraylength(updates-list)}"}))))

(defwidget net []
  (button :class "module net" :visible {cfg-modules.net} :onclick "scripts/panel.sh toggle control-center"
          :onrightclick ""
    (label :text {network.icon})))

(defwidget bt []
  (button :class "module bt" :visible {cfg-modules.bt} :onclick "scripts/panel.sh toggle control-center"
          :onrightclick ""
    (label :text {bluetooth.icon})))

(defwidget vol []
  (eventbox
    :onscroll "test {} = up && wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ || wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
    (button :class "module vol" :visible {cfg-modules.vol} :onclick "scripts/panel.sh toggle control-center"
            :onrightclick "scripts/volume.sh mute"
      (label :text "${volume.icon}  ${volume.percent}%"))))

;; ---------------------------------------------------------------------------
;;  CONTROL CENTER  (slides in top-right)
;; ---------------------------------------------------------------------------

(defwidget toggle-tile [icon label active onclick onrc]
  (button :class {active ? "tile active" : "tile"} :onclick onclick :onrightclick onrc
    (box :orientation "vertical" :space-evenly false :spacing 5
      (label :class "tile-icon"  :text icon)
      (label :class "tile-label" :limit-width 14 :text label))))

(defwidget action-btn [icon label cmd]
  (button :class "action" :onclick cmd
    (box :orientation "vertical" :space-evenly false :spacing 4
      (label :class "action-icon"  :text icon)
      (label :class "action-label" :text label))))

(defwidget vol-slider []
  (box :orientation "vertical" :space-evenly false :spacing 8
    (box :class "slider-row" :space-evenly false :spacing 10
      ;; dropdown caret on the LEFT — opens the output-device menu below
      (button :class {audio-menu ? "audio-caret open" : "audio-caret"}
              :onclick "eww update audio-menu=${!audio-menu}"
        (label :text {audio-menu ? "▴" : "▾"}))
      (button :class "slider-icon" :onclick "scripts/volume.sh mute"
        (label :text {volume.icon}))
      (scale :hexpand true :min 0 :max 100 :value {volume.percent}
             :onchange "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ {}%")
      (label :class "slider-val" :text "${volume.percent}%"))
    (revealer :transition "slidedown" :duration "200ms" :reveal audio-menu
      (box :class "audio-list" :orientation "vertical" :space-evenly false :spacing 4
        (for s in sinks
          (button :class {s.default ? "audio-dev active" : "audio-dev"}
                  :onclick "scripts/audio.sh set-default ${s.id} && eww update audio-menu=false"
            (box :space-evenly false :spacing 8
              (label :class "audio-dev-icon" :text "󰕾")
              (label :halign "start" :hexpand true :limit-width 30 :text {s.name})
              (label :class "audio-dev-check" :visible {s.default} :text "󰄬"))))))))

(defwidget cc-music []
  (revealer :transition "slidedown" :duration "200ms"
    :reveal {music.status != "Stopped" && music.title != ""}
    (box :class "cc-music" :space-evenly false :spacing 12
      (overlay
        (box :class "cc-cover" :style "background-image: url('${music.cover}');")
        (label :class "cc-cover-fallback" :visible {music.cover == ""} :text "󰝚"))
      (box :orientation "vertical" :hexpand true :space-evenly false :spacing 2 :valign "center"
        (label :class "cc-m-title"  :halign "start" :limit-width 22 :text {music.title})
        (label :class "cc-m-artist" :halign "start" :limit-width 26 :text {music.artist})
        (box :class "cc-m-controls" :halign "start" :space-evenly false :spacing 16
          (button :class "cc-m-btn" :onclick "playerctl previous"   "󰒮")
          (button :class "cc-m-btn play" :onclick "playerctl play-pause" {music.icon})
          (button :class "cc-m-btn" :onclick "playerctl next"       "󰒭"))))))

(defwidget power-item [icon label action]
  (button :class "power-item" :onclick "scripts/power.sh ${action}"
    (box :space-evenly false :spacing 12
      (label :class "power-item-icon" :text icon)
      (label :halign "start" :hexpand true :text label))))

;; generic slide-in wrapper for panels + spotlight (top-anchored → slide down).
;; Pure OPACITY fade (not a revealer): the content is always realized, so it
;; renders ONCE (during the realize wait, while still at opacity 0) and then only
;; the opacity animates via a CSS transition — perfectly even, no size change, no
;; per-frame re-layout. `open` just toggles the `.shown` class.
(defwidget reveal-box [open]
  (box :class {open ? "panel-fade shown" : "panel-fade"}
    (children)))

(defwidget control-center []
  (box :class {colorscheme == "light" ? "panel cc light" : "panel cc"} :orientation "vertical" :space-evenly false :spacing 14
    ;; header: title (left) · settings gear · power
    (box :class "cc-header" :space-evenly false :spacing 6
      (label :class "panel-title" :halign "start" :hexpand true :text "Control Center")
      (button :class "cc-iconbtn" :onclick "scripts/settings.sh && scripts/panel.sh close"
        (label :text "󰒓"))
      (button :class {power-open ? "cc-iconbtn active" : "cc-iconbtn"}
              :onclick "eww update power-open=${!power-open}"
        (label :text "󰐥")))
    ;; power dropdown (replaces the old block of action buttons)
    (revealer :transition "slidedown" :duration "200ms" :reveal power-open
      (box :class "power-menu" :orientation "vertical" :space-evenly false :spacing 2
        (power-item :icon "󰌾"  :label "Lock"     :action "lock")
        (power-item :icon "󰍃" :label "Logout"   :action "logout")
        (power-item :icon "󰒲"  :label "Sleep"    :action "sleep")
        (power-item :icon "󰜉" :label "Reboot"   :action "reboot")
        (power-item :icon "󰐥"  :label "Shutdown" :action "shutdown")))
    (box :spacing 12 :homogeneous true
      (toggle-tile :icon {network.icon}   :label {network.label}
                   :active {network.label != "Offline"}
                   :onclick "scripts/network.sh toggle"   :onrc "")
      (toggle-tile :icon {bluetooth.icon} :label {bluetooth.label}
                   :active {bluetooth.on}
                   :onclick "scripts/bluetooth.sh toggle" :onrc ""))
    (box :spacing 12 :homogeneous true
      (toggle-tile :icon "󰖔" :label "Night Light" :active {nightlight}
                   :onclick "scripts/night.sh toggle" :onrc "")
      (toggle-tile :icon "󰂛" :label "Do not Disturb" :active {dnd}
                   :onclick "scripts/notifications.sh dnd-toggle" :onrc ""))
    (cc-music)
    (vol-slider)
    (notif-list)))

;; ---------------------------------------------------------------------------
;;  NOTIFICATIONS  (history list in the Control Center + top-right toast)
;; ---------------------------------------------------------------------------

(defwidget notif-card [n]
  (box :class "notif-card" :space-evenly false :spacing 8
    (box :orientation "vertical" :hexpand true :space-evenly false :spacing 2
      (box :space-evenly false
        (label :class "notif-app"  :halign "start" :hexpand true :limit-width 24 :text {n.app})
        (label :class "notif-time" :halign "end" :text {n.time}))
      (label :class "notif-summary" :halign "start" :wrap true :limit-width 36 :text {n.summary})
      (label :class "notif-body"    :halign "start" :wrap true :limit-width 40 :show-truncated false
             :visible {n.body != ""} :text {n.body}))
    (button :class "notif-x" :valign "start" :onclick "scripts/notifications.sh dismiss ${n.id}" "󰅖")))

(defwidget notif-list []
  (box :class "notif-list" :orientation "vertical" :space-evenly false :spacing 8
    (box :class "notif-head" :space-evenly false
      (label :class "panel-title" :halign "start" :hexpand true :text "Notifications")
      (button :class "notif-clear" :visible {arraylength(notifications) > 0}
              :onclick "scripts/notifications.sh clear" "Clear all"))
    (box :visible {arraylength(notifications) == 0}
      (label :class "notif-empty" :halign "start" :text "Nothing new"))
    (scroll :vscroll true :height 220 :visible {arraylength(notifications) > 0}
      (box :orientation "vertical" :space-evenly false :spacing 8
        (for n in notifications
          (notif-card :n n))))))

(defwidget notif-popup []
  (box :class {colorscheme == "light" ? "panel notif-popup light" : "panel notif-popup"} :space-evenly false :spacing 8
    (box :orientation "vertical" :hexpand true :space-evenly false :spacing 2
      (box :space-evenly false
        (label :class "notif-app"  :halign "start" :hexpand true :text {popup.app})
        (label :class "notif-time" :halign "end" :text {popup.time}))
      (label :class "notif-summary" :halign "start" :wrap true :limit-width 36 :text {popup.summary})
      (label :class "notif-body"    :halign "start" :wrap true :limit-width 40
             :visible {popup.body != ""} :text {popup.body}))
    (button :class "notif-x" :valign "start" :onclick "scripts/notifications.sh popup-close" "󰅖")))

;; ---------------------------------------------------------------------------
;;  CALENDAR  (slides in top-center, opened by the clock)
;; ---------------------------------------------------------------------------

(defwidget cal []
  (box :class {colorscheme == "light" ? "panel cal light" : "panel cal"} :orientation "vertical" :space-evenly false :spacing 6
    (calendar :class "cal-widget")))

;; ---------------------------------------------------------------------------
;;  MUSIC PANEL
;; ---------------------------------------------------------------------------

(defwidget music-panel []
  (box :class {colorscheme == "light" ? "panel music-panel light" : "panel music-panel"} :orientation "vertical" :space-evenly false :spacing 12
    (box :class "cover" :height 180
         :style "background-image: url('${music.cover}');")
    (label :class "m-title"  :limit-width 30 :text {music.title})
    (label :class "m-artist" :limit-width 34 :text {music.artist})
    (box :class "m-controls" :halign "center" :space-evenly false :spacing 24
      (button :class "m-btn"      :onclick "playerctl previous"   "󰒮")
      (button :class "m-btn play" :onclick "playerctl play-pause" {music.icon})
      (button :class "m-btn"      :onclick "playerctl next"       "󰒭"))))

;; ---------------------------------------------------------------------------
;;  SYSTEM MONITOR PANEL
;; ---------------------------------------------------------------------------

(defwidget metric [icon label value]
  (box :class "metric" :space-evenly false :spacing 12
    (label :class "metric-icon" :text icon)
    (box :orientation "vertical" :hexpand true :space-evenly false :spacing 4
      (box :space-evenly false
        (label :halign "start" :hexpand true :text label)
        (label :halign "end" :text "${value}%"))
      (scale :class "metric-bar" :min 0 :max 100 :value value :active false))))

(defwidget sysmon-panel []
  (box :class {colorscheme == "light" ? "panel sysmon light" : "panel sysmon"} :orientation "vertical" :space-evenly false :spacing 16
    (label :class "panel-title" :halign "start" :text "System")
    (metric :icon "󰓅"  :label "CPU"  :value {sys.cpu})
    (metric :icon "󰍛"  :label "RAM"  :value {sys.mem})
    (metric :icon "󰋊" :label "Disk" :value {sys.disk})
    (box :class "temp-row" :space-evenly false :spacing 12
      (label :class "metric-icon" :text "󰔏")
      (label :halign "start" :hexpand true :text "Temp")
      (label :halign "end" :text "${sys.temp}°C"))))

;; ---------------------------------------------------------------------------
;;  UPDATES PANEL  (count + dropdown list, ~10 visible then scroll)
;; ---------------------------------------------------------------------------

(defwidget updates-panel []
  (box :class {colorscheme == "light" ? "panel updates-panel light" : "panel updates-panel"}
       :orientation "vertical" :space-evenly false :spacing 10
    (box :class "upd-head" :space-evenly false :spacing 8
      (label :class "panel-title" :halign "start" :text "Updates available")
      (label :class "upd-count" :text {arraylength(updates-list)})
      (box :hexpand true)
      (button :class {updates-open ? "audio-caret open" : "audio-caret"}
              :onclick "eww update updates-open=${!updates-open}"
        (label :text {updates-open ? "▴" : "▾"})))
    (revealer :transition "slidedown" :duration "200ms" :reveal updates-open
      (scroll :vscroll true :height {arraylength(updates-list) > 9 ? 260 : arraylength(updates-list) * 28}
        (box :class "upd-list" :orientation "vertical" :space-evenly false :spacing 3
          (for u in updates-list
            (box :class "upd-row" :space-evenly false :spacing 8
              (label :class "upd-src aur" :visible {u.src == "aur"} :text "AUR")
              (label :class "upd-name" :halign "start" :hexpand true :limit-width 24 :text {u.name})
              (label :class "upd-old"   :text {u.old})
              (label :class "upd-arrow" :text "→")
              (label :class "upd-new"   :text {u.new}))))))
    (revealer :transition "slidedown" :duration "150ms" :reveal {!upd-running}
      (button :class "action upd-all" :onclick "scripts/doupdate.sh &"
        (label :text "Update all")))
    (revealer :transition "slidedown" :duration "150ms" :reveal upd-running
      (box :class "upd-prog" :space-evenly false :spacing 10 :valign "center"
        (label :class "upd-prog-spin" :text "󰦖")
        (label :class "upd-prog-text" :halign "start" :hexpand true
               :limit-width 40 :show-truncated false :text upd-progress)))))

;; ---------------------------------------------------------------------------
;;  DOCK  (auto-hide, left edge, vertical — real app icons)
;; ---------------------------------------------------------------------------

(defwidget dock []
  ;; Auto-hide (niri has no cursor IPC): the dock + every icon write "show" on
  ;; hover (keepalive). "Leaving" is NOT detected by the dock's own onhoverlost
  ;; (GTK fires that spuriously when the pointer crosses onto a child icon, so it
  ;; would hide while you're still on the dock). Instead a separate `dock-catcher`
  ;; surface BELOW the dock fires "leave" the moment the pointer is anywhere off
  ;; the dock — a reliable signal. So while hovering the dock there is only ever
  ;; "show"; the dock stays put until you actually move off it.
  (eventbox :class "dock-evt"
            :onhover "scripts/dock.sh keepalive"
            :onrightclick "scripts/dock.sh bg-menu"
  (revealer :transition "slideright" :duration "200ms" :reveal dock-open
  (box :class {colorscheme == "light" ? "dock light" : "dock"} :orientation "vertical" :space-evenly false :spacing 10 :valign "center"
    (for a in dock-apps
      (button :class {a.pinned ? "dock-item" : "dock-item unpinned"}
              :onhover "scripts/dock.sh keepalive"
              :onclick "scripts/dock.sh launch ${a.class} ${a.exec}"
              :onrightclick "scripts/dock.sh menu-open '${a.class}' '${a.exec}' ${a.pinned} ${a.running} ${a.idx}"
        (box :space-evenly false :spacing 4 :halign "center"
          (box :class {a.running ? "dock-dot on" : "dock-dot"} :valign "center")
          ;; CSS background-image renders the SVG scale-aware (sharp at 1.25)
          (box :class "dock-icon" :style "background-image: url('${a.icon}'); min-width: ${cfg-icon-size}px; min-height: ${cfg-icon-size}px;")
          ;; mirror of the dot's width on the right so the icon stays centred
          (box :class "dock-dot-spacer"))))))))

;; thin always-present hover strip at the left edge — replaces the cursor poll.
;; Hovering it shows the dock; leaving is detected by the dock-catcher (below),
;; so the trigger only needs to fire "show".
(defwidget dock-trigger []
  (eventbox :onhover "scripts/dock.sh show"
    (box :class "dock-trigger")))

;; full-screen-ish catcher BELOW the dock (opened by the watcher while the dock is
;; visible). The pointer entering it = the pointer is off the dock → "leave"
;; (debounced hide). A click anywhere off the dock dismisses immediately. It
;; starts at x=6 so it never covers the left-edge trigger.
(defwidget dock-catcher []
  (eventbox :hexpand true :vexpand true
            :onhover "scripts/dock.sh hide"
            :onclick "scripts/dock.sh hide-now"
            :onrightclick "scripts/dock.sh hide-now"
    (box :class "dock-catcher" :hexpand true :vexpand true)))

;; right-click on the EMPTY dock area (not an icon) -> jump to settings
(defwidget dock-bg-menu []
  (box :class {colorscheme == "light" ? "dock-menu light" : "dock-menu"} :orientation "vertical" :space-evenly false :spacing 2
    (label :class "dock-menu-title" :halign "start" :text "Dock & Bar")
    (button :class "dock-menu-item"
            :onclick "scripts/settings.sh --page panel-dock ; eww close dock-bg-menu dock-menu-backdrop"
      (label :halign "start" :text "  Panel & Dock Settings…"))
    (button :class "dock-menu-item"
            :onclick "scripts/panelcfg.sh toggle-autohide ; eww close dock-bg-menu dock-menu-backdrop"
      (label :halign "start" :text "  Toggle auto-hide"))
    (button :class "dock-menu-item"
            :onclick "eww close dock-bg-menu dock-menu-backdrop ; $HOME/projects/eww/launch.sh"
      (label :halign "start" :text "  Restart bar"))))

(defwidget dock-menu []
  (box :class {colorscheme == "light" ? "dock-menu light" : "dock-menu"} :orientation "vertical" :space-evenly false :spacing 2
      (label :class "dock-menu-title" :halign "start" :limit-width 18 :text {dock-sel-class})
      ;; window picker — only when more than one instance is open
      (box :visible {arraylength(dock-sel-windows) > 1} :orientation "vertical" :space-evenly false :spacing 2
        (for w in dock-sel-windows
          (button :class "dock-menu-item dock-menu-win" :onclick "scripts/dock.sh focus-win ${w.address}"
            (box :space-evenly false :spacing 8
              (label :halign "start" :hexpand true :limit-width 20 :text {w.title})
              (label :class "dock-menu-ws" :text "  ${w.ws}"))))
        (box :class "dock-menu-sep"))
      (button :class "dock-menu-item" :onclick "scripts/dock.sh new ${dock-sel-exec}"
        (label :halign "start" :text "  New Window"))
      (button :class "dock-menu-item" :visible {!dock-sel-pinned}
              :onclick "scripts/dock.sh pin ${dock-sel-class}"
        (label :halign "start" :text "  Pin to Dock"))
      (button :class "dock-menu-item" :visible {dock-sel-pinned}
              :onclick "scripts/dock.sh unpin ${dock-sel-class}"
        (label :halign "start" :text "  Unpin"))
      (button :class "dock-menu-item" :visible {dock-sel-pinned}
              :onclick "scripts/dock.sh move-up ${dock-sel-class}"
        (label :halign "start" :text "  Move up"))
      (button :class "dock-menu-item" :visible {dock-sel-pinned}
              :onclick "scripts/dock.sh move-down ${dock-sel-class}"
        (label :halign "start" :text "  Move down"))
      (button :class "dock-menu-item danger" :visible {dock-sel-running}
              :onclick "scripts/dock.sh close ${dock-sel-class}"
        (label :halign "start" :text "  Close"))))

;; ---------------------------------------------------------------------------
;;  SPOTLIGHT LAUNCHER
;; ---------------------------------------------------------------------------

(defwidget spot-row [r]
  (button :class "spot-row" :onclick "scripts/spotlight.sh activate ${r.idx}"
    (box :space-evenly false :spacing 12 :valign "center"
      (box :class "spot-icon" :valign "center"
        (box :visible {r.img != ""} :class "spot-img"
             :style "background-image: url('${r.img}');")
        (label :visible {r.img == ""} :class "spot-glyph" :text {r.icon}))
      (box :orientation "vertical" :space-evenly false :valign "center" :hexpand true
        (label :class "spot-title" :halign "start" :limit-width 64 :text {r.title})
        (label :class "spot-sub"   :halign "start" :limit-width 76 :show-truncated false :text {r.sub})))))

(defwidget spot-pill [mode icon label ?key]
  (button :class {spot-mode == mode ? "spot-pill active" : "spot-pill"}
          :onclick "eww update spot-mode='${mode}' spot-q='' && scripts/spotlight.sh qmode ${mode} ''"
    (box :space-evenly false :spacing 6 :valign "center"
      (label :class "spot-pill-icon" :text icon)
      (label :class "spot-pill-label" :text label)
      (label :class "spot-pill-key" :visible {(key ?: "") != ""} :text {key ?: ""}))))

(defwidget spotlight []
  (box :class {colorscheme == "light" ? "spot light" : "spot"}
       :orientation "vertical" :space-evenly false
    ;; The search box is OUTSIDE the slide revealer so the input is realized the
    ;; moment the window maps — GTK then auto-focuses it (it's the first focusable
    ;; widget) and, since the surface has exclusive keyboard focus, you can type
    ;; immediately without clicking. (Wrapping it in a collapsed revealer left the
    ;; input unrealized → keys went nowhere until you clicked it.)
    (box :class "spot-search" :space-evenly false :spacing 12 :valign "center"
      (label :class "spot-search-icon" :text "")
      (input :class "spot-input" :hexpand true :value spot-q
             :onchange "scripts/spotlight.sh type '{}'"
             :onaccept "scripts/spotlight.sh activate 0"))
    ;; pills + results slide in (the animated part)
    (revealer :transition "slidedown" :duration "200ms" :reveal spot-open
      (box :orientation "vertical" :space-evenly false
        (box :class "spot-pills" :space-evenly false :spacing 6 :halign "start"
          (spot-pill :mode "apps"  :icon "󰀻" :label "Apps")
          (spot-pill :mode "clip"  :icon "󰅍" :label "Pastebin" :key ";")
          (spot-pill :mode "emoji" :icon "󰞅" :label "Emoji" :key ":")
          (spot-pill :mode "calc"  :icon "󰪚" :label "Rechner" :key "=")
          (spot-pill :mode "web"   :icon "󰖟" :label "Web" :key "?")
          (spot-pill :mode "files" :icon "󰈔" :label "Dateien" :key "/"))
        (revealer :transition "slidedown" :duration "120ms" :reveal {arraylength(spot-results) > 0}
          (box :class "spot-list" :orientation "vertical" :space-evenly false :spacing 2
            (for r in spot-results
              (spot-row :r r))))))))

;; ---------------------------------------------------------------------------
;;  WINDOWS
;; ---------------------------------------------------------------------------

;; NOTE: no outer reveal-box — the spotlight widget keeps its input always
;; realized (so it gets keyboard focus on open) and animates its body internally.
(defwindow spotlight
  :monitor 0
  :geometry (geometry :y "-10%" :width "640px" :anchor "center")
  :stacking "overlay"
  :focusable "exclusive"
  (spotlight))

;; click-outside catcher for Spotlight — opened BEFORE spotlight so it stacks
;; below. A click anywhere outside the launcher dismisses it. This is the only
;; reliable dismiss in niri (no global Escape bind) and self-heals a stuck
;; spotlight surface — which, being :focusable, would otherwise hold the keyboard.
(defwindow spot-backdrop
  :monitor 0
  :geometry (geometry :width "100%" :height "100%" :anchor "center")
  :stacking "overlay"
  :focusable false
  ;; Animated dismiss: spotlight.sh close drops the backdrop instantly (lockout-
  ;; safe) then slides the body out + unmaps from a detached process.
  (eventbox :hexpand true :vexpand true
            :onclick "scripts/spotlight.sh close"
            :onrightclick "scripts/spotlight.sh close"
    (box :class "spot-backdrop" :hexpand true :vexpand true)))

(defwindow bar
  :geometry (geometry :x "0%" :y "0%" :width "100%" :height "26px" :anchor "top center")
  :stacking "fg"
  :exclusive true
  :focusable false
  (bar))

(defwindow control-center
  :monitor 0
  :geometry (geometry :x "0px" :y "2px" :width "380px" :anchor "top right")
  :stacking "overlay"
  :focusable false
  (reveal-box :open panel-open (control-center)))

(defwindow calendar
  :monitor 0
  :geometry (geometry :x "0px" :y "2px" :width "320px" :anchor "top center")
  :stacking "overlay"
  :focusable false
  (reveal-box :open panel-open (cal)))

(defwindow music
  :monitor 0
  :geometry (geometry :x "0px" :y "2px" :width "320px" :anchor "top right")
  :stacking "overlay"
  :focusable false
  (reveal-box :open panel-open (music-panel)))

(defwindow sysmon
  :monitor 0
  :geometry (geometry :x "0px" :y "2px" :width "340px" :anchor "top right")
  :stacking "overlay"
  :focusable false
  (reveal-box :open panel-open (sysmon-panel)))

(defwindow updates
  :monitor 0
  :geometry (geometry :x "0px" :y "2px" :width "440px" :anchor "top right")
  :stacking "overlay"
  :focusable false
  (reveal-box :open panel-open (updates-panel)))

;; click-outside catcher for the panels — covers everything BELOW the bar (so
;; bar symbols stay clickable to switch panels). A click closes the open panel.
(defwindow panel-backdrop
  :monitor 0
  :geometry (geometry :x "0px" :y "36px" :width "100%" :height "100%" :anchor "top left")
  :stacking "overlay"
  :focusable false
  ;; Animated dismiss: panel.sh close drops the backdrop instantly (lockout-safe)
  ;; then fades the panel out + unmaps from a detached process. (An instant close
  ;; here skipped the fade-out entirely.)
  (eventbox :hexpand true :vexpand true
            :onclick "scripts/panel.sh close"
            :onrightclick "scripts/panel.sh close"
    (box :class "panel-backdrop" :hexpand true :vexpand true)))

(defwindow notification-popup
  :monitor 0
  :geometry (geometry :x "0px" :y "8px" :width "360px" :anchor "top right")
  :stacking "overlay"
  :focusable false
  (notif-popup))

;; the dock — content-sized; opened on hover of the trigger strip, hidden by the
;; dock's own hover/hoverlost (catcher-free; niri has no cursor IPC).
;; Opened only while visible. On open the revealer is collapsed (invisible), then
;; dock-open flips true and it slides in; on hide it slides out, then the window
;; closes. (niri doesn't animate layer map/unmap, so the revealer does it.)
;; NO fixed width — width is the axis `slideright` animates, so it must be free to
;; grow with the revealer (a fixed width makes the layer surface full-size at once
;; and the slide looks instant). Height is content-sized too. Mirrors how the
;; panels leave their animated axis (height, for slidedown) unfixed.
(defwindow dock
  :monitor 0
  :geometry (geometry :x "6px" :y "0px" :anchor "left center")
  :stacking "overlay"
  :focusable false
  (dock))

;; leave-catcher: full-screen, opened by the watcher while the dock is visible.
;; It lives on the `fg` (top) layer-shell layer while the dock + trigger are on
;; `overlay` (which is ABOVE fg). So the dock/trigger are ALWAYS above the catcher
;; regardless of map order — hovering the dock never hits the catcher. The catcher
;; is still above normal app windows, so moving the pointer anywhere off the dock
;; enters it → reliable "leave" (debounced hide), in every direction.
(defwindow dock-catcher
  :monitor 0
  :geometry (geometry :x "0px" :y "0px" :width "100%" :height "100%" :anchor "center")
  :stacking "fg"
  :focusable false
  (dock-catcher))

;; always-on hover strip (6px) at the left edge that opens the dock.
(defwindow dock-trigger
  :monitor 0
  :geometry (geometry :x "0px" :y "0px" :width "6px" :height "60%" :anchor "left center")
  :stacking "overlay"
  :focusable false
  (dock-trigger))

;; (no dock-backdrop window — auto-hide is catcher-free; see the dock widget.)

;; full-screen transparent catcher behind the menu — a click anywhere outside
;; the menu dismisses it. Opened BEFORE the menu so the menu stacks on top.
;; starts to the RIGHT of the dock (~104px) so the dock icons stay
;; right-clickable — right-clicking another icon switches the menu. A click
;; anywhere on the backdrop (left or right button) dismisses it.
(defwindow dock-menu-backdrop
  :monitor 0
  :geometry (geometry :x "104px" :y "0px" :width "100%" :height "100%" :anchor "top left")
  :stacking "overlay"
  :focusable false
  ;; onhover keeps the dock alive while the menu is up; clicking dismisses both
  ;; the menu and (debounced) the dock.
  (eventbox :hexpand true :vexpand true :onhover "scripts/dock.sh show"
            :onclick "eww close dock-menu dock-bg-menu dock-menu-backdrop && scripts/dock.sh hide"
            :onrightclick "eww close dock-menu dock-bg-menu dock-menu-backdrop && scripts/dock.sh hide"
    (box :class "dock-menu-backdrop" :hexpand true :vexpand true)))

;; right-click context menu — right of the dock, vertically centred. NO fixed
;; height: with a fixed height eww centres that tiny box and the real content
;; spills downward (looked vertically shifted). Content-sized + center-left
;; anchors the whole menu's middle to the screen middle. (niri exposes no cursor
;; Y, so we can't track the clicked icon's height; centred is the clean default.)
(defwindow dock-menu
  :monitor 0
  :geometry (geometry :x "104px" :y "0px" :anchor "center left")
  :stacking "overlay"
  :focusable false
  (dock-menu))

;; vertically centred next to the dock (no fixed y/height — a fixed height made
;; eww centre the tiny box and the content spilled, and y=150 sat too high).
(defwindow dock-bg-menu
  :monitor 0
  :geometry (geometry :x "104px" :y "0px" :width "210px" :anchor "center left")
  :stacking "overlay"
  :focusable false
  (dock-bg-menu))
