diff --git a/musicdb-server/src/web.rs b/musicdb-server/src/web.rs
index 3604cbb..3fde47b 100755
--- a/musicdb-server/src/web.rs
+++ b/musicdb-server/src/web.rs
@@ -4,7 +4,7 @@ use std::sync::{mpsc, Arc, Mutex};
 use musicdb_lib::data::album::Album;
 use musicdb_lib::data::artist::Artist;
 use musicdb_lib::data::database::Database;
-use musicdb_lib::data::queue::{QueueContent, QueueFolder};
+use musicdb_lib::data::queue::{Queue, QueueContent, QueueFolder};
 use musicdb_lib::data::song::Song;
 use musicdb_lib::data::SongId;
 use musicdb_lib::server::Command;
@@ -34,7 +34,7 @@ use rocket::{get, routes, Config, State};
 */
 
 const HTML_START: &'static str =
-    "
";
+    "";
 const HTML_SEP: &'static str = "";
 const HTML_END: &'static str = "";
 
@@ -44,8 +44,10 @@ struct Data {
 }
 
 #[get("/")]
-async fn index(data: &State) -> RawHtml {
+fn index(data: &State) -> RawHtml {
+    dbg!(());
     let script = r#""#;
-    let buttons = "";
+    let script2 = r#""#;
+    let buttons = "";
     let search = "
 ";
     let db = data.db.lock().unwrap();
-    let now_playing =
-        if let Some(current_song) = db.queue.get_current_song().and_then(|id| db.get_song(id)) {
-            format!(
-                "Now Playing
{}
",
-                html_escape::encode_safe(¤t_song.title),
-            )
-        } else {
-            format!("Now Playing
nothing
",)
-        };
+    let now_playing = gen_now_playing(&db);
+    let mut queue = String::new();
+    gen_queue_html(&db.queue, &mut queue, &db);
+    dbg!(&queue);
     drop(db);
     RawHtml(format!(
-        "{HTML_START}MusicDb{script}{HTML_SEP}{now_playing}{buttons}
{search}
{HTML_END}",
+        "{HTML_START}MusicDb{script}{HTML_SEP}no javascript? reload to see updated information.
{now_playing}
{buttons}
{search}
{queue}
{script2}{HTML_END}",
     ))
 }
+#[get("/now-playing-html")]
+fn now_playing_html(data: &State) -> RawHtml {
+    RawHtml(gen_now_playing(&*data.db.lock().unwrap()))
+}
+#[get("/queue-html")]
+fn queue_html(data: &State) -> RawHtml {
+    let mut str = String::new();
+    let db = data.db.lock().unwrap();
+    gen_queue_html(&db.queue, &mut str, &db);
+    RawHtml(str)
+}
+fn gen_now_playing(db: &Database) -> String {
+    if let Some(current_song) = db.queue.get_current_song().and_then(|id| db.get_song(id)) {
+        format!(
+            "Now Playing
{}
",
+            html_escape::encode_safe(¤t_song.title),
+        )
+    } else {
+        format!("Now Playing
nothing
",)
+    }
+}
+fn gen_queue_html(queue: &Queue, str: &mut String, db: &Database) {
+    gen_queue_html_impl(queue, str, db, true, &mut "".to_owned());
+}
+fn gen_queue_html_impl(
+    queue: &Queue,
+    str: &mut String,
+    db: &Database,
+    active_highlight: bool,
+    path: &mut String,
+) {
+    match queue.content() {
+        QueueContent::Song(id) => {
+            if let Some(song) = db.songs().get(id) {
+                str.push_str("");
+                if active_highlight {
+                    str.push_str("");
+                }
+                str.push_str(&format!("");
+                if active_highlight {
+                    str.push_str("");
+                }
+                str.push_str("");
+                if let Some(artist) = db.artists().get(&song.artist) {
+                    str.push_str(" by ");
+                    str.push_str(&html_escape::encode_text(&artist.name));
+                }
+                if let Some(album) = song.album.as_ref().and_then(|id| db.albums().get(id)) {
+                    str.push_str(" on ");
+                    str.push_str(&html_escape::encode_text(&album.name));
+                }
+                str.push_str(&format!(
+                    ""
+                ));
+                str.push_str("
");
+            } else {
+                str.push_str("unknown song
");
+            }
+        }
+        QueueContent::Folder(f) => {
+            let html_shuf: &'static str = " shuffled";
+            if f.content.is_empty() {
+                str.push_str("[0/0] ");
+                if active_highlight {
+                    str.push_str("");
+                }
+                str.push_str(&html_escape::encode_text(&f.name));
+                if active_highlight {
+                    str.push_str("");
+                }
+                if f.order.is_some() {
+                    str.push_str(html_shuf);
+                }
+            } else {
+                str.push_str(&format!("[{}/{}] ", f.index + 1, f.content.len(),));
+                if active_highlight {
+                    str.push_str("");
+                }
+                str.push_str(&html_escape::encode_text(&f.name));
+                if active_highlight {
+                    str.push_str("");
+                }
+                if f.order.is_some() {
+                    str.push_str(html_shuf);
+                }
+                str.push_str("");
+                for (i, v) in f.iter().enumerate() {
+                    str.push_str("- ");
+                    if !path.is_empty() {
+                        path.push('_');
+                    }
+                    path.push_str(&format!("{i}"));
+                    gen_queue_html_impl(v, str, db, active_highlight && i == f.index, path);
+                    while !(path.is_empty() || path.ends_with('_')) {
+                        path.pop();
+                    }
+                    path.pop();
+                    str.push_str("
 ");
+                }
+                str.push_str("
");
+            }
+        }
+        QueueContent::Loop(d, t, i) => {
+            if active_highlight {
+                str.push_str("");
+            }
+            if *t == 0 {
+                str.push_str(&format!("[{}/∞]", d + 1));
+            } else {
+                str.push_str(&format!("[{}/{}]", d + 1, t));
+            }
+            if active_highlight {
+                str.push_str("");
+            }
+            if !path.is_empty() {
+                path.push('_');
+            }
+            path.push('0');
+            gen_queue_html_impl(i, str, db, active_highlight, path);
+            while !(path.is_empty() || path.ends_with('_')) {
+                path.pop();
+            }
+            path.pop();
+        }
+    }
+}
+
+#[get("/queue-remove/")]
+fn queue_remove(data: &State, path: &str) {
+    if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() {
+        data.command_sender
+            .send(Command::QueueRemove(path))
+            .unwrap();
+    }
+}
+#[get("/queue-goto/")]
+fn queue_goto(data: &State, path: &str) {
+    if let Some(path) = path.split('_').map(|v| v.parse().ok()).collect() {
+        data.command_sender.send(Command::QueueGoto(path)).unwrap();
+    }
+}
 
 #[get("/play")]
-async fn play(data: &State) {
+fn play(data: &State) {
     data.command_sender.send(Command::Resume).unwrap();
 }
 #[get("/pause")]
-async fn pause(data: &State) {
+fn pause(data: &State) {
     data.command_sender.send(Command::Pause).unwrap();
 }
 #[get("/skip")]
-async fn skip(data: &State) {
+fn skip(data: &State) {
     data.command_sender.send(Command::NextSong).unwrap();
 }
 #[get("/clear-queue")]
-async fn clear_queue(data: &State) {
+fn clear_queue(data: &State) {
     data.command_sender
         .send(Command::QueueUpdate(
             vec![],
@@ -129,7 +298,7 @@ async fn clear_queue(data: &State) {
 }
 
 #[get("/add-song/")]
-async fn add_song(data: &State, id: SongId) {
+fn add_song(data: &State, id: SongId) {
     data.command_sender
         .send(Command::QueueAdd(
             vec![],
@@ -139,7 +308,7 @@ async fn add_song(data: &State, id: SongId) {
 }
 
 #[get("/search?&&&&&")]
-async fn search(
+fn search(
     data: &State,
     artist: Option<&str>,
     album: Option<&str>,
@@ -371,7 +540,19 @@ pub async fn main(
         .manage(Data { db, command_sender })
         .mount(
             "/",
-            routes![index, play, pause, skip, clear_queue, add_song, search],
+            routes![
+                index,
+                play,
+                pause,
+                skip,
+                clear_queue,
+                queue_goto,
+                queue_remove,
+                add_song,
+                search,
+                now_playing_html,
+                queue_html
+            ],
         )
         .launch()
         .await