diff -NarU2 a/contrib/plugins/graft-thumbs.js b/contrib/plugins/graft-thumbs.js
--- a/contrib/plugins/graft-thumbs.js	2026-07-08 15:13:32.813795232 +0000
+++ b/contrib/plugins/graft-thumbs.js	2026-07-08 15:11:40.605178933 +0000
@@ -83,13 +83,13 @@
 
 	// ...and then the trick! near the end of loadgrid,
-	// thegrid.bagit is called to initialize the baguettebox
+	// msel.bagit is called to initialize the baguettebox
 	// (image/video gallery); this is the perfect function to
 	// "hook" (hijack) so we can run our code :^)
 
 	// need to grab a backup of the original function first,
-	var orig_func = thegrid.bagit;
+	var orig_func = msel.bagit;
 
 	// and then replace it with our own:
-	thegrid.bagit = function (isrc) {
+	msel.bagit = function (isrc) {
 
 		if (isrc !== '#ggrid')
@@ -105,5 +105,5 @@
 		setTimeout(graft_thumbs, 1);
 
-		// and finally, call the original thegrid.bagit function
+		// and finally, call the original msel.bagit function
 		return orig_func(isrc);
 	};
diff -NarU2 a/contrib/plugins/minimal-up2k.html b/contrib/plugins/minimal-up2k.html
--- a/contrib/plugins/minimal-up2k.html	2026-07-08 15:13:32.813795232 +0000
+++ b/contrib/plugins/minimal-up2k.html	2026-05-22 21:41:25.288307384 +0000
@@ -19,5 +19,5 @@
     #ops, #tree, #path, #wfp,  /* main tabs and navigators (tree/breadcrumbs) */
 
-    #u2conf tr:first-child>td[rowspan]:not(#u2btn_cw),  /* most of the config options */
+    #u2conf tr:first-child>td[rowspan],  /* most of the config options */
 
     #srch_dz, #srch_zd,  /* the filesearch dropzone */
@@ -31,5 +31,4 @@
     /* add some margins because now it's weird */
     .opview {margin-top: 2.5em}
-    #op_up2k {margin-top: 6em}
 
     /* and embiggen the upload button */
@@ -37,8 +36,5 @@
 
     /* adjust the button area a bit */
-    #u2conf.w, #u2conf.ww {width: 35em !important; margin: 5em auto}
-
-    /* a */
-    #op_up2k {min-height: 0}
+    #u2conf.w {width: 35em !important; margin: 5em auto}
 
 </style>
diff -NarU2 a/contrib/plugins/minimal-up2k.js b/contrib/plugins/minimal-up2k.js
--- a/contrib/plugins/minimal-up2k.js	2026-07-08 15:13:32.813795232 +0000
+++ b/contrib/plugins/minimal-up2k.js	2026-07-08 15:11:40.605178933 +0000
@@ -40,18 +40,4 @@
 }
 #u2conf {margin:5em auto 0 auto !important}
-#u2conf.ww {width:70em}
-#u2conf.w {width:50em}
-#u2conf.w .c,
-#u2conf.w #u2btn_cw {text-align:left}
-#u2conf.w #u2btn_cw {width:70%}
-#u2etaw {margin:3em auto}
-#u2etaw.w {
-  text-align: center;
-  margin: -3.5em auto 5em auto;
-}
-#u2etaw.w #u2etas {margin-right:-37em}
-#u2etaw.w #u2etas.o {margin-top:-2.2em}
-#u2etaw.ww {margin:-1em auto}
-#u2etaw.ww #u2etas {padding-left:4em}
 #u2etas {
   background: none !important;
diff -NarU2 a/contrib/plugins/rave.js b/contrib/plugins/rave.js
--- a/contrib/plugins/rave.js	2026-07-08 15:13:32.813795232 +0000
+++ b/contrib/plugins/rave.js	2026-05-22 21:41:25.288307384 +0000
@@ -120,5 +120,5 @@
             document.body.style.marginLeft = hrand() * uofs + 'px';
             ebi('tree').style.marginLeft = hrand() * uofs + 'px';
-            for (var a of QSA('#ops>a, #path>a, #pctl>a'))
+            for (var a of QSA('#ops>a, #path>a, .pctl>a'))
                 a.style.transform = 'translate(' + hrand() * uofs * 1 + 'px, ' + hrand() * uofs * 0.7 + 'px) rotate(' + Math.random() * uofs * 0.7 + 'deg)'
         }
diff -NarU2 a/copyparty/authsrv.py b/copyparty/authsrv.py
--- a/copyparty/authsrv.py	2026-07-28 23:16:17.534485289 +0000
+++ b/copyparty/authsrv.py	2026-07-28 23:14:14.881200160 +0000
@@ -3260,4 +3260,10 @@
                 continue  # propagates later in this func
             vf = vn.flags
+            if "theme" in vf:
+                if re.match(r'[0-9]', vf["theme"]):
+                    zs = int(vf["theme"])
+                    bri = "zy"[zs % 2 :][:1]
+                    ch = "abcdefghijklmnopqrstuvwx"[int(zs / 2)]
+                    vf["theme"] = "{0}{1} {0} {1}".format(ch, bri)
             vn.js_ls = {
                 "idx": "e2d" in vf,
@@ -3278,4 +3284,7 @@
                 "sb_md": "" if "no_sb_md" in vf else (vf.get("md_sbf") or "y"),
                 "rw_edit": vf["rw_edit"],
+                "dtheme": vf["theme"],
+                "bg_img": vf["bg_img"],
+                "tcolor": vf["tcolor"],
             }
             if "ufavico_h" in vf:
@@ -3304,4 +3313,7 @@
                 "sba_lg": vf.get("lg_sba") or "",
                 "rw_edit": vf["rw_edit"],
+                "dtheme": vf["theme"],
+                "bg_img": vf["bg_img"],
+                "tcolor": vf["tcolor"],
                 "txt_ext": self.args.textfiles.replace(",", " "),
                 "def_hcols": list(vf.get("mth") or []),
diff -NarU2 a/copyparty/cfg.py b/copyparty/cfg.py
--- a/copyparty/cfg.py	2026-07-28 23:16:17.534485289 +0000
+++ b/copyparty/cfg.py	2026-07-28 23:14:14.881707228 +0000
@@ -104,4 +104,5 @@
     for k in (
         "apnd_who",
+        "bg_img",
         "bup_ck",
         "cachectl",
@@ -114,4 +115,5 @@
         "db_xattr",
         "du_who",
+        "theme",
         "epilogues",
         "ufavico",
@@ -307,6 +309,6 @@
     },
     'database, audio tags\n"mte", "mth", "mtp", "mtm" all work the same as -mte, -mth, ...': {
-        "mte=artist,title": "media-tags to index/display",
-        "mth=fmt,res,ac": "media-tags to hide by default",
+        "mte=artist,title": "media-tags/file-columns to index/display",
+        "mth=fmt,res,ac": "media-tags/file-columns to hide by default",
         "mtp=.bpm=f,audio-bpm.py": 'uses the "audio-bpm.py" program to\ngenerate ".bpm" tags from uploads (f = overwrite tags)',
         "mtp=ahash,vhash=media-hash.py": "collects two tags at once",
@@ -349,4 +351,6 @@
     "client and ux": {
         "grid": "show grid/thumbnails by default",
+        "bg_img": "set a background image",
+        "theme": "set a default theme",
         "gsel": "select files in grid by ctrl-click",
         "sort": "default sort order",
diff -NarU2 a/copyparty/httpcli.py b/copyparty/httpcli.py
--- a/copyparty/httpcli.py	2026-07-28 23:16:17.536485324 +0000
+++ b/copyparty/httpcli.py	2026-07-28 23:14:14.882573726 +0000
@@ -367,4 +367,5 @@
         self.out_headers = {
             "Cache-Control": "no-store, max-age=0",
+            "Service-Worker-Allowed": "/",
         }
 
@@ -1553,4 +1554,7 @@
                 return self.tx_svcs()
 
+            if "config" in self.uparam:
+                return self.tx_config()
+
             if "shares" in self.uparam:
                 return self.tx_shares()
@@ -5537,5 +5541,5 @@
         # so url-param "raster" returns a png/webp instead
         # (useragent-sniffing kinshi due to caching proxies)
-        mime, ico = self.conn.hsrv.ico.get(txt, not small, "raster" in self.uparam)
+        mime, ico = self.conn.hsrv.ico.get(txt, not small, "raster" in self.uparam, self.uparam.get('a'))
 
         lm = formatdate(self.E.t0)
@@ -5684,4 +5688,15 @@
         return True
 
+    def tx_config(self) -> bool:
+        html = self.j2s(
+            "config",
+            this=self,
+            accs=bool(self.asrv.acct),
+            s="s" if self.is_https else "",
+            bg_img=self.args.bg_img,
+        )
+        self.reply(html.encode("utf-8"))
+        return True
+
     def tx_svcs(self) -> bool:
         aname = re.sub("[^0-9a-zA-Z]+", "", self.args.vname) or "a"
@@ -6897,5 +6912,5 @@
                 "lead": "",
                 "href": "%s/" % (x,),
-                "ext": "---",
+                "ext": "",
                 "sz": 0,
                 "ts": 0,
@@ -7107,5 +7122,5 @@
 
                     if is_dir:
-                        return self.tx_svg("folder")
+                        return self.reply(b"(no thumbnail)")
 
                 thp = None
@@ -7127,5 +7142,5 @@
                     raise Pebkac(415)
 
-                return self.tx_ico(rem)
+                return self.reply(b"(no thumbnail)")
 
         elif self.can_write and th_fmt is not None:
@@ -7231,4 +7246,5 @@
 
         zi = vn.flags["du_iwho"]
+        volspace = []
         if zi and (
             zi == 9
@@ -7255,7 +7271,10 @@
                         except:
                             pass
-                h1 = humansize(free or 0)
+                if not free:
+                    free = 0
+                h1 = humansize(free)
                 h2 = humansize(total)
                 srv_info.append("{} free of {}".format(h1, h2))
+                volspace = [h1, h2, int((total - free) / total * 10000)]
             elif zs:
                 self.log("diskfree(%r): %s" % (abspath, zs), 3)
@@ -7308,4 +7327,5 @@
             "taglist": [],
             "srvinf": srv_infot,
+            "volspace": volspace,
             "acct": self.uname,
             "perms": perms,
@@ -7316,4 +7336,5 @@
             "acct": self.uname,
             "perms": perms,
+            "volspace": volspace,
         }
         # also see `js_htm` in authsrv.py
@@ -7331,5 +7352,8 @@
             "title": html_escape("%s %s" % (self.args.bname, self.vpath), crlf=True),
             "srv_info": srv_infot,
+            "srv_name": "" if self.args.nih else self.args.name,
+            "srv_url": self.args.name_url if self.args.name_url else "/",
             "dtheme": self.args.theme,
+            "unfun": self.args.unfun,
         }
 
@@ -7476,10 +7500,10 @@
                 elif add_dk:
                     zs = absreal(fspath)
-                    margin = '<a href="%s?k=%s&zip=crc" rel="nofollow">zip</a>' % (
+                    margin = '<a href="%s?k=%s&zip=crc" rel="nofollow" class="dir">zip</a>' % (
                         quotep(href),
                         self.gen_fk(2, self.args.dk_salt, zs, 0, 0)[:add_dk],
                     )
                 else:
-                    margin = '<a href="%s?zip=crc" rel="nofollow">zip</a>' % (
+                    margin = '<a href="%s?zip=crc" rel="nofollow" class="dir">zip</a>' % (
                         quotep(href),
                     )
@@ -7505,5 +7529,5 @@
 
             if is_dir:
-                ext = "---"
+                ext = ""
             elif "." in fn:
                 ext = ptn_hr.sub("@", fn.rsplit(".", 1)[1])
diff -NarU2 a/copyparty/httpsrv.py b/copyparty/httpsrv.py
--- a/copyparty/httpsrv.py	2026-07-28 23:16:17.536485324 +0000
+++ b/copyparty/httpsrv.py	2026-07-28 23:14:14.882763044 +0000
@@ -190,4 +190,5 @@
             "browser2",
             "cf",
+            "config",
             "idp",
             "md",
diff -NarU2 a/copyparty/ico.py b/copyparty/ico.py
--- a/copyparty/ico.py	2026-07-28 23:16:17.536485324 +0000
+++ b/copyparty/ico.py	2026-07-28 23:14:14.882839870 +0000
@@ -12,22 +12,17 @@
 
 
+RE_CSS_COLOR = re.compile(r"[^a-zA-Z0-9#()%.,/-]")
+
+
 class Ico(object):
     def __init__(self, args: argparse.Namespace) -> None:
         self.args = args
 
-    def get(self, ext: str, as_thumb: bool, chrome: bool) -> tuple[str, bytes]:
+    def get(self, ext: str, as_thumb: bool, png: bool, accent: str) -> tuple[str, bytes]:
         """placeholder to make thumbnails not break"""
 
         bext = ext.encode("ascii", "replace")
         ext = bext.decode("utf-8")
-        zb = hashlib.sha1(bext).digest()[2:4]
-        if PY2:
-            zb = [ord(x) for x in zb]  # type: ignore
-
-        c1 = colorsys.hsv_to_rgb(zb[0] / 256.0, 1, 0.3)
-        c2 = colorsys.hsv_to_rgb(zb[0] / 256.0, 0.8 if HAVE_PILF else 1, 1)
-        ci = [int(x * 255) for x in list(c1) + list(c2)]
-        c = "".join(["%02x" % (x,) for x in ci])
-
+        
         w = 100
         h = 30
@@ -36,5 +31,11 @@
             h = int(100.0 / (float(sw) / float(sh)))
 
-        if chrome:
+        if accent:
+            accent = RE_CSS_COLOR.sub("", accent)
+
+        # obsolete. 2000+ svgs slows chrome down massively, but no crash.
+        # further, unique svgs are only used for non-standard thumbnails,
+        # so it's unrealistic to see that many in use
+        if png & 0:
             # cannot handle more than ~2000 unique SVGs
             if HAVE_PILF:
@@ -49,9 +50,9 @@
                     h = int(128.0 * h / w)
                     w = 128
-                    img = Image.new("RGB", (w, h), "#" + c[:6])
+                    img = Image.new("RGBA", (w, h), "#00000000")
                     pb = ImageDraw.Draw(img)
                     _, _, tw, th = pb.textbbox((0, 0), ext2, font_size=16)
                     xy = (int((w - tw) / 2), int((h - th) / 2))
-                    pb.text(xy, ext2, fill="#" + c[6:], font_size=16)
+                    pb.text(xy, ext2, fill=accent, font_size=16)
 
                     img = img.resize((w * 2, h * 2), Image.NEAREST)
@@ -70,5 +71,5 @@
                 h = int(64.0 * h / w)
                 w = 64
-                img = Image.new("RGB", (w, h), "#" + c[:6])
+                img = Image.new("RGBA", (w, h), "#00000000")
                 pb = ImageDraw.Draw(img)
                 try:
@@ -80,5 +81,5 @@
                 cw = tw // len(ext)
                 x = ((w - tw) // 2) - (cw * 2) // 3
-                fill = "#" + c[6:]
+                fill = accent
                 for ch in ext:
                     pb.text((x, (h - th) // 2), " %s " % (ch,), fill=fill)
@@ -94,7 +95,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <svg version="1.1" viewBox="0 0 100 {}" xmlns="http://www.w3.org/2000/svg"><g>
-<rect width="100%" height="100%" fill="#{}" />
+<rect width="100%" height="100%" fill="#0000" />
 <text x="50%" y="{}" dominant-baseline="middle" text-anchor="middle" xml:space="preserve"
-  fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
+  fill="{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
 </g></svg>
 """
@@ -110,5 +111,5 @@
             y = "50%"
 
-        svg = svg.format(h, c[:6], y, c[6:], txt)
+        svg = svg.format(h, y, accent or '#000', txt)
 
         return "image/svg+xml", svg.encode("utf-8")
diff -NarU2 a/copyparty/__init__.py b/copyparty/__init__.py
--- a/copyparty/__init__.py	2026-07-28 23:16:17.533485272 +0000
+++ b/copyparty/__init__.py	2026-07-28 23:14:14.880319846 +0000
@@ -73,5 +73,9 @@
 web/browser2.html
 web/cf.html
+web/config.css
+web/config.html
+web/config.js
 web/copyparty.gif
+web/copyparty.svg
 web/deps/busy.mp3
 web/deps/easymde.css
@@ -90,4 +94,5 @@
 web/idp.html
 web/iiam.gif
+web/manifest.json
 web/md.css
 web/md.html
@@ -111,4 +116,5 @@
 web/svcs.html
 web/svcs.js
+web/sw.js
 web/tl/chi.js
 web/tl/cze.js
diff -NarU2 a/copyparty/__main__.py b/copyparty/__main__.py
--- a/copyparty/__main__.py	2026-07-28 23:16:17.533485272 +0000
+++ b/copyparty/__main__.py	2026-07-28 23:14:14.880755109 +0000
@@ -1922,6 +1922,6 @@
     ap2.add_argument("--db-xattr", metavar="t,t", type=u, default="", help="read file xattrs as metadata tags; [\033[32ma,b\033[0m] reads keys \033[33ma\033[0m and \033[33mb\033[0m as tags \033[33ma\033[0m and \033[33mb\033[0m, [\033[32ma=foo,b=bar\033[0m] reads keys \033[33ma\033[0m and \033[33mb\033[0m as tags \033[33mfoo\033[0m and \033[33mbar\033[0m, [\033[32m~~a,b\033[0m] does everything except \033[33ma\033[0m and \033[33mb\033[0m, [\033[32m~~\033[0m] does everything. NOTE: Each tag must also be enabled with \033[33m-mte\033[0m (volflag=db_xattr)")
     ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="\033[34mREPEATABLE:\033[0m add/replace metadata mapping")
-    ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE)
-    ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH)
+    ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags/file-columns to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE)
+    ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags/file-columns to hide by default (comma-sep.); assign/add/remove same as \033[33m-mte\033[0m", default=DEF_MTH)
     ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="\033[34mREPEATABLE:\033[0m read tag \033[33mM\033[0m using program \033[33mBIN\033[0m to parse the file")
     ap2.add_argument("--have-db-xattr", action="store_true", help=argparse.SUPPRESS)
@@ -1963,15 +1963,16 @@
 
 def add_ui(ap, retry: int):
-    THEMES = 10
+    THEMES = 12
     ap2 = ap.add_argument_group("ui options")
     ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
-    ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
+    ap2.add_argument("--bg-img", metavar="M,M,M", type=u, default="", help="URL to .jpg/png/gif/svg/webm/mp4/mkv/m4v/mov file to use as background, for example \033[32m/folder/image.jpg,opacity=0.5(optional),blur=5(optional)\033[0m supports relative paths. (volflag=bg_img)")
+    ap2.add_argument("--no-gsel", action="store_true", help="ctrl-click in gridview will open-in-new-tab instead of select-file (volflag=-gsel)")
     ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
-    ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="1", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI)")
+    ap2.add_argument("--ui-filesz", metavar="FMT", type=u, default="6", help="default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI)")
     ap2.add_argument("--gauto", metavar="PERCENT", type=int, default=0, help="switch to gridview if more than \033[33mPERCENT\033[0m of files are pics/vids; 0=disabled")
     ap2.add_argument("--rcm", metavar="TXT", default="yy", help="rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double")
     ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language, for example \033[32meng\033[0m / \033[32mnor\033[0m / ...")
     ap2.add_argument("--glang", action="store_true", help="guess the browser's default language, otherwise fall back to \033[33m--lang\033[0m")
-    ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d)" % (THEMES - 1,))
+    ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..%d) (volflag=theme)" % (THEMES - 1,))
     ap2.add_argument("--themes", metavar="NUM", type=int, default=THEMES, help="number of themes installed")
     ap2.add_argument("--au-vol", metavar="0-100", type=int, default=50, choices=range(0, 101), help="default audio/video volume percent")
@@ -1981,5 +1982,5 @@
     ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions")
     ap2.add_argument("--qdel", metavar="LVL", type=int, default=2, help="number of confirmations to show when deleting files (2/1/0)")
-    ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag:dlni)")
+    ap2.add_argument("--dlni", action="store_true", help="force download (don't show inline) when files are clicked (volflag=dlni)")
     ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
     ap2.add_argument("--dothidden", action="store_true", help="hide specific files in a folder by listing them in a file named .hidden -- WARNING: Mostly cosmetic! Download-as-zip/tar will still download them. Do not rely on this for security (volflag=dothidden)")
@@ -2029,4 +2030,6 @@
     ap2.add_argument("--ui-norepl", action="store_true", help="hide repl-button in the UI (volflag=ui_norepl)")
     ap2.add_argument("--have-unlistc", action="store_true", help=argparse.SUPPRESS)
+    ap2.add_argument("--gsel", action="store_true", default=True, help=argparse.SUPPRESS)
+    ap2.add_argument("--unfun", action="store_true", help="hides many of the emojis in the UI")
 
 
diff -NarU2 a/copyparty/util.py b/copyparty/util.py
--- a/copyparty/util.py	2026-07-28 23:16:17.539485376 +0000
+++ b/copyparty/util.py	2026-07-28 23:14:14.885165997 +0000
@@ -471,5 +471,8 @@
 
 
-UNPLICATIONS = [["no_dav", "daw"]]
+UNPLICATIONS = [
+    ["no_dav", "daw"],
+    ["no_gsel", "gsel"],
+]
 
 
diff -NarU2 a/copyparty/web/baguettebox.js b/copyparty/web/baguettebox.js
--- a/copyparty/web/baguettebox.js	2026-07-08 15:13:32.819482011 +0000
+++ b/copyparty/web/baguettebox.js	2026-07-28 23:09:10.621788298 +0000
@@ -29,5 +29,5 @@
             readDirRtl: false,
         },
-        overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnZoom, btnVmode, btnReadDir, btnGoPage, btnClose,
+        overlay, slider, btnPrev, btnNext, btnHelp, btnAnim, btnRotL, btnRotR, btnSel, btnFull, btnZoom, btnPixelated, btnVmode, btnReadDir, btnGoPage, btnClose,
         currentGallery = [],
         currentIndex = 0,
@@ -38,6 +38,6 @@
         scrollTimer = 0,
         re_i = APPLE ?
-            /^[^?]+\.(a?png|avif|bmp|gif|hei[cf]s?|jfif|jpe?g|jxl|svg|tiff?|webp)(\?|$)/i :
-            /^[^?]+\.(a?png|avif|bmp|gif|jfif|jpe?g|jxl|svg|tiff?|webp)(\?|$)/i,
+            /^[^?]+\.(a?png|avif|bmp|gif|hei[cf]s?|jfif|jpe?g|jxl|svg|ico|tiff?|webp)(\?|$)/i :
+            /^[^?]+\.(a?png|avif|bmp|gif|jfif|jpe?g|jxl|svg|ico|tiff?|webp)(\?|$)/i,
         re_v = /^[^?]+\.(webm|mkv|mp4|m4v|mov)(\?|$)/i,
         re_cbz = /^[^?]+\.(cbz)(\?|$)/i,
@@ -154,4 +154,7 @@
     }
 
+    var userAction = false;
+    function getUserAction(){return userAction}
+    function resetUserAction(){userAction = false}
     function bindImageClickListeners(selector, userOptions) {
         var galleryNodeList = QSA(selector);
@@ -185,4 +188,10 @@
 
                     e.preventDefault ? e.preventDefault() : e.returnValue = false;
+                    var newHash = '#g' + this.getAttribute('id') + getsort();
+                    if(location.hash != newHash){
+                        console.log("opened bbox manually");
+                        userAction = true;
+                        hist_push(newHash);
+                    }
                     prepareOverlay(gallery, userOptions);
                     showOverlay(imageIndex);
@@ -318,20 +327,21 @@
             var ctr = mknod('div');
             ctr.innerHTML = (
-                '<div id="bbox-overlay" role="dialog">' +
+                '<div id="bbox-overlay" class="normalrcm" role="dialog">' +
                 '<div id="bbox-slider"></div>' +
-                '<button id="bbox-prev" class="bbox-btn" type="button" aria-label="Previous">&lt;</button>' +
-                '<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">&gt;</button>' +
+                '<div id="bbox-prev"><a class="btn" aria-label="Previous">◀</a></div>' +
+                '<div id="bbox-next"><a class="btn" aria-label="Next">▶</a></div>' +
                 '<div id="bbox-btns">' +
-                '<button id="bbox-help" type="button">?</button>' +
-                '<button id="bbox-anim" type="button" tt="a">-</button>' +
-                '<button id="bbox-readdir" type="button" tt="a">ltr</button>' +
-                '<button id="bbox-rotl" type="button">↶</button>' +
-                '<button id="bbox-rotr" type="button">↷</button>' +
-                '<button id="bbox-tsel" type="button">sel</button>' +
-                '<button id="bbox-full" type="button" tt="full-screen">⛶</button>' +
-                '<button id="bbzoom" type="button" tt="zoom/stretch">z</button>' +
-                '<button id="bbox-vmode" type="button" tt="a"></button>' +
-                '<button id="bbox-gopage" type="button" tt="go to page">pg</button>' +
-                '<button id="bbox-close" type="button" aria-label="Close">X</button>' +
+                    '<a id="bbox-close" class="btn" aria-label="Close"><span class="x">×</span></a>' +
+                    '<a id="bbox-gopage" type="button" tt="go to page">pg</a>' +
+                    '<a id="bbox-vmode" class="btn" tt="a"></a>' +
+                    '<a id="bbox-full" class="btn" tt="full-screen">⛶</a>' +
+                    '<a id="bbzoom" class="tgl btn" tt="zoom/stretch smaller images to fill screen">↕</a>' +
+                    '<a id="bbpixelated" class="tgl btn" tt="pixelated rendering (good for pixel art)">👾</a>' +
+                    '<a id="bbox-tsel" class="tgl btn">☑️sel</a>' +
+                    '<a id="bbox-rotr" class="btn">↷</a>' +
+                    '<a id="bbox-rotl" class="btn">↶</a>' +
+                    '<a id="bbox-readdir" class="btn" tt="a">ltr</a>' +
+                    '<a id="bbox-anim" class="btn" tt="a">-</a>' +
+                    '<a id="bbox-help" class="btn">?</a>' +
                 '</div></div>'
             );
@@ -351,4 +361,5 @@
         btnFull = ebi('bbox-full');
         btnZoom = ebi('bbzoom');
+        btnPixelated = ebi('bbpixelated');
         btnVmode = ebi('bbox-vmode');
         btnGoPage = ebi('bbox-gopage');
@@ -357,4 +368,6 @@
         bcfg_bind(options, 'bbzoom', 'bbzoom', false, setzoom);
         setzoom();
+        bcfg_bind(options, 'bbpixelated', 'bbpixelated', false, setpixelated);
+        setpixelated();
     }
 
@@ -373,4 +386,5 @@
             ['F', 'toggle fullscreen'],
             ['Z', 'toggle zoom/stretch'],
+            ['X', 'toggle pixelated rendering'],
             ['S', 'toggle file selection'],
             ['G', 'cbz: go to page'],
@@ -458,4 +472,6 @@
         else if (kl == "z")
             btnZoom.click();
+        else if (kl == "x")
+            btnPixelated.click();
         else if (kl == "s")
             tglsel();
@@ -591,4 +607,10 @@
     }
 
+    function setpixelated() {
+        var sel = clgot(btnPixelated, 'on')
+        clmod(ebi('bbox-overlay'), 'pixelated', sel);
+        btnState(btnPixelated, sel);
+    }
+
     function tglsel() {
         var o = findfile()[3];
@@ -614,6 +636,5 @@
                 sel = true;
 
-        ebi('bbox-overlay').style.background = sel ?
-            'rgba(153,34,85,0.7)' : '';
+        clmod(ebi('bbox-overlay'), 'sel', sel);
 
         img.style.borderRadius = sel ? '1em' : '';
@@ -622,8 +643,5 @@
 
     function btnState(btn, sel) {
-        btn.style.color = sel ? '#fff' : '';
-        btn.style.background = sel ? '#d48' : '';
-        btn.style.textShadow = sel ? '1px 1px 0 #b38' : '';
-        btn.style.boxShadow = sel ? '.15em .15em 0 #502' : '';
+        clmod(btn, 'on', sel);
     }
 
@@ -772,8 +790,10 @@
             options.buttons = false;
 
-        btnPrev.style.display = btnNext.style.display = (options.buttons ? '' : 'none');
+        QS('#bbox-next .btn').style.display = QS('#bbox-prev .btn').style.display = (options.buttons ? '' : 'none');
     }
 
     function showOverlay(chosenImageIndex) {
+        clmod(ebi('wrap'), 'waiting', true);
+
         if (options.noScrollbars) {
             var a = document.documentElement.style.overflowY,
@@ -799,4 +819,5 @@
             preloadNext(currentIndex);
             preloadPrev(currentIndex);
+            clmod(ebi('wrap'), 'waiting', false);
         });
 
@@ -827,5 +848,4 @@
         ev(e);
         playvid(false);
-        removeFromCache('#files');
         if (options.noScrollbars) {
             document.documentElement.style.overflowY = scrollCSS[0];
@@ -849,5 +869,11 @@
             options.duringHide();
 
-        sethash('');
+        if(userAction){
+            console.log("closed bbox after manual open");
+            if(location.hash.startsWith('#gf-'))
+                history.back();
+        }
+        else
+            sethash('');
         unbindEvents();
 
@@ -1276,7 +1302,5 @@
 
     function show_buttons(v) {
-        clmod(ebi('bbox-btns'), 'off', v);
-        clmod(btnPrev, 'off', v);
-        clmod(btnNext, 'off', v);
+        clmod(ebi('bbox-overlay'), 'immersive', v);
     }
 
@@ -1340,9 +1364,9 @@
                 fx = x / (rc.right - rc.left);
 
-            if (fx < 0.3)
-                return showLeftImage();
+            // if (fx < 0.3)
+            //     return showLeftImage();
 
-            if (fx > 0.7)
-                return showRightImage();
+            // if (fx > 0.7)
+            //     return showRightImage();
 
             show_buttons('t');
@@ -1406,7 +1430,17 @@
         playpause: playpause,
         hide: hideOverlay,
-        destroy: destroyPlugin
+        destroy: destroyPlugin,
+        getUserAction: getUserAction,
+        resetUserAction: resetUserAction,
     };
 })();
 
+function bboxinit() {
+    if (msel && msel.bbox != true) {
+        timer.rm(bboxinit);
+        msel.bagit();
+    }
+}
+timer.add(bboxinit, true);
+
 J_BBX = 2;
diff -NarU2 a/copyparty/web/browser.css b/copyparty/web/browser.css
--- a/copyparty/web/browser.css	2026-07-08 15:13:32.819482011 +0000
+++ b/copyparty/web/browser.css	2026-07-28 23:09:10.622226326 +0000
@@ -1,4 +1,6 @@
 :root {
 	color-scheme: dark;
+  	--transparent: transparent;
+	--font-mono: monospace;
 
 	--grid-sz: 10em;
@@ -8,40 +10,18 @@
 	--sbh: 0.5em;
 
-	--fg: #ccc;
-	--fg-max: #fff;
-	--fg2-max: #fff;
-	--fg-weak: #bbb;
-
-	--bg-u6: #4c4c4c;
-	--bg-u5: #444;
-	--bg-u4: #383838;
-	--bg-u3: #333;
-	--bg-u2: #2b2b2b;
-	--bg-u1: #282828;
-	--bg: #222;
-	--bgg: var(--bg);
-	--bg-d1: #1c1c1c;
-	--bg-d2: #181818;
-	--bg-d3: #111;
-	--bg-max: #000;
+	--tab-alt: var(--a);
+	--row-alt: color-mix(in oklab, color-mix(in oklab, transparent 85%, var(--fg-max) 10%) 95%, var(--bg-max));
 
-	--tab-alt: #f5a;
-	--row-alt: #282828;
-
-	--scroll: #eb0;
 	--sel-fg: var(--bg-d1);
 	--sel-bg: var(--fg);
 
-	--a: #fc5;
-	--a-b: #c90;
-	--a-hil: #fd9;
-	--a-dark: #e70;
-	--a-gray: #666;
-
 	--btn-fg: var(--a);
+	--btn-bg-a: rgba(128,128,128,0.15);
+	--btn-bg-a: color-mix(in oklab, var(--bg-max) 85%, var(--fg-max) 20%);
 	--btn-bg: rgba(128,128,128,0.15);
+	--btn-bg: color-mix(in oklab, var(--bg-u5), transparent);
 	--btn-h-fg: var(--a-hil);
-	--btn-h-bg: #805;
-	--btn-1-fg: #400;
+	--btn-h-bg: color-mix(in oklab, var(--a-gray) 45%, var(--bg-max));
+	--btn-1-fg: var(--bg);
 	--btn-1-bg: var(--a);
 	--btn-h-bs: var(--btn-bs);
@@ -50,14 +30,15 @@
 	--btn-1-bb: var(--btn-bb);
 	--btn-1h-fg: var(--btn-1-fg);
-	--btn-1h-bg: #fe8;
+	--btn-1h-bg: var(--a-hil);
 	--btn-1h-bs: var(--btn-1-bs);
+	--btn-bb: 1px solid var(--bg-u3);
 	--btn-1h-bb: var(--btn-1-bb);
 	--chk-fg: var(--tab-alt);
 	--txt-sh: var(--bg-d2);
-	--txt-bg: var(--btn-bg);
+	--txt-bg: var(--btn-bg-a);
 
 	--op-aa-fg: var(--a);
 	--op-aa-bg: var(--bg-d2);
-	--op-a-sh: rgba(0,0,0,0.5);
+	--op-a-sh: color-mix(in oklab, var(--bg-max) 50%, transparent 50%);
 
 	--u2-btn-b1: #999;
@@ -66,11 +47,11 @@
 	--u2-tab-bg: linear-gradient(to bottom, var(--bg), var(--bg-u1));
 	--u2-tab-b1: rgba(128,128,128,0.8);
-	--u2-tab-1-fg: #fd7;
-	--u2-tab-1-bg: linear-gradient(to bottom, #353, var(--bg) 80%);
-	--u2-tab-1-b1: #7c5;
-	--u2-tab-1-b2: #583;
-	--u2-tab-1-sh: #280;
+	--u2-tab-1-fg: var(--a-hil);
+	--u2-tab-1-bg: var(--bg);
+	--u2-tab-1-b1: #999;
+	--u2-tab-1-b2: #999;
+	--u2-tab-1-sh: transparent;
 	--u2-b-fg: #fff;
-	--u2-b1-bg: #c38;
+	--u2-b1-bg: #c83;
 	--u2-b2-bg: #d80;
 	--u2-inf-bg: #07a;
@@ -82,6 +63,6 @@
 	--ud-b1: #888;
 
-	--sort-1: #fb0;
-	--sort-2: #d09;
+	--sort-1: var(--a);
+	--sort-2: var(--a-gray);
 
 	--sz-b: #aaa;
@@ -94,32 +75,31 @@
 	--srv-1: #aaa;
 	--srv-2: #a73;
-	--srv-3: #f4c;
-	--srv-3b: rgba(255,68,204,0.6);
+	--srv-3: #fc4;
+	--srv-3b: rgba(255,204,68,0.6);
 
-	--tree-bg: #2b2b2b;
+	--tree-bg: var(--bg-u1);
 
 	--g-play-bg: #750;
-	--g-play-b1: #c90;
-	--g-play-b2: #da4;
-	--g-play-sh: #b83;
+	--g-play-b1: var(--a-dark);
+	--g-play-b2: var(--a);
 
-	--g-sel-fg: #fff;
-	--g-sel-bg: #925;
-	--g-sel-b1: #e39;
-	--g-sel-sh: #b36;
-	--g-fsel-bg: #d39;
-	--g-fsel-b1: #f4a;
-	--g-fsel-ts: #804;
-	--g-dfg: var(--srv-3);
-	--g-fg: var(--a-hil);
+	--g-sel-fg: var(--fg-max);
+	--g-sel-bg: color-mix(in xyz, var(--g-sel-b1) 30%, transparent 70%);
+	--g-sel-b1: var(--a-dark);
+	--g-sel-sh: hsl(from var(--a) h calc(s * 1.05) calc(l * .3));
+	--g-fsel-bg: color-mix(in xyz, var(--g-sel-b1) 70%, transparent 30%);
+	--g-fsel-b1: var(--a);
+	--g-fsel-ts: hsl(from var(--a) h calc(s * 1.05) calc(l * .3));
+	--g-dfg: #bbb;
+	--g-fg: var(--fg);
 	--g-bg: var(--bg-u2);
-	--g-b1: var(--bg-u4);
+	--g-b1: var(--bg-u3);
 	--g-b2: var(--bg-u5);
 	--g-g1: var(--bg-u2);
 	--g-g2: var(--bg-u5);
-	--g-f-bg: var(--bg-u4);
+	--g-f-bg: var(--bg-u3);
 	--g-f-b1: var(--bg-u5);
-	--g-f-fg: var(--a-hil);
-	--g-sh: rgba(0,0,0,0.3);
+	--g-f-fg: var(--fg-max);
+	--g-sh: color-mix(in oklab, var(--bg-max) 30%, transparent 70%);
 
 	--f-sh1: 0.33;
@@ -128,12 +108,14 @@
 	--f-h-b1: rgba(128,128,128,0.7);
 
-	--f-play-bg: #fc5;
-	--f-play-fg: #000;
-	--f-sel-sh: #fc0;
+	--f-play-bg: var(--a-dark);
+	--f-play-fg: var(--fg-max);
+	--f-sel-sh: var(--a);
 	--f-gray: #999;
 
-	--fm-off: #f6c;
-	--mp-sh: var(--bg-d3);
-	--mp-b-bg: rgba(0,0,0,0.2);
+	--fm-off: var(--a-hil);
+	--mp-sh: #1116;
+	--mp-b-bg: color-mix(in oklab, var(--bg-max) 20%, transparent 80%);
+
+	--scrl-hint: var(--bg-u2);
 
 	--err-fg: #fff;
@@ -142,101 +124,214 @@
 	--err-ts: #500;
 }
-html.y {
-	color-scheme: light;
-
-	--fg: #222;
-	--fg-max: #000;
-	--fg-weak: #555;
-
-	--bg-d3: #fff;
-	--bg-d2: #fff;
-	--bg-d1: #fff;
-	--bg: #eaeaea;
-	--bg-u1: #fff;
-	--bg-u2: #f7f7f7;
-	--bg-u3: #eaeaea;
-	--bg-u4: #fff;
-	--bg-u5: #ccc;
-	--bg-u6: #ddd;
-	--bg-max: #fff;
-
-	--tab-alt: #c07;
-	--row-alt: #f2f2f2;
-
-	--scroll: #490;
-
-	--a: #06a;
-	--a-b: #08b;
-	--a-hil: #058;
-	--a-gray: #bbb;
-	--a-dark: #c0f;
-
-	--btn-fg: #555;
-	--btn-h-fg: #222;
-	--btn-h-bg: #caf;
-	--btn-1-fg: #fff;
-	--btn-1-bg: #4a0;
-	--btn-1h-bg: #5c0;
-	--chk-fg: var(--fg);
-	--txt-sh: #aaa;
-	--txt-bg: rgba(255,255,255,0.6);
-
-	--op-a-sh: #fff;
-
-	--u2-txt-bg: var(--bg-max);
-	--u2-tab-1-sh: #0ad;
-	--u2-tab-1-b1: #09c;
-	--u2-tab-1-b2: #05a;
-	--u2-tab-1-fg: var(--fg-max);
-	--u2-tab-1-bg: inherit;
-	--ud-b1: #bbb;
-
-	--sort-1: #059;
-	--sort-2: #f5d;
-
-	--sz-b: #777;
-	--sz-k: #380;
-
-	--srv-1: #555;
-	--srv-2: #c83;
-	--srv-3: #c0a;
 
-	--tree-bg: #fff;
-
-	--g-fg: var(--a);
-	--g-bg: var(--bg-u2);
-	--g-b1: var(--bg-u6);
-	--g-b2: var(--bg-u6);
-	--g-g1: var(--bg-u2);
-	--g-g2: var(--bg-u5);
-	--g-f-bg: var(--bg-u4);
-	--g-f-b1: var(--bg-u5);
-	--g-sh: rgba(0,0,0,0.07);
-
-	--f-sh1: 0.3;
-	--f-sh2: 0.5;
-	--f-sh3: 0.02;
+html.y {
+	--scrl-hint: var(--bg);
+}
+html.b {
+	--op-aa-sh: 0 0 .2em var(--bg-d3) inset;
 
-	--f-sel-sh: #e80;
+	--btn-bg: linear-gradient(-5deg, color-mix(in oklab, var(--btn-bg-a) 40%, transparent), color-mix(in oklab, var(--btn-bg-a)70%, var(--a-gray)));
+	--btn-bs: .1em .2em .1em var(--mp-sh);
 
-	--fm-off: #c4a;
-	--mp-sh: #bbb;
-	--mp-b-bg: transparent;
+	--g-bg: color-mix(var(--bg-u2), transparent);
 
-	text-shadow: none;
+	--a: #139cf1;
+	--b: color-mix(in srgb, var(--bg) 30%, var(--a) 5%);
+	--radius: 15px;
+	--a-dark: hsl(from color-mix(in oklab, var(--a) 80%, var(--fg-max) 20%) h calc(s * 1.5) l);
+	--bg-u3: color-mix(in oklab, var(--bg-max) 70%, var(--b) 30%);
+	--g-fsel-ts: transparent;
+	--scrl-hint: transparent;
+	--bg-u1: color-mix(in oklab, transparent 92%, var(--fg-max));
+	--w2: color-mix(in srgb, transparent 60%, var(--bg));
+	--row-alt: color-mix(in srgb, var(--bg-max) 50%, transparent);
+	--g-sel-bg: var(--a);
+
+
+	--glow-intensity: 0.7;
+	--bottom-glow: radial-gradient(
+		farthest-corner at bottom center,
+		color-mix(in srgb, var(--a-hil) 10%, rgba(255, 255, 255, var(--glow-intensity))),
+		transparent
+	);
+	--btn-bg: linear-gradient(
+		-5deg, 
+		color-mix(in oklab, var(--btn-bg-a) 30%, var(--a) 20%), 
+		color-mix(in oklab, var(--btn-bg-a) 30%, var(--b)));
+	
+  	text-shadow: 0px 0px 4px rgb(255, 255, 255);
 }
-html.a {
-	--op-aa-sh: 0 0 .2em var(--bg-d3) inset;
-
-	--btn-bs: 0 0 .2em var(--bg-d3);
+html.bz {
+	--a: #5fb9b8;
+	--btn-1-bs: .05em .1em .2em var(--a-dark) inset;
+	text-shadow: 0px 0px 4px #000;
+	--fg-weak: var(--fg-max);
+
+	--bottom-glow: radial-gradient(
+		farthest-corner at bottom center,
+		color-mix(in srgb, var(--a-hil) 10%, var(--bg) 20%),
+		transparent
+	);
+	--btn-fg: var(--fg);
+	--btn-h-fg: var(--fg);
+}
+html.b #srchfolder_div,
+html.b #up_quick_more a,
+html.b #spaceTotal_bar,
+html.b #s_list h3,
+html.b #wtico,
+html.b #wtoggle,
+html.b #ggrid>a,
+html.b #ops a,
+html.b #tree ul a.hl,
+html.b .btn {
+	transition: all .15s;
+	background: var(--bottom-glow), var(--btn-bg);
+	border-radius: var(--radius);
+	box-shadow: 0 4px 4px rgba(0, 0, 0, 0.4) !important;
 }
-html.az {
-	--btn-1-bs: 0 0 .1em var(--fg) inset;
+html.b .btn:not(.on){
+	color: var(--fg);
+	background-color: color-mix(in srgb, var(--bg) 70%, transparent);
 }
-html.ay {
-	--op-aa-sh: 0 .1em .2em #ccc;
-	--op-aa-bg: var(--bg-max);
+html.b #files thead th {
+	background: none;
 }
-html.b {
+html.b #s_list h3{
+	border-radius: var(--radius) var(--radius) 0 0;
+	position: relative;
+}
+html.b #wtico {
+	border-radius: var(--radius) 0 0 0;
+	position: relative;
+}
+html.b #up_quick_more a:hover,
+html.b #ggrid>a:hover,
+html.b #ops a:hover,
+html.b .btn:hover {
+	background: var(--bottom-glow), 
+	linear-gradient(
+		to bottom, 
+		color-mix(in oklab, transparent, var(--a-dark)),
+		color-mix(in oklab, transparent, var(--a)));
+}
+html.bz #up_quick_more a:hover,
+html.bz #ggrid>a:hover,
+html.bz #ops a:hover,
+html.bz .btn:hover {
+	background: 
+		var(--bottom-glow), 
+		linear-gradient( 
+			to bottom, 
+			color-mix(in oklab, #0006, var(--a-dark)), 
+			color-mix(in oklab, transparent, #000b))
+}
+html.b #up_quick_more a:active,
+html.b #ggrid>a:active,
+html.b .btn:active {
+	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4) !important;
+	transform: translateY(2px);
+}
+html.b #widgeti {
+	background: color-mix(in srgb, var(--bg) 40%, var(--a));
+}
+html.b #mu_outer {
+	background: color-mix(in oklab, var(--b) 50%, transparent);
+}
+html.b .divider {
+	display: none;
+}
+html.b summary {
+	color: var(--fg);
+}
+html.b #pvolbg {
+	color: var(--bg);
+}
+html.b #srchfolder_div,
+html.b .modalcontent,
+html.b,
+html.b #ops a.act,
+html.b #ggrid>a.sel,
+html.b #tree ul a.hl:hover,
+html.b #tree ul a.hl,
+html.b .tgl.btn.on {
+	background:
+		var(--bottom-glow),
+		linear-gradient(to bottom, var(--a-dark), var(--a));
+}
+html.b #wtoggle a {
+	position: relative;
+}
+html.b #up_quick_more a{
+	position: relative;
+	margin: .5em 0;
+}
+html.b #up_quick_more{
+	background: none;
+	border: none;
+}
+html.b #srchfolder_div::after,
+html.b #up_quick_more a::after,
+html.b #wtico::after,
+html.b #s_list h3::after,
+html.b #spaceUsed_bar::after,
+html.b #wtoggle a:after,
+html.b a:not(.play) .imgcontainer::before,
+html.b #ops a:after,
+html.b #path:after,
+html.b #tree ul a.hl:after,
+html.b .btn::after {
+  content: "" !important;
+  position: absolute;
+  top: 4%;
+  left: 5px;
+  width: calc(100% - 10px);
+  height: 40%;
+  background: linear-gradient(
+    -165deg,
+    rgba(255, 255, 255, 0.7),
+    rgba(255, 255, 255, 0.1)
+  );
+  --r2: calc(var(--radius) - 3px);
+  border-radius: var(--r2) var(--r2) calc(var(--r2) / 2) calc(var(--r2) / 2);
+  transition: background 400ms ease;
+  pointer-events: none;
+}
+html.b a:not(.play) .imgcontainer::before{
+  	z-index: 2;
+}
+html.b #ops a {
+	position: relative;
+	margin-left: 4px;
+}
+html.b #srchfolder_div {
+	left: 3.2em;
+}
+html.b #spaceTotal_bar,
+html.b #spaceUsed_bar{
+	height: 1.6em;
+}
+html.b #files tbody tr:not(.sel):hover td, 
+html.b #files tbody tr:not(.sel):hover td+td {
+	background: rgba(255, 255, 255, 0.6);
+}
+html.b #spaceMax {
+	display: none;
+}
+html.b #spaceFree {
+	position: absolute;
+    z-index: 1;
+    bottom: 2.1em;
+	max-width: initial;
+    width: calc(100% - 3em);
+    text-align: center;
+}
+html.b #ops a.act {
+	margin-top: -.15em;
+	margin-bottom: -.15em;
+	padding-top: .3em;
+}
+
+html.a {
 	--btn-bs: 0 .05em 0 var(--bg-d3) inset;
 	--btn-1-bs: 0 .05em 0 var(--btn-1h-bg) inset;
@@ -244,13 +339,13 @@
 	--tree-bg: var(--bg);
 
-	--g-bg: var(--bg);
-	--g-b1: var(--bg);
-	--g-b2: var(--bg);
-	--g-g1: var(--bg);
+	--g-bg: transparent;
+	--g-b1: transparent;
+	--g-b2: transparent;
+	--g-g1: transparent;
 	--g-sh: rgba(0,0,0,0);
+	--g-f-bg: color-mix(var(--fg), var(--bg), transparent 60%);
 
 	--op-aa-bg: rgba(255,255,255,0.06);
 
-	--u2-sbtn-b1: #fc0;
 	--u2-txt-bg: transparent;
 	--u2-tab-1-sh: var(--bg);
@@ -261,59 +356,7 @@
 	--mp-b-bg: transparent;
 }
-html.bz {
-	--fg: #cce;
-	--fg-weak: #bbd;
-
-	--bg-u5: #3b3f58;
-	--bg-u4: #1e2130;
-	--bg-u3: #1e2130;
-	--bg-u1: #1e2130;
-	--bg: #11121d;
-	--bg-d1: #232536;
-	--bg-d2: #34384e;
-	--bg-d3: #34384e;
-
-	--row-alt: #181a27;
-
-	--a-b: #fb4;
-
-	--btn-bg: #202231;
-	--btn-h-bg: #2d2f45;
-	--btn-1-bg: #eb6;
-	--btn-1-fg: #000;
-	--btn-1h-fg: #000;
-	--btn-1h-bg: #ff9;
-	--txt-sh: a;
-
-	--u2-tab-b1: var(--bg-u5);
-	--u2-tab-1-fg: var(--fg-max);
-	--u2-tab-1-bg: var(--bg);
-
-	--srv-1: #79b;
-
-	--g-sel-bg: #ba2959;
-	--g-fsel-bg: #e6336e;
-
-	--f-h-b1: #34384e;
-	--mp-sh: #11121d;
-	/*--mp-b-bg: #2c3044;*/
-	--f-play-bg: var(--btn-1-bg);
-}
-html.by {
-	--bg: #f2f2f2;
-
-	--row-alt: #f9f9f9;
-
-	--scroll: var(--a);
-
-	--btn-1-bg: #07a;
-	--btn-1h-bg: var(--a-hil);
-
-	--op-aa-bg: #fff;
-
-	--u2-sbtn-b1: #c70;
-	--u2-tab-1-b1: #999;
-	--u2-tab-1-b2: #aaa;
-	--u2-b-fg: #444;
+html.ay {
+	--a: #07c;
+	--bg: var(--bg-u2);
 }
 html.c {
@@ -324,5 +367,5 @@
 	--bg-u5: #409;
 	--bg-u2: linear-gradient(-35deg, #fd7233, #cd27a0, #5d47a5 49.5%, #16e9fb 50%, #3b6cc8 50.4%, #0e51ac);
-	--bg: #37235d;
+	--bg: #376ac5;
 	--bg-u3: #407;
 
@@ -331,10 +374,8 @@
 
 	--tab-alt: #6ef;
-	--row-alt: #47237d;
-	--scroll: #ff0;
+	--row-alt: #4630ac;
 
 	--btn-fg: #fff;
-	--btn-bg: #9019bf;
-	--btn-h-bg: #a039ff;
+	--btn-bg-a: #9019bf;
 	--chk-fg: #d90;
 
@@ -354,4 +395,5 @@
 	--btn-1-bb: .2em solid #e90;
 	--btn-1-bs: 0 .1em .8em rgba(255,205,0,0.9);
+	--btn-h-bg: #a039ff;
 
 	--sz-b: #ddd;
@@ -364,100 +406,52 @@
 }
 html.cy {
+	--a: #ff0;
+	--b: #f00;
+	--b: hsl(from var(--a) calc(h - 60) s l);
+
 	--fg: #fff;
 	--fg-weak: #fff;
-	--bg: #ff0;
-	--bg-u2: #f00;
-	--bg-u3: #f00;
+	--bg: var(--a);
+	--bg-u2: var(--b);
+	--bg-u3: var(--b);
 	--bg-u5: #999;
-	--bg-d3: #f77;
-	--bg-d2: #ff0;
+	--bg-d2: var(--a);
 
 	--sel-bg: #f77;
 
-	--a: #fff;
-	--a-hil: #fff;
+	--a-hil: var(--b);
 	--a-h-bg: #000;
 
-	--tab-alt: #f00;
+	--tab-alt: var(--b);
 	--row-alt: #fff;
-	--scroll: #fff;
 
 	--btn-bg: #000;
-	--btn-fg: #ff0;
-	--btn-h-fg: #fff;
-	--btn-1-bg: #ff0;
+	--btn-fg: var(--a);
+	--btn-1-bg: var(--a);
 	--btn-1-fg: #000;
-	--btn-bs: 0 .25em 0 #f00;
+	--btn-h-bg: var(--a);
+	--btn-bs: 0 .25em 0 var(--b);
 	--chk-fg: #fd0;
 
 	--txt-bg: #000;
-	--srv-1: #f00;
+	--srv-1: var(--b);
 	--srv-3: #fff;
 	--op-aa-bg: #fff;
 
-	--u2-b1-bg: #f00;
-	--u2-b2-bg: #f00;
+	--u2-b1-bg: var(--b);
+	--u2-b2-bg: var(--b);
 
 	--g-sel-fg: #fff;
 	--g-sel-bg: #aaa;
 	--g-fsel-bg: #aaa;
+
+	--scrl-hint: var(--b);
 }
 html.dz {
-	--fg: #4d4;
-	--fg-weak: #2a2;
+	--a: #32fe32;
+	--fg-max: rgb(14, 233, 14);
+	--fg-max: var(--a);
 
-	--bg-u6: #020;
-	--bg-u5: #050;
-	--bg-u4: #020;
-	--bg-u3: #020;
-	--bg-u2: #020;
-	--bg-u1: #020;
-	--bg: #010;
-	--bg-d1: #000;
-	--bg-d2: #020;
-	--bg-d3: #000;
-
-	--tab-alt: #6f6;
-	--row-alt: #030;
-
-	--scroll: #0f0;
-
-	--a: #9f9;
-	--a-b: #cfc;
-	--a-hil: #cfc;
-	--a-dark: #afa;
-	--a-gray: #2a2;
-
-	--btn-bg: rgba(64,128,64,0.15);
-	--btn-h-bg: #050;
-	--btn-1-fg: #000;
-	--btn-1-bg: #4f4;
-	--btn-1h-bg: #3f3;
-	--btn-bs: 0 0 0 .1em #080 inset;
-	--btn-1-bs: a;
-
-	--u2-btn-b1: var(--fg-weak);
-	--u2-sbtn-b1: var(--fg-weak);
-	--u2-tab-b1: var(--fg-weak);
-	--u2-tab-1-fg: #fff;
-	--u2-tab-1-bg: linear-gradient(to bottom, #151, var(--bg) 80%);
-	--u2-b1-bg: #3a3;
-	--u2-b2-bg: #3a3;
-
-	--sort-1: #fff;
-	--sort-2: #3f3;
-
-	--srv-1: #3e3;
-	--srv-2: #1a1;
-	--srv-3: #0f0;
-	--srv-3b: #070;
-
-	--tree-bg: #010;
-
-	--g-sel-b1: #c37;
-	--g-sel-sh: #b36;
-	--g-fsel-b1: #d48;
-
-	--f-h-b1: #3b3;
+	--radius: 0;
 
 	text-shadow: none;
@@ -476,8 +470,6 @@
 	--bg-u1: #fff;
 	--bg-u2: #fff;
-	--bg-u3: #fff;
-	--bg-u4: #fff;
+	--bg-u3: #000;
 	--bg-u5: #fff;
-	--bg-u6: #fff;
 	--bg-max: #fff;
 
@@ -485,12 +477,10 @@
 	--row-alt: #eee;
 
-	--scroll: #000;
-
 	--a: #000;
-	--a-b: #000;
 	--a-hil: #000;
 	--a-gray: #bbb;
 	--a-dark: #000;
 
+	--btn-bg: #fff;
 	--btn-fg: #000;
 	--btn-h-fg: #000;
@@ -501,5 +491,5 @@
 	--chk-fg: a;
 	--txt-sh: a;
-	--txt-bg: a;
+	--txt-bg: #fff;
 
 	--op-a-sh: a;
@@ -527,4 +517,5 @@
 
 	--g-sel-bg: #000;
+	--g-sel-fg: #fff;
 	--g-fsel-bg: #444;
 	--g-fsel-ts: #000;
@@ -549,4 +540,31 @@
 	--mp-b-bg: #fff;
 }
+html.dy #files tr.sel a, 
+html.dy #files tr.sel a.play {
+	color: #fff;
+}
+html.dy tr.play td:nth-child(1) a {
+  color: #000;
+  background: #fff;
+  border: #000 solid 1px;
+}
+html.dy #spaceUsed_bar {
+	background: #000;
+}
+html.dy #barpos {
+	border: var(--btn-bb);
+}
+html.dy #pathBar {
+	background: var(--bg);
+}
+html.dy #spaceTotal_bar,
+html.dy select,
+html.dy input {
+	border: #000 solid 1px !important;
+}
+html.dy h3 {
+	border-top: #000 solid 1px;
+	border-radius: 0 !important;
+}
 * {
 	line-height: 1.2em;
@@ -558,5 +576,5 @@
 }
 html,body,tr,th,td,#files,a,#blogout {
-	color: inherit;
+	/* color: inherit; */
 	background: none;
 	font-weight: inherit;
@@ -565,4 +583,15 @@
 	border: none;
 }
+body {
+	background: #1a1c20;
+}
+@supports (background: var(--bg)) {
+	body {
+		background: none;
+	}
+}
+#files {
+	margin-top: .2em;
+}
 html {
 	color: var(--fg);
@@ -588,5 +617,5 @@
 	color: var(--bg);
 }
-html .ayjump:focus {
+html .ayjump:focus-visible {
 	z-index: 80386;
 	color: #fff;
@@ -607,29 +636,100 @@
 	font-size: 1em;
 }
+#pathBar {
+	display: flex;
+	margin: 0;
+	align-content: center;
+	position: sticky;
+	z-index: 11;
+	background: linear-gradient(to bottom, var(--bg), transparent);
+	padding: .6em;
+	top: 0;
+	margin: 0 -.6em;
+}
+#pathBar.shifted #hspace {
+	min-width: 3.2em;
+}
 #path {
+	position: relative;
 	color: var(--fg);
 	text-shadow: 1px 1px 0 var(--bg-max);
 	font-weight: normal;
+	display: block;
+	margin: 0 .5em 0 0;
+	width: calc(100% - .6em);
+	padding: 0 .25em;
+	font-size: 1.2em;
+	white-space: nowrap;
+	overflow-x: auto;
+	overflow-y: hidden;
+	background: #3a3d41;
+	background: var(--bg-u2);
+	border-radius: 5px;
+	border-radius: var(--radius);
+}
+#pathBar #folder_search {
+	margin: 0;
+	display: block;
+	position: absolute;
+	right: .2em;
+	left: .2em;
+	top: .2em;
+	bottom: .2em;
+}
+#qs_btns {
+	position: absolute;
+	right: .2em;
+	top: .2em;
+	font-size: 1.3em;
+	cursor: pointer;
+	display: flex;
+}
+#qs_btns a {
+	background: color-mix(in oklab, var(--txt-bg), transparent);
+	width: 1em;
+	height: 1em;
+	line-height: 1em;
 	display: inline-block;
-	padding: .35em .5em .2em .5em;
-	margin: 1.3em 0 -.2em 0;
-	font-size: 1.4em;
+	text-align: center;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	vertical-align: center;
+}
+#qs_btns a:hover {
+	background: var(--bg);
+}
+#bbox-btns .x,
+#qs_btns a .x {
+	line-height: .6em;
+	vertical-align: middle;
+  	vertical-align: center;
+}
+#moresearch span {
+	font-size: .6em;
+  	vertical-align: center;
 }
 html.y #path {
 	text-shadow: none;
 }
+#path a.dtarget {
+	text-shadow: var(--f-sel-sh) 0 0 5px;
+	background: color-mix(in oklab, var(--a), transparent);
+}
 #path #entree {
 	margin-left: -.7em;
 }
 #files {
+	overflow-x: auto;
+	display: block;
 	z-index: 1;
 	top: -.3em;
 	border-spacing: 0;
 	position: relative;
+	margin: .2em -.5em;
+  	padding: 0 .5em;
 }
 #files tbody a {
 	display: block;
-	padding: .5em 0;
-	margin: -.3em 0;
+	padding: .5em;
 	scroll-margin-top: 45vh;
 }
@@ -638,23 +738,37 @@
 	scroll-margin-bottom: 20vh;
 }
+.td_w {
+	width: 30em;
+	max-width: 30em;
+}
+.td_s {
+	width: 14em;
+	max-width: 14em;
+}
 #files tbody div a {
 	color: var(--tab-alt);
 }
 a, #blogout, #files tbody div a:last-child {
-	color: var(--a);
+	color: #d7dbe0;
+	color: var(--fg);
 	padding: .2em;
 	text-decoration: none;
 }
+#goh,
 #blogout {
-	margin: -.2em;
+	margin: -.1em;
+	text-shadow: 1px 1px 0px var(--bg-max);
 }
 #blogout:hover,
+input[type=submit]:hover,
+#goh:hover,
 a:hover {
 	color: var(--a-hil);
 	background: var(--a-h-bg);
 }
+#files .imgcontainer:hover,
 #files a:hover {
 	color: var(--fg-max);
-	background: var(--bg-d3);
+	background-color: color-mix(in oklab, var(--a) 20%, transparent);
 	text-decoration: underline;
 }
@@ -669,11 +783,13 @@
 .s0:after,
 .s1:after {
-	content: '⌄';
-	margin-left: -.15em;
+	content: '▲';
+	font-size: .7em;
+	/* margin-left: -.15em; */
 }
 .s0r:after,
 .s1r:after {
-	content: '⌃';
-	margin-left: -.15em;
+	content: '▼';
+	font-size: .7em;
+	/* margin-left: -.15em; */
 }
 .s0:after,
@@ -690,6 +806,9 @@
 #files tbody tr:hover td,
 #files tbody tr:hover td+td {
-	background: var(--bg-d1);
-	box-shadow: 0 1px 0 var(--bg-u5) inset, 0 -1px 0 var(--bg-u5) inset;
+	background: color-mix(in oklab, var(--a) 15%, transparent);
+}
+a.dir {
+	color: #3584e4;
+	color: var(--a);
 }
 #files thead th {
@@ -703,7 +822,8 @@
 }
 html #files.hhpick thead th {
-	color: #f7d;
-	background: #000;
-	box-shadow: .1em .2em 0 #f6c inset, -.1em -.1em 0 #f6c inset;
+	color: rgb(0, 0, 0);
+	background: #df5555;
+	text-shadow: none;
+	border: red solid 2px;
 }
 #files td {
@@ -715,4 +835,7 @@
 	overflow: hidden;
 }
+#files td:has(a) {
+	padding: 0;
+}
 #files tr.fade a {
 	color: #999;
@@ -725,4 +848,5 @@
 }
 #files tr:nth-child(2n) td {
+	background: #24272b;
 	background: var(--row-alt);
 }
@@ -734,11 +858,13 @@
 }
 #files td:first-child {
-	border-radius: .25em 0 0 .25em;
+	border-radius: 5px 0 0 5px;
+	border-radius: var(--radius) 0 0 var(--radius);
 	white-space: nowrap;
 }
 #files td:last-child {
-	border-radius: 0 .25em .25em 0;
+	border-radius: 0 5px 5px 0;
+	border-radius: 0 var(--radius) var(--radius) 0;
 }
-#files tbody td:nth-child(3) {
+#files tbody td:nth-child(4) {
 	font-family: 'scp', monospace, monospace;
 	font-family: var(--font-mono), 'scp', monospace, monospace;
@@ -769,7 +895,16 @@
 	display: inline;
 }
+#files tr.dtarget {
+	box-shadow: 0 0 5px 0 var(--f-sel-sh) inset;
+	background: color-mix(in oklab, var(--a), transparent);
+}
+#files tr.dtarget td {
+	background: none;
+}
 #path a {
-	padding: 0 .35em;
+	padding: .4em .35em;
 	position: relative;
+	display: inline-block;
+	height: 100%;
 	z-index: 1;
 	/* ie: */
@@ -777,21 +912,16 @@
 	margin-right: 1em\9;
 }
-#path a:first-child {
-	padding-left: .8em;
-}
 #path i {
-	width: 1.05em;
-	height: 1.05em;
-	margin: -.5em .15em -.15em -.7em;
+	width: 2em;
+	height: 2.5em;
+	margin: -1em .3em -1em -1.7em;
 	display: inline-block;
-	border: 1px solid rgba(255,224,192,0.3);
-	border-width: .05em .05em 0 0;
-	transform: rotate(45deg);
-	background: linear-gradient(45deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.25) 75%, rgba(0,0,0,0.35));
+	transform: skew(-25deg);
+	border-right: #222 solid 2px;
+	border-right: var(--bg) solid 2px;
+	background: linear-gradient(70deg, transparent 40%, color-mix(in oklab, var(--bg-max) 25%, transparent) 75%, color-mix(in oklab, var(--bg-max) 35%, transparent));
 }
-html.y #path i {
-	background: none;
-	border-color: rgba(0,0,0,0.2);
-	border-width: .1em .1em 0 0;
+html.b #path i {
+	border-right: var(--bg) solid 1px;
 }
 #path a:hover {
@@ -799,4 +929,8 @@
 	background: linear-gradient(90deg, rgba(0,0,0,0), rgba(0,0,0,0.2), rgba(0,0,0,0));
 }
+html.a #ggrid>a.dir:before,
+html.a #path i {
+	background: none;
+}
 html.y #path a:hover {
 	background: none;
@@ -812,8 +946,13 @@
 }
 .logue.raw {
+	background: #1a1c20;
+	background: var(--transparent);
 	white-space: pre;
 	font-family: 'scp', 'consolas', monospace;
 	font-family: var(--font-mono), 'scp', 'consolas', monospace;
 }
+.iframe {
+  background: none;
+}
 #doc>iframe,
 .logue>iframe {
@@ -821,12 +960,12 @@
 	border: 1px solid var(--bgg);
 	border-width: 0 .3em 0 .3em;
-	border-radius: .5em;
+	border-radius: var(--radius);
 	visibility: hidden;
-	margin: 0 -.3em;
-	width: 100%;
+	margin: 0 .3em;
+	width: calc(100% - 1em);
 	height: 0;
 }
-#doc>iframe.focus,
-.logue>iframe.focus {
+#doc>iframe.focus-visible,
+.logue>iframe.-visible {
 	box-shadow: 0 0 .1em .1em var(--a);
 }
@@ -853,12 +992,5 @@
 .mdo>h2:first-child,
 .mdo>h3:first-child {
-	margin-top: 1.5rem;
-}
-.mdo {
-	max-width: 52em;
-}
-.mdo.sb,
-.logue.mdo>iframe {
-	max-width: 54em;
+	margin-top: 0.5rem;
 }
 .mdo,
@@ -875,7 +1007,7 @@
 #srv_info,
 #acc_info {
-	position: absolute;
+	position: relative;
 	font-size: .8em;
-	top: .5em;
+	bottom: 18.5em;
 }
 #srv_info {
@@ -883,4 +1015,5 @@
 	padding-right: .5em;
 }
+#accessType,
 #acc_info,
 #srv_info span,
@@ -892,19 +1025,20 @@
 }
 #srv_info2 {
-	display: none;
+	display: block;
 }
 #acc_info {
-	right: 2em;
+	left: 2em;
 }
 #acc_info > span:not([id]) {
 	color: var(--srv-1);
-	margin-right: .6em;
+	margin-left: .6em;
 }
 #acc_info span.warn {
 	color: var(--srv-3);
-	border-bottom: 1px solid var(--srv-3b);
+	border-top: 1px solid var(--srv-3b);
 }
 #flogout {
 	display: inline;
+	direction: rtl;
 }
 html.dz #flogout {
@@ -918,6 +1052,10 @@
 }
 #repl {
+	color: color-mix(in srgb, var(--a) 30%, transparent);
 	padding: .33em;
 }
+#repl:hover{
+	color: var(--a);
+}
 #files a.doc {
 	color: var(--a-gray);
@@ -926,13 +1064,20 @@
 	color: var(--tab-alt);
 }
-#files a.play {
+tr td:nth-child(1) a {
+	border-radius: var(--radius);
+}
+/* #files a.play {
 	color: var(--a-dark);
 	padding: .3em;
 	margin: -.3em;
+} */
+tr.play td:nth-child(1) a {
+  background: var(--btn-1-bg);
+  color:var(--btn-1-fg);
+  text-shadow: none;
 }
-#files tbody tr.play td,
+/* #files tbody tr.play td,
 #files tbody tr.play td+td,
 #files tbody tr.play div a {
-	background: #fc0;
 	background: var(--f-play-bg);
 	color: var(--f-play-fg);
@@ -947,5 +1092,5 @@
 	box-shadow: var(--btn-1h-bs);
 	border-bottom: var(--btn-1h-bb);
-}
+} */
 #ggrid {
 	margin: -.2em -.5em;
@@ -962,9 +1107,10 @@
 #ggrid>a {
 	display: inline-block;
-	width: 10em;
+	width: 9em;
 	width: var(--grid-sz);
 	vertical-align: top;
 	overflow-wrap: break-word;
-	border-radius: .3em;
+	border-radius: 5px;
+	border-radius: var(--radius);
 	padding: .3em;
 	margin: .5em;
@@ -975,5 +1121,8 @@
 	box-shadow: 0 .1em .3em var(--g-sh);
 }
-#ggrid>a:focus,
+#ggrid>a:hover {
+	background: #303337;
+}
+#ggrid>a:focus-visible,
 #ggrid>a:hover {
 	color: var(--g-f-fg);
@@ -982,22 +1131,144 @@
 	box-shadow: 0 .1em .3em var(--g-sh);
 }
+#ggrid>a:focus-visible .gselchk{
+	display: block;
+}
+#ggrid>a:hover .gselchk {
+	/* separate to allow IE to only use this rule */
+	display: block;
+}
+#files .play .thumb,
+#files .play img,
+#ggrid>a.play .thumb,
+#ggrid>a.play img {
+	background: var(--bg-u2);
+}
+.thumb,
+.imgcontainer img,
+#ggrid>a .thumb,
 #ggrid>a img {
-	border-radius: .2em;
+	color: #3584e4;
+	z-index: 1;
+	border-radius: 5px;
+	border-radius: calc(var(--radius) - 2px);
 	max-width: 10em;
-	max-width: var(--grid-sz);
-	max-height: 8em;
-	max-height: calc(var(--grid-sz)/1.25);
+	max-width: calc(var(--grid-sz) - 4px);
 	margin: 0 auto;
 	display: block;
+	width: 100%;
+	height: calc(var(--grid-sz) - 2.5em);
+	object-fit: cover;
+}
+.imgcontainer img,
+#ggrid>a img {
+	position: absolute;
+	opacity: 0;
+}
+#ggrid>a.dir.thumbed .thumb,
+#ggrid>a.dir.thumbed img {
+	max-width: calc(var(--grid-sz) - 20px);
+	margin: 10px auto 0 auto;
+}
+#ggrid>a.dir.thumbed .imgcontainer img {
+    height: calc(var(--grid-sz) - 2.5em - 10px);
+}
+#ggrid.noupscale>a img {
+	width: auto;
+	height: auto;
+}
+#ggrid.noupscale.gallery.nocrop>a img {
+	object-fit: scale-down;
 }
 #ggrid.nocrop>a img {
 	max-height: 20em;
 	max-height: calc(var(--grid-sz)*2);
+	object-fit: contain;
+	height: 100%;
 }
-#ggrid>a.dir:before {
-	content: '📂';
+
+.imgcontainer {
+	display: flex;
+	overflow: hidden;
+	position: relative;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	box-sizing: border-box;
+	margin: -3px -2px;
+	padding: 4px 3px;
+	justify-content: center;
+}
+#files .imgcontainer img,
+#files .imgcontainer {
+	border-radius: 0;
+}
+.th_ext {
+	z-index: 2;
+	position: absolute;
+	text-align: center;
+	width: 100%;
+	width: calc(100% - 8px);
+	left: 4px;
+	right: 4px;
+	top: 50%;
+	top: calc(55% - .5em);
+	color: #07c;
+	color: var(--a);
+	text-shadow: none;
+	font-family: monospace;
+	font-family: var(--font-mono);
+}
+#ggrid>a.thumbed .thumb,
+#ggrid>a.thumbed .th_ext,
+.thumbed .thumb,
+.thumbed .th_ext {
+	display: none !important;
+}
+#ggrid > a.thumbed img,
+.thumbed img,
+.thumbed #mu_th img {
+	position: static;
+	opacity: 1;
 }
-#ggrid>a.dir>span {
-	color: var(--g-dfg);
+.sel .gselchk {
+	display: block;
+}
+.srch_hdr .gselchk {
+	display: none !important;
+}
+.gselchk {
+	position: absolute;
+	display: none;
+	top: .3em;
+	right: .3em;
+	z-index: 4;
+	font-size: 1.15em;
+	width: 1em;
+  	height: 1em;
+	opacity: .69;
+}
+
+@keyframes rotate {
+	100% {
+		transform: rotate(1turn);
+	}
+}
+
+.play .imgcontainer::before {
+	content: '';
+	box-sizing: border-box;
+	position: absolute;
+	left: -50%;
+	top: -50%;
+	width: 200%;
+	height: 200%;
+	background-repeat: no-repeat;
+	background-size: 50% 50%, 50% 50%;
+	background-position: 0 0, 100% 0, 100% 100%, 0 100%;
+	background-image: linear-gradient(var(--a-hil), var(--a-hil)), linear-gradient(transparent, transparent), linear-gradient(var(--a-hil), var(--a-hil)), linear-gradient(transparent, transparent);
+	animation: rotate 10s linear infinite;
+	filter: blur(5px);
+}
+html:not(.e) #ggrid>a.thumbed.dir:before {
+	content: '📂';
 }
 #ggrid>a.au:before {
@@ -1005,4 +1276,5 @@
 }
 #ggrid>a:before {
+	z-index: 4;
 	display: block;
 	position: absolute;
@@ -1011,9 +1283,10 @@
 	text-shadow: 0 0 .1em var(--bg-max);
 	background: linear-gradient(135deg,rgba(255,255,255,0) 50%,rgba(255,255,255,0.2));
-	border-radius: .3em;
+	border-radius: 5px;
+	border-radius: var(--radius);
 	font-size: 2em;
 	transition: font-size .15s, margin .15s;
 }
-#ggrid>a:focus:before,
+#ggrid>a:focus-visible:before,
 #ggrid>a:hover:before {
 	font-size: 2.5em;
@@ -1021,7 +1294,7 @@
 }
 #ggrid>a[tt] {
-	background: linear-gradient(135deg, var(--g-g1) 95%, var(--g-g2) 95%);
+	background: linear-gradient(135deg, var(--g-g1) 95%, var(--g-g2) 95%) no-repeat;
 }
-#ggrid>a[tt]:focus,
+#ggrid>a[tt]:focus-visible,
 #ggrid>a[tt]:hover {
 	background: var(--g-f-bg);
@@ -1030,9 +1303,6 @@
 #ggrid>a[tt].play {
 	color: var(--g-sel-fg);
-	background: #fc0;
-	background: var(--g-play-bg);
 	border-color: var(--g-play-b1);
 	border-top: 1px solid var(--g-play-b2);
-	box-shadow: 0 .1em 1.2em var(--g-play-sh);
 }
 #files tbody tr.sel td,
@@ -1041,5 +1311,5 @@
 #ggrid>a[tt].sel {
 	color: var(--g-sel-fg);
-	background: #f3c;
+	background: #172e55;
 	background: var(--g-sel-bg);
 	border-color: var(--g-sel-b1);
@@ -1055,7 +1325,7 @@
 }
 #files tbody tr.sel:hover td,
-#files tbody tr.sel:focus td,
+#files tbody tr.sel:focus-visible td,
 #ggrid>a.sel:hover,
-#ggrid>a.sel:focus {
+#ggrid>a.sel:focus-visible {
 	color: var(--g-sel-fg);
 	background: var(--g-fsel-bg);
@@ -1063,22 +1333,18 @@
 	text-shadow: 1px 1px 0 var(--g-fsel-ts);
 }
-#ggrid>a.sel img,
-#ggrid>a.play img {
-	opacity: .7;
-	filter: contrast(130%) brightness(107%);
-}
 #ggrid>a.sel img {
 	box-shadow: 0 0 1em var(--g-sel-sh);
 }
-#ggrid>a.play img {
-	box-shadow: 0 0 1em var(--g-play-sh);
-}
 #ggrid a {
 	scroll-margin-top: 25vh;
 	scroll-margin-bottom: 20vh;
 }
+#ggrid a.dtarget {
+	box-shadow: 0 0 5px 0 var(--f-sel-sh);
+	background: color-mix(in oklab, var(--a), transparent);
+}
 #files tr.sel a,
 #files tr.sel a.play {
-	color: var(--fg2-max);
+	color: var(--fg-max);
 }
 #files tr.sel a:hover {
@@ -1087,15 +1353,15 @@
 }
 #files tr.sel a.play.act {
-	text-shadow: 0 0 1px var(--fg2-max);
+	text-shadow: 0 0 1px var(--fg-max);
 }
-#files tr:focus {
+#files tr:focus-visible {
 	outline: none;
 	position: relative;
 }
-#files tr:focus td+td {
+#files tr:focus-visible td+td {
 	background: var(--bg-d3);
 	box-shadow: 0 .2em 0 var(--f-sel-sh), 0 -.2em 0 var(--f-sel-sh);
 }
-#files tr:focus:not(.play):not(.sel) td:first-child {
+#files tr:focus-visible:not(.play):not(.sel) td:first-child {
 	background: var(--bg-d3);
 	box-shadow: -.2em .2em 0 var(--f-sel-sh), -.2em -.2em 0 var(--f-sel-sh);
@@ -1109,41 +1375,63 @@
 	left: 0;
 	right: 0;
-	bottom: -6em;
-	height: 6em;
-	width: 100%;
-	z-index: 3;
+	bottom: 0;
+	z-index: 11;
 	touch-action: none;
+	box-shadow: 0 0 .5em var(--mp-sh);
 }
 #widget.anim {
-	transition: bottom 0.15s;
+	transition: margin-left 0; /* can't set this above 0 due to onwidgetresize */
 }
-#widget.open {
-	box-shadow: 0 0 1em rgba(0,48,64,0.2);
-	bottom: 0;
+#widget.anim #widgeti {
+	transition: height 0.15s;
 }
-html.y #widget.open {
-	border-top: .2em solid var(--bg-u2);
+#widget.open #widgeti{
+	/* box-shadow: 0 0 1em rgba(0,48,64,0.2); */
+	display: block;
 }
-#widgeti {
-	position: relative;
-	z-index: 10;
-	width: 100%;
-	height: 100%;
+
+#pbarthinpos {
+	display: none;
+	align-content: center;
+	padding: .2em .5em;
+	padding-bottom: 0;
+	height: 2em;
 }
-#fshr,
-#wtgrid,
-#wtico {
+#widget.thin #pctl{
+	display: flex;
+}
+#widget.thin #pbarthinpos{
+	display: block;
+}
+#widget.thin #trackname {
+	margin-right: auto;
+	width: stretch;
+	width: -moz-available;
+}
+
+#np_inf {
+	height: 0;
+}
+#fshr {
 	position: relative;
 	font-size: .9em;
 	top: -.04em;
+	margin-right: .3em;
 }
 #wtgrid {
-	font-size: .75em;
+	padding: 0;
+}
+.btn.svgIcon {
+	width: 1.5em;
+  	height: 1.5em;
 	padding: .1em;
-	top: -.12em;
 }
-#wtico {
-	cursor: pointer;
+.btn.svgIcon.gb_grd,
+.btn.svgIcon.gb_glr {
+	width: 1.7em;
+  	height: 1.7em;
+	padding: 0;
 }
+
 @keyframes spin {
 	100% {transform: rotate(360deg)}
@@ -1153,23 +1441,145 @@
 	100% {opacity: 1}
 }
-#wtoggle {
+#wtico,
+.open #up_quick_more,
+#wtoggle,
+#widgeti {
+	background: #2f3236;
+	background: var(--bg-u2);
+	border: 1px solid #3a3d41;
+	border: 1px solid var(--bg-u3);
+}
+#widgeti {
+	position: relative;
+	display: none;
+	border-width: 1px 0 0 0;
+}
+#wtico {
+	display: inline-block;
+	cursor: pointer;
+	line-height: 1.2em;
+  	vertical-align: bottom;
+	padding: .05em .5em .1em .3em;
+	border-radius: 5px 0 0 0;
+	border-radius: var(--radius) 0 0 0;
+	pointer-events: all;
+	border-width: 1px 0 0 1px;
+}
+#wstack {
+	display: block;
 	position: absolute;
+	text-wrap-mode: nowrap;
+	text-wrap: nowrap;
 	white-space: nowrap;
-	top: -1em;
+	width: 100%;
+	bottom: 100%;
 	right: 0;
-	height: 1em;
 	font-size: 2em;
-	line-height: 1em;
+	text-align: right;
+	color: var(--fg-max);
+	pointer-events: none;
+}
+#wstack div {
+	pointer-events: all;
+	vertical-align: bottom;
+}
+#wtoggle {
+	display: inline-block;
+	text-wrap-mode: nowrap;
+	text-wrap: nowrap;
+	white-space: nowrap;
 	text-align: center;
-	text-shadow: none;
+	max-width: 79vw;
+	max-width: calc(100% - 3.4em);
+	min-height: 1.25em;
+	overflow-x: auto;
+	overflow-y: hidden;
+	margin: 0 .1em .1em .1em;
 	box-shadow: 0 0 .5em var(--mp-sh);
-	border-radius: .3em 0 0 0;
-	padding: 0 0 0 .1em;
-	color: var(--fg-max);
+	border-radius: 5px;
+	border-radius: var(--radius);
+	overflow-x: auto;
 }
-#wtoggle,
-#widgeti {
-	background: #fff;
-	background: var(--bg-u3);
+#up_quick {
+	display: inline-block;
+	position: relative;
+	margin: 0 .2em .2em .1em;
+	font-size: .7em;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	background: #2f3236;
+    background: var(--bg-u2);
+}
+#up_quick_more {
+	position: absolute;
+	bottom: 100%;
+	right: -2px;
+	font-weight: bold;
+	box-shadow: 0 0 .5em color-mix(in oklab, var(--a) 20%, transparent);
+	border-radius: 10px;
+	border-radius: var(--radius);
+	text-align: right;
+}
+.open #up_quick_more {
+	box-shadow: 0 0 .5em color-mix(in oklab, var(--mp-sh) 20%, transparent);
+}
+#up_quick_more {
+	display: none;
+	min-width: 4em;
+	font-weight: normal;
+	margin-bottom: .2em;
+	text-wrap-mode: wrap;
+	text-wrap: nowrap;
+	white-space: nowrap;
+}
+#up_quick_more.vis {
+	display: block;
+}
+#up_quick_more a:hover {
+	background: var(--btn-h-bg);
+}
+	#up_quick_more a {
+		position: static;
+		font-size: large;
+		display: block;
+		text-wrap-mode: nowrap;
+		text-wrap: nowrap;
+		white-space: nowrap;
+		padding: .5em;
+		cursor: pointer;
+		border-radius: 10px;
+		border-radius: var(--radius);
+	}
+#up_quick .overlay_plus {
+	right: .3em;
+	margin-top: -1.5em;
+}
+#up_quick .btn{
+	transition: all .25s;
+	margin: -1px;
+	border: none;
+	cursor: pointer;
+	width: max-content;
+	border: 1px solid var(--bg-u3);
+	font-family: initial;
+}
+html:not(.e):not(.d) #up_quick .btn {
+	transform: rotate(0deg);
+}
+html:not(.e):not(.d) #up_quick .btn.on {
+	/* transform: rotate(-90deg); */
+	box-shadow: -.05em .02em .3em color-mix(in oklab, var(--a) 20%, transparent);
+}
+#up_quick .btn .rotatable {
+	transition: all .20s;
+	transform: rotate(-45deg);
+
+	background: none;
+
+	font-size: 1.2em;
+	line-height: .9em;
+}
+#up_quick .btn.on .rotatable {
+	transform: none;
 }
 #wfs, #wfm, #wzip, #wnp, #wm3u {
@@ -1177,11 +1587,16 @@
 }
 #wfs, #wzip, #wnp, #wm3u {
-	margin-right: .2em;
+	margin-left: .2em;
 	padding-right: .2em;
-	border: 1px solid var(--bg-u5);
-	border-width: 0 .1em 0 0;
+	border: 0 solid var(--bg-u5);
 }
 #wzip1 {
-	margin-right: .2em;
+	margin-right: 0em;
+}
+#wzip {
+    margin-left: 0 !important;
+    padding-left: 0 !important;
+    margin-right: 0 !important;
+    padding-right: 0 !important;
 }
 #wfm.act+#wzip1+#wzip,
@@ -1189,5 +1604,5 @@
 	margin-left: .2em;
 	padding-left: .2em;
-	border-left-width: .1em;
+	border-left-width: 2px;
 }
 #wfs.act,
@@ -1195,7 +1610,12 @@
 	display: inline-block;
 }
+#wfm .x {
+	font-size: 1.5em;
+	line-height: .68em !important;
+}
 #wtoggle,
 #wtoggle * {
 	line-height: 1em;
+	vertical-align: middle;
 }
 #wtoggle.sel #wzip,
@@ -1208,17 +1628,17 @@
 	display: none;
 }
-#wfm a,
-#wnp a,
-#wm3u a,
-#zip1,
-#wzip a {
+#wtoggle a {
 	font-size: .5em;
-	padding: 0 .3em;
-	margin: -.3em .1em;
-	position: relative;
+	padding: .5em .5em;
+	margin: 0em .1em -.23em .1em;
 	display: inline-block;
 }
-#zip1 {
-	font-size: .38em;
+#wtoggle #zip1 {
+	padding: .2em .4em;
+  	position: relative;
+	white-space: nowrap;
+	width: 2em;
+	height: 2em;
+	vertical-align: top;
 }
 #wm3u a {
@@ -1231,5 +1651,5 @@
 	line-height: 1.3em;
 	padding: 0 .3em 0 0;
-	border-width: 0 .25em 0 0;
+	border-width: 0 2px 0 0;
 }
 #wfm span,
@@ -1237,10 +1657,32 @@
 #zip1 span,
 #wnp span {
-	font-size: .6em;
+	font-size: .7em;
 	display: block;
 }
+#zip1:hover {
+	--fg: var(--a);
+}
 #zip1 span {
-	font-size: .9em;
+	text-shadow: none;
+	background: var(--fg);
+	color: var(--bg-max);
+	border-radius: 2px;
+	padding: 1px 3px;
+	position: absolute;
+	z-index: 1;
+	top: 1.4em;
+	font-size: .6em;
+	margin-left: .2em;
+}
+
+.unfun #up_quick_more a:after {
+	content: "+"
+}
+
+.unfun #up_quick_more a span,
+.unfun #favico_onpage {
+	display: none;
 }
+
 #wnp span {
 	font-size: .7em;
@@ -1276,77 +1718,126 @@
 	margin: -.3em .1em;
 }
-#wtoggle.sel .l1 {
-	top: -.6em;
-	padding: .4em .3em;
+#trackname {
+	font-size: small;
+	font-weight: bold;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	margin-left: .3em;
+	padding: 1.2em 0;
+	min-height: 1em;
+  	border-radius: var(--radius);
+}
+#progbar {
+	display: none;
+	margin: 0 .3em;
+	font-size: 20px;
+}
+#progbar.vis {
+	display: block;
+}
+#altprogbar {
+	display: none;
+	grid-template-columns: max-content auto max-content;
+	text-align: center;
+}
+#altprogbar.vis {
+	display: grid;
+}
+#altprogbar span {
+	font-size: 16px;
+	font-family: monospace;
+	font-family: var(--font-mono);
+	color: var(--fg-weak);
+	margin: .3em;
+	align-self: center;
+}
+#sliderpos {
+	width: 100%;
 }
 #barpos,
 #barbuf {
+	border-radius: 10px;
+	border-radius: var(--radius);
 	position: absolute;
-	bottom: 1em;
-	left: 1em;
-	height: 2em;
-	border-radius: 9em;
-	width: calc(100% - 2em);
+	top: 0;
+	height: 100%;
+	width: 100%;
 }
 #barbuf {
 	background: var(--mp-b-bg);
-	z-index: 21;
+	filter: contrast(1) saturate(1.1) brightness(1.1);
 }
 #barpos {
 	box-shadow: -.03em -.03em .7em rgba(0,0,0,0.5) inset;
-	z-index: 22;
+	border: var(--a-dark) solid 1px;
 }
 #pctl {
-	position: absolute;
-	top: .5em;
-	left: 1em;
+	display: flex;
+	display: grid;
+  	grid-template-columns: max-content max-content max-content 20% auto max-content max-content max-content;
+	align-items: center;
+	margin-right: .4em;
+	margin-left: .1em;
+	padding: .1em;
 }
-#pctl a {
-	display: inline-block;
-	font-size: 1.25em;
-	width: 1.3em;
-	height: 1.2em;
-	text-align: center;
-	border-radius: .3em;
+.pctl .icon {
+	width: 1.5em;
+	height: 1.5em;
+}
+.pctl .icon.btn {
+	padding: .2em;
+	margin: .1em;
+}
+#favico_onpage svg {
+	margin: -1px 7px 0 0;
+    vertical-align: top;
+	height: 1.5em;
+}
+.pctl svg {
+	width: 1.5em;
+	height: 1.5em;
+}
+.pctl .btn {
+	font-size: 1em;
+	padding: 0;
+	opacity: .4;
+	cursor: pointer;
+}
+.pctl .btn.on {
+	opacity: 1;
 }
 #pvol {
-	position: absolute;
-	top: .7em;
-	right: 1em;
-	height: 1.6em;
 	border-radius: 9em;
-	max-width: 12em;
-	width: calc(100% - 10.5em);
+	max-width: 9em;
+	height: .5em;
+	aspect-ratio: 8;
 	background: rgba(0,0,0,0.2);
+	border: 1px solid var(--bg-u1);
+	margin-left: .3em;
+	transition: opacity .15s;
 }
-#widget.cmp {
-	height: 1.6em;
-	bottom: -1.6em;
+#pvolbg {
+	height: 1.3em;
+	padding: .3em;
+	cursor: pointer;
+	align-items: center;
+	display: flex;
 }
-#widget.cmp.open {
-	bottom: 0;
+#pvolbg svg {
+	height: 1.3em;
 }
-#widget.cmp #wtoggle {
-	font-size: 1.2em;
+#widget.cmp #wstack {
+	font-size: 1.4em;
 }
-#widget.cmp #fshr,
-#widget.cmp #wtgrid {
+#widget.cmp #fshr {
 	display: none;
 }
-#widget.cmp #pctl {
-	top: 0;
-	left: 0;
-	font-size: .75em;
-}
-#widget.cmp #pctl a {
-	margin: 0;
+#widget.cmp #widgeti {
+	font-size: .7em;
 }
 #widget.cmp #barpos,
 #widget.cmp #barbuf {
-	height: 1.6em;
-	width: calc(100% - 11em);
 	border-radius: 0;
-	left: 5em;
-	top: 0;
 }
 #widget.cmp #pvol {
@@ -1356,25 +1847,142 @@
 	border-radius: 0;
 }
+
+#music {
+	margin: 0;
+	border-radius: 0;
+	border: none;
+	background: #ccc;
+	background: var(--bg-u2);
+}
+#music a {
+	cursor: pointer;
+}
+html.f #mu_outer,
+html.b #mu_outer {
+	background: color-mix(in oklab, color-mix(in oklab, var(--a) 10%, var(--bg-u2) 10%) 20%, transparent);
+	backdrop-filter: blur(32px);
+}
+#h_music {
+	position: absolute;
+	top: 0;
+	left: 0;
+}
+#mu_outer {
+	display: flex;
+  	justify-content: center;
+	width: 100%;
+}
+#mu_inner {
+	align-content: center;
+	text-align: center;
+	position: relative;
+	width: 100%;
+}
+#mu_th {
+	cursor: default !important;
+	display: flex;
+	position: relative;
+	height: 35vh;
+}
+#mu_th svg {
+	margin: 0 auto;
+	min-width: 100%;
+	height: 100%;
+}
+#mu_th span {
+	font-size: 8vh;
+}
+#mu_th img {
+	opacity: 0;
+	margin: 0 auto;
+	position: absolute;
+	max-width: 100%;
+	object-fit: scale-down;
+}
+#mu_tn {
+	display: block;
+	margin-top: .5em;
+	min-height: 2em;
+	font-size: 1.3em;
+}
+#mu_pbc {
+	font-size: 1.6em;
+}
+#mu_pbb {
+	height: 3em;
+	margin: .5em auto;
+	width: 80vw;
+	display: block;
+	position: relative;
+	align-content: center;
+	text-align: left;
+}
+#mu_vol {
+	position: absolute;
+	right: 1em;
+	bottom: 1em;
+	font-size: 1.2em;
+}
+
+
 .opview {
 	display: none;
+	margin-bottom: 1em;
 }
+.opview.splitsub {
+	margin-bottom: 0;
+}
+#acc_cbopen:checked+span,
 .opview.act {
 	display: block;
 }
+#op_search.opview.act {
+	display: none;
+}
+#op_search.opview.act.vis {
+	display: block;
+}
+#ops input[type=submit],
 #ops a {
+	/* fallback if emojis don't render */
+	border: #3a3e42 solid 1px;
+	border-radius: 5px;
+	min-width: 1em;
+
+	border-radius: var(--radius);
+	border: var(--transparent) solid 0px;
+	color: #07c;
 	color: var(--a);
-	text-shadow: 1px 1px 1px var(--op-a-sh);
 	font-size: 1.5em;
-	padding: .25em .4em;
+	padding: .15em .4em;
 	margin: 0;
 }
+#ops a:first-child {
+  margin-left: auto;
+}
+.unfun #ops a {
+	font-size: 1.2em;
+	padding-top: .35em !important;
+}
+.unfun #srchfolder_div {
+	left: 5.2em;
+}
+#opa_mkd {
+	display: none;
+}
+#ops a.vis {
+	display: block;
+}
 #ops a.act {
 	color: #fff;
 	color: var(--op-aa-fg);
-	background: #000;
+	background: rgb(85, 144, 255);
 	background: var(--op-aa-bg);
-	border-radius: 0 0 .2em .2em;
-	border-bottom: .3em solid var(--a-b);
+	border-radius: 0 0 5px 5px;
+	border-radius: 0 0 var(--radius) var(--radius);
+	border-bottom: .3em solid var(--a);
 	box-shadow: var(--op-aa-sh);
+	margin: 0 0 -.35em 0;
+	padding-top: calc(.2em - 1px);
 }
 #ops a svg {
@@ -1386,7 +1994,42 @@
 	stroke: black;
 }
+#srv_name {
+	padding-top: .5em;
+	padding-bottom: .3em;
+	margin-left: 2.2em;
+	font-size: x-large;
+	text-wrap-mode: nowrap;
+	text-wrap: nowrap;
+	white-space: nowrap;
+	display: block;
+	line-height: 1.5em;
+}
+#favico_onpage {
+	display: none;
+}
+@supports (text-wrap: nowrap) {
+	#favico_onpage {
+		display: inline-block;
+	}
+}
+#treeToggleBtn{
+	z-index: 30;
+	position: fixed;
+	top: 0;
+	left: 0;
+	margin: .5em;
+	padding: 0;
+	width: 2em;
+	height: 2em;
+	vertical-align: bottom;
+}
 #ops {
-	padding: .3em .6em;
+	padding: 0 .5em;
 	white-space: nowrap;
+	display: flex;
+	height: 2.5em;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	background: var(--bg-u2);
 }
 #noie {
@@ -1394,33 +2037,70 @@
 	margin: 0 0 0 .5em;
 }
+.pfp {
+	/* aspect-ratio: 1 / 1; */
+	min-width: 1.25em;
+
+	background: #3584e4;
+	color: #000;
+
+	background: var(--btn-1-bg);
+	box-shadow: var(--btn-1-bs);
+	color: var(--bg);
+
+	border: var(--btn-bg-a) solid 1px;
+	border-radius: 5px;  
+	border-radius: var(--radius);  
+	justify-content: center;
+  	display: flex;
+	text-shadow: none;
+}
+.pfp.placeholder {
+	text-shadow: 1px 1px 0 var(--bg-half), 1px -1px 0 var(--bg-half), -1px -1px 0 var(--bg-half), -1px 1px 0 var(--bg-half);
+}
+#op_acc:not(.act) {
+	display: none;
+}
 .opbox {
 	padding: .5em;
-	border-radius: 0 .3em .3em 0;
-	border-width: 1px 1px 1px 0;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	border-width: 1px;
 	max-width: 41em;
-	max-width: min(41em, calc(100% - 2.6em));
 }
 .opbox input {
 	position: relative;
-	margin: .5em;
+	margin: .2em;
 }
-#op_cfg input[type=text] {
-	top: -.3em;
+#op_bup {
+	margin: 0;
+	border-radius: 0 0 var(--radius) var(--radius);
 }
+#op_cfg {
+	display: none;
+}
+#pathBar input[type=text],
 .opview select,
-.opview input[type=text] {
+.opview input[type=number],
+.opview input[type=text],
+.opview input[type=color],
+.opview input[type=date] {
+	color: #fff;
 	color: var(--fg);
+	background: #222529;
 	background: var(--txt-bg);
 	border: none;
 	box-shadow: 0 0 2px var(--txt-sh);
-	border-bottom: 1px solid #999;
+	border-bottom: 1px solid #3584e4;
 	border-color: var(--a);
-	border-radius: .2em;
+	border-radius: 5px;
+	border-radius: var(--radius);
 	padding: .2em .3em;
+	font-size: medium;
+	min-width: 3em;
+	margin: .3em;
+	font-family: var(--font-main);
 }
-.opview select {
-	padding: .3em;
-	margin: .2em .4em;
-	background: var(--bg-u3);
+input[type=color] {
+	vertical-align: bottom;
 }
 .opview input.err {
@@ -1437,5 +2117,5 @@
 input[type="radio"]:checked+label,
 input[type="checkbox"]:checked+label {
-	color: #0e0;
+	color: rgb(9, 185, 9);
 	color: var(--btn-1-bg);
 }
@@ -1454,8 +2134,18 @@
 }
 .opview input.i {
-	width: calc(100% - 16.2em);
+	width: calc(100% - 12em);
+}
+input.eq_gain[type=range]{
+	writing-mode: vertical-lr;
+    direction: rtl;
+	color: var(--a);
+}
+input.eq_gain[type=text]{
+    width: 2em;
+	min-width: 0;
+	margin: 0 .15em .5em .15em;
+	text-align: center;
 }
 input.drc_v,
-input.eq_gain,
 input.ssconf_v {
 	width: 3em;
@@ -1478,11 +2168,4 @@
 }
 #au_ss,
-#au_drc,
-#au_eq {
-	display: block;
-	margin-top: .5em;
-	padding: 1.3em .3em;
-}
-#au_ss,
 #au_drc {
 	padding: .4em .3em;
@@ -1493,7 +2176,4 @@
 	overflow: hidden;
 }
-#ico1 {
-	cursor: pointer;
-}
 
 
@@ -1522,7 +2202,7 @@
 #srch_q {
 	white-space: pre;
-	color: var(--a-b);
+	color: var(--a);
 	min-height: 1em;
-	margin: .2em 0 -1em 1.6em;
+	margin-top: .2em;
 }
 #srch_q.err {
@@ -1542,5 +2222,23 @@
 	display: block;
 }
-#files td div span {
+
+#hspace {
+	display: inline-block;
+}
+#wfp{
+	margin: 0;
+	white-space: nowrap;
+	display: inline-block;
+}
+#wfp .btn {
+	min-width: 2em;
+	width: 2em;
+	height: 2em;
+	font-size: 1em;
+	padding: .2em;
+	margin: 0 .6em 0 0;
+}
+
+#files td div:not(.imgcontainer) span {
 	color: var(--fg-max);
 	padding: 0 .4em;
@@ -1563,38 +2261,63 @@
 	width: 100%;
 }
+
 #wrap {
-	margin: 1.8em 1.5em 0 1.5em;
-	min-height: 70vh;
-	padding-bottom: 7em;
+	padding:  0 .6em 10em .6em;
+	min-height: calc(100vh - 10em);
 }
 #tree {
 	display: none;
-	position: absolute;
+	position: fixed;
+	top: 0;
 	left: 0;
 	bottom: 0;
-	top: 7em;
 	overflow-x: hidden;
 	overflow-y: auto;
 	-ms-scroll-chaining: none;
 	overscroll-behavior-y: none;
-	box-shadow: 0 0 1em var(--bg-d2), 0 -1px 0 rgba(128,128,128,0.3);
+	box-shadow: 0 0 1em var(--mp-sh), 0 -1px 0 rgba(128,128,128,0.3);
+	border: 1px solid var(--bg-u3);
+	transition: width .05s;
+	z-index: 20;
+	padding-bottom: 3em;
+	border-width: 0 1px 0 0;
 }
+#files,
+#wtoggle,
+#ghead,
 #tree,
+#path,
+.splitsub,
+.modalsplit,
 html {
-	scrollbar-color: var(--scroll) var(--bg-u3);
+	scrollbar-face-color: #3584e4;
+	scrollbar-face-color: var(--a);
+	scrollbar-highlight-color: #1a1c20;
+	scrollbar-track-color: #2f3236;
+	scrollbar-arrow-color: #3584e4;
+	scrollbar-shadow-color: #1a1c20;
+
+	scrollbar-color: var(--a) color-mix(in oklab, var(--bg-u3), transparent);
 }
-#treeh {
+#treesuperh {
 	position: sticky;
-	z-index: 1;
 	top: 0;
-	height: 2.2em;
-	line-height: 2.2em;
+	z-index: 1;
 	background: var(--tree-bg);
+}
+#treeh {
+	line-height: 2.2em;
 	border-bottom: 1px solid var(--bg-d3);
-	overflow: hidden;
+	display: block;
+	padding: .3em .5em;
+	text-wrap-mode: nowrap;
+	text-wrap: nowrap;
+	white-space: nowrap;
+	overflow-x: auto;
+	overflow-y: hidden;
 }
 #treepar {
-	z-index: 1;
 	position: fixed;
+	z-index: 1;
 	background: #fff;
 	background: var(--tree-bg);
@@ -1608,18 +2331,8 @@
 	display: none;
 }
-.np_open #thx_ff {
-	padding: 4.5em 0;
+#thx_ff {
+	padding: 1em 0;
 	/* widget */
 }
-#tree::-webkit-scrollbar-track,
-#tree::-webkit-scrollbar {
-	background: var(--bg-u3);
-}
-#tree::-webkit-scrollbar-thumb {
-	background: var(--scroll);
-}
-#tree:hover {
-	z-index: 2;
-}
 #treeul {
 	position: relative;
@@ -1627,25 +2340,52 @@
 	width: calc(100% + 2em);
 }
+#reszbar {
+	position: fixed;
+	z-index: 25;
+	top: 0;
+	bottom: 0;
+	margin-left: -5px;
+	width: 10px;
+	opacity: 0;
+	background: #888;
+	background: var(--a);
+	border: var(--bg) solid 1px;
+	border-width: 0 1px;
+	cursor: ew-resize;
+	touch-action: none;
+	user-select: none;
+	transition: opacity .1s;
+}
+#reszbar:hover{
+	opacity: .6;
+}
+.resz,
+.resz * {
+	transition: none !important;
+}
 .btn {
+	color: #3584e4;
 	color: var(--btn-fg);
-	background: #eee;
+	background: #2f3236;
 	background: var(--btn-bg);
 	box-shadow: var(--btn-bs);
+	border: 1px solid #3a3d41;
+	border: 1px solid var(--bg-u3);
 	border-bottom: var(--btn-bb);
-	border-radius: .3em;
+	border-radius: 5px;
+	border-radius: var(--radius);
 	padding: .2em .4em;
 	font-size: 1.2em;
 	margin: .2em;
 	display: inline-block;
-	white-space: pre;
+	text-wrap-mode: wrap;
+	white-space-collapse: preserve;
 	position: relative;
-	top: -.12em;
+	vertical-align: top;
+	cursor: pointer;
 }
 html.c .btn,
-html.a .btn {
-	border-radius: .2em;
-}
-html.dz .btn {
-	font-size: 1em;
+html.b .btn {
+	border-radius: var(--radius);
 }
 .btn:hover {
@@ -1656,7 +2396,7 @@
 }
 .tgl.btn.on {
-	background: #000;
+	background: rgb(85, 144, 255);
 	background: var(--btn-1-bg);
-	color: #fff;
+	color: #222;
 	color: var(--btn-1-fg);
 	text-shadow: none;
@@ -1695,4 +2435,8 @@
 	z-index: 3;
 }
+#tree li a.dtarget {
+	box-shadow: 0 0 5px 0 var(--f-sel-sh) inset;
+	background: color-mix(in oklab, var(--a) 30%, transparent);
+}
 #tree ul a.sel {
 	background: #000;
@@ -1702,22 +2446,27 @@
 	color: var(--fg-max);
 }
+#tree .ntree a + a.hl:hover {
+	background: var(--btn-1h-bg);
+}
 #tree ul a.hl {
 	color: #fff;
 	color: var(--btn-1-fg);
-	background: #000;
+	background: rgb(85, 144, 255);
 	background: var(--btn-1-bg);
+	box-shadow: var(--btn-1-bs);
 	text-shadow: none;
 }
 #tree ul a.ld::before {
-	font-weight: bold;
-	font-family: sans-serif;
-	display: inline-block;
-	text-align: center;
+	position: absolute;
 	width: 1em;
-	margin: 0 .3em 0 -1.3em;
-	color: var(--fg-max);
-	opacity: 0;
-	content: '◠';
-	animation: .5s linear infinite forwards spin, ease .25s 1 forwards fadein;
+	height: 1em;
+	margin: -4px 0 0 -.3em;
+	content: "";
+	animation: .7s linear infinite forwards spin, ease .25s 1 forwards fadein;
+	border: 2px solid;
+	border-radius: 1em;
+	border-color: currentColor currentColor transparent transparent;
+	position: absolute;
+	z-index: 1;
 }
 #tree ul a.par {
@@ -1725,6 +2474,12 @@
 }
 #tree ul a {
-	border-radius: .3em;
+	position: relative;
+	border-radius: 5px;
+	border-radius: var(--radius);
 	display: inline-block;
+	vertical-align: middle;
+}
+.ntree a {
+	padding: .3em .2em;
 }
 .ntree a+a {
@@ -1736,17 +2491,13 @@
 	white-space: nowrap;
 }
-#tree.nowrap .ntree a+a:hover {
-	background: rgba(16, 16, 16, 0.67);
-	min-width: calc(var(--nav-sz) - 2em);
-	width: auto;
+#docul a:hover {
+	color: var(--btn-h-fg);
 }
-html.y #tree.nowrap .ntree a+a:hover {
-	background: rgba(255, 255, 255, 0.67);
-	color: var(--fg-max);
+#tree .ntree a + a:hover {
+	background: var(--btn-h-bg);
+	color: var(--btn-h-fg);
 }
-#docul a:hover,
-#tree .ntree a+a:hover {
-	background: var(--bg-d2);
-	color: var(--fg-max);
+#tree .ntree a + a.hl:hover {
+	color: var(--btn-1-fg);
 }
 .ntree a:first-child {
@@ -1754,5 +2505,5 @@
 	font-family: var(--font-mono), 'scp', monospace, monospace;
 	font-size: 1.2em;
-	line-height: 0;
+	line-height: .7em;
 }
 .dumb_loader_thing {
@@ -1767,5 +2518,5 @@
 	position: fixed;
 	top: .3em;
-	z-index: 9;
+	z-index: 31;
 }
 #dlt_t {
@@ -1781,7 +2532,7 @@
 }
 #files th:hover .cfg {
-	display: block;
+	display: none;
 	width: 1em;
-	border-radius: .2em;
+	border-radius: var(--radius);
 	margin: -1.2em auto 0 auto;
 	background: var(--bg-u5);
@@ -1795,7 +2546,45 @@
 	display: none;
 }
-#files td:nth-child(2n) {
+#files td:nth-child(3){
 	color: var(--tab-alt);
 }
+#files td:has(.imgcontainer) {
+	padding: 0 1px;
+}
+#files .imgcontainer {
+	width: 2.3em;
+	height: 2.3em;
+	cursor: pointer;
+	margin: 0;
+	padding: 0;
+}
+#files .play .imgcontainer {
+	margin: -2px 0px;
+	padding: 2px;
+}
+#files .thumb,
+#files img {
+	height: 100%;
+}
+#files tbody tr.sel span.th_ext span,
+#files tbody tr.sel span.th_ext {
+	background: none;
+}
+#files .dir.thumbed img,
+#files .dir .thumbed img {
+	position: absolute;
+	opacity: .6;
+}
+#files .dir.thumbed .thumb,
+#files .dir .thumbed .thumb {
+	display: block !important;
+	z-index: 2;
+}
+#files .au .imgcontainer::after {
+  content: "▶";
+  position: absolute;
+  z-index: 2;
+  top: .6em;
+}
 #plazy {
 	width: 1px;
@@ -1818,5 +2607,4 @@
 #srch_form {
 	max-width: none;
-	margin-right: 1.5em;
 }
 .opwide>div {
@@ -1832,13 +2620,9 @@
 	padding: 0;
 }
-#op_cfg>div>div>span {
-	display: inline-block;
-	padding: .2em .4em;
-}
 .opbox h3 {
 	margin: .8em 0 0 .6em;
 	padding: 0;
 }
-#thumbs,
+.disabled,
 #au_prescan,
 #au_fullpre,
@@ -1848,10 +2632,17 @@
 	opacity: .3;
 }
-#griden.on+#thumbs,
-#au_preload.on+#au_prescan,
-#au_preload.on+#au_prescan+#au_fullpre,
-#au_os_ctl.on+#au_os_seek,
-#au_os_ctl.on+#au_os_seek+#au_osd_cv,
-#u2turbo.on+#u2tdate {
+.disabled {
+	cursor: default;
+}
+#up_quick.disabled {
+	display: none;
+}
+.setting:has(#au_preload.on)+.setting #au_prescan,
+.setting:has(#au_preload.on)+.setting #au_prescan,
+.setting:has(#au_preload.on)~.setting #au_fullpre,
+.setting:has(#au_os_ctl.on)+.setting #au_os_seek,
+.setting:has(#au_os_ctl.on)+.setting #au_os_seek,
+.setting:has(#au_os_ctl.on)~.setting #au_osd_cv,
+.setting:has(#u2turbo.on)+.setting #u2tdate {
 	opacity: 1;
 }
@@ -1860,16 +2651,16 @@
 }
 .ghead {
-	background: #fff;
+	position: sticky;
+	background: #1a1c20;
 	background: var(--bg-u2);
-	border-radius: .3em;
-	padding: .2em .5em;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	padding: 0 .2em;
 	line-height: 2.3em;
 	margin-bottom: 1.5em;
-}
-#hdoc,
-#ghead {
-	position: sticky;
-	top: -.3em;
-	z-index: 2;
+	z-index: 10;
+	margin-top: 0;
+	top: 3.7em;
+	transition: opacity .15s;
 }
 .ghead .btn {
@@ -1877,8 +2668,53 @@
 	top: 0;
 }
+#fsav,
+#gridsel.temp {
+	animation: pulsating 1s alternate infinite;
+}
+@keyframes pulsating {
+	0% {
+		border-color: var(--btn-bg);
+		background: transparent;
+	}
+
+	100% {
+		border-color: var(--btn-1-bg);
+		background: color-mix(in oklab, var(--btn-1-bg) 50%, transparent);
+	}
+}
+#twig,
+#gridchop a:first-child,
+#gridzoom a:first-child {
+  line-height: 1.1em;
+  padding-bottom: .3em;
+}
 .ghead>span {
 	white-space: pre;
 	padding-left: .3em;
 }
+.ghead details {
+	display: inline-block;
+	background: transparent;
+	box-shadow: var(--btn-bs);
+}
+.ghead summary {
+	background: var(--btn-bg);
+}
+.ghead details .setting {
+	position: absolute;
+	background: #2f3236;
+	background: var(--bg-u2);
+	border: 1px solid var(--bg-u3);
+  	border-bottom: var(--btn-bb);
+	border-radius: 5px;
+	border-radius: var(--radius);
+	max-width: 11em;
+	max-height: 30vh;
+	overflow-y: auto;
+	z-index: 11;
+}
+#hcol_content:has(#hcolsh.on) {
+  margin-top: 3.5em;
+}
 #tailbtns {
 	display: none;
@@ -1902,4 +2738,10 @@
 	text-align: right;
 }
+#unpost table {
+	overflow-x: auto;
+	display: block;
+	margin: 0 -1em;
+	padding: 0 1em;
+}
 #shui,
 #rui {
@@ -1950,4 +2792,7 @@
 	width: 100%;
 }
+input[type="text"].invalid {
+	border-color: var(--err-b1);
+}
 #rui #rn_n_d,
 #rui #rn_n_s,
@@ -1989,12 +2834,14 @@
 	margin: -1em 0 .5em 0;
 	padding: 1em 0 1em 0;
-	border-radius: .3em;
+	border-radius: var(--radius);
+	overflow: auto;
+	box-shadow: 0 0 .3em var(--bg-d2);
+	background: var(--bg-u2);
 }
 #doc.wrap {
 	white-space: pre-wrap;
 }
-html.y #doc {
-	box-shadow: 0 0 .3em var(--bg-u5);
-	background: #f7f7f7;
+#bdoc {
+	margin-top: .3em;
 }
 #docul {
@@ -2004,8 +2851,14 @@
 	text-align: center;
 	padding: .5em;
+	white-space: normal;
 }
 #docul li.bn span {
-	font-weight: bold;
-	color: var(--fg-max);
+	color: var(--fg-weak);
+	vertical-align: middle;
+	text-shadow: none;
+}
+#docul li a {
+	display: inline;
+	padding: 0 !important;
 }
 #doc.prism {
@@ -2026,5 +2879,5 @@
 }
 #doc .line-highlight {
-	border-radius: .3em;
+	border-radius: var(--radius);
 	box-shadow: 0 0 .5em rgba(128,128,128,0.2);
 	background: linear-gradient(90deg, var(--bg-d3), var(--bg));
@@ -2040,5 +2893,5 @@
 }
 #seldoc.sel {
-	color: var(--fg2-max);
+	color: var(--fg-max);
 	background: #f0f;
 	background: var(--g-sel-b1);
@@ -2047,12 +2900,10 @@
 #barbuf,
 #barpos,
-a.btn,
+.btn,
 #u2btn,
-#u2conf label,
 #rui label,
 #modal-ok,
 #modal-ng,
-#ops,
-#ico1 {
+#ops {
 	-webkit-user-select: none;
 	-moz-user-select: none;
@@ -2088,4 +2939,10 @@
 }
 html.noscroll,
+html.noscroll .modalsplit,
+html.noscroll .splitsub,
+html.noscroll #treeh,
+html.noscroll .ghead,
+html.noscroll #wtoggle,
+html.noscroll #path,
 html.noscroll .sbar {
 	scrollbar-width: none;
@@ -2143,5 +3000,5 @@
 	width: 100%;
 	height: 100%;
-	z-index: 10;
+	z-index: 50;
 	background: rgba(0, 0, 0, 0.8);
 	transition: opacity .3s ease;
@@ -2150,4 +3007,7 @@
 	opacity: 1;
 }
+#bbox-overlay.sel {
+	background: color-mix(in oklab, var(--a-dark) 70%, transparent);
+}
 .full-image {
 	display: inline-block;
@@ -2176,4 +3036,9 @@
 	transition: transform .23s, left .23s, top .23s, width .23s, height .23s;
 }
+.immersive .full-image img,
+.immersive .full-image video {
+	max-height: 100%;
+	margin-bottom: 0;
+}
 .full-image img.asap,
 .full-image video.asap {
@@ -2186,4 +3051,15 @@
     object-fit: contain;
 }
+#bbox-overlay:has(video) { 	
+	#bbox-next,
+	#bbox-prev {
+		height: 0;
+	}
+}
+#bbox-overlay.pixelated .full-image img,
+#bbox-overlay.pixelated .full-image video {
+	-ms-interpolation-mode: nearest-neighbor;
+	image-rendering: pixelated;
+}
 html.bb_fsc .full-image img,
 html.bb_fsc .full-image video {
@@ -2204,4 +3080,8 @@
 	background: #222;
 }
+#bbox-overlay.immersive figcaption {
+	opacity: 0;
+	pointer-events: none;
+}
 .full-image figcaption {
 	display: block;
@@ -2213,8 +3093,9 @@
 	color: var(--fg);
 	z-index: 1;
+	transition: opacity .15s;
 }
 #bbox-overlay figcaption a {
 	background: rgba(0, 0, 0, 0.6);
-	border-radius: .4em;
+	border-radius: var(--radius);
 	padding: .3em .6em;
 }
@@ -2260,59 +3141,74 @@
 	0% {filter: brightness(1.5)}
 }
-#bbox-next,
-#bbox-prev {
-	top: 50%;
-	top: calc(50% - 30px);
-	width: 44px;
-	height: 60px;
-	transition: background-color .3s ease, color .3s ease, left .3s ease, right .3s ease;
+#bbox-overlay #bbox-next,
+#bbox-overlay #bbox-prev {
+	position: fixed;
+	top: 0;
+	bottom: 0;
+	width: 70px;
+	left: 0;
+	cursor: pointer;
 }
-#bbox-btns button {
-	transition: background-color .3s ease, color .3s ease;
+#bbox-overlay #bbox-next {
+	right: 0;
+	left: auto;
 }
-#bbox-btns {
-	transition: top .3s ease;
+#bbox-overlay #bbox-next:hover a,
+#bbox-overlay #bbox-prev:hover a {
+	background: #fff2;
+	background: var(--btn-h-bg);
 }
-.bbox-btn {
+#bbox-overlay #bbox-next a,
+#bbox-overlay #bbox-prev a {
 	position: fixed;
+	top: 50%;
+	top: calc(50% - 30px);
+	width: 30px;
+	height: 60px;
+	transition: opacity .3s ease;
+	text-align: center;
+	line-height: 60px;
 }
-#bbox-next.off {
-	right: -2.6em;
+#bbox-btns .btn {
+	cursor: pointer;
+	direction: ltr;
 }
-#bbox-prev.off {
-	left: -2.6em;
+#bbox-overlay.immersive #bbox-next a,
+#bbox-overlay.immersive #bbox-prev a {
+	opacity: 0;
 }
-#bbox-btns.off {
-	top: -2.2em;
+#bbox-overlay.immersive #bbox-btns {
+	opacity: 0;
+	top: -3.5em;
+	pointer-events: none;
 }
-#bbox-overlay button {
+#bbox-overlay .btn {
 	cursor: pointer;
-	outline: none;
-	padding: 0 .3em;
-	margin: 0 .4em;
-	border: 0;
-	border-radius: 15%;
-	background: rgba(50, 50, 50, 0.5);
-	color: rgba(255,255,255,0.7);
+	padding: 0 .4em;
+	margin: 0 .1em .3em .1em;
 	font-size: 1.4em;
-	line-height: 1.4em;
+	line-height: 2em;
 	vertical-align: top;
 	font-variant: small-caps;
+	color: var(--fg);
 }
-#bbox-overlay button:focus,
-#bbox-overlay button:hover {
-	color: rgba(255,255,255,0.9);
-	background: rgba(50, 50, 50, 0.9);
+#bbox-overlay .btn.on {
+	color: var(--bg);
+}
+#bbox-overlay .btn:hover {
+	opacity: 1 !important;
 }
-#bbox-next {
+#bbox-next a {
 	right: 1%;
 }
-#bbox-prev {
+#bbox-prev a {
 	left: 1%;
 }
 #bbox-btns {
 	top: .5em;
-	right: 2%;
+	right: .2em;
 	position: fixed;
+	direction: rtl;
+	transition: top .3s ease, opacity .3s ease;
 }
 #bbox-halp {
@@ -2323,5 +3219,5 @@
 	top: 0;
 	left: 0;
-	z-index: 20;
+	z-index: 51;
 	padding: .4em;
 }
@@ -2386,20 +3282,210 @@
 
 
+/* modals */
+.modalcontent{
+	display: grid;
+  	grid-template-rows: max-content auto;
+	margin: 5vh 5vw;
+	border-radius: 10px;
+	border-radius: var(--radius);
+	border: var(--a) solid 1px;
+	background: #1a1c20;
+	background: var(--bg);
+	max-width: 66.6em;
+	position: relative;
+	overflow: auto;
+	box-shadow: .1em .2em 1em var(--mp-sh);
+}
+html.c .modalcontent {
+	background: var(--bg-u2);
+}
+.modalheader {
+	display: flex;
+	align-items: center;
+	margin: 0;
+	padding: .7em 3em .7em .7em;
+	background: var(--ttlbar);
+	font-size: large;
+	font-weight: bold;
+	color: var(--fg);
+}
+.modalheader:hover {
+	color: var(--fg);
+}
+.winbtn,
+.close{
+	position: absolute;
+	cursor: pointer;
+	top: 0;
+	right: 0;
+	margin: .3em;
+	color: var(--fg);
+	height: 1.8em;
+	width: 1.8em;
+	text-align: center;
+	display: block;
+	padding: 0;
+	align-content: center;
+}
+.winbtn {
+	right: 2.3em;
+}
+.winbtn span {
+	font-family: initial;
+	font-size: small;
+    vertical-align: middle;
+}
+
+.closepane {
+	position: absolute;
+	cursor: default;
+	top: 0;
+	left: 0;
+	bottom: 0;
+	right: 0;
+	background: none !important;
+}
+
+/* settings */
+.modalsplit {
+	display: grid;
+  	grid-template-columns: auto auto auto;
+	min-height: 0;
+}
+.splitsub {
+	display: block;
+	overflow-y: auto;
+	padding: 0 .5em 5em .5em;
+}
+.divider{
+	background: var(--a);
+	width: 1px;
+	margin: 1em 0;
+}
+.divider_h {
+	background: var(--bg);
+	height: 1px;
+	margin: .3em 0;
+}
+#s_nav .btn::after{
+	position: absolute;
+	content: ">";
+	right: .5em;
+}
+#s_nav .btn{
+	display: block;
+	padding-right: 2em;
+	border: 1px solid var(--bg-u3);
+	color: var(--fg);
+}
+#s_nav .btn:hover{
+	color: var(--a-hil);
+}
+#s_nav .sub {
+	margin-left: 1em;
+}
+#s_nav .sub:before {
+	content: "";
+	width: 2px;
+	background: var(--bg-u3);
+	position: absolute;
+	left: -.5em;
+	top: -.2em;
+	bottom: -.1em;
+}
+.s_section {
+	border-radius: var(--radius);
+	margin-bottom: 1.5em;
+}
+.s_section .setting:last-child {
+	border-radius: 0 0 5px 5px;
+	border-radius: 0 0 var(--radius) var(--radius);
+}
+.setting{
+	padding: .5em;
+	border: var(--bg-u5) solid 1px;
+	border-top: 0;
+	overflow-x: auto;
+	position: relative;
+}
+.modal .setting:hover{
+	background: var(--bg-u1);
+}
+.s_desc{
+	margin: 0 .3em;
+	font-size: medium;
+	color: var(--fg-weak);
+}
+#s_list h3{
+	background-color: var(--bg-u5);
+	border-radius: 5px 5px 0 0;
+	border-radius: var(--radius) var(--radius) 0 0;
+	padding: .5em;
+	margin: 0;
+}
+#s_list .sub_section h3 {
+	background: none;
+	border-top: var(--bg-u5) solid 1px;
+    border-radius: 0;
+    margin: 0 -.4em;
+    text-align: center;
+}
 
+.modal.vis.unmodal {
+	position: static;
+	max-height: 50vh;
+	z-index: 1;
+}
+.modal.vis.unmodal .modalcontent {
+	width: 100%;
+	max-width: none;
+	margin: 0;
+}
+.modal.vis.unmodal .closepane,
+.modal.vis.unmodal .close {
+	display: none;
+}
+.modal.vis.unmodal .splitsub {
+	margin-bottom: .5em;
+}
 
+#cfg.unmodal .sub_section,
+#cfg.unmodal #s_nav,
+#cfg.unmodal .divider,
+#cfg.unmodal .s_desc {
+	display: none;
+}
 
-/* upload.css */
+#cfg.unmodal .setting,
+#cfg.unmodal #s_list {
+	display: inline-block;
+	display: contents;
+	border: none;
+}
 
-#op_up2k {
-	padding: 0 1em 1em 1em;
+#cfg.unmodal .s_section {
+	margin: 0 .3em;
 }
-#drops {
+
+.c {
+	display: inline-block;
+}
+
+
+/* upload.css */
+
+.modal {
 	display: none;
-	z-index: 3;
+	z-index: 40;
 	background: rgba(48, 48, 48, 0.7);
 }
-#drops.vis,
+.modal.vis,
 .dropzone {
+	/* fallback */
 	display: block;
+	overflow-y: auto;
+
+	display: flex;
+  	justify-content: center;
 	position: fixed;
 	top: 0;
@@ -2413,5 +3499,5 @@
 	left: 10%;
 	width: 78%;
-	height: 26%;
+	height: 78%;
 	margin: 0;
 	font-size: 3em;
@@ -2420,7 +3506,8 @@
 	background: rgba(224, 224, 224, 0.2);
 	box-shadow: 0 0 0 #999;
-	border: .5em solid var(--ud-b1);
-	border-radius: .5em;
-	border-width: 1vw;
+	border: .1em solid var(--ud-b1);
+	border-radius: 10px;
+	border-radius: var(--radius);
+	border-width: 2px;
 	color: #fff;
 	transition: all 0.12s;
@@ -2428,9 +3515,9 @@
 .dropdesc.hl.ok {
 	border-color: #fff;
-	box-shadow: 0 0 1em .4em #cf5, 0 0 1em #000 inset;
+	box-shadow: 0 0 1em .4em var(--a), 0 0 1em #000 inset;
 	background: rgba(24, 24, 24, 0.7);
 	left: 8%;
 	width: 82%;
-	height: 32%;
+	height: 82%;
 	margin: -3vh 0;
 }
@@ -2474,24 +3561,30 @@
 .dropzone {
 	z-index: 80386;
-	height: 50%;
+	height: 100%;
 }
 #up_dz {
-	bottom: 50%;
+	position: absolute;
+	bottom: 0;
 }
 #srch_dz {
-	top: 50%;
+	position: absolute;
+	top: 0;
 }
 #up_zd {
+	position: absolute;
 	top: 12%;
+	bottom: 12%;
 }
 #srch_zd {
+	position: absolute;
+	top: 12%;
 	bottom: 12%;
 }
 .dropdesc span {
 	color: #fff;
-	background: #490;
-	border-bottom: .3em solid #6d2;
+	background: var(--a-dark);
+	border-bottom: .3em solid var(--a);
 	text-shadow: 1px 1px 1px #000;
-	border-radius: .3em;
+	border-radius: var(--radius);
 	padding: .4em .8em;
 	font-size: .4em;
@@ -2513,4 +3606,9 @@
 	border: 0px solid var(--bg-u5);
 }
+#up_status_h {
+	margin: -1.8em .5em 0 .5em;
+	position: absolute;
+	z-index: 1;
+}
 #u2err.err {
 	color: var(--a-dark);
@@ -2525,33 +3623,36 @@
 	line-height: 1.3em;
 	border: .15em dashed var(--u2-btn-b1);
-	border-radius: .4em;
+	border-radius: var(--radius);
 	text-align: center;
-	font-size: 1.5em;
-	margin: .5em auto;
-	padding: .8em 0;
-	width: 16em;
+	font-size: 1.3em;
+	padding: 1em 2em;
 	cursor: pointer;
+	box-shadow: .4em .4em 0 var(--bg-d1);
 }
-#op_up2k.srch #u2btn {
-	border-color: var(--u2-sbtn-b1);
+#u2btn sup {
+	font-size: 1.1em;
 }
-#u2conf.ww #u2btn {
-	line-height: 1em;
-	padding: .5em 0;
-	margin: -2em 1em -3em 0;
+#u2btn:hover {
+	background: linear-gradient(var(--a) 0%, var(--a) 20%, color-mix(in oklab, transparent 85%, var(--a) 15%));
+	border-color: var(--bg);
+	border-style: solid;
+	color: var(--fg-max);
 }
-#u2conf #u2btn {
-	padding: .4em 0;
-	margin: -2em 0;
-	font-size: 1.25em;
-	width: 100%;
-	max-width: 12em;
-	display: inline-block;
+#u2c3t {
+	margin: 1em 0;
 }
-#u2conf #u2btn_cw {
-	text-align: right;
+#op_up2k {
+	display: block;
+	padding-top: .5em;
+}
+#op_up2k.srch #u2btn {
+	border-color: var(--u2-sbtn-b1);
+}
+#up2k_qs {
+	margin-top: .5em;
 }
 #u2bm {
-	display: block;
+	display: flex;
+	justify-content: center;
 }
 #u2bm sup {
@@ -2574,5 +3675,4 @@
 	min-height: 0;
 	transition: min-height .2s;
-	margin: 2em 0;
 }
 #u2tabw.na>table {
@@ -2602,9 +3702,4 @@
 	width: 40%;
 }
-#u2tab.up.ok td:nth-child(3),
-#u2tab.up.bz td:nth-child(3),
-#u2tab.up.q  td:nth-child(3) {
-	width: 18em;
-}
 @media (max-width: 65em) {
 	#u2tab {
@@ -2612,11 +3707,4 @@
 	}
 }
-@media (max-width: 50em) {
-	#u2tab.up.ok td:nth-child(3),
-	#u2tab.up.bz td:nth-child(3),
-	#u2tab.up.q  td:nth-child(3) {
-		width: 16em;
-	}
-}
 #op_up2k.srch td.prog {
 	font-family: sans-serif;
@@ -2633,5 +3721,4 @@
 	cursor: pointer;
 	text-align: center;
-	white-space: nowrap;
 	display: inline-block;
 	font-family: 'scp', monospace, monospace;
@@ -2649,7 +3736,6 @@
 }
 #u2etaw {
-	width: 18em;
-	font-size: .94em;
-	margin: 1.8em auto .5em auto;
+	display: flex;
+	justify-content: center;
 }
 #u2etas.o #u2etaw {
@@ -2657,28 +3743,12 @@
 }
 #u2cards {
-	padding: 1em 1em .42em 1em;
-	margin: 0 auto;
-	white-space: nowrap;
 	text-align: center;
-	overflow: hidden;
-	min-width: 24em;
-}
-#u2cards.w {
-	width: 48em;
-	text-align: left;
-}
-#u2cards.ww {
-	display: inline-block;
-}
-#u2etaw.w {
-	width: 55em;
-	text-align: right;
-	margin: 2em auto -2.7em auto;
-}
-#u2etaw.ww {
-	margin: -1em 2em 1em 1em;
+	overflow-y: hidden;
+	overflow-x: auto;
 }
 #u2cards a {
+	display: inline-block;
 	padding: .2em 1em;
+	padding-bottom: .3em;
 	background: var(--u2-tab-bg);
 	border: 1px solid #999;
@@ -2687,13 +3757,12 @@
 }
 #u2cards a:first-child {
-	border-radius: .4em 0 0 0;
+	border-radius: var(--radius) 0 0 0;
 }
 #u2cards a:last-child {
-	border-radius: 0 .4em 0 0;
+	border-radius: 0 var(--radius) 0 0;
 }
 #u2cards a.act {
-	padding-bottom: .5em;
 	border-width: 1px 1px .1em 1px;
-	border-radius: .3em .3em 0 0;
+	border-radius: var(--radius) var(--radius) 0 0;
 	margin-left: -1px;
 	background: var(--u2-tab-1-bg);
@@ -2713,102 +3782,21 @@
 	min-width: 1.3em;
 }
-#u2conf {
-	margin: 1em auto;
-	width: 30em;
-}
-#u2conf.w {
-	width: 51em;
+#u2conft {
+	width: max-content;
 }
-#u2conf.ww {
-	width: 82em;
-}
-#u2conf.ww #u2c3w {
-	width: 29em;
-}
-#u2conf.ww #u2c3w.s {
-	width: 39em;
-}
-#u2conf .c,
-#u2conf .c * {
-	text-align: center;
-	line-height: 1em;
-	margin: 0;
-	padding: 0;
-	border: none;
-}
-#u2conf .txtbox {
-	width: 3em;
-	color: var(--fg-max);
-	background: var(--u2-txt-bg);
-	border: 1px solid #777;
-	font-size: 1.2em;
-	padding: .15em 0;
-	height: 1.05em;
-}
-#u2conf .txtbox.err {
-	color: var(--err-fg);
-	background: var(--err-bg);
-}
-#u2conf a.b {
-	color: var(--u2-b-fg);
-	background: var(--u2-b1-bg);
-	text-decoration: none;
-	border-radius: .1em;
-	font-size: 1.5em;
-	padding: .1em 0;
-	margin: 0 -1px;
-	width: 1.5em;
-	height: 1em;
-	display: inline-block;
-	position: relative;
-	bottom: -0.08em;
-}
-#u2conf input+a.b {
-	background: var(--u2-b2-bg);
-}
-html.b #u2conf a.b:hover {
-	background: var(--btn-h-bg);
-}
-#u2conf .c label {
-	font-size: 1.6em;
-	width: 2em;
-	height: 1em;
-	padding: .4em 0;
-	display: block;
-	border-radius: .25em;
-}
-#u2conf input[type="checkbox"] {
-	position: relative;
-	opacity: .02;
-	top: 2em;
-}
-#u2conf input[type="checkbox"]+label,
-#u2conf input[type="checkbox"]:checked+label {
-	position: relative;
-	cursor: pointer;
-	background: var(--btn-bg);
-	box-shadow: var(--btn-bs);
-	border-bottom: var(--btn-bb);
-	text-shadow: 1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000;
-}
-#u2conf input[type="checkbox"]:checked+label {
-	background: var(--btn-1-bg);
-	box-shadow: var(--btn-1-bs);
-	border-bottom: var(--btn-1-bb);
+#u2conf details[open] {
+	border-bottom: none;
+	border-radius: var(--radius) var(--radius) 0 0;
 }
-#u2conf input[type="checkbox"]+label:hover {
-	background: var(--btn-h-bg);
-	box-shadow: var(--btn-h-bs);
-	border-bottom: var(--btn-h-bb);
+details .setting {
+	border-left: none;
+	border-right: none;
 }
-#u2conf input[type="checkbox"]:checked+label:hover {
-	background: var(--btn-1h-bg);
-	box-shadow: var(--btn-1h-bs);
-	border-bottom: var(--btn-1h-bb);
+.c>input[type=checkbox] {
+	position: absolute;
+  	opacity: 0;
 }
-#op_up2k.srch #u2conf td:nth-child(2)>*,
-#op_up2k.srch #u2conf td:nth-child(3)>* {
-	background: #777;
-	border-color: var(--fg);
+#u2conf.srch #multitask,
+#u2conf.srch #potato {
 	box-shadow: none;
 	opacity: .2;
@@ -2881,5 +3869,5 @@
 	color: #fff;
 	padding: .22em;
-	border-radius: .2em;
+	border-radius: var(--radius);
 	border: 2px solid #f0f;
 }
@@ -2906,8 +3894,48 @@
 .fsearch_explain {
 	color: var(--a-dark);
-	padding-left: .7em;
+	padding: .7em;
 	font-size: 1.1em;
 	line-height: 0;
 }
+details {
+	border: var(--bg-u3) solid 1px;
+	border-radius: var(--radius);
+	margin: .3em;
+}
+details:not(.open) .setting {
+	display: none;
+}
+summary {
+	cursor: pointer;
+	background: #303337;
+	background: var(--bg-u1);
+	color: #3584e4;
+	color: var(--a);
+	padding: .3em;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	font-size: large;
+}
+.modal summary:hover {
+	background: var(--bg-u3);
+}
+#actionsArea {
+	position: relative;
+	height: max-content;
+	margin-left: auto;
+}
+#srchfolder_div {
+	display: none;
+	position: absolute;
+	left: 3.5em;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	margin-left: .3em;
+}
+.x {
+	font-size: 1.5em;
+	font-family: initial;
+}
 
 
@@ -2919,57 +3947,27 @@
 
 
-
-
-
-html.c #path,
-html.a #path {
-	border-radius: 0 .3em .3em 0;
-}
-html.c #pctl a,
-html.a #pctl a {
-	background: rgba(0,0,0,0.1);
-	margin-right: .5em;
-	box-shadow: -.02em -.02em .3em rgba(0,0,0,0.2) inset;
-}
-html.d #pctl,
-html.b #pctl {
-	left: .5em;
-}
-html.d #ops,
-html.c #ops,
-html.a #ops {
-	margin: 1.7em 1.5em 0 1.5em;
-	border-radius: .3em;
-	border-width: 1px 0;
-}
-html.c .opbox,
-html.a .opbox {
-	margin: 1.5em 0 0 0;
-}
 html.dz .opview input.i {
 	width: calc(100% - 18em);
 }
-html.c #tree,
-html.c #treeh,
-html.a #tree,
-html.a #treeh {
-	border-radius: 0 .3em 0 0;
+#tree,
+#treesuperh {
+	background: #2f3236;
 	background: var(--bg-u2);
 }
 html.c #treepar,
-html.a #treepar {
+html.b #treepar {
 	background: var(--bg-u2);
 }
 html.c #tree li,
-html.a #tree li {
+html.b #tree li {
 	border-top: 1px solid var(--bg-u5);
 	border-bottom: 1px solid var(--bg-d3);
 }
 html.c #tree li:last-child,
-html.a #tree li:last-child {
+html.b #tree li:last-child {
 	border-bottom: none;
 }
 html.c .opbox h3,
-html.a .opbox h3 {
+html.b .opbox h3 {
 	border-bottom: 1px solid var(--bg-u5);
 }
@@ -2980,24 +3978,32 @@
 html.c .ghead,
 
-html.a #ops,
-html.a .opbox,
-html.a #path,
-html.a #srch_form,
-html.a .ghead {
-	background: var(--bg-u2);
+html.b #ops,
+html.b .opbox,
+html.b #path,
+html.b #srch_form,
+html.b .ghead {
+	background: #c1d1ed;
+	background: var(--btn-bg);
 	border: 1px solid var(--bg-u3);
-	box-shadow: 0 0 .3em var(--bg-d3);
+	border-bottom: var(--btn-bb);
+	box-shadow: var(--btn-bs);
 }
-html.c #u2btn,
-html.a #u2btn {
-	color: #eee;
-	background: var(--bg-u5);
-	background: -moz-linear-gradient(top, #367 0%, #489 50%, #38788a 51%, #367 100%);
-	background: -webkit-linear-gradient(top, #367 0%, #489 50%, #38788a 51%, #367 100%);
-	background: linear-gradient(to bottom, #367 0%, #489 50%, #38788a 51%, #367 100%);
-	box-shadow: .4em .4em 0 var(--bg-d3);
-	border: 1px solid #222;
+
+html.cz .ghead,
+html.cz #wfp .btn:not(:hover),
+html.cz #treeToggleBtn:not(:hover):not(.on),
+html.cz #ops,
+html.cz #path,
+html.cz #wtoggle,
+html.cz #up_quick_more,
+html.cz #up_quick_btn:not(:hover):not(.on) {
+	backdrop-filter: blur(10px);
+	background: var(--btn-bg);
+}
+
+html.c .modal .setting:hover {
+	background: color-mix(in oklab, var(--bg-u5) 30%, transparent);
 }
-html.ay #u2btn {
+html.b #u2btn {
 	box-shadow: .4em .4em 0 #ccc;
 }
@@ -3005,185 +4011,151 @@
 	letter-spacing: -.033em;
 }
-html.c #u2conf.ww #u2btn,
-html.a #u2conf.ww #u2btn {
-	margin: -2em .5em -3em 0;
-	padding: .9em 0;
-}
-html.c #op_up2k.srch #u2btn,
-html.a #op_up2k.srch #u2btn {
-	background: linear-gradient(to bottom, #ca3 0%, #fd8 50%, #fc6 51%, #b92 100%);
-	text-shadow: 1px 1px 1px #fc6;
-	color: #333;
-}
-html.c #u2conf #u2btn,
-html.a #u2conf #u2btn {
-	padding: .6em 0;
-	margin-top: -2.6em;
-}
 html.c #u2etas,
-html.a #u2etas {
+html.b #u2etas {
 	background: var(--bg-d1);
 	border: 1px solid var(--bg-u1);
-	border-width: .1em 0;
-	border-radius: .5em;
-	border-width: .25em 0;
-}
-html.c #u2cards,
-html.a #u2cards {
-	margin: 0 auto -1em auto;
+	border-width: 2px;
+	border-radius: var(--radius);
 }
 html.c #u2foot:empty,
-html.a #u2foot:empty {
+html.b #u2foot:empty {
 	margin-bottom: -1em;
 }
-html.ay #ops,
-html.ay .opbox,
-html.ay #path,
-html.ay #doc,
-html.ay #srch_form,
-html.ay .ghead,
-html.ay #u2etas {
-	border-color: var(--bg-u2);
-	box-shadow: 0 0 .3em var(--bg-u5);
-}
-html.ay #tree li,
-html.ay #treepar {
-	border-color: #f7f7f7 var(--bg-max) #ddd var(--bg-max);
-}
-html.ay #path {
-	background: #f7f7f7;
-	box-shadow: 0 0 .3em #bbb;
-}
-html.ay #treeh,
-html.ay #treepar {
-	background: #f7f7f7;
-	border-color: #ddd;
+html.b #ops{
+	background: none;
+	backdrop-filter: none;
+	border: none;
+	box-shadow: none;
+	margin: 0;
+	padding-left: 0;
+	padding-right: 0;
 }
-html.ay #tree {
-	border-color: #ddd;
-	box-shadow: 0 0 1em #ddd;
-	background: #f7f7f7;
+html.b .opbox,
+html.b #path,
+html.b #doc,
+html.b #srch_form,
+html.b .ghead,
+html.b #u2etas {
+	border-color: var(--bg-u3);
+	box-shadow: 0 4px 4px rgba(0, 0, 0, 0.4);
+	background: var(--bottom-glow), var(--btn-bg);
 }
-
-
-
-
-
 html.b #path {
-	margin: .3em 0;
-	line-height: 1.7em;
+	margin-right: .1em;
 }
-html.b.op_open #path {
-	margin-top: .2em;
+html.b #wfp a {
+	margin-right: .3em;
 }
-html.b #srv_info {
-	display: none;
+html.fy #pathBar.shifted #hspace,
+html.b #pathBar.shifted #hspace{
+	margin-right: -.3em;
 }
-html.b #srv_info2 {
-	display: inline-block;
+html.b #up_quick_more a,
+html.b #spaceTotal_bar,
+html.b #ops>a {
+	border: var(--bg-u3) solid 1px;
 }
-html.b #srv_info2:after {
-	content: '//';
-	margin: 0 .4em;
+html.b #tree li,
+html.b #treepar {
+	border-color: var(--bg-u5) var(--bg-max) transparent var(--bg-max);
 }
-html.b #acc_info {
-	right: .5em;
+html.b #treesuperh,
+html.b #treepar {
+	backdrop-filter: blur(10px);
+	background: color-mix(in srgb, var(--bg) 40%, transparent);
+	border-color: #ddd;
+}
+html.b #tree {
+	background: var(--b);
+}
+html.b #tree_footer {
+	 background: linear-gradient(to top, var(--b), color-mix(in oklab, var(--b) 80%, transparent), transparent);
 }
-html.b #wtoggle {
-	border-radius: .1em 0 0 0;
+html.b #pathBar {
+	background: linear-gradient(to bottom, var(--a), transparent);
 }
-html.d #barpos,
-html.d #barbuf,
-html.d #pvol,
+
+
+
+
+
 html.b #barpos,
 html.b #barbuf,
 html.b #pvol {
-	border-radius: .2em;
+	border-radius: calc(var(--radius) * 3);
 }
-html.b #barpos {
+html.a #barpos {
 	box-shadow: 0 0 0 1px rgba(0,0,0,0.4);
 }
-html.by #barpos {
+html.ay #barpos {
 	box-shadow: 0 0 0 1px rgba(0,0,0,0.2) inset;
 }
-html.b #ops {
-	max-width: 1em;
-	margin-bottom: 1.7em;
-	position: relative;
-	z-index: 2;
-}
-html.b #ops a {
+html.a #pathBar {
 	background: var(--bg);
 }
-html.b .opview {
-	margin: 1em 0;
-}
-html.b #srch_q {
+html.a #srch_q {
 	margin: .2em 0 0 1.6em;
 }
-html.b #srch_q:empty {
+html.a #srch_q:empty {
 	margin-bottom: -1em;
 }
-html.b #op_up2k {
-	margin-top: 3em;
-}
-html.b #tree {
+html.a #tree {
 	box-shadow: 0 -1px 0 rgba(128,128,128,0.4);
 }
-html.bz #tree {
+html.az #tree {
 	box-shadow: 0 -1px 0 var(--bg-d3);
 }
-html.b #treeh,
-html.b #tree li {
+html.a #treeh,
+html.a #tree li {
 	border: none;
 }
-html.b #tree li {
+html.a #tree li {
 	margin-left: .8em;
 }
-html.b #docul a,
-html.b .ntree a {
+html.a #docul a,
+html.a .ntree a {
 	padding: .6em .2em;
 }
-html.b #treepar {
+html.dy *,
+html.cy *,
+html.a * {
+	box-shadow: none !important;
+}
+html.a #path,
+html.a #ops {
+	background: #2f3236;
+	background: color-mix(in oklab, var(--bg-u5), transparent);
+}
+html.a #treepar {
 	margin-left: .63em;
 	width: calc(.1em + var(--nav-sz) - var(--sbw));
 	border-bottom: .2em solid var(--f-h-b1);
 }
-html.b #wrap {
-	margin-top: 2em;
+html.d #path i,
+html.a #path i {
+	border-right: #3584e4 solid 2px;
+	border-right: var(--a) solid 2px;
+	height: 1.5em;
+  	margin: -.4em .3em -.4em -1.7em;
 }
-html.by .ghead,
-html.bz .ghead {
+html.fy .ghead,
+html.dy .ghead,
+html.a .ghead {
 	background: var(--bg);
 	padding: .2em 0;
+	border-bottom: #303337 solid 1px;
+	border-bottom: var(--btn-bb);
+	border-radius: 0;
+	margin: 0 -.6em 1.5em -.6em;
+	padding-left: .5em;
+	padding-right: .5em;
 }
-html.b #files td {
-	padding: .5em .7em;
-}
-html.b .btn {
-	top: -.1em;
-}
-html.b #op_up2k.srch sup {
-	color: #fc0;
-}
-html.by #u2btn sup {
-	color: #06b;
-}
-html.by #op_up2k.srch sup {
-	color: #b70;
+html.a #files tbody a {
+	padding: .7em;
 }
-html.bz #u2cards a.act {
+html.az #u2cards a.act {
 	box-shadow: 0 -.1em .2em var(--bg-d2);
 }
-html.b #u2conf {
-	margin: 2em auto 0 auto;
-}
-html.b #u2conf .txtbox {
-	border: none;
-}
-html.b #u2conf a.b {
-	border-radius: .2em;
-}
-html.by #u2cards a.act {
+html.ay #u2cards a.act {
 	border-width: 2px;
 }
@@ -3193,27 +4165,38 @@
 
 
+html.cy .pfp {
+	color: var(--b);
+}
+html.cy #path a:hover {
+	color: var(--b);
+}
 html.cy .mdo a {
-	background: #f00;
+	background: var(--b);
 }
-html.cy #wrap,
 html.cy #acc_info a,
-html.cy #op_up2k,
 html.cy #files,
 html.cy #files a,
 html.cy #files tbody div a:last-child {
-	color: #000;
+	color: var(--b);
 }
 html.cy #u2tab a,
 html.cy #u2cards a {
-	color: #f00;
+	color: var(--b);
 }
 html.cy #unpost a {
-	color: #ff0;
+	color: var(--a);
+}
+html.cy #path i {
+	background: transparent;
 }
-html.cy #barbuf {
-	filter: hue-rotate(267deg) brightness(0.8) contrast(4);
+html.cy #up_quick_btn.on:not(:hover) {
+	background: var(--b);
 }
-html.cy #pvol {
-	filter: hue-rotate(4deg) contrast(2.2);
+html.cy .ghead span {
+	color: var(--b);
+}
+html.cy #s_list h3 {
+	background: var(--a);
+	color: #000;
 }
 
@@ -3222,6 +4205,8 @@
 
 
-html.dz * {
-	border-radius: 0 !important;
+html.dz #s_nav .btn,
+html.dz .btn {
+	border-width: .1em;
+	border-color: var(--bg-u5);
 }
 html.d #treepar {
@@ -3231,71 +4216,303 @@
 
 
-
-@media (max-width: 32em) {
-	#u2conf {
-		font-size: .9em;
+/* basically a mobile / phone layout */
+#pathBar.thin {
+	display: block;
+}
+#pathBar.thin #path {
+	position: relative;
+	height: 2.2em;
+	margin: .5em 0 0 0;
+}
+#pathBar.thin #path a {
+	padding-top: .45em;
+}
+#pathBar.thin.shifted #hspace {
+	min-width: 0;
+}
+#pathBar.thin #actionsArea {
+	position: fixed;
+	top: .6em;
+	right: .6em;
+	z-index: 11;
+	max-width: 80%;
+	/*overflow-x: auto;*/
+}
+#pathBar.thin .popup {
+	position: relative;
+}
+html.e #pathBar.thin #path {
+	margin: 0;
+}
+html:not(.e) #wrap.thin .ghead {
+	top: 7em;
+	/* padding: .3em; */
+}
+html:not(.e):not(.fy) #wrap.thin #wtc {
+	margin-left: 0;
+	margin-right: 0;
+}
+html:not(.e) #wrap.thin #ghead {
+	text-wrap-mode: nowrap;
+	text-wrap: nowrap;
+	white-space: nowrap;
+	overflow-x: auto;
+	overflow-y: hidden;
+}
+html.cz #wrap.thin .ghead{
+	top: 7.4em;
+}
+html.fy #wrap.thin .ghead,
+html.dy #wrap.thin .ghead,
+html.a #wrap.thin .ghead {
+	top: 6.9em;
+	margin-left: -.6em;
+	margin-right: -.6em;
+}
+html.fy #wrap.thin .ghead {
+	top: 6.1em;
+	border-left: none;
+	border-right: none;
+}
+#wrap.thin .td_w {
+		width: calc(100vw - 8.5em);
 	}
+#wrap.thin .td_s {
+	width: calc(50vw - 5em);
+}
+html:not(.e) #treeh:not(.noa):after,
+html:not(.e) #wrap.thin #ghead:not(.noa)::after {
+	content: "";
+	position: sticky;
+	right: -.5em;
+	background: linear-gradient(to left, var(--scrl-hint), transparent);
+	width: 3em;
+	height: 3.5em;
+	display: inline-block;
+	margin: -1.4em -0.5em -1.6em -1.4em;;
+	pointer-events: none;
 }
-@media (max-width: 28em) {
-	#u2conf {
-		font-size: .8em;
-	}
-}
-@media (min-width: 70em) {
-	#barpos,
-	#barbuf {
-		width: calc(100% - 21em);
-		left: 9.8em;
-		top: .7em;
-		height: 1.6em;
-		bottom: auto;
-	}
-	html.d #barpos,
-	html.b #barpos,
-	html.d #barbuf,
-	html.b #barbuf {
-		width: calc(100% - 19em);
-		left: 8em;
+html.a #wrap.thin #ghead:not(.noa)::after {
+	background: linear-gradient(to left, var(--bg), transparent);
+}
+html.e #wrap.thin .ghead {
+	top: 5.1em;
+}
+@media (max-width: 50em){
+	#path {
+		position: relative;
+		height: 2.2em;
+		margin: .5em 0 0 0;
+	}
+	html.dy .ghead,
+	html.a .ghead,
+	html.dy #wrap.thin .ghead,
+	html.a #wrap.thin .ghead {
+		top: 7.2em;
+		padding-left: 1em;
+	}
+	html:not(.f) .ntree a + a {
+		padding: .6em .2em;
+	}
+	#files tbody a {
+		padding: .8em;
+	}
+	html:not(.e) body {
+		font-size: 1.1em;
+	}
+	html:not(.e) #treeToggleBtn {
+		margin: .8em;
+	}
+	html:not(.e):not(.f) #srv_name {
+		padding-top: .9em;
+		padding-bottom: .6em;
+		margin-left: 2.9em;
+	}
+	html.fz #srv_name {
+		padding-top: .9em !important;
+		padding-bottom: .6em;
+		margin-left: 3em !important;
+	}
+	#widget, #wrap {
+		margin-left: 0 !important;
 	}
 	#widget {
-		bottom: -3.2em;
-		height: 3.2em;
+		font-size: 1.4em;
 	}
-	#pvol {
-		max-width: 9em;
+	#wtoggle.sel {
+		display: block;
+		max-width: 100%;
 	}
-	html.d #ops,
-	html.b #ops {
-		padding-left: 1.7em;
+	#wstack.sel #up_quick,
+	#wstack.sel #wtico {
+		display: none;
 	}
-	html.d .opview,
-	html.b .opview {
-		margin: 1em;
+	#pathBar {
+		display: block;
+		padding: .5em 1em;
+		padding-top: 1em;
+		margin-left: -1em;
+		margin-right: -1em;
 	}
-	html.d #path,
-	html.b #path {
-		padding-left: 1.3em;
+	#actionsArea,
+	#pathBar.thin #actionsArea {
+		position: fixed;
+		top: 1em;
+		right: 1em;
+		z-index: 11;
+		max-width: 80%;
+		/*overflow-x: auto;*/
 	}
-}
-@media (max-width: 35em) {
-	#ops>a[data-dest="new_md"],
-	#ops>a[data-dest="msg"] {
+	.popup,
+	#pathBar.thin .popup {
+		position: relative;
+	}
+	#folder_search {
+		margin: .5em 1em 0 .2em;
+	}
+	html:not(.e) #wrap {
+		padding-left: 1em;
+		padding-right: 1em;
+	}
+	#wfp {
+		margin-left: .1em;
+	}
+	html:not(.e) #gfiles {
+		margin: 0em -0.4em;
+	}
+	.ghead {
+		margin: 0 .4em 1em .4em;
+	}
+	.modalheader {
+		padding: .8em 3em .8em .7em;
+	}
+	html.f .modalheader {
+		padding-top: .6em;
+		padding-bottom: .6em;
+	}
+	.modalsplit {
+		display: block;
+		overflow-y: auto;
+		padding-bottom: 5em;
+	}
+	.splitsub {
+		padding-bottom: .5em;
+	}
+	.divider {
 		display: none;
 	}
-	#op_mkdir.act+div,
-	#op_mkdir.act+div+div {
+	#up_status_h {
+		margin: .5em .5em 0 .5em;
+		position: inherit;
+	}
+	#u2btn {
+		padding: 1em .3em;
+	}
+	#pctl {
+		padding: .2em .5em .5em .5em;
+	}
+	#pvol {
+		border: .7em solid var(--bg-u3);
+		transform: rotate(-90deg);
+		position: fixed;
+		bottom: 4em;
+		right: -.7em;
+		opacity: 0;
+		pointer-events: none;
+		box-shadow: 0 0 .5em var(--mp-sh);
+	}
+	#music #pvol {
+		position: absolute;
+		bottom: 3.5em;
+		right: -1.7em;
+	}
+	#pvolbg:hover #pvol {
 		display: block;
-		margin-top: 1em;
+		opacity: 1;
+		pointer-events: all;
+	}
+	#up_quick_more a {
+		padding: 1em;
+	}
+	#up_quick .overlay_plus {
+		right: 1em;
+	}
+	#s_nav .btn {
+		padding: .5em;
+		padding-right: 2em;
+	}
+	html:not(.e) #hspace {
+		min-width: 3em !important;
+	}
+	#wrap.thin .ghead {
+		top: 7em;
+	}
+	html.cy .ghead,
+	html.b .ghead,
+	html.cy #wrap.thin .ghead,
+	html.b #wrap.thin .ghead {
+		top: 7.3em;
+	}
+	html.cz .ghead,
+	html.cz #wrap.thin .ghead {
+		top: 7.7em;
+	}
+	.gselchk {
+		font-size: 1.4em;
+		top: 5px;
+		right: 5px;
+	}
+	html:not(.e):not(.f) #mu_outer {
+		padding: 1em;
+	}
+	#mu_pbb {
+		width: 100%;
+	}
+	html:not(.e):not(.f) #mu_inner {
+		margin: -.5em;
+	}
+	html.b #tree::before {
+		content: "";
+		position: fixed;
+		backdrop-filter: blur(10px);
+		top: 0;
+		left: 0;
+		bottom: 0;
+		width: var(--nav-sz);
+	}
+}
+
+@media (max-width: 31em) {
+	#ops a {
+		padding-left: .25em;
+		padding-right: .25em;
+	}
+	#srchfolder_div {
+		left: 3em;
+	}
+	html.b #srchfolder_div {
+		left: 2.8em;
+	}
+}
+@media (max-width: 37em) {
+	#wfp #gon,
+	#wfp #gop {
+		display: none;
 	}
 }
 @media (max-width: 54em) {
-	html.b #ops {
-		margin-top: 1.7em;
+	#opa_msg {
+		display: none;
 	}
 }
+
+/* prevent load of new thumbnails */
+#ggrid.waiting a:not(.thumbed) img {
+	display: none;
+}
 @supports (display: grid) and (gap: 1em) {
 	#ggrid {
 		display: grid;
-		margin: 0em 0.25em;
+		margin: .5em;
 		padding: unset;
 		grid-template-columns: repeat(auto-fit,var(--grid-sz));
@@ -3304,20 +4521,87 @@
 	}
 
-	html.b #ggrid {
-		padding: 0 0 2em 0;
-		gap: 1em 1.5em;
-	}
-
 	#ggrid > a {
 		margin: unset;
 		padding: unset;
-  	}
+	}
 
-  	#ggrid>a>span {
+	#ggrid > a > span {
 		text-align: center;
 		padding: .2em .2em .15em .2em;
+		user-select: text;
 	}
+		/*cropped gallery*/
+		#ggrid.gallery {
+			gap: 2px 3px;
+			margin: .2em -.5em;
+		}
+
+			#ggrid.gallery a::before {
+				padding: .1em 0;
+				margin: .3em;
+			}
+
+			#ggrid.gallery a.img.thumbed {
+				max-height: none;
+			}
+
+				#ggrid.gallery a.img.thumbed span:not(.th_ext) {
+					display: none;
+				}
+
+				#ggrid.gallery a.img.thumbed .thumb,
+				#ggrid.gallery a.img.thumbed img {
+					max-height: none;
+					height: calc(var(--grid-sz) - 8px);
+				}
 }
 
+/*uncropped gallery*/
+#ggrid.gallery.nocrop {
+	text-align: center;
+	display: block;
+	margin: .2em -.5em;
+}
+
+#ggrid.gallery.nocrop a::before {
+	padding: .1em 0;
+	margin: .3em;
+}
+
+#ggrid.gallery.nocrop a {
+	margin-bottom: .2em;
+}
+
+#ggrid.gallery.nocrop a .thumb,
+#ggrid.gallery.nocrop a img {
+	height: calc(var(--grid-sz) - 1.5em);
+}
+
+#ggrid.gallery.nocrop a.img.thumbed {
+	min-width: calc(var(--grid-sz) / 2);
+	width: auto;
+}
+
+#ggrid.gallery.nocrop a.img.thumbed .thumb {
+	height: var(--grid-sz);
+}
+
+#ggrid.gallery.nocrop a.img.thumbed img {
+	max-width: 100%;
+	height: var(--grid-sz);
+}
+
+#ggrid.gallery.nocrop a.img.thumbed span:not(.th_ext) {
+	display: none;
+}
+
+#ggrid.gallery.nocrop a.dir .thumb,
+#ggrid.gallery.nocrop a.dir img {
+	height: calc(var(--grid-sz) - 1.5em - 10px);
+	margin-left: 10px;
+	margin-right: 10px;
+}
+
+
 
 
@@ -3333,5 +4617,7 @@
 	#ggrid>a:before,
 	#widget.anim,
+	#tree,
 	#u2tabw,
+	#up_quick,
 	.dropdesc,
 	.dropdesc b,
@@ -3339,6 +4625,6 @@
 		transition: none;
 	}
-	#bbox-next,
-	#bbox-prev,
+	#bbox-next a,
+	#bbox-prev a,
 	#bbox-btns {
 		transition: background-color .3s ease, color .3s ease;
@@ -3356,6 +4642,6 @@
   --fg-max: #0000ff;
   --fg-weak: #0000ff;
-  --bg: #c6c3c6;
-  --bg-d3: #ff0;
+  --bg: #c0c0c0;
+  --bg-d3: #7eb8f591;
   --bg-d2: var(--w3);
   --bg-d1: var(--bg);
@@ -3368,8 +4654,6 @@
   --g-fsel-b1: #fff;
   --row-alt: var(--w);
-  --scroll: var(--silver);
   --f-sel-sh: transparent;
   --a: #000;
-  --a-b: #fff;
   --a-hil: #fff;
   --a-h-bg: var(--bg);
@@ -3392,4 +4676,5 @@
   --sort-1: var(--fg-weak);
   --tree-bg: var(--w);
+  --g-bg: transparent;
   --g-b1: a;
   --g-b2: a;
@@ -3406,5 +4691,4 @@
   --grey: grey;
   --silver: silver;
-  --transparent: transparent;
   --shadow-color-1: #0a0a0a;
   --shadow-color-2: #808080;
@@ -3469,8 +4753,6 @@
   --g-fsel-b1: #fff;
   --row-alt: #555555;
-  --scroll: #555555;
   --f-sel-sh: transparent;
   --a: var(--fg);
-  --a-b: var(--fg);
   --a-hil: var(--fg);
   --btn-1h-bg: var(--bg-d3);
@@ -3547,15 +4829,47 @@
 html.e {
   text-shadow: none;
+  --bg-u1: #0002;
+  --g-sel-fg: #fff;
+  --radius: 0;
+}
+html.e #ggrid > a.sel {
+	box-shadow: none;
+}
+html.e #files tr.sel a, #files tr.sel a.play {
+	color: #fff;
+}
+html.e tr.play td:nth-child(1) a {
+  	background: var(--g-fsel-bg);
+	color: #fff;
+}
+html.e .pfp {
+	background: transparent;
+	color: var(--fg);
+}
+html.e .modalcontent {
+	border-color: var(--fg);
+}
+html.e .divider {
+	background: var(--fg);
+}
+html.e #s_nav .sub::before {
+	background: var(--fg);
+}
+html.e #h_music {
+	right: 0;
+	color: #fff;
 }
+html.e #wfp {
+	padding-left: .3em;
+}
+html.e details[open] summary:hover,
 html.e #files,
-html.e #u2conf input[type="checkbox"]:hover + label,
 html.e .tgl.btn.on:hover,
 html.e body {
   background: var(--bg);
 }
-html.e #pctl a,
+html.e summary,
+html.e .pctl a,
 html.e #repl,
-html.e #u2conf a,
-html.e #u2conf input[type="checkbox"] + label,
 html.e #wfp a,
 html.e .btn,
@@ -3563,20 +4877,27 @@
 html.e input[type="submit"] {
   box-shadow: var(--shadow-outset);
-  border-radius: var(--radius);
   background: var(--bg);
   border: 0;
 }
+html.e #wfp a {
+	margin: 0;
+}
+html.e #pathBar.shifted #hspace{
+	margin-right: -.4em;
+}
+html.e #ops {
+	background: transparent;
+}
+html.e details[open] summary,
 a.s0r,
 html.e #ghead a.s0,
-html.e #u2conf input[type="checkbox"]:checked + label,
 html.e .tgl.btn.on,
 html.e input[type="submit"]:active {
   box-shadow: var(--shadow-inset) !important;
 }
+html.e summary:hover,
 html.e #ops a:hover,
-html.e #pctl a:hover,
+html.e .pctl a:hover,
 html.e #repl:hover,
-html.e #u2conf a:hover,
-html.e #u2conf input[type="checkbox"]:hover + label,
 html.e #wfp a:hover,
 html.e .btn:hover,
@@ -3597,7 +4918,7 @@
   box-shadow: none;
 }
-html.e #pctl a:focus,
+html.e summary:focus,
+html.e .pctl a:focus,
 html.e #repl:hover,
-html.e #u2conf input[type="checkbox"]:focus + label,
 html.e #wfp a:focus,
 html.e .btn:focus,
@@ -3627,7 +4948,4 @@
   background: var(--w2);
 }
-html.e #files tr {
-  background-color: var(--black);
-}
 html.e #srv_info span,
 html.e label {
@@ -3638,5 +4956,5 @@
   color: var(--white);
   height: 2em;
-  left: 1em;
+  right: 1em;
   width: fit-content;
 }
@@ -3644,18 +4962,30 @@
 html.e #ops,
 html.e #srv_info {
-  display: flex;
   align-items: center;
 }
+html.e .overlay_plus {
+	margin-top: -.3em;
+	text-shadow: none;
+}
+html.e #spaceUsed_bar {
+	background: var(--ttlbar);
+}
+html.e #h_up2k,
+html.e #up_status_h,
+html.e #h_cfg {
+	color: #fff;
+}
+html.e .modalheader:hover {
+	background: var(--ttlbar);
+}
 html.e #acc_info span.warn,
 html.e #acc_info a {
   color: var(--white);
 }
-html.e #flogout:before {
-  padding-left: 0.2em;
-  padding-right: 0.4em;
-  content: " | ";
+html.e #accessType {
+	color: var(--fg);
 }
 html.e #blogout {
-  color: var(--w);
+  color: var(--fg);
   box-shadow: none;
   background: transparent;
@@ -3677,10 +5007,4 @@
 html.e #u2btn {
   border: var(--border-dashed-black);
-  border-radius: var(--border-radius);
-  transform: translateY(30%);
-}
-html.e #ops,
-html.e #ops a {
-  border-radius: var(--radius);
 }
 @media only screen and (max-width: 600px) {
@@ -3690,7 +5014,7 @@
     height: fit-content;
     align-items: center;
-    top: 3.2em;
-    right: 1em;
-    left: auto;
+    bottom: 9.2em;
+    left: 1em;
+    right: auto;
     display: flex;
     gap: 0.2em;
@@ -3701,12 +5025,16 @@
 }
 html.e #ops {
-  background: var(--ttlbar);
-  /*HC*/
-  box-shadow: inset 0-1px grey, inset 0-2px var(--shadow-color-1);
-  height: 2em;
-  gap: 0.6em;
-  padding: 0.2em;
-  flex-direction: row-reverse;
-  margin-bottom: 1.2em;
+	/*HC*/
+	font-size: larger;
+	padding: 0;
+	height: 2em;
+}
+html.e #srchfolder_div {
+	left: 2.5em;
+	top: -.2em;
+	bottom: -.2em;
+}
+html.e #qs_btns {
+  	top: .3em;
 }
 html.e #srch_form,
@@ -3716,22 +5044,22 @@
   max-width: 100vw;
 }
+html.e #srv_name {
+	padding-top: .2em;
+	margin-left: 1.4em;
+}
 html.e #ghead,
 html.e #ops a {
   align-items: center;
-  display: flex;
 }
 html.e #ops a {
   text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5);
-  height: 1.4em;
-  padding: 0;
+  height: 1.3em;
   box-shadow: var(--shadow-outset);
   background: var(--bg);
-  aspect-ratio: 1/1;
   justify-content: center;
   font-size: 1.25em;
-  z-index: 4;
 }
-html.e #blogout:focus,
-html.e #ops a:focus {
+html.e #blogout:focus-visible,
+html.e #ops a:focus-visible {
   outline: 1px dashed var(--w) !important;
 }
@@ -3741,18 +5069,11 @@
 }
 
-html.e #ops > a:not(:first-child).act {
-  height: 1.4em;
-  width: 1.4em;
-  padding-bottom: 0.3em;
-  margin-top: 0.3em;
+html.e #ops a.act {
+  border-bottom: 0;
+  margin-top: -.4em;
   border-top-left-radius: 3px;
   border-top-right-radius: 3px;
   box-shadow: var(--shadow-inset-left), var(--shadow-inset-top),
     var(--shadow-inset-right);
-  z-index: 6;
-}
-html.e #ops a.act {
-  box-shadow: var(--shadow-inset);
-  border-bottom: 0;
 }
 html.e a:active {
@@ -3763,30 +5084,15 @@
   border: 0 !important;
   outline-offset: 1px;
-  border-radius: var(--radius) !important;
   box-shadow: inherit;
 }
-html.e #opa_x {
-  text-shadow: 0 0 0 var(--transparent) !important;
-  color: var(--bg) !important;
-  display: flex;
-}
-html.e #opa_x:before {
-  content: "⨯";
-  color: var(--fg) !important;
-  margin-top: -0.1em;
-  font-size: 1.75em;
-  position: absolute;
-}
 html.e .opbox {
-  margin: -1.2em 0 0;
+  margin: -.3em 0 0;
   box-shadow: var(--shadow-inset-bottom), var(--shadow-inset-left),
     var(--shadow-inset-right);
   border-radius: var(--radius);
-  z-index: 5;
   background: var(--bg);
 }
 html.e #srch_form {
   margin: 0;
-  border-radius: var(--radius);
 }
 html.e #op_unpost {
@@ -3838,5 +5144,4 @@
   box-sizing: border-box;
   padding: 3px 4px;
-  border-radius: var(--radius);
   border: 0;
 }
@@ -3854,5 +5159,5 @@
   padding: 1.5em;
   margin: 0;
-  overflow-x: scroll;
+  overflow-x: auto;
 }
 html.e #ghead {
@@ -3860,8 +5165,6 @@
   justify-content: flex-end;
   gap: 0.4em;
-  padding: 0;
-  overflow: auto;
-  top: 0px;
-  border-radius: 0px;
+  padding-top: .2em;
+  top: 2.3em;
 }
 html.e #ghead a {
@@ -3869,4 +5172,7 @@
   border-radius: var(--radius);
 }
+html.e #treeToggleBtn {
+	margin: 0;
+}
 html.e ::-webkit-scrollbar,
 html.e::-webkit-scrollbar {
@@ -3881,5 +5187,5 @@
   width: 16px !important;
   height: 16px !important;
-  background: var(--scroll) !important;
+  background: var(--a) !important;
   /*HC*/
   box-shadow: var(--shadow-outset);
@@ -3931,6 +5237,7 @@
 html.e #tree {
   background: var(--bg);
-  padding-left: 0.4em;
+  /* padding-left: 0.4em; */
   padding-top: 0;
+  padding-bottom: 0;
   margin-left: var(--negative-space);
 }
@@ -3943,8 +5250,6 @@
   background: var(--bg);
   box-shadow: var(--shadow-outset-top), var(--shadow-outset-bottom);
-  width: calc(1.5em + var(--nav-sz) - var(--sbw));
   height: 2.4em;
   border: none;
-  top: -2px;
   display: flex;
   align-items: center;
@@ -3967,5 +5272,4 @@
   text-align: center;
   align-content: center;
-  border-radius: var(--radius) !important;
   padding: 0.057em;
   border: 1px solid var(--black);
@@ -3983,6 +5287,7 @@
   position: static;
   margin: 0 !important;
-  min-height: 100%;
+  min-height: calc(100% - 3em);
   height: max-content;
+  width: calc(100% - 1.1em);
 }
 html.e .ntree a:last-of-type:before {
@@ -4003,5 +5308,4 @@
   outline-offset: -2px;
   color: var(--fg);
-  border-radius: var(--radius) !important;
 }
 html.e #treepar {
@@ -4014,4 +5318,9 @@
   margin-left: calc(2.1em - (1em - var(--negative-space))) !important;
 }
+html.e .under,
+html.e #up_quick_more,
+html.e #wtico,
+html.e #ghead,
+html.e #op_acc,
 html.e #path,
 html.e #widgeti,
@@ -4031,12 +5340,26 @@
   margin-left: 0 !important;
 }
+html.e #spaceFree {
+	background: var(--inset-bg);
+}
+html.e #tree_footer {
+	background: transparent;
+}
 html.e #wrap {
-  transform: translateX(calc((var(--negative-space) * 2) - 1.2em));
-  padding-right: var(--negative-space);
-  position: relative;
-  margin-right: calc((var(--negative-space) * 2) - 1.2em);
-  margin-top: var(--negative-space);
-  margin-left: 1.2em;
-  /*overflow-x: auto; fix for OOB table when screen space is limited (mobile), but removes sticky header*/
+	padding-left: 0;
+	padding-right: 0;
+}
+html.e #pathBar {
+	padding: 0;
+	margin: 0;
+	top: 0;
+	background: var(--ttlbar);
+}
+html.e #pathBar.shifted #hspace {
+	min-width: 2.5em;
+}
+html.e   #pathBar.thin #actionsArea {
+	top: 0 !important;
+	right: 0 !important;
 }
 html.e input[type="radio"] {
@@ -4044,37 +5367,27 @@
 }
 html.e #path {
-  width: calc(100% - 0.4em);
-  display: flex;
-  align-items: center;
-  margin: 0;
-  padding: 0.2em;
-  overflow-x: auto;
+	background: var(--bg-u2);
+	margin-right: 0;
+	width: auto;
 }
 html.e #path i {
   border: 1px solid var(--w);
   border-color: var(--w);
-  margin: 0;
-  border-width: 0.1em 0.1em 0 0;
-  height: 0.5em;
-  width: 0.5em;
-}/*
-html.e #hovertree:after {
-  color: red;
-  content: "BUGGY";
-html.ez #hovertree:after {
-  color: rgb(255 98 98);
-  content: "BUGGY";
+  border-width: 0 0.1em 0 0;
+  transform: none;
+  width: 1.8em;
+  box-shadow: inset -1px 0 var(--shadow-color-1), inset -2px 0 var(--grey);
 }
-}*/
-html.e #widget {
-  box-shadow: 0 0;
-  border: 0 !important;
+html.e #path *:last-child:is(i) {
+    display: none;
+}
+html.e .dir span::before {
+  content: "📁";
 }
-html.e #wtico,
 html.e #zip1 {
   box-shadow: 0 0 !important;
 }
-html.e #wtgrid {
-  top: -0.09em;
+html.e #wtc {
+  top: 0.2em;
 }
 html.e #wfs,
@@ -4089,5 +5402,4 @@
 }
 html.e #barpos {
-  /* border-radius: var(--radius); */
   box-shadow: var(--shadow-inset);
 }
@@ -4095,13 +5407,4 @@
   border-left: 0.1em solid var(--bg-u5);
 }
-html.e #wfp {
-  margin: var(--negative-space);
-  font-size: 0;
-  display: inline-block;
-}
-html.e #wfp a {
-  font-size: large;
-  display: inline-block;
-}
 html.e #repl {
   font-size: large;
@@ -4113,5 +5416,5 @@
   text-align: center;
   text-wrap-mode: nowrap;
-  margin: 0px;
+  margin: .5em;
 }
 
@@ -4129,5 +5432,4 @@
   background: var(--inset-bg);
   margin: 0.2em;
-  border-radius: var(--radius);
 }
 
@@ -4136,14 +5438,24 @@
 }
 
+html.e #wtc {
+	margin: -.2em 0 -.5em .1em;
+}
+
 #rcm {
-	position: absolute;
+	position: fixed;
+	overflow-y: auto;
 	display: none;
-	background: #fff;
-	background: var(--bg);
-	border: 1px solid var(--bg-u3);
+	background: #2f3236;
+	background: var(--bg-u2);
+	border: 1px solid #46494d;
+	border: 1px solid var(--bg-u5);
 	outline: none;
-	border-radius: .3rem;
+	border-radius: 5px;
+	border-radius: var(--radius);
 	box-shadow: 0 0 .3rem var(--bg-d3);
-	z-index: 3;
+	z-index: 60;
+}
+#rcm.large a {
+	padding: 1em;
 }
 
@@ -4153,5 +5465,5 @@
 
 #rcm > a {
-	padding: .2rem .3rem;
+	padding: .3rem;
 }
 
@@ -4166,5 +5478,11 @@
 #rcm > .sep {
 	margin: 0 .2rem;
-	border-bottom: 1px solid var(--a-gray);
+	border-bottom: 1px solid #46494d;
+	border-bottom: 1px solid var(--bg-u5);
+}
+
+#rcm_tmp {
+	min-height: 5em;
+	max-width: var(--grid-sz);
 }
 
@@ -4176,5 +5494,5 @@
 	border-bottom: 1px solid #999;
 	border-color: var(--a);
-	border-radius: .2em;
+	border-radius: var(--radius);
 	padding: .2em .3em;
 }
@@ -4189,2 +5507,700 @@
 	z-index: 99;
 }
+
+#spaceFree, #rtt_latency {
+	color: var(--fg);
+	text-align: left;
+	padding: 2px;
+	font-size: small;
+	cursor: default;
+	pointer-events: all;
+	max-width: max-content;
+	background: #2f3236;
+	background: var(--transparent);
+}
+#rtt_latency {
+	opacity: .5;
+}
+#spaceUsed_bar, #spaceTotal_bar{
+	height: .3em;
+	border-radius: 5px;
+	border-radius: var(--radius);
+}
+#spaceUsed_bar{
+	background: rgb(85, 144, 255);
+	background: var(--a);
+}
+html.b #spaceUsed_bar {
+  	background: linear-gradient(to right, var(--a), var(--a-dark));
+}
+#spaceTotal_bar {
+	position: relative;
+	margin-bottom: .5em;
+	background: #24272b;
+	background: var(--bg-u1);
+}
+#spaceMax {
+	position: absolute;
+	background: var(--a);
+	right: 0;
+	top: 0;
+	width: .3em;
+	height: .3em;
+	border-radius: var(--radius);
+}
+#tree_footer {
+	width: 0;
+	display: none;
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	padding: 1em;
+	pointer-events: none;
+	transition: width .05s;
+	background: linear-gradient(to top, var(--bg-u2), color-mix(in oklab, var(--bg-u2) 80%, transparent), transparent);
+}
+.popup_button {
+	border-radius: var(--radius);
+	margin: 0;
+	color: var(--btn-1-fg);
+	text-align: left;
+	text-shadow: none;
+}
+#acc_button p{
+	margin: 0;
+}
+#acc_button {
+	margin: 0;
+	cursor: pointer;
+	display: flex;
+}
+#op_acc {
+	font-size: medium;
+	margin-top: .3em;
+	text-align: right;
+	left: auto;
+}
+#op_acc a:not([hidden]):not(.btn),
+#op_acc input:not([hidden]) {
+	font-size: 1.1em;
+	display: block;
+	cursor: pointer;
+}
+#opa_acc label {
+	cursor: pointer !important;
+}
+#ops input[type=submit],
+#ops #goh {
+	font-size: medium;
+	padding-bottom: .2em;
+}
+
+.popup {
+	display: none;
+	width: fit-content;
+	background: #222;
+	background: var(--bg-u2);
+	border: 1px solid var(--bg-u3);
+	box-shadow: 0 .5em 1em var(--txt-sh);
+	text-align: center;
+	border-radius: 5px;
+	border-radius: var(--radius);
+	padding: .5em;
+	position: absolute;
+	z-index: 12;
+	bottom: 2.5em;
+}
+.under {
+	top: calc(100% + .2em);
+	left: 0;
+	bottom: auto;
+	right: 0;
+  	text-align: left;
+	width: auto;
+}
+#accessType {
+	font-size: small;
+}
+
+#srchfolder_div:focus-within .popup {
+	display: block;
+}
+/* has to be separate because IE will ignore the whole rule if it can't do focus within */
+.popup.act {
+	display: block;
+}
+
+.overlay_plus{
+	position: absolute;
+	margin: -.5em 0 0 .8em;
+	color: var(--fg);
+	text-shadow: -1px 1px 1px var(--bg-max);
+}
+
+
+
+
+html.f {
+	--a: #e3f903;
+	--bg-u2: #191919;
+	--radius: 4px;
+	--g-sel-bg: color-mix(in xyz, var(--g-sel-b1) 70%, var(--bg-u2));
+	--bg-u1: color-mix(in srgb, var(--fg) 10%, transparent);
+	--font-main: 'orbitron', sans-serif;
+	--font-mono: 'orbitron', monospace;
+	font-family: 'orbitron', sans-serif;
+	font-optical-sizing: auto;
+	letter-spacing: .05em;
+}
+
+html.f #path i {
+	border-color: var(--a);
+	background: none;
+}
+html.f #spaceUsed_bar {
+	background: repeating-linear-gradient(-45deg, var(--a), var(--a) 6px, transparent 6px, transparent 12px);
+}
+html.f #spaceTotal_bar {
+	border: var(--a) solid 1px;
+}
+html.f #spaceMax {
+	display: none;
+}
+html.f #spaceFree {
+	font-size: .8em;
+}
+html.f #srv_name {
+	padding-top: .3em;
+}
+html.f #gridsel {
+	content: "m.-select";
+}
+html.f #ggrid > a::before {
+	background: transparent;
+}
+html.f #ggrid > a::before,
+html.f #ggrid > a:focus-visible::before,
+html.f #ggrid > a:hover::before {
+	font-size: 1.7em;
+	padding: .3em .2em;
+	margin: 0;
+}
+html.f #ggrid > a.au::before {
+	padding: .4em .2em .4em .3em;
+	margin: 0;
+}
+html.f #ggrid > a:focus-visible .imgcontainer::before,
+html.f #ggrid > a:hover .imgcontainer::before {
+	border-color: var(--g-f-bg) transparent transparent var(--g-f-bg) !important;
+}
+html.f #ggrid > a.sel:focus-visible .imgcontainer::before,
+html.f #ggrid > a.sel:hover .imgcontainer::before {
+	border-color: var(--g-fsel-bg) transparent transparent var(--g-fsel-bg) !important;
+}
+/* #wrap:not(.thin) .ghead::after, */
+html.f #ggrid .dir.thumbed .imgcontainer::before,
+html.f #ggrid .au.thumbed:not(.play) .imgcontainer::before {
+	content: "";
+	position: absolute;
+	left: 0;
+	border: var(--bg-u2) solid;
+	z-index: 3;
+	border-width: 4em 4em 0 0;
+	border-color: var(--g-bfbg) transparent transparent var(--g-bfbg);
+	margin: 0 3px;
+}
+html.f #ggrid > a.play::before {
+	background: var(--a);
+}
+html.f #ggrid > a::before {
+	text-shadow: none !important;
+}
+html.f #ggrid > a.sel .imgcontainer::before {
+	border-color: var(--g-sel-bg) transparent transparent var(--g-sel-bg) !important;
+}
+html.f #ggrid > a.sel img {
+	box-shadow: none;
+}
+html.f .close:not(:hover),
+html.f #s_nav .btn:not(:hover),
+html.f #wfp .btn:not(:hover),
+html.f #treeToggleBtn:not(:hover):not(.on) {
+	background: var(--bg-u2);
+}
+html.f #s_nav .btn {
+	border-color: var(--a);
+	border-width: 0 0 0 2px;
+}
+html.f #s_nav .sub::before {
+	background: var(--bg-u2);
+}
+html.f #pctl {
+	margin-top: .1em;
+}
+html.f a {
+	/* text-decoration: underline; */
+	text-decoration-color: color-mix(in oklab, currentColor 20%, transparent);
+}
+html.f .ntree a + a {
+	line-height: 1.45em;
+}
+html.f #tree .hl {
+	width: calc(100% - 4.2em);
+}
+html.f #tree .hl::after {
+	content: "";
+	right: -2.2em;
+	left: -1.2em;
+	margin-top: -.3em;
+	margin-bottom: -2px;
+	z-index: -1;
+	position: absolute;
+	border: solid;
+	border-width: 2.08em 2.08em 0 0;
+	border-color: var(--btn-1-bg) transparent transparent var(--btn-1-bg);
+}
+html.f #tree .hl:hover::after {
+	border-color: var(--btn-1h-bg) transparent transparent var(--btn-1h-bg);
+}
+html.f #wrap:not(.thin) .ghead {
+	max-width: max-content;
+}
+
+html.f #wrap:not(.thin) .ghead::after {
+	left: calc(100% - 2px);
+	border-width: 2.7em 2.7em 0 0;
+}
+html.f #s_list h3 {
+	background: linear-gradient(35deg, var(--bg-u2) 60%, var(--bg) 75%, var(--a2) 85%, var(--fg-weak) 90%);
+}
+html.f .close {
+	margin: 1px;
+}
+html.f .modalheader {
+	background: var(--a);
+	text-shadow: none;
+	color: var(--bg-u2);
+	padding-top: .5em;
+	padding-bottom: .5em;
+}
+html.f #treeToggleBtn {
+	padding: .07em;
+}
+html.f #widget:not(.thin) #pvol {
+	border-color: var(--a-dark);
+}
+html.f #h_music {
+	right: 0;
+}
+html.f .winbtn {
+	margin: .1em;
+}
+html.f #u2tab tbody tr:hover td {
+	background: transparent;
+}
+html.f #op_unpost a {
+	border-bottom: solid 1px var(--a);
+}
+	html.f #op_unpost a:not(:hover) {
+		color: color-mix(in oklab, var(--a), var(--fg));
+	}
+html.f #u2c3t span {
+	font-size: .8em;
+}
+html.f #u2cards span {
+	font-size: .8em;
+}
+html.f #gridzoom a,
+html.f #gridchop a,
+html.f #twig, html.f #twobytwo,
+html.f #nthread_sub, html.f #nthread_add {
+	font-family: monospace;
+	font-size: 1.6em;
+}
+html.f #qs_btns a {
+	margin: .1em 0;
+	padding: .3em;
+}
+html.f .unmodal,
+html.f #wrap > .opview {
+	margin-left: -.6em;
+	margin-right: -.6em;
+	border-radius: 0 0 var(--radius) var(--radius);
+}
+html.f .th_ext .inner {
+	font-size: .7em;
+}
+
+html.fz {
+	--bg: #2e258c;
+	--bg: hsl(from var(--a) calc(h * 2.5 + 55) calc(s * .6) calc(l * .7));
+	--a2: hsl(from var(--bg) calc(h - 40) calc(s * 1.2) calc(l * 1.4));
+	--g-bfbg: var(--bg-u2);
+}
+html.fz #wrap::before {
+	content: '';
+	position: fixed;
+	inset: 0;
+	background-image: url("data:image/svg+xml,%3C!-- svg: first layer --%3E%3Csvg viewBox='0 0 250 250' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='4' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
+	opacity: .4;
+	filter: brightness(50%) contrast(300%) saturate(0);
+	z-index: -1;
+}
+html.fz #srv_name {
+	padding-top: .5em;
+}
+html.fz #up_status_h {
+	color: var(--bg-u2);
+	text-shadow: none;
+	font-size: large;
+}
+html.fz .ghead,
+html.fz #path,
+html.fz #ops {
+	border: var(--btn-bb);
+}
+html.fz .ghead {
+	margin-top: -.2em;
+	top: 3.6em;
+}
+html.fz #wfp .btn {
+	padding: .25em .4em;
+	margin-right: .3em;
+}
+html.fz #path{
+	margin-right: .3em;
+}
+
+html.fy {
+	--a: #3071b7;
+	--as: color-mix(in oklab, hsl(from var(--a) h calc(s * 1.5) 50%) 30%, var(--bg));
+	--fg: #f3f3f3;
+	--fg-weak: var(--a);
+	--a2: hsl(from var(--a) calc(h + 190) calc(s * 1.1) calc(l * .5));
+	--btn-bg: var(--fg);
+	--scrl-hint: var(--bg-u2);
+	--tree-bg: var(--bg-u2);
+	--g-f-bg: color-mix(in oklab, var(--bg-u3), var(--bg));
+	--g-fg: var(--bg-u2);
+  	--g-bg: color-mix(var(--bg-u2) 5%, transparent);
+  	--g-g1: color-mix(var(--bg-u2) 5%, transparent);
+	--g-bfbg: color-mix(in oklab, var(--bg-max) 95%, var(--fg-max));
+}
+html.fy * {
+	box-shadow: none !important;
+	text-shadow: none !important;
+	/* transition: all .15s; */
+}
+
+html.fy #wrap:not(.thin) .ghead {
+	top: 2.8em;
+	border-bottom-right-radius: var(--radius);
+	border-top-width: 0;
+	border-left-width: 0;
+}
+html.fy #blogout {
+	color: var(--fg) !important;
+}
+html.fy #blogout:hover {
+	color: var(--a) !important;
+}
+html.fy select,
+html.fy input,
+html.fy .setting {
+	color: var(--bg-u2) !important;
+}
+html.fy #s_list h3 {
+	background: linear-gradient(45deg, var(--a) calc(100% - 3em), #ccc calc(100% - 3em), #ccc calc(100% - 2em), transparent calc(100% - 2em));
+}
+html.fy #up_status_h,
+html.fy .modalheader {
+	border-bottom: #f3f3f3 solid 1px;
+	color: #f3f3f3;
+}
+html.fy #up_status_h {
+	border: none;
+	font-size: large;
+}
+html.fy #reloc_up:not(:hover) {
+	color: #f3f3f3;
+}
+html.fy .close:not(:hover) {
+	background: none;
+	border-color: transparent;
+	font-weight: bolder;
+	color: #f3f3f3;
+}
+html.fy .close:hover {
+	background: rgb(207, 22, 28);
+	color: #f3f3f3;
+}
+html.fy #up2k,
+html.fy #files {
+	--fg: #191919;
+	color: var(--fg);
+	--chk-fg: var(--fg);
+}
+html.fy #path i {
+	border-color: var(--fg);
+	height: 1.2em;
+	margin: 0 .3em -.25em -1.7em;
+	opacity: .4;
+	transform: none;
+}
+
+html.fy #path a {
+	padding: .6em .4em;
+  	height: auto;
+}
+
+html.fy #path a:hover {
+	color: var(--a);
+}
+
+html.fy #wfs,
+html.fy #tree ul a.par {
+	color: var(--fg);
+}
+html.fy #tree li {
+	border-top: 1px solid color-mix(var(--bg-u5) 25%, transparent);
+}
+html.fy #treeToggleBtn {
+	padding: .2em;
+	margin: 0 !important;
+	border: none;
+	border-radius: 0 0 var(--radius) 0;
+}
+html.fy #srv_name {
+	line-height: 1.4em;
+}
+html.fy #pathBar,
+html.fy .opbox:not(#op_bup) {
+	background: var(--bg-u2);
+	border-bottom: var(--btn-bb);
+}
+html.fy #pathBar {
+	padding: 0;
+}
+html.fy .ghead {
+	border: var(--btn-bb);
+	border-top: none;
+	--fg: var(--a);
+	color: var(--fg);
+	--scrl-hint: var(--bg);
+}
+html.fy #actionsArea,
+html.fy #wfp,
+html.fy #wfp .btn {
+	margin: 0;
+	border: none;
+}
+html.fy #actionsArea {
+	margin-left: auto;
+}
+html.fy #wfp .btn {
+	padding: .45em;
+}
+html.fy #bbox-overlay #bbox-next .btn,
+html.fy #bbox-overlay #bbox-prev .btn,
+html.fy #bbox-btns .btn:not(.on) {
+	background: var(--bg-u2);
+}
+html.fy #tth {
+	display: none;
+}
+html.fy #wrap {
+	background-size: 20px 20px;
+	/* background-image: radial-gradient(color-mix(in oklab, var(--bg-u2) 10%, transparent) 1px, transparent 1px); */
+}
+html.fy .modalheader::after {
+	position: absolute;
+	border: solid;
+	border-color: transparent transparent var(--bg) transparent;
+	border-width: 10px;
+	content: "";
+	width: 30%;
+	margin-bottom: -1.2em;
+	right: 5.7em;
+}
+html.fy #ops {
+	height: auto;
+	margin-right: .3em;
+}
+html.fy #srchfolder_div {
+	right: .5em;
+}
+html.fy #ops a {
+	padding: .29em .4em;
+}
+
+html.fy #ops a:not(:hover):not(.act) {
+	color: var(--fg);
+}
+html.fy #actionsArea {
+	top: 0 !important;
+	right: 0 !important;
+}
+html.fy #wtoggle {
+	margin: 0;
+	border-bottom: none;
+	border-radius: var(--radius) 0 0 0;
+}
+html.fy #up_quick {
+	margin: 0;
+	border-radius: 0;
+}
+html.fy #up_quick_btn {
+	border-radius: 0;
+	border-width: 1px 0 0 0;
+	margin: 0;
+}
+html.fy #wtico {
+	line-height: 1.1em;
+	border-radius: 0;
+}
+html.fy #wstack #wtico:hover{
+	background: var(--bg-u2) !important;
+}
+html.fy #wtoggle,
+html.fy #wtoggle * {
+	line-height: .99em;
+}
+html.fy #repl::before,
+html.fy #files:before,
+html.fy #ggrid:before,
+html.fy #files:after,
+html.fy #ggrid::after,
+html.fy #wrap:before {
+	--w: 20px;
+	content: "";
+	width: 150px;
+	height: 220px;
+	border-radius: 10px;
+	position: fixed;
+	z-index: -1;
+	right: 10px;
+	filter: blur(5px);
+	background: repeating-linear-gradient(-45deg, var(--as), var(--as) var(--w), transparent var(--w), transparent calc(var(--w) * 2));
+	margin: 15vh 15vw;
+}
+html.fy #repl::before,
+html.fy #files:before,
+html.fy #ggrid:before{
+	width: 100px;
+	height: 100px;
+	margin: 33vh 6vw;
+	--w: 15px;
+	filter: blur(7px);
+	opacity: .7;
+}
+html.fy #files:after,
+html.fy #ggrid::after {
+	width: 300px;
+	height: 90px;
+	margin: 0 calc(50vw - 150px);
+	right: auto;
+	bottom: -10px;
+}
+html.fy #repl::before{
+	top: 0;
+	left: var(--nav-sz);
+	margin: 15vh -10px;
+	width: 150px;
+	height: 130px;
+	--w: 25px;
+	opacity: .5;
+}
+html.fy #qs_btns a {
+	color: var(--bg-u2);
+}
+html.fy .winbtn:not(:hover) {
+	background: none;
+	border-color: transparent;
+}
+html.fy #doc {
+	background: var(--bg);
+}
+html.fy .logue {
+	color: var(--a);
+}
+/*
+Source - https://stackoverflow.com/a/61913549
+Posted by Temani Afif, modified by community. See post 'Timeline' for change history
+Retrieved 2026-06-20, License - CC BY-SA 4.0
+*/
+html.fy #repl::after,
+html.fy a:hover .imgcontainer,
+html.fy .imgcontainer:hover,
+html.fy *:not(#ggrid)>a:not(.btn):not(.modalheader):hover,
+html.fy #wrap:after {
+	--b: 2px;   /* thickness of the border */
+	--c: var(--a);   /* color of the border */
+	--w: 10px;  /* width of border */
+	
+
+	outline: var(--b) solid #0000; /* space for the border */
+	--_g: #0000 90deg,var(--c) 0;
+	--_p: var(--w) var(--w) border-box no-repeat;
+	border-radius: 0 !important;
+	background:
+		conic-gradient(from 90deg  at top    var(--b) left  var(--b),var(--_g)) 0    0    / var(--_p),
+		conic-gradient(from 180deg at top    var(--b) right var(--b),var(--_g)) 100% 0    / var(--_p),
+		conic-gradient(from 0deg   at bottom var(--b) left  var(--b),var(--_g)) 0    100% / var(--_p),
+		conic-gradient(from -90deg at bottom var(--b) right var(--b),var(--_g)) 100% 100% / var(--_p) !important;
+}
+html.fy #ggrid>a:hover .imgcontainer {
+	--b: 3px;
+  	--w: 15px;
+}
+html.fy #repl::after,
+html.fy #wrap:after{
+	--b: 10px;
+	--c: var(--as);
+	--w: 40px;
+	
+	margin: 45vh 15vw;
+	font-size: 130px;
+
+	position: fixed;
+	top: 0;
+	content: "🌐";
+	color: transparent;
+	text-shadow: 0 0 0 var(--as);
+	height: 165px;
+	line-height: 145px;
+	
+	filter: blur(7px);
+	z-index: -1;
+	pointer-events: none;
+	padding: 10px;
+}
+html.fy #repl::after{
+	--w: 30px;
+	content: "🎉";
+	top: auto;
+	bottom: 0;
+	margin: 14vh 60vw;
+	height: 90px;
+	line-height: 85px;
+	font-size: 70px;
+	filter: blur(5px);
+	opacity: .6;
+}
+html.fy #files thead th {
+	background: transparent;
+}
+html.fy #files tr:not(.sel) .imgcontainer img {
+	z-index: -1;
+}
+html.fy #zip1 {
+	--bg-max: var(--bg-u2);
+}
+
+html.f.unfun #ops a {
+	padding: .5em;
+}
+html.f.unfun #srchfolder_div {
+	left: 6.7em;
+}
+
+
diff -NarU2 a/copyparty/web/browser.html b/copyparty/web/browser.html
--- a/copyparty/web/browser.html	2026-07-08 15:13:32.819482011 +0000
+++ b/copyparty/web/browser.html	2026-07-28 23:09:10.622344208 +0000
@@ -8,4 +8,5 @@
 	<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/browser.css?_={{ ts }}">
@@ -16,115 +17,273 @@
 </head>
 
-<body>
-	<div id="ops"></div>
-
-	<div id="op_search" class="opview">
-		{%- if have_tags_idx %}
-		<div id="srch_form" class="tags opbox"></div>
-		{%- else %}
-		<div id="srch_form" class="opbox"></div>
-		{%- endif %}
-		<div id="srch_q"></div>
-	</div>
+<video id="bg_vid" autoplay muted loop playsinline></video>
 
-	<div id="op_player" class="opview opbox opwide"></div>
+<body>
+	<a href="#" id="treeToggleBtn" class="tgl btn normalrcm"></a>
 
-	<div id="op_bup" class="opview opbox {% if not ls0 %}act{% endif %}">
-		<div id="u2err"></div>
-		<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
-			<input type="hidden" name="act" value="bput" />
-			<input type="file" name="f" multiple /><br />
-			<input type="submit" value="start upload">
-		</form>
-		<a id="bbsw" href="?b=u" rel="nofollow"><br />switch to basic browser</a>
-	</div>
+	<div id="tree" class="sbar">
+		<div id="treesuperh">
+			<a href="{{ srv_url }}" id="srv_name"><span id="favico_onpage"></span>{{ srv_name }}</a>
+			<div id="treeh"></div>
+		</div>
+		<ul class="ntree" id="treepar"></ul>
+		<ul id="docul"></ul>
+		<ul class="ntree" id="treeul"></ul>
+		<div id="thx_ff">&nbsp;</div>
+
+		<div id="tree_footer">
+			<div id="rtt_latency"></div>
+			<div id="spaceFree">&nbsp;</div>
+			<div id="spaceTotal_bar">
+				<div id="spaceUsed_bar"></div>
+				<div id="spaceMax"></div>
+			</div>
+		</div>
 
-	<div id="op_mkdir" class="opview opbox {% if not ls0 %}act{% endif %}">
-		<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
-			<input type="hidden" name="act" value="mkdir" />
-			📂<input type="text" name="name" class="i" placeholder="awesome mix vol.1">
-			<input type="submit" value="make directory">
-		</form>
+		<div id="reszbar"></div>
 	</div>
 
-	<div id="op_new_md" class="opview opbox">
-		<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
-			<input type="hidden" name="act" value="new_md" />
-			📝<input type="text" name="name" class="i" placeholder="weekend-plans">
-			<input type="submit" value="new file">
-		</form>
-		<span id="new_mdi"></span>
-	</div>
-
-	<div id="op_msg" class="opview opbox {% if not ls0 %}act{% endif %}">
-		<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
-			📟<input type="text" name="msg" class="i" placeholder="lorem ipsum dolor sit amet">
-			<input type="submit" value="send msg to srv log">
-		</form>
-	</div>
+	<div id="wrap">
 
-	<div id="op_unpost" class="opview opbox"></div>
-
-	<div id="op_up2k" class="opview"></div>
+		<div id="pathBar" class="normalrcm shifted">
+			<div id="hspace"></div><div id="wfp">
+				<a href="#" id="gop" class="btn"></a><a 
+				href="#" id="gon" class="btn"></a><a 
+				href="#" id="gou" class="btn"></a>
+			</div>
+			<div id="path">
+				{%- for n in vpnodes %}
+				<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
+				{%- endfor %}
+			</div>
 
-	<div id="op_cfg" class="opview opbox opwide"></div>
+			<div id="actionsArea">
+				<div id="ops">
+					<a href="#" class="disabled" id="opa_srch" data-perm="read" data-dep="idx" data-dest="search">🔎</a>
+					<a href="#" class="disabled" id="opa_del" data-perm="write" data-dest="unpost">🧯</a>
+					<a href="#" class="disabled" id="opa_up" data-dest="up2k">🚀</a>
+					<a href="#" class="disabled" id="opa_bup" data-perm="write" data-dest="bup">🎈</a>
+					<a href="#" class="disabled" id="opa_mkd" data-perm="write" data-dest="mkdir"><p class="overlay_plus">+</p>📂</a>
+					<a href="#" class="disabled" id="opa_msg" data-dest="msg">📟</a>
+					<a href="#" class="disabled" id="opa_cfg" data-dest="cfg">⚙️</a>
+					<a href="#" id="opa_acc" data-dest="acc">
+						<label for="acc_cbopen" id="acc_pfp" class="pfp">👤</label>
+					</a>
+				</div>
+
+				<input type="checkbox" id="acc_cbopen" style="display: none;" />
+				<span id="op_acc" class="opview popup under">
+					<a id="acc_button" class="tgl btn on" href="{{ r }}/?h">
+						<p id="acc_name">Login</p>
+					</a>
+					<div id="accessType" style="display: block;">{{ srv_info }}</div>
+					<form id="flogout" style="display: block;" method="post" enctype="multipart/form-data">
+						<input type="hidden" name="act" value="logout" />
+						<input id="blogout" type="submit" />
+					</form>
+					<a href="{{ r }}/?h" id="goh">control-panel</a>
+					<div class="sep" style="display: block; height: 1px; background: rgba(120, 120, 120, .5); margin: .2em;"></div>
+					<a id="inst" hidden>install as app</a>
+					<a id="pb" href="https://github.com/9001/copyparty">about copyparty</a>
+				</span>
+				<div id="opdesc"></div>
+
+				<div id="srchfolder_div">
+					<input id="folder_search" type="text" />
+					<span id="srch_quickopts" class="popup under">
+						<input type="checkbox" id="srch_recursivec" checked="true">
+						<label for="srch_recursivec" id="srch_recursivelbl" tabindex="0"></label>
+					</span>
+					<div id="qs_btns">
+						<a id="moresearch"><span>▼</span></a>
+						<a id="closesearch"><span class="x">×</span></a>
+					</div>
+				</div>
+			</div>
+		</div>
+
+
+		<div id="op_search" class="opview">
+			{%- if have_tags_idx %}
+			<div id="srch_form" class="tags opbox"></div>
+			{%- else %}
+			<div id="srch_form" class="opbox"></div>
+			{%- endif %}
+			<div id="srch_q"></div>
+		</div>
+
+		<div id="op_player" class="opview opbox opwide"></div>
+
+		<div id="op_bup" class="opview opbox {% if not ls0 %}act{% endif %}">
+			<div id="u2err"></div>
+			<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
+				<input type="hidden" name="act" value="bput" />
+				<input id="bup_btn" type="file" name="f" multiple /><br />
+				<input type="submit" value="start upload">
+			</form>
+			<a id="bbsw" href="?b=u" rel="nofollow"><br />switch to basic browser</a>
+		</div>
+
+		<div id="op_mkdir" class="opview opbox {% if not ls0 %}act{% endif %}">
+			<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
+				<input type="hidden" name="act" value="mkdir" />
+				📂<input type="text" name="name" class="i" placeholder="awesome mix vol.1">
+				<input type="submit" value="make directory">
+			</form>
+		</div>
+
+		<div id="op_new_md" class="opview opbox">
+			<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
+				<input type="hidden" name="act" value="new_md" />
+				📝<input type="text" name="name" class="i" placeholder="weekend-plans">
+				<input type="submit" value="new file">
+			</form>
+			<span id="new_mdi"></span>
+		</div>
+
+		<div id="op_msg" class="opview opbox {% if not ls0 %}act{% endif %}">
+			<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
+				📟<input type="text" name="msg" class="i" placeholder="lorem ipsum dolor sit amet">
+				<input type="submit" value="send msg to srv log">
+			</form>
+		</div>
 
-	<h1 id="path">
-		<a href="#" id="entree">🌲</a>
-		{%- for n in vpnodes %}
-		<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
-		{%- endfor %}
-	</h1>
+		<div id="op_unpost" class="opview opbox"></div>
 
-	<div id="tree"></div>
+		<div id="op_cfg" class="opview opbox opwide"></div>
 
-<div id="wrap">
 
-	{%- if doc %}
-	<div id="bdoc"><pre>{{ doc|e }}</pre></div>
-	{%- else %}
-	<div id="bdoc"></div>
-	{%- endif %}
+		{%- if doc %}
+		<div id="bdoc"><pre>{{ doc|e }}</pre></div>
+		{%- else %}
+		<div id="bdoc"></div>
+		{%- endif %}
 
-	<div id="pro" class="logue">{{ "" if sb_lg else logues[0] }}</div>
+		<div id="pro" class="logue">{{ "" if sb_lg else logues[0] }}</div>
 
-	<table id="files">
-		<thead>
-			<tr>
-				<th name="lead"><span>c</span></th>
-				<th name="href"><span>File Name</span></th>
-				<th name="sz" sort="int"><span>Size</span></th>
-				{%- for k in taglist %}
+		<div id="cfg" class="modal">
+			<a id="s_outside" class="closepane"></a>
+			<div id="s_content" class="modalcontent">
+				<a id="h_cfg" class="modalheader" href="#h_cfg"></a>
+				<div id="s_hor" class="modalsplit">
+					<div id="s_nav" class="splitsub"></div>
+					<div class="divider"></div>
+					<div id="s_list" class="opview splitsub"></div>
+				</div>
+				<a id="reloc_cfg" class="btn winbtn"><span>▼</span></a>
+				<a id="cl_cfg" class="close btn"><span class="x">×</span></a>
+			</div>
+		</div>
+
+		<div id="up2k" class="modal">
+			<a id="up_outside" class="closepane"></a>
+			<div id="up_content" class="modalcontent">
+				<a id="h_up2k" class="modalheader" href="#h_up2k">🚀 Upload</a>
+				<div id="up_hor" class="modalsplit">
+					<div id="op_up2k" class="opview splitsub"></div>
+					<div class="divider"></div>
+					<div id="up_info" class="splitsub"></div>
+				</div>
+				<a id="reloc_up" class="btn winbtn"><span>▼</span></a>
+				<a id="cl_up" class="close btn"><span class="x">×</span></a>
+			</div>
+		</div>
+
+		<div id="music" class="modal">
+			<div id="mu_outer">
+				<div id="mu_inner">
+					<a id="h_music" class="modalheader" href="#">💿 Music Player</a>
+					
+					<a id="mu_th">
+						<svg class="thumb" width="100%" height="100%" version="1.1">
+							<use href="#file-icon"/>
+						</svg>
+						<span class="th_ext"></span>
+						<img/>
+					</a>
+					<span id="mu_tn"></span>				
+					<span id="mu_deets"></span>				
+					<div id="mu_pbb"></div>			
+					<div id="mu_pbc" class="pctl"></div>			
+					<div id="mu_toggles"></div>				
+					<div id="mu_vol"></div>	
+
+					<a id="cfg_mu" class="btn winbtn" href="#h_mp">⚙️</a>
+					<a id="cl_mu" class="close btn"><span class="x">×</span></a>	
+				</div>
+			</div>
+		</div>
+
+		<!-- define symbol in hidden svg document -->
+		<svg style="display: none" version="1.1">
+			<defs>
+				<symbol id="folder-icon" viewBox="0 0 24 24">
+					<path fill-rule="evenodd" clip-rule="evenodd" d="M3 7C3 5.89543 3.89543 5 5 5L8.67157 5C9.20201 5 9.71071 5.21071 10.0858 5.58579L10.9142 6.41421C11.2893 6.78929 11.798 7 12.3284 7H19C20.1046 7 21 7.89543 21 9V17C21 18.1046 20.1046 19 19 19H5C3.89543 19 3 18.1046 3 17V7Z"
+						  style="fill:#07c; fill:var(--a);" />
+				</symbol>
+				<symbol id="file-icon" viewBox="0 0 48 48">
+					<path fill="currentColor"
+						  opacity=".7"
+						  d="m 28.22643,4.6905246 0.0063,9.1096644 a 1.6331061,1.6331061 43.698111 0 0 1.649296,1.632007 l 9.339558,-0.09259 z" />
+					<path class="a"
+						  d="M39.5,15.5h-9a2,2,0,0,1-2-2v-9h-18a2,2,0,0,0-2,2v35a2,2,0,0,0,2,2h27a2,2,0,0,0,2-2Z"
+						  id="path1"
+						  style="stroke:#07c; stroke:var(--a);"
+						  stroke-width="1" fill="none" stroke-linecap="round" stroke-linejoin="round" />
+					<line class="a"
+						  x1="28.5"
+						  y1="4.5"
+						  x2="39.5"
+						  y2="15.5"
+						  style="stroke:#07c; stroke:var(--a);"
+						  stroke-width="1" fill="none" stroke-linecap="round" stroke-linejoin="round" />
+				</symbol>
+			</defs>
+		</svg>
+
+		<div id="wtc" class="ghead"></div>
+		<table id="files">
+			<thead>
+				<tr>
+					<th name="lead"><span>!</span></th>
+					<th name="href"><span>File Name</span></th>
+					<th name="sz" sort="int"><span>Size</span></th>
+					{%- for k in taglist %}
 					{%- if k.startswith('.') %}
-				<th name="tags/{{ k }}" sort="int"><span>{{ k[1:] }}</span></th>
+					<th name="tags/{{ k }}" sort="int"><span>{{ k[1:] }}</span></th>
 					{%- else %}
-				<th name="tags/{{ k }}"><span>{{ k[0]|upper }}{{ k[1:] }}</span></th>
+					<th name="tags/{{ k }}"><span>{{ k[0]|upper }}{{ k[1:] }}</span></th>
 					{%- endif %}
+					{%- endfor %}
+					<th name="ext"><span>Ext</span></th>
+					<th name="ts"><span>Date</span></th>
+				</tr>
+			</thead>
+			<tbody>
+
+				{%- for f in files %}
+				<tr>
+					<td>{{ f.lead }}</td>
+					<td><a href="{{ f.href }}" class="flink">{{ f.name|e }}</a></td>
+					<td>{{ f.sz }}</td>
+					{%- if f.tags is defined %}
+					{%- for k in taglist %}
+					<td>{{ f.tags[k]|e }}</td>{%- endfor %}
+					{%- endif %}
+					<td>{{ f.ext }}</td>
+					<td>{{ f.dt }}</td>
+				</tr>
 				{%- endfor %}
-				<th name="ext"><span>T</span></th>
-				<th name="ts"><span>Date</span></th>
-			</tr>
-		</thead>
-<tbody>
-
-{%- for f in files %}
-<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td>
-{%- if f.tags is defined %}
-	{%- for k in taglist %}<td>{{ f.tags[k]|e }}</td>{%- endfor %}
-{%- endif %}<td>{{ f.ext }}</td><td>{{ f.dt }}</td></tr>
-{%- endfor %}
 
-		</tbody>
-	</table>
+			</tbody>
+		</table>
 
-	<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
+		<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
 
-	<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
+		<a href="#" id="repl">π</a>
 
-	<a href="#" id="repl">π</a>
-
-</div>
+	</div>
 
-	<div id="srv_info"><span>{{ srv_info }}</span></div>
+	<div id="srv_info" style="display: none;"><span>{{ srv_info }}</span></div>
 
 	<div id="widget"></div>
@@ -141,4 +300,5 @@
 			lang = "{{ lang }}",
 			dfavico = "{{ favico }}",
+			unfun = "{{ unfun }}",
 			have_tags_idx = {{ have_tags_idx }},
 			logues = {{ logues|tojson if sb_lg else "[]" }},
@@ -152,7 +312,7 @@
 	<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/tl/{{ lang }}.js?_={{ ts }}"></script>
 	{%- endif %}
-	<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/baguettebox.js?_={{ ts }}"></script>
 	<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/browser.js?_={{ ts }}"></script>
-	<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/up2k.js?_={{ ts }}"></script>
+	<!-- <script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/baguettebox.js?_={{ ts }}"></script>
+	<script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/up2k.js?_={{ ts }}"></script> -->
 	{%- if js %}
 	<script nonce="{{ js_nonce }}" src="{{ js }}_={{ ts }}"></script>
@@ -160,8 +320,8 @@
 	<script nonce="{{ js_nonce }}">
 		Date.now();function jsldp(a,b){2!=window[a]&&alert("FATAL ERROR: cannot load "+b+".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R")}
-		jsldp("J_UTL","util");
-		jsldp("J_BBX","baguettebox");
-		jsldp("J_BRW","browser");
-		jsldp("J_U2K","up2k");
+		jsldp("J_UTL", "util");
+		jsldp("J_BRW", "browser");
+		// jsldp("J_BBX", "baguettebox");
+		// jsldp("J_U2K", "up2k");
 	</script>
 </body>
diff -NarU2 a/copyparty/web/browser.js b/copyparty/web/browser.js
--- a/copyparty/web/browser.js	2026-07-28 23:16:17.539485376 +0000
+++ b/copyparty/web/browser.js	2026-07-28 23:14:14.885971119 +0000
@@ -3,4 +3,18 @@
 var J_BRW = 1;
 
+import_mjs('baguettebox', 'J_BBX');
+import_mjs('up2k', 'J_U2K');
+
+// disables emojis
+var fun_tgl = sread('fun_tgl');
+if( fun_tgl == null)
+	fun_tgl = window.unfun != 'True';
+else
+	fun_tgl = fun_tgl != 0;
+console.log('fun_tgl: ' + fun_tgl);
+if(!fun_tgl){
+	clmod(document.documentElement, 'unfun', true);
+}
+
 if (window.dgauto === undefined)
 	alert('FATAL ERROR: receiving stale data from the server; this may be due to a broken reverse-proxy (stuck cache). Try restarting copyparty and press CTRL-SHIFT-R in the browser');
@@ -13,5 +27,5 @@
 
 		"cols": {
-			"c": "action buttons",
+			"!": "action buttons",
 			"dur": "duration",
 			"q": "quality / bitrate",
@@ -22,5 +36,5 @@
 			"Vhash": "video checksum",
 			"Res": "resolution",
-			"T": "filetype",
+			"Ext": "filetype",
 			"aq": "audio quality / bitrate",
 			"vq": "video quality / bitrate",
@@ -41,5 +55,5 @@
 				["T", "toggle thumbnails / icons"],
 				["⇧ A/D", "thumbnail size"],
-				["ctrl-K", "delete selected"],
+				["ctrl-K/Del", "delete selected"],
 				["ctrl-X", "cut selection to clipboard"],
 				["ctrl-C", "copy selection to clipboard"],
@@ -115,10 +129,14 @@
 
 		"goh": "control-panel",
-		"gop": 'previous sibling">prev',
-		"gou": 'parent folder">up',
-		"gon": 'next folder">next',
+		"gop": 'previous sibling',
+		"gou": 'parent folder',
+		"gon": 'next folder',
 		"logout": "Logout ",
 		"login": "Login",
 		"access": " access",
+		"options": "options",
+		"more_options": "more options",
+		"bup": "basic uploader",
+		"up2k": "up2k",
 		"ot_close": "close submenu",
 		"ot_search": "`search for files by attributes, path / name, music tags, or any combination of those$N$N`foo bar` = must contain both «foo» and «bar»,$N`foo -bar` = must contain «foo» but not «bar»,$N`^yana .opus$` = start with «yana» and be an «opus» file$N`&quot;try unite&quot;` = contain exactly «try unite»$N$Nthe date format is iso-8601, like$N`2009-12-31` or `2020-09-12 23:30:00`",
@@ -145,4 +163,5 @@
 		"wt_cut": "cut selected items &lt;small&gt;(then paste somewhere else)&lt;/small&gt;$NHotkey: ctrl-X",
 		"wt_cpy": "copy selected items to clipboard$N(to paste them somewhere else)$NHotkey: ctrl-C",
+		"wt_sav": "confirm upload location",
 		"wt_pst": "paste a previously cut / copied selection$NHotkey: ctrl-V",
 		"wt_selall": "select all files$NHotkey: ctrl-A (when file focused)",
@@ -156,4 +175,5 @@
 		"wt_m3uc": "copy m3u playlist to clipboard",
 		"wt_grid": "toggle grid / list view$NHotkey: G",
+		"wt_gallery": "toggle gallery view with larger image previews",
 		"wt_prev": "previous track$NHotkey: J",
 		"wt_play": "play / pause$NHotkey: P",
@@ -165,5 +185,5 @@
 		"ut_ow": "overwrite existing files on the server?$N🛡️: never (will generate a new filename instead)$N🕒: overwrite if server-file is older than yours$N♻️: always overwrite if the files are different$N⏭️: unconditionally skip all existing files",
 		"ut_mt": "continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck",
-		"ut_ask": 'ask for confirmation before upload starts">💭',
+		"ut_ask": 'ask for confirmation before upload starts',
 		"ut_pot": "improve upload speed on slow devices$Nby making the UI less complex",
 		"ut_srch": "don't actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)",
@@ -213,10 +233,14 @@
 		"u_nav_b": '<a href="#" id="modal-ok">Files</a><a href="#" id="modal-ng">One folder</a>',
 
-		"cl_opts": "switches",
+		"cl_opts": "general",
 		"cl_hfsz": "filesize",
 		"cl_themes": "theme",
+		"cl_overr_thm": "override server theme. reload the page when turning this off",
+		"cl_accent": "accent color (keep empty for default)$Nsupports any css color, like rgba(255, 210, 0, 1)",
+		"cl_radius": "corner radius in pixels (keep empty for default)",
+		"cl_fun": "enables or disables the FUN (hides most emojis) 🎉🚀👽🐹🥳❗$Nreload the page after changing this setting.",
 		"cl_langs": "language",
 		"cl_ziptype": "folder download",
-		"cl_uopts": "up2k switches",
+		"cl_uopts": "upload",
 		"cl_favico": "favicon",
 		"cl_bigdir": "big dirs",
@@ -233,7 +257,8 @@
 		"ct_grid": '田 the grid',
 		"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
-		"ct_thumb": 'in grid-view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs',
+		"ct_thumb": 'toggle icons or thumbnails$NHotkey: T">🖼️ thumbs',
 		"ct_csel": 'use CTRL and SHIFT for file selection in grid-view">sel',
 		"ct_dsel": 'use drag-selection in grid-view">dsel',
+		"ct_den": 'use dragging to move files into other folders">mvd',
 		"ct_dl": 'force download (don\'t display inline) when a file is clicked">dl',
 		"ct_ihop": 'when the image viewer is closed, scroll down to the last viewed file">g⮯',
@@ -246,4 +271,5 @@
 		"ct_idxh": 'show index.html instead of folder listing">htm',
 		"ct_sbars": 'show scrollbars">⟊',
+		"ct_enbg": 'en-/disable custom backgrounds',
 
 		"cut_umod": "if a file already exists on the server, update the server's last-modified timestamp to match your local file (requires write+delete permissions)\">re📅",
@@ -296,8 +322,9 @@
 		"ml_ss": "skip silence",
 
-		"mt_loop": "loop/repeat one song\">🔁",
+		"mt_loop": "loop/repeat one song",
 		"mt_one": "stop after one song\">1️⃣",
-		"mt_shuf": "shuffle the songs in each folder\">🔀",
+		"mt_shuf": "shuffle the songs in each folder",
 		"mt_aplay": "autoplay if there is a song-ID in the link you clicked to access the server$N$Ndisabling this will also stop the page URL from being updated with song-IDs when playing music, to prevent autoplay if these settings are lost but the URL remains\">a▶",
+		"mt_afade": "fade in / out when pausing / unpausing audio\">fade",
 		"mt_preload": "start loading the next song near the end for gapless playback\">preload",
 		"mt_prescan": "go to the next folder before the last song$Nends, keeping the webbrowser happy$Nso it doesn't stop the playback\">nav",
@@ -313,6 +340,6 @@
 		"mt_compact": "compact controls\">⟎",
 		"mt_uncache": "clear cache &nbsp;(try this if your browser cached$Na broken copy of a song so it refuses to play)\">uncache",
-		"mt_mloop": "loop the open folder\">🔁 loop",
-		"mt_mnext": "load the next folder and continue\">📂 next",
+		"mt_mloop": "loop the open folder",
+		"mt_mnext": "load the next folder and continue",
 		"mt_mstop": "stop playback\">⏸ stop",
 		"mt_cflac": "convert flac / wav to {0}\">flac",
@@ -382,4 +409,5 @@
 		"fc_eperm": 'cannot cut:\nyou do not have “move” permission in this folder',
 		"fp_eperm": 'cannot paste:\nyou do not have “write” permission in this folder',
+		"fp_esrch": 'cannot paste:\npasting disabled in search results',
 		"fr_emore": "select at least one item to rename",
 		"fd_emore": "select at least one item to delete",
@@ -491,4 +519,5 @@
 		"gt_msel": "enable file selection; ctrl-click a file to override$N$N&lt;em&gt;when active: doubleclick a file / folder to open it&lt;/em&gt;$N$NHotkey: S\">multiselect",
 		"gt_crop": "center-crop thumbnails\">crop",
+		"gt_upscale": "scale up small image previews to fit",
 		"gt_3x": "hi-res thumbnails\">3x",
 		"gt_zoom": "zoom",
@@ -503,5 +532,5 @@
 
 		"sm_w8": "searching...",
-		"sm_prev": "search results below are from a previous query:\n  ",
+		"sm_prev": "search results below are from a previous query:  ",
 		"sl_close": "close search results",
 		"sl_hits": "showing {0} hits",
@@ -525,4 +554,6 @@
 		"s_t1": "tags contains &nbsp; (^=start, end=$)",
 		"s_a1": "specific metadata properties",
+		"s_dir": "search this folder",
+		"s_rec": "include subfolders",
 
 		"md_eshow": "cannot render ",
@@ -799,55 +830,186 @@
 
 
-// toolbar
-ebi('ops').innerHTML = (
-	'<a href="#" id="opa_x" data-dest="" tt="' + L.ot_close + '">--</a>' +
-	'<a href="#" id="opa_srch" data-perm="read" data-dep="idx" data-dest="search" tt="' + L.ot_search + '">🔎</a>' +
-	(have_del ? '<a href="#" id="opa_del" data-perm="write" data-dest="unpost" tt="' + L.ot_unpost + '">🧯</a>' : '') +
-	'<a href="#" id="opa_up" data-dest="up2k">🚀</a>' +
-	'<a href="#" id="opa_bup" data-perm="write" data-dest="bup" tt="' + L.ot_bup + '">🎈</a>' +
-	'<a href="#" id="opa_mkd" data-perm="write" data-dest="mkdir" tt="' + L.ot_mkdir + '">📂</a>' +
-	'<a href="#" id="opa_md" data-perm="read write" data-dest="new_md" tt="' + L.ot_md + '">📝</a>' +
-	'<a href="#" id="opa_msg" data-dest="msg" tt="' + L.ot_msg + '">📟</a>' +
-	'<a href="#" id="opa_auc" data-dest="player" tt="' + L.ot_mp + '">🎺</a>' +
-	'<a href="#" id="opa_cfg" data-dest="cfg" tt="' + L.ot_cfg + '">⚙️</a>' +
-	(IE ? '<span id="noie">' + L.ot_noie + '</span>' : '') +
-	'<div id="opdesc"></div>'
-);
+var opa = QSA('#ops>a, #wfp>a')
+for(var i = 0; i <opa.length; i++)
+	get_tt(opa[i])
+
+function get_tt(el) {
+	var tlid = 'tt_' + el.id
+	if(!L[tlid] && el.getAttribute('data-dest'))
+		tlid = 'ot_' + el.getAttribute('data-dest')
+	if(!L[tlid])
+		tlid = el.id
+	if(L[tlid])
+		el.setAttribute('tt', L[tlid])
+}
+if(!have_del)
+	ebi('opa_del').style.display = 'none'
+
+if(!fun_tgl){
+	ebi('opa_srch').innerHTML = 'search'
+	ebi('opa_del').innerHTML = 'undo'
+	ebi('opa_up').innerHTML = 'upload'
+	ebi('opa_bup').innerHTML = 'bup'
+	ebi('opa_msg').innerHTML = 'message'
+	ebi('opa_cfg').innerHTML = 'settings'
+	ebi('acc_pfp').innerHTML = 'account'
+}
+else if(!EMOJI){
+	ebi('opa_srch').innerHTML = 's?'
+	ebi('opa_del').innerHTML = 'un'
+	ebi('opa_up').innerHTML = 'u▲'
+	ebi('opa_bup').innerHTML = 'b▲'
+	ebi('opa_msg').innerHTML = 'msg'
+	ebi('opa_cfg').innerHTML = '★'
+	ebi('acc_pfp').innerHTML = 'acc'
+}
+
+// mkdir + md
+function mktemp(is_dir) {
+	if(!has(perms, 'read')){
+		if(is_dir)
+			goto('mkdir');
+		else
+			goto('new_md');
+	}
+	qsr('#rcm_tmp');
+	if (!thegrid.en) {
+		var row = mknod('tr', 'rcm_tmp',
+			'<td>-new-</td><td colspan="' + (QSA("#files thead th").length - 1) + '"><input id="tempname" class="i" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></td>');
+		QS("#files tbody").appendChild(row);
+	}
+	else {
+		var row = mknod('a', 'rcm_tmp',
+			'<span class="dir" style="align-self:end"><input id="tempname" class="dir" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></span>');
+		if (is_dir)
+			row.className = 'dir';
+		row.style.display = 'flex';
+		QS("#ggrid").appendChild(row);
+	}
+
+	function sendit(name) {
+		name = ('' + name).trim();
+		if (!name)
+			return;
+		var data = new FormData();
+		data.append("act", is_dir ? "mkdir" : "new_md");
+		data.append("name", name);
+
+		var req = new XHR();
+		req.open("POST", get_evpath());
+		req.onload = req.onerror = function() {
+			if (req.status == 405 || req.status == 500)
+				return toast.err(3, "a " + (is_dir ? "folder" : "file") + " with that name already exists.");
+			if (req.status < 200 || req.status > 399)
+				return toast.err(3, "couldn't create " + (is_dir ? "folder" : "file") + ": <br><code>" + esc(req.responseText) + '</code>');
+			treectl.goto();
+		};
+		req.send(data);
+	}
 
+	var input = ebi("tempname");
+	input.onblur = function() {
+		sendit(input.value);
+		// Chrome blurs elements when calling remove for some reason
+		input.onblur = null;
+		if(row.remove)
+			row.remove();
+		else
+			row.style.display = 'none';
+	};
+	input.onkeydown = function(e) {
+		if (e.key == "Enter")
+			sendit(input.value);
+		if (e.key == "Enter" || e.key == "Escape") {
+			input.onblur = null;
+			if(row.remove)
+				row.remove();
+			else
+				row.style.display = 'none';
+			ev(e);
+		}
+	};
+	input.focus();
+}
 
 // media player
+
+// svg from https://www.svgrepo.com/collection/iconcino-interface-icons/8
+var svg_box = '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">';
+var svg_options = 'stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
+var svg_prev = svg_box + '<path d="M8 6V18M11.4761 12.7809L16.3753 16.7002C17.0301 17.2241 18 16.7579 18 15.9194V8.08062C18 7.24212 17.0301 6.77595 16.3753 7.29976L11.4761 11.2191C10.9757 11.6195 10.9757 12.3805 11.4761 12.7809Z" ' + svg_options + '/></svg>';
+var svg_next = svg_box + '<path d="M17 6V18M13.5239 12.7809L8.6247 16.7002C7.96993 17.2241 7 16.7579 7 15.9194V8.08062C7 7.24212 7.96993 6.77595 8.6247 7.29976L13.5239 11.2191C14.0243 11.6195 14.0243 12.3805 13.5239 12.7809Z" ' + svg_options + '/></svg>';
+var svg_play = svg_box + '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" ' + svg_options + '/><path fill-rule="evenodd" clip-rule="evenodd" d="M10.4706 8.78431C9.80448 8.42906 9 8.91175 9 9.66667V14.3333C9 15.0883 9.80448 15.5709 10.4706 15.2157L15.6728 12.4412C16.0257 12.2529 16.0257 11.7471 15.6728 11.5588L10.4706 8.78431Z" ' + svg_options + '/></svg>'
+var svg_pause = svg_box + '<path d="M10 9V15M14 9V15M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" ' + svg_options + '/></svg>'
+var svg_vol = svg_box + '<path d="M16.0004 9.00009C16.6281 9.83575 17 10.8745 17 12.0001C17 13.1257 16.6281 14.1644 16.0004 15.0001M18 5.29177C19.8412 6.93973 21 9.33459 21 12.0001C21 14.6656 19.8412 17.0604 18 18.7084M4.6 9.00009H5.5012C6.05213 9.00009 6.32759 9.00009 6.58285 8.93141C6.80903 8.87056 7.02275 8.77046 7.21429 8.63566C7.43047 8.48353 7.60681 8.27191 7.95951 7.84868L10.5854 4.69758C11.0211 4.17476 11.2389 3.91335 11.4292 3.88614C11.594 3.86258 11.7597 3.92258 11.8712 4.04617C12 4.18889 12 4.52917 12 5.20973V18.7904C12 19.471 12 19.8113 11.8712 19.954C11.7597 20.0776 11.594 20.1376 11.4292 20.114C11.239 20.0868 11.0211 19.8254 10.5854 19.3026L7.95951 16.1515C7.60681 15.7283 7.43047 15.5166 7.21429 15.3645C7.02275 15.2297 6.80903 15.1296 6.58285 15.0688C6.32759 15.0001 6.05213 15.0001 5.5012 15.0001H4.6C4.03995 15.0001 3.75992 15.0001 3.54601 14.8911C3.35785 14.7952 3.20487 14.6422 3.10899 14.4541C3 14.2402 3 13.9601 3 13.4001V10.6001C3 10.04 3 9.76001 3.10899 9.54609C3.20487 9.35793 3.35785 9.20495 3.54601 9.10908C3.75992 9.00009 4.03995 9.00009 4.6 9.00009Z" ' + svg_options + '/></svg>'
+var svg_dlzip = svg_box + '<path style="fill:none;stroke:currentColor;stroke-width:1.3;stroke-linecap:butt;stroke-linejoin:round" d="M 9.3594717,18.427162 H 5.2685174 A 1.461412,1.461412 0 0 1 3.8071053,16.96575 V 4.2658445 A 1.3894971,1.3894971 0 0 1 5.1966025,2.8763474 h 4.9079295 l 2.640559,2.7481735 h 7.599855 A 1.3638132,1.3638132 0 0 1 21.70876,6.9883341 V 17.109258 a 1.2976047,1.2976047 0 0 1 -1.292207,1.297595 l -4.882326,0.02031" /> <path style="stroke:currentColor;stroke-width:3.78;stroke-linecap:butt" d="m 12.434773,17.545979 v 3.876985"  /> <path style="fill:currentColor" d="m 12.400955,23.183771 6.171837,-2.663485 -12.3007157,0 z" /></svg>'
+
 ebi('widget').innerHTML = (
-	'<div id="wtoggle">' +
-	'<span id="wfs"></span>' +
-	'<span id="wfm"><a' +
-	' href="#" id="fshr" tt="' + L.wt_shr + '">📨<span>share</span></a><a' +
-	' href="#" id="fren" tt="' + L.wt_ren + '">✎<span>name</span></a><a' +
-	' href="#" id="fdel" tt="' + L.wt_del + '">⌫<span>del.</span></a><a' +
-	' href="#" id="fcut" tt="' + L.wt_cut + '">✂<span>cut</span></a><a' +
-	' href="#" id="fcpy" tt="' + L.wt_cpy + '">⧉<span>copy</span></a><a' +
-	' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
-	'</span><span id="wzip1"><a' +
-	' href="#" id="zip1" tt="' + L.wt_zip1 + '">📦<span>zip</span></a>' +
-	'</span><span id="wzip"><a' +
-	' href="#" id="selall" tt="' + L.wt_selall + '">sel.<br />all</a><a' +
-	' href="#" id="selinv" tt="' + L.wt_selinv + '">sel.<br />inv.</a><a' +
-	' href="#" id="selzip" class="l1" tt="' + L.wt_selzip + '">zip</a><a' +
-	' href="#" id="seldl" class="l1" tt="' + L.wt_seldl + '">dl</a>' +
-	'</span><span id="wnp"><a' +
-	' href="#" id="npirc" tt="' + L.wt_npirc + '">📋<span>irc</span></a><a' +
-	' href="#" id="nptxt" tt="' + L.wt_nptxt + '">📋<span>txt</span></a>' +
-	'</span><span id="wm3u"><a' +
-	' href="#" id="m3ua" tt="' + L.wt_m3ua + '">📻<span>add</span></a><a' +
-	' href="#" id="m3uc" tt="' + L.wt_m3uc + '">📻<span>copy</span></a>' +
-	'</span><a' +
-	'	href="#" id="wtgrid" tt="' + L.wt_grid + '">田</a><a' +
-	'	href="#" id="wtico">♫</a>' +
+	'<div id="wstack">' +
+
+		'<div id="wtoggle">' +
+		'<span id="wfs"></span>' +
+			'<span id="wfm"><a' +
+				' href="#" id="fclr"><span class="x">×</span><span>cancel</span></a><a' +
+				' href="#" id="fshr" tt="' + L.wt_shr + '">📨<span>share</span></a><a' +
+				' href="#" id="fcpl" tt="' + L.rc_cpl + '">🔗<span>link</span></a><a' +
+				' href="#" id="fren" tt="' + L.wt_ren + '">' + (fun_tgl ? '✏️' : '✎') + '<span>name</span></a><a' +
+				' href="#" id="fdel" tt="' + L.wt_del + '">🗑️<span>del.</span></a><a' +
+				' href="#" id="fcut" tt="' + L.wt_cut + '">' + (fun_tgl ? '✂️' : '✂') + '<span>cut</span></a><a' +
+				' href="#" id="fcpy" tt="' + L.wt_cpy + '">⧉<span>copy</span></a><a' +
+				' href="#" id="fsav" tt="' + L.wt_sav + '">💾<span>save</span></a><a' +
+				' href="#" id="fpst" tt="' + L.wt_pst + '">📋<span>paste</span></a>' +
+			'</span><span id="wzip1"><a' +
+				' href="#" id="zip1" tt="' + L.wt_zip1 + '">' + svg_dlzip + '<span>zip</span></a>' +
+			'</span><span id="wzip"><a' +
+				' href="#" id="selall" tt="' + L.wt_selall + '">' + (fun_tgl ? '✅<br/>sel. ' : 'sel.<br/>') + 'all</a><a' +
+				' href="#" id="selinv" tt="' + L.wt_selinv + '">' + (fun_tgl ? '🔛<br/>sel. ' : 'sel.<br/>') + 'inv.</a><a' +
+	' href="#" id="selzip" class="l1" tt="' + L.wt_selzip + '">' + (fun_tgl ? '📦<br/>' : '') + 'zip</a><a' +
+				' href="#" id="seldl" class="l1" tt="' + L.wt_seldl + '">' + (fun_tgl ? '⬇️<br/>' : '') + 'files</a>' +
+			'</span><span id="wnp"><a' +
+				' href="#" id="npirc" tt="' + L.wt_npirc + '">📋<span>irc</span></a><a' +
+				' href="#" id="nptxt" tt="' + L.wt_nptxt + '">📋<span>txt</span></a>' +
+			'</span><span id="wm3u"><a' +
+				' href="#" id="m3ua" tt="' + L.wt_m3ua + '">📻<span>add</span></a><a' +
+				' href="#" id="m3uc" tt="' + L.wt_m3uc + '">📻<span>copy</span></a>' +
+			'</span>' +
+		'</div>' +
+
+		'<div id="up_quick">' +
+		'	<div id="up_quick_more">' +
+		'		<a id="uq_nd">' + L.rc_nfo + ' <span><p class="overlay_plus">+</p>📂</span></a>\n' +
+		'		<a id="uq_nf">' + L.rc_nfi + ' <span><p class="overlay_plus">+</p>📝</span></a>\n' +
+		'		<a id="uq_up">' + L.cl_uopts + ' <span>🚀<span></a>\n' +
+		'	</div>' +
+		'	<a id="up_quick_btn" class="tgl btn on"><div class="rotatable">+</div></a>' +
+		'</div>' +
+
+		'<a href="#" id="wtico">' + (EMOJI ? '♫' : '♪') + '</a>' +
+
 	'</div>' +
+
 	'<div id="widgeti">' +
-	'	<div id="pctl"><a href="#" id="bprev" tt="' + L.wt_prev + '">⏮</a><a href="#" id="bplay" tt="' + L.wt_play + '">▶</a><a href="#" id="bnext" tt="' + L.wt_next + '">⏭</a></div>' +
-	'	<canvas id="pvol" width="288" height="38"></canvas>' +
-	'	<canvas id="barpos"></canvas>' +
-	'	<canvas id="barbuf"></canvas>' +
-	'</div>' +
+	'	<div id="pbarthinpos"></div>' +
+	'	<div id="pctl" class="pctl">' +
+	'		<a href="#" id="bprev" class="icon btn on" tt="' + L.wt_prev + '">' + svg_prev + '</a>' +
+	'		<a href="#" id="bplay" class="icon btn on" tt="' + L.wt_play + '">' + svg_play + '</a>' +
+	'		<a href="#" id="bnext" class="icon btn on" tt="' + L.wt_next + '">' + svg_next + '</a>' +
+	'		<a id="trackname" href="#h_music"></a>' +
+	'		<div id="progbar" style="position: relative; height: 80%;">' +
+	'			<canvas id="barbuf"></canvas>' +
+	'			<canvas id="barpos"></canvas>' +
+	'		</div>' +
+	'		<div id="altprogbar" class="vis">' +
+	'			<span id="txtpos">0:00</span>' +
+	'			<input type="range" min="0" max="1" step="0.001" value="0" id="sliderpos"></input>' +
+	'			<span id="txtsongend"/>0:00</span>' +
+	'		</div>' +
+	'		<div id="au_tgls" style="display: contents;">' +
+	'			<a class="tgl btn" tabindex="0" id="cyclebtn_loopmode" tt="' + L.mt_loop + '$N' + L.mt_mloop + '$N' + L.mt_mnext + '">🔁</a>' +
+	'			<a class="tgl btn" tabindex="0" id="au_shuf" tt="' + L.mt_shuf + '">🔀</a>' +
+	'		</div>' +
+	'		<div id="pvolbg" tabindex="0">' + svg_vol + '<canvas id="pvol"></canvas></div>' +
+	'	</div>' +
+	'</div>'+
 	'<div id="np_inf">' +
 	'	<img id="np_img" />' +
@@ -863,43 +1025,37 @@
 );
 
+ebi('mu_pbc').innerHTML = (
+	'<a href="#" id="bprev2" class="icon btn on" tt="' + L.wt_prev + '">' + svg_prev + '</a>' +
+	'<a href="#" id="bplay2" class="icon btn on" tt="' + L.wt_play + '">' + svg_play + '</a>' +
+	'<a href="#" id="bnext2" class="icon btn on" tt="' + L.wt_next + '">' + svg_next + '</a>'
+);
+
+ebi('wtoggle').addEventListener('wheel', function (e) {
+	scroll_v_to_h(e, this);
+});
+
+ebi('up_quick').onclick = function(){
+	if(!has(perms, 'write'))
+		return;
+	var btn = ebi('up_quick_btn');
+	clmod(btn, 'on', 't');
+	var isOff = clgot(btn, 'on'); // button has inverted display logic
+	clmod(ebi('up_quick_more'), 'vis', !isOff)
+	clmod(ebi('up_quick'), 'open', !isOff)
+}
+ebi('uq_nd').onclick = function(){
+	mktemp(true);
+};
+ebi('uq_nf').onclick = function(){
+	mktemp();
+};
+
 
 // up2k ui
+ebi('h_up2k').innerHTML = (fun_tgl ? '🚀 ' : '') + L.cl_uopts;
 ebi('op_up2k').innerHTML = (
 	'<form id="u2form" method="post" enctype="multipart/form-data"></form>\n' +
 
-	'<table id="u2conf">\n' +
-	'	<tr>\n' +
-	'		<td class="c" data-perm="read"><br />' + L.ul_par + '</td>\n' +
-	'		<td class="c" rowspan="2">\n' +
-	'			<input type="checkbox" id="multitask" />\n' +
-	'			<label for="multitask" tt="' + L.ut_mt + '">🏃</label>\n' +
-	'		</td>\n' +
-	'		<td class="c" rowspan="2">\n' +
-	'			<input type="checkbox" id="potato" />\n' +
-	'			<label for="potato" tt="' + L.ut_pot + '">🥔</label>\n' +
-	'		</td>\n' +
-	'		<td class="c" rowspan="2">\n' +
-	'			<input type="checkbox" id="u2rand" />\n' +
-	'			<label for="u2rand" tt="' + L.ut_rand + '">🎲</label>\n' +
-	'		</td>\n' +
-	'		<td class="c" rowspan="2">\n' +
-	'			<input type="checkbox" id="u2ow" />\n' +
-	'			<label for="u2ow" tt="' + L.ut_ow + '">?</a>\n' +
-	'		</td>\n' +
-	'		<td class="c" data-perm="read" data-dep="idx" rowspan="2">\n' +
-	'			<input type="checkbox" id="fsearch" />\n' +
-	'			<label for="fsearch" tt="' + L.ut_srch + '">🔎</label>\n' +
-	'		</td>\n' +
-	'		<td data-perm="read" rowspan="2" id="u2btn_cw"></td>\n' +
-	'		<td data-perm="read" rowspan="2" id="u2c3w"></td>\n' +
-	'	</tr>\n' +
-	'	<tr>\n' +
-	'		<td class="c" data-perm="read">\n' +
-	'			<a href="#" class="b" id="nthread_sub">&ndash;</a><input\n' +
-	'				class="txtbox" id="nthread" value="" tt="' + L.ut_par + '"/><a\n' +
-	'				href="#" class="b" id="nthread_add">+</a><br />&nbsp;\n' +
-	'		</td>\n' +
-	'	</tr>\n' +
-	'</table>\n' +
+	'	<div data-perm="read" rowspan="2" id="u2c3w"></div>\n' +
 
 	'<div id="u2notbtn"></div>\n' +
@@ -911,14 +1067,32 @@
 	'</div>\n' +
 
+	'	<div id="up2k_qs">' +
+	'		<label tabindex="0" for="fsearch" id="fsearch_copy" class="tgl btn" tt="' + L.ut_srch +'">🔎</label>\n' +
+	'		<label tabindex="0" for="u2ow" id="u2ow_copy" class="tgl btn on" tt="' + L.ut_ow +'"></label>\n' +
+	'		<label tabindex="0" for="u2rand" id="u2rand_copy" class="tgl btn" tt="' + L.ut_rand +'">🎲</label>\n' +
+	'		<label tabindex="0" for="multitask" id="multitask_copy" class="tgl btn" tt="' + L.ut_mt +'">🏃</label>\n' +
+	'		<a id="up_moreopts" class="btn" href="#h_up2kcfg">↗️ ' + L.more_options + '</a>\n' +
+	'	</div>\n' +
+
 	'<div id="u2c3t">\n' +
 
-	'<div id="u2etaw"><div id="u2etas"><div class="o">\n' +
+	'	<div id="u2etaw">' +
+	'		<div id="u2etas">' +
+	'			<div class="o">\n' +
 	L.ul_hash + ': <span id="u2etah" tt="' + L.ut_etah + '">(' + L.ul_idle1 + ')</span><br />\n' +
 	L.ul_send + ': <span id="u2etau" tt="' + L.ut_etau + '">(' + L.ul_idle1 + ')</span><br />\n' +
-	'	</div><span class="o">' +
+	'			</div><span class="o">' +
 	L.ul_done + ': </span><span id="u2etat" tt="' + L.ut_etat + '">(' + L.ul_idle1 + ')</span>\n' +
-	'</div></div>\n' +
+	'		</div>' +
+	'	</div>' +
+	'</div>' +
+
+	'<details id="bup_tgl" href="#v=bup" data-dest="bup" data-perm="write" tt="' + L.ot_bup + '"><summary id="h_bup">' + (IE ? '>> ' : '') + L.bup + (IE ? ' <<' : '') + '</summary></details>'
+);
+
+ebi('up_info').innerHTML = (
+	'<h3 id="up_status_h">' + L.utl_stat + '</h3>' +
 
-	'<div id="u2cards">\n' +
+	'</div>\n<div id="u2cards">\n' +
 	'	<a href="#" act="ok" tt="' + L.uct_ok + '">ok <span>0</span></a><a\n' +
 	'	href="#" act="ng" tt="' + L.uct_ng + '">ng <span>0</span></a><a\n' +
@@ -926,7 +1100,6 @@
 	'	href="#" act="bz" tt="' + L.uct_bz + '" class="act">busy <span>0</span></a><a\n' +
 	'	href="#" act="q" tt="' + L.uct_q + '">que <span>0</span></a>\n' +
-	'</div>\n' +
-
-	'</div>\n' +
+	//'	href="#" style="pointer-events: none; width: 90%; display: inline-block; font-size: 1.1em;"><span style="opacity: 0">_</span></a>\n' +
+	'</div>\n</div>\n' +
 
 	'<div id="u2tabw" class="na"><table id="u2tab">\n' +
@@ -949,4 +1122,28 @@
 };
 
+ebi('h_up2k').onclick = function(){
+	ebi('up_info').scrollTop =
+	ebi('op_up2k').scrollTop =
+	ebi('up_hor').scrollTop =
+	ebi('up_content').scrollTop =
+		0;
+}
+
+ebi('cl_up').onclick =
+ebi('up_outside').onclick =
+	function () {
+		hist_push('#')
+		modaltoggle('up2k', false);
+	}
+
+var up2k_lgcy = false;
+ebi('reloc_up').onclick = function(){
+	up2k_lgcy = !up2k_lgcy;
+	this.innerHTML = up2k_lgcy ? '<span>▲</span>' : '<span>▼</span>';
+	clmod(ebi('up2k'), 'unmodal', up2k_lgcy);
+	swrite('up2k_lgcy', up2k_lgcy);
+}
+if(sread('up2k_lgcy') == 'true')
+	ebi('reloc_up').click();
 
 ebi('wrap').insertBefore(mknod('div', 'lazy'), ebi('epi'));
@@ -956,11 +1153,25 @@
 	'<input type="checkbox" id="uput" name="uput"><label for="uput">' + L.u_uput + '</label>'), x);
 
+var bup = ebi('op_bup');
+ebi('bup_tgl').appendChild(bup);
+ebi('h_bup').onclick = function() {
+	var open = !clgot(bup, 'act');
+	if(open){
+		location.hash = '#h_bup';
+		modaltoggle('bup', true);
+	}
+	else{
+		location.hash = '#h_up2k';
+		modaltoggle('up2k', true);
+	}
+	ebi('bup_tgl').open = !open;
+};
 
 (function () {
 	var o = mknod('div');
 	o.innerHTML = (
-		'<div id="drops">\n' +
-		'	<div class="dropdesc" id="up_zd"><div>🚀 ' + L.udt_up + '<br /><span></span><div>🚀<b>' + L.udt_up + '</b></div><div><b>' + L.udt_up + '</b>🚀</div></div></div>\n' +
-		'	<div class="dropdesc" id="srch_zd"><div>🔎 ' + L.udt_srch + '<br /><span></span><div>🔎<b>' + L.udt_srch + '</b></div><div><b>' + L.udt_srch + '</b>🔎</div></div></div>\n' +
+		'<div id="drops" class="modal">\n' +
+		'	<div class="dropdesc" id="up_zd"><div>🚀 ' + L.udt_up + '<br /><span></span></div></div>\n' +
+		'	<div class="dropdesc" id="srch_zd"><div>🔎 ' + L.udt_srch + '<br /><span></span></div></div>\n' +
 		'	<div class="dropzone" id="up_dz" v="up_zd"></div>\n' +
 		'	<div class="dropzone" id="srch_dz" v="srch_zd"></div>\n' +
@@ -970,36 +1181,95 @@
 })();
 
-
 // config panel
+
+var musicSettings = (
+	'<div class="sub_section"><h3 id="h_mp">🎵 ' + L.ot_mp + '</h3></div>' +
+
+	'<div class="sub"><h3 id="h_mpopts">🎧 ' + L.cl_opts + '</h3><div>' +
+	'<a class="tgl btn" id="au_loop" tt="' + L.mt_loop + '">🔂</a>' +
+	'<a class="tgl btn" id="au_shuf2" tt="' + L.mt_shuf + '">🔀</a>' +
+	'<a class="tgl btn" id="au_one" tt="' + L.mt_one + '</a>' +
+	'<a class="tgl btn" id="au_aplay" tt="' + L.mt_aplay + '</a>' +
+	'<a class="tgl btn" id="au_afade" tt="' + L.mt_afade + '</a>' +
+	'<a class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
+	'<a class="tgl btn" id="au_prescan" tt="' + L.mt_prescan + '</a>' +
+	'<a class="tgl btn" id="au_fullpre" tt="' + L.mt_fullpre + '</a>' +
+	'<a class="tgl btn" id="au_fau" tt="' + L.mt_fau + '</a>' +
+	'<a class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' +
+	'<a class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' +
+	'<a class="tgl btn" id="au_m3u_c" tt="' + L.mt_m3u_c + '</a>' +
+	'<a class="tgl btn" id="au_os_ctl" tt="' + L.mt_octl + '</a>' +
+	'<a class="tgl btn" id="au_os_seek" tt="' + L.mt_oseek + '</a>' +
+	'<a class="tgl btn" id="au_osd_cv" tt="' + L.mt_oscv + '</a>' +
+	'<a class="tgl btn" id="au_follow" tt="' + L.mt_follow + '</a>' +
+	'<a class="tgl btn" id="au_compact" tt="' + L.mt_compact + '</a>' +
+	'</div></div>' +
+
+	'<div class="sub"><h3 id="h_cmds">▶️ ' + L.ml_btns + '</h3><div>' +
+	'<a class="btn" id="au_uncache" tt="' + L.mt_uncache + '</a>' +
+	'</div></div>' +
+
+	'<div class="sub"><h3 id="h_pbmode">📁 ' + L.ml_pmode + '</h3><div id="pb_mode">' +
+	'<a class="tgl btn" m="loop" tt="' + L.mt_mloop + '">🔁</a>' +
+	'<a class="tgl btn" m="next" tt="' + L.mt_mnext + '">📂⏭️</a>' +
+	'<a class="tgl btn" m="stop" tt="' + L.mt_mstop + '</a>' +
+	'</div></div>' +
+
+	(have_acode ? (
+		'<div class="sub"><h3 id="h_transfrom">🏹 ' + L.ml_tcode + '</h3><div>' +
+		'<a id="ac_flac" class="tgl btn" tt="' + L.mt_cflac + '</a>' +
+		'<a id="ac_aac" class="tgl btn" tt="' + L.mt_caac + '</a>' +
+		'<a id="ac_oth" class="tgl btn" tt="' + L.mt_coth + '</a>' +
+		'</div></div>' +
+		'<div class="sub"><h3 id="h_transto">🎯 ' + L.ml_tcode2 + '</h3><div>' +
+		'<a id="ac2opus" class="tgl btn" tt="' + L.mt_c2opus + '</a>' +
+		'<a id="ac2owa" class="tgl btn" tt="' + L.mt_c2owa + '</a>' +
+		'<a id="ac2caf" class="tgl btn" tt="' + L.mt_c2caf + '</a>' +
+		'<a id="ac2mp3" class="tgl btn" tt="' + L.mt_c2mp3 + '</a>' +
+		'<a id="ac2flac" class="tgl btn" tt="' + L.mt_c2flac + '</a>' +
+		'<a id="ac2wav" class="tgl btn" tt="' + L.mt_c2wav + '</a>' +
+		'</div></div>'
+	) : '') +
+
+	'<div class="sub"><h3 id="h_tint">🕶️ ' + L.ml_tint + '</h3><div>' +
+	'<input type="text" id="pb_tint" value="0" ' + NOAC + ' style="width:2.4em" tt="' + L.mt_tint + '" />' +
+	'</div></div>' +
+
+	'<div class="sub"><h3 id="h_drc">🐘 ' + L.ml_drc + '</h3><div><div id="audio_drc"></div></div></div>' +
+	'<div class="sub"><h3 id="h_eq">📊 ' + L.ml_eq + '</h3><div><div id="audio_eq"></div></div></div>' +
+	'<div class="sub"><h3 id="h_silence">🔇 ' + L.ml_ss + '</h3><div><div id="audio_ss"></div></div></div>' +
+	'');
+
 ebi('op_cfg').innerHTML = (
 	'<div>\n' +
-	'	<h3>' + L.cl_opts + '</h3>\n' +
+	'	<h3 id="h_general">✅ ' + L.cl_opts + '</h3>\n' +
 	'	<div>\n' +
-	'		<a id="tooltips" class="tgl btn" href="#" tt="' + L.ct_ttips + '</a>\n' +
-	'		<a id="griden" class="tgl btn" href="#" tt="' + L.wt_grid + '">' + L.ct_grid + '</a>\n' +
-	'		<a id="thumbs" class="tgl btn" href="#" tt="' + L.ct_thumb + '</a>\n' +
-	'		<a id="csel" class="tgl btn" href="#" tt="' + L.ct_csel + '</a>\n' +
-	'		<a id="dsel" class="tgl btn" href="#" tt="' + L.ct_dsel + '</a>\n' +
-	'		<a id="dlni" class="tgl btn" href="#" tt="' + L.ct_dl + '</a>\n' +
-	'		<a id="ihop" class="tgl btn" href="#" tt="' + L.ct_ihop + '</a>\n' +
-	'		<a id="dotfiles" class="tgl btn" href="#" tt="' + L.ct_dots + '</a>\n' +
-	'		<a id="qdel" class="tgl btn" href="#" tt="' + L.ct_qdel + '</a>\n' +
-	'		<a id="dir1st" class="tgl btn" href="#" tt="' + L.ct_dir1st + '</a>\n' +
-	'		<a id="nsort" class="tgl btn" href="#" tt="' + L.ct_nsort + '</a>\n' +
-	'		<a id="utctid" class="tgl btn" href="#" tt="' + L.ct_utc + '</a>\n' +
-	'		<a id="ireadme" class="tgl btn" href="#" tt="' + L.ct_readme + '</a>\n' +
-	'		<a id="idxh" class="tgl btn" href="#" tt="' + L.ct_idxh + '</a>\n' +
-	'		<a id="sbars" class="tgl btn" href="#" tt="' + L.ct_sbars + '</a>\n' +
+	'		<a id="tooltips" class="tgl btn" tt="' + L.ct_ttips + '</a>\n' +
+	'		<a id="thumbs" class="tgl btn" tt="' + L.ct_thumb + '</a>\n' +
+	'		<a id="griden" class="tgl btn" tt="' + L.wt_grid + '">' + L.ct_grid + '</a>\n' +
+	'		<a id="csel" class="tgl btn" tt="' + L.ct_csel + '</a>\n' +
+	'		<a id="dsel" class="tgl btn" tt="' + L.ct_dsel + '</a>\n' +
+	'		<a id="den" class="tgl btn" tt="' + L.ct_den + '</a>\n' +
+	'		<a id="dlni" class="tgl btn" tt="' + L.ct_dl + '</a>\n' +
+	'		<a id="ihop" class="tgl btn" tt="' + L.ct_ihop + '</a>\n' +
+	'		<a id="dotfiles" class="tgl btn" tt="' + L.ct_dots + '</a>\n' +
+	'		<a id="qdel" class="tgl btn" tt="' + L.ct_qdel + '</a>\n' +
+	'		<a id="dir1st" class="tgl btn" tt="' + L.ct_dir1st + '</a>\n' +
+	'		<a id="nsort" class="tgl btn" tt="' + L.ct_nsort + '</a>\n' +
+	'		<a id="utctid" class="tgl btn" tt="' + L.ct_utc + '</a>\n' +
+	'		<a id="ireadme" class="tgl btn" tt="' + L.ct_readme + '</a>\n' +
+	'		<a id="idxh" class="tgl btn" tt="' + L.ct_idxh + '</a>\n' +
 	'	</div>\n' +
 	'</div>\n' +
+	'<div><h3 id="h_mouse">🖱️ ' + L.cl_rcm + '</h3><div><a id="rcm_en" class="tgl btn" tt="' + L.cdt_ren + '</a><a id="rcm_db" class="tgl btn" tt="' + L.cdt_rdb + '</a></div></div>' +
 	'<div>\n' +
-	'	<h3>' + L.cl_gauto + '</h3>\n' +
+	'	<h3 id="h_gauto">🅰️ ' + L.cl_gauto + '</h3>\n' +
 	'	<div>\n' +
-	'		<a id="gauto" class="tgl btn" href="#" tt="' + L.tt_gauto + '">' + L.enable + '</a>\n' +
+	'		<a id="gauto" class="tgl btn" tt="' + L.tt_gauto + '">' + L.enable + '</a>\n' +
 	'		<input type="text" id="ga_thresh" value="" ' + NOAC + ' style="width:1.5em" tt="' + L.tt_gathr + '" />' +
 	'	</div>\n' +
 	'</div>\n' +
 	'<div>\n' +
-	'	<h3>' + L.cl_hfsz + '</h3>\n' +
+	'	<h3 id="h_filesize">🔢 ' + L.cl_hfsz + '</h3>\n' +
 	'	<div><select id="fszfmt">\n' +
 	'		<option value="0">0 ┃ 2345678</option>\n' +
@@ -1021,52 +1291,110 @@
 	'</div>\n' +
 	'<div>\n' +
-	'	<h3>' + L.cl_themes + '</h3>\n' +
-	'	<div><select id="themes"></select></div>\n' +
+	'	<h3 id="h_theme">🎨 ' + L.cl_themes + '</h3>\n' +
+	'	<div>' +
+	'		<div id="thmdiv" tt="' + L.cl_overr_thm +'">' +
+	'			<a id="override_thm" class="tgl btn">override</a>\n' +
+	'			<select id="themes"></select>' +
+	'		</div>' +
+	'		<div id="colordiv" tt="' + L.cl_accent +'">' +
+	'			<input type="text" id="accent" placeholder="#color"></input>' +
+	'			<input type="color" id="accent_picker"></input>' +
+	'		</div>' +
+	'		<input tt="' + L.cl_radius +'" type="number" id="radius" min="-1" placeholder="[0-inf]"></input>' +
+	'		<a id="fun_tgl" class="tgl btn" tt="' + L.cl_fun + '">🥳</a>\n' +
+	'		<a id="sbars" class="tgl btn" tt="' + L.ct_sbars + '</a>\n' +
+	'		<a id="enbg" class="tgl btn" tt="' + L.ct_enbg + '">🖼️ bg</a>\n' +
+	'	</div>\n' +
+	'</div>\n' +
+	'<div>\n' +
+	'	<h3 id="h_favico">🎉 ' + L.cl_favico + '</h3>\n' +
+	'	<div>\n' +
+	'		<input type="text" id="icot" value="" ' + NOAC + ' style="width:1.3em" tt="' + L.cft_text + '" />' +
+	'		<input type="text" id="icof" value="" ' + NOAC + ' style="width:2em" tt="' + L.cft_fg + '" />' +
+	'		<input type="text" id="icob" value="" ' + NOAC + ' style="width:2em" tt="' + L.cft_bg + '" />' +
+	'		<a id="ico1" class="btn">🔄️</a>\n' +
 	'	</div>\n' +
 	'</div>\n' +
 	'<div>\n' +
-	'	<h3>' + L.cl_langs + '</h3>\n' +
+	'	<h3 id="h_language">🌐 ' + L.cl_langs + '</h3>\n' +
 	'	<div><select id="langs"></select></div>\n' +
 	'</div>\n' +
 	(have_zip ? (
-		'<div><h3>' + L.cl_ziptype + '</h3><div id="arc_fmt"></div></div>\n'
+		'<div><h3 id="h_zip">⬇️ ' + L.cl_ziptype + '</h3><div><div id="arc_fmt"></div></div></div>\n'
 	) : '') +
-	'<div>\n' +
-	'	<h3>' + L.cl_uopts + '</h3>\n' +
-	'	<div>\n' +
-	'		<a id="ask_up" class="tgl btn" href="#" tt="' + L.ut_ask + '</a>\n' +
-	'		<a id="u2ts" class="tgl btn" href="#" tt="' + L.ut_u2ts + '</a>\n' +
-	'		<a id="umod" class="tgl btn" href="#" tt="' + L.cut_umod + '</a>\n' +
-	'		<a id="hashw" class="tgl btn" href="#" tt="' + L.cut_mt + '</a>\n' +
-	'		<a id="nosubtle" class="tgl btn" href="#" tt="' + L.cut_wasm + '</a>\n' +
-	'		<a id="u2turbo" class="tgl btn ttb" href="#" tt="' + L.cut_turbo + '</a>\n' +
-	'		<a id="u2tdate" class="tgl btn ttb" href="#" tt="' + L.cut_datechk + '</a>\n' +
-	'		<input type="text" id="u2szg" value="" ' + NOAC + ' style="width:3em" tt="' + L.cut_u2sz + '" />' +
-	'		<a id="flag_en" class="tgl btn" href="#" tt="' + L.cut_flag + '">💤</a>\n' +
-	'		<a id="u2sort" class="tgl btn" href="#" tt="' + L.cut_az + '">az</a>\n' +
-	'		<a id="upnag" class="tgl btn" href="#" tt="' + L.cut_nag + '">🔔</a>\n' +
-	'		<a id="upsfx" class="tgl btn" href="#" tt="' + L.cut_sfx + '">🔊</a>\n' +
-	'		</td>\n' +
+
+	'<div class="sub_section"><h3 id="h_up">☁️ ' + L.cl_uopts + '</h3></div>' +
+
+	'<div class="sub">\n' +
+	'	<h3 id="h_up2kcfg">🚀 ' + L.up2k + '</h3>\n' +
+	'	<div id="u2conf">\n' +
+	'		<div class="c" data-perm="read" data-dep="idx" tt="' + L.ut_srch +'">\n' +
+	'			<input type="checkbox" id="fsearch" />\n' +
+	'			<label class="tgl btn" for="fsearch">🔎</label>\n' +
+	'		</div>\n' +
+
+	'		<div class="c" tt="' + L.ut_ow +'">\n' +
+	'			<input type="checkbox" id="u2ow" />\n' +
+	'			<label class="tgl btn on" for="u2ow">?</label>\n' +
+	'		</div>\n' +
+
+	'		<div id="u2conft" tt="' + L.ut_par +'">\n' +
+	'			<tr>\n' +
+	'				<span class="c" data-perm="read" style="display: block; margin-bottom: .5em;">' + L.ul_par + '</span>\n' +
+	'			</tr>\n' +
+	'			<tr>\n' +
+	'				<div class="c" data-perm="read">\n' +
+	'					<a class="b btn" id="nthread_sub">&ndash;</a><input\n' +
+	'						class="txtbox" type="text" id="nthread" style="width: 3em;" value=""/><a\n' +
+	'						class="b btn" id="nthread_add">+</a>&nbsp;\n' +
+	'				</div>\n' +
+	'			</tr>\n' +
+	'		</div>\n' +
+
+	'		<div class="c" tt="' + L.ut_mt +'">\n' +
+	'			<input type="checkbox" id="multitask" />\n' +
+	'			<label class="tgl btn" for="multitask">🏃</label>\n' +
+	'		</div>\n' +
+
+	'		<div class="c" tt="' + L.ut_pot +'">\n' +
+	'			<input type="checkbox" id="potato" />\n' +
+	'			<label class="tgl btn" for="potato">🥔</label>\n' +
+	'		</div>\n' +
+
+	'		<div class="c" tt="' + L.ut_rand +'">\n' +
+	'			<input type="checkbox" id="u2rand" />\n' +
+	'			<label class="tgl btn" for="u2rand">🎲</label>\n' +
+	'		</div>\n' +
 	'	</div>\n' +
 	'</div>\n' +
-	'<div>\n' +
-	'	<h3>' + L.cl_favico + ' <span id="ico1">🎉</span></h3>\n' +
+
+	'<div class="sub">\n' +
+	'	<h3 id="h_upload">🎈 ' + L.cl_opts + '</h3>\n' +
 	'	<div>\n' +
-	'		<input type="text" id="icot" value="" ' + NOAC + ' style="width:1.3em" tt="' + L.cft_text + '" />' +
-	'		<input type="text" id="icof" value="" ' + NOAC + ' style="width:2em" tt="' + L.cft_fg + '" />' +
-	'		<input type="text" id="icob" value="" ' + NOAC + ' style="width:2em" tt="' + L.cft_bg + '" />' +
-	'		</td>\n' +
+	'		<a id="ask_up" class="tgl btn" tt="' + L.ut_ask + '">💭</a>\n' +
+	'		<a id="u2ts" class="tgl btn" tt="' + L.ut_u2ts + '</a>\n' +
+	'		<a id="umod" class="tgl btn" tt="' + L.cut_umod + '</a>\n' +
+	'		<a id="hashw" class="tgl btn" tt="' + L.cut_mt + '</a>\n' +
+	'		<a id="nosubtle" class="tgl btn" tt="' + L.cut_wasm + '</a>\n' +
+	'		<a id="u2turbo" class="tgl btn ttb" tt="' + L.cut_turbo + '</a>\n' +
+	'		<a id="u2tdate" class="tgl btn ttb" tt="' + L.cut_datechk + '</a>\n' +
+	'		<input type="text" id="u2szg" value="" ' + NOAC + ' style="width:3em" tt="' + L.cut_u2sz + '" />' +
+	'		<a id="flag_en" class="tgl btn" tt="' + L.cut_flag + '">💤</a>\n' +
+	'		<a id="u2sort" class="tgl btn" tt="' + L.cut_az + '">az</a>\n' +
+	'		<a id="upnag" class="tgl btn" tt="' + L.cut_nag + '">🔔</a>\n' +
+	'		<a id="upsfx" class="tgl btn" tt="' + L.cut_sfx + '">🔊</a>\n' +
 	'	</div>\n' +
 	'</div>\n' +
+
 	'<div>\n' +
-	'	<h3>' + L.cl_bigdir + '</h3>\n' +
+	'	<h3 id="h_bigdir">📁 ' + L.cl_bigdir + '</h3>\n' +
 	'	<div>\n' +
 	'		<input type="text" id="bd_lim" value="250" ' + NOAC + ' style="width:4em" tt="' + L.cdt_lim + '" />' +
-	'		<a id="bd_ask" class="tgl btn" href="#" tt="' + L.cdt_ask + '">ask</a>\n' +
+	'		<a id="bd_ask" class="tgl btn" tt="' + L.cdt_ask + '">ask</a>\n' +
 	'		</td>\n' +
 	'	</div>\n' +
 	'</div>\n' +
 	'<div>\n' +
-	'	<h3>' + L.cl_hsort + '</h3>\n' +
+	'	<h3 id="h_sort">🔃 ' + L.cl_hsort + '</h3>\n' +
 	'	<div>\n' +
 	'		<input type="text" id="hsortn" value="" ' + NOAC + ' style="width:3em" tt="' + L.cdt_hsort + '" />' +
@@ -1074,31 +1402,128 @@
 	'	</div>\n' +
 	'</div>\n' +
-	'<div><h3>' + L.cl_keytype + '</h3><div><select id="key_notation"></select></div></div>\n' +
-	(!MOBILE ? '<div><h3>' + L.cl_rcm + '</h3><div><a id="rcm_en" class="tgl btn" href="#" tt="' + L.cdt_ren + '</a><a id="rcm_db" class="tgl btn" href="#" tt="' + L.cdt_rdb + '</a></div></div>' : '') +
-	'<div><h3>' + L.cl_hiddenc + ' &nbsp;' + (MOBILE ? '<a href="#" id="hcolsh">' + L.cl_hidec + '</a> / ' : '') + '<a href="#" id="hcolsr">' + L.cl_reset + '</a></h3><div id="hcols"></div></div>'
+	musicSettings +
+	'<div><h3 id="h_keytype">🎼 ' + L.cl_keytype + '</h3><div><select id="key_notation"></select></div></div>\n'
 );
 
+// modalize settings
+(function () {
+	ebi('h_cfg').innerHTML = (fun_tgl ? '⚙️ ' : '') + L.ot_cfg;
+	var sections = ebi('op_cfg').children;
+	for (var i = 0; i < sections.length; i++){
+		var h = sections[i].children[0];
+		var sName = h.innerHTML;
+		if(!fun_tgl){
+			if(sName && sName.match(' ') && sName[0] != '<')
+				sName = sName.split(' ').slice(1).join(' ');
+		}
+		var sId = h.id;
+		h.id = ''
+		var subSettings = sections[i].children[1];
+		var section = '<div id="' + sId + '" class="s_section ' + sections[i].classList +'"><h3>' + sName + '</h3>';
+		if(subSettings != null){
+			for (var ii = 0; ii < subSettings.children.length; ii++){
+				var s = subSettings.children[ii];
+				var info = tt.parse(s.getAttribute('tt'));
+				s.removeAttribute('tt');
+				section += '<div class="setting">' +
+					s.outerHTML +
+					((info != null && info.length > 0) ? '<p class="s_desc">' + info + '</p>' : '') +
+					'</div>';
+			}
+			subSettings.innerHTML = '';
+		}
+		section += '</div>'
+		ebi('s_list').innerHTML += section;
+		ebi('s_nav').innerHTML += '<a href="#' + sId + '" class="btn ' + sections[i].classList +'">' + sName + '</a>\n';
+	}
+
+	ebi('h_cfg').onclick = function(){
+		ebi('s_list').scrollTop =
+		ebi('s_hor').scrollTop =
+		ebi('s_nav').scrollTop =
+		ebi('s_content').scrollTop =
+			0;
+	}
+
+	ebi('cl_cfg').onclick =
+	ebi('s_outside').onclick =
+	function () {
+			hist_push('#')
+			modaltoggle('cfg');
+		}
+})();
+
+var cfg_lgcy = false;
+ebi('reloc_cfg').onclick = function(){
+	cfg_lgcy = !cfg_lgcy;
+	this.innerHTML = cfg_lgcy ? '<span>▲</span>' : '<span>▼</span>';
+	clmod(ebi('cfg'), 'unmodal', cfg_lgcy);
+	swrite('cfg_lgcy', cfg_lgcy);
+}
+if(sread('cfg_lgcy') == 'true')
+	ebi('reloc_cfg').click();
+
+// accent color
+ebi('accent').oninput = ebi('accent_picker').oninput = function () {
+	var validcolor = parseColor(this.value);
+	console.log(this.value);
+
+	if(this == ebi('accent')){
+		ebi('accent_picker').value = this.value;
+	}
+	else if(this == ebi('accent_picker')){
+		if(this.value == '#000000')
+			return; // firefox submits the color picker value on browser startup
+		ebi('accent').value = this.value;
+	}
+
+	clmod(ebi('accent'), 'invalid', this.value.length != 0 && validcolor.length == 0);
+	if(validcolor == accent)
+		return;
+
+	setTimeout(function(){
+		setColor(validcolor);
+	}, 100);
+}
+if(accent && accent.length > 3){
+	ebi('accent').value = ebi('accent_picker').value = accent;
+}
+
+// corner radius
+ebi('radius').oninput = function () {
+	var r = parseFloat(this.value);
+
+	clmod(ebi('radius'), 'invalid', this.value.length != 0 && r.length == 0);
+	if(r === radius)
+		return;
+
+	var usefallback = isNaN(r) || r < 0 || r === '';
+	var setV = usefallback ? '' : (r + 'px');
+	swrite('radius', !usefallback ? r : '');
+	document.documentElement.style.setProperty('--radius', setV);
+	console.log(setV);
+}
+var radius = sread('radius');
+if(radius){
+	console.log('read radius from settings: ' + radius);
+	document.documentElement.style.setProperty('--radius', parseFloat(radius) + 'px');
+	ebi('radius').value = radius;
+}
+
+
+// no fun allowed
+bcfg_bind(this, 'fun_tgl', 'fun_tgl', fun_tgl);
 
 // navpane
-ebi('tree').innerHTML = (
-	'<div id="treeh">\n' +
-	'	<a href="#" id="detree" tt="' + L.tt_detree + '">🍞...</a>\n' +
-	'	<a href="#" class="btn" step="2" id="twobytwo" tt="Hotkey: D">+</a>\n' +
-	'	<a href="#" class="btn" step="-2" id="twig" tt="Hotkey: A">&ndash;</a>\n' +
-	'	<a href="#" class="btn" id="visdir" tt="' + L.tt_visdir + '">🎯</a>\n' +
-	'	<a href="#" class="tgl btn" id="filetree" tt="' + L.tt_ftree + '">📃</a>\n' +
-	'	<a href="#" class="tgl btn" id="parpane" tt="' + L.tt_pdock + '">📌</a>\n' +
-	'	<a href="#" class="tgl btn" id="dyntree" tt="' + L.tt_dynt + '">a</a>\n' +
-	'	<a href="#" class="tgl btn" id="wraptree" tt="' + L.tt_wrap + '">↵</a>\n' +
-	'	<a href="#" class="tgl btn" id="hovertree" tt="' + L.tt_hover + '">👀</a>\n' +
-	'</div>\n' +
-	'<ul id="docul"></ul>\n' +
-	'<ul class="ntree" id="treepar"></ul>\n' +
-	'<ul class="ntree" id="treeul"></ul>\n' +
-	'<div id="thx_ff">&nbsp;</div>'
+ebi('treeh').innerHTML += (
+	'<a href="#" class="btn" step="-2" id="twig" tt="Hotkey: A">&ndash;</a>\n' +
+	'<a href="#" class="btn" step="2" id="twobytwo" tt="Hotkey: D">+</a>\n' +
+	'<a href="#" class="btn" id="visdir" tt="' + L.tt_visdir + '">🎯</a>\n' +
+	'<a href="#" class="tgl btn" id="filetree" tt="' + L.tt_ftree + '">📃</a>\n' +
+	'<a href="#" class="tgl btn" id="parpane" tt="' + L.tt_pdock + '">📌</a>\n' +
+	'<a href="#" class="tgl btn" id="dyntree" tt="' + L.tt_dynt + '">a</a>\n' +
+	'<a href="#" class="tgl btn" id="wraptree" tt="' + L.tt_wrap + '">↵</a>\n' +
+	'<a href="#" class="tgl btn" id="hovertree" tt="' + L.tt_hover + '">👀</a>\n'
 );
-clmod(ebi('tree'), 'sbar', 1);
-ebi('entree').setAttribute('tt', L.tt_entree);
-ebi('goh').textContent = L.goh;
 QS('#op_mkdir input[type="submit"]').value = L.ab_mkdir;
 QS('#op_new_md input[type="submit"]').value = L.ab_mkdoc;
@@ -1107,29 +1532,29 @@
 // right-click menu
 ebi('rcm').innerHTML = (
-	'<a href="#" id="ropn">' + L.rc_opn + '</a>' +
-	'<a href="#" id="rply">' + L.rc_ply + '</a>' +
-	'<a href="#" id="rpla">' + L.rc_pla + '</a>' +
-	'<a href="#" id="rtxt">' + L.rc_txt + '</a>' +
-	'<a href="#" id="rmd">' + L.rc_md + '</a>' +
+	'<a href="#" id="ropn">' + (fun_tgl ? '↗️ ' : '') + L.rc_opn + '</a>' +
+	'<a href="#" id="rply">' + (fun_tgl ? '▶ ' : '') + L.rc_ply + '</a>' +
+	'<a href="#" id="rpla">' + (fun_tgl ? '🎧 ' : '') + L.rc_pla + '</a>' +
+	'<a href="#" id="rtxt">' + (fun_tgl ? '📄 ' : '') + L.rc_txt + '</a>' +
+	'<a href="#" id="rmd">' + (fun_tgl ? '📜 ' : '') + L.rc_md + '</a>' +
 	'<div id="rs1" class="sep"></div>' +
-	'<a href="#" id="rcpl">' + L.rc_cpl + '</a>' +
-	'<a href="#" id="rdl">' + L.rc_dl + '</a>' +
+	'<a href="#" id="rcpl">' + (fun_tgl ? '🔗 ' : '') + L.rc_cpl + '</a>' +
+	'<a href="#" id="rdl">' + (fun_tgl ? '⬇️ ' : '') + L.rc_dl + '</a>' +
 	(have_zip ?
-		'<a href="#" id="rzip">' + L.rc_zip + '</a>'
+		'<a href="#" id="rzip">' + (fun_tgl ? '📦 ' : '') + L.rc_zip + '</a>'
 	: '') +
 	'<div id="rs2" class="sep"></div>' +
-	(have_del ? '<a href="#" id="rdel">' + L.rc_del + '</a>' : '') +
-	(have_mv ? '<a href="#" id="rcut">' + L.rc_cut + '</a>' : '') +
-	'<a href="#" id="rcpy">' + L.rc_cpy + '</a>' +
+	(have_del ? '<a href="#" id="rdel">' + (fun_tgl ? '🗑️ ' : '') + L.rc_del + '</a>' : '') +
+	(have_mv ? '<a href="#" id="rcut">' + (fun_tgl ? '✂️ ' : '') + L.rc_cut + '</a>' : '') +
+	'<a href="#" id="rcpy">' + (fun_tgl ? '⧉ ' : '') + L.rc_cpy + '</a>' +
 	(has(perms, "write") ?
-		'<a href="#" id="rpst">' + L.rc_pst + '</a>' +
-		(have_mv ? '<a href="#" id="rrnm">' + L.rc_rnm + '</a>' : '') +
+		'<a href="#" id="rpst">' + (fun_tgl ? '📋 ' : '') + L.rc_pst + '</a>' +
+		(have_mv ? '<a href="#" id="rrnm">' + (fun_tgl ? '✏️ ' : '') + L.rc_rnm + '</a>' : '') +
 		'<div id="rs3" class="sep"></div>' +
-		'<a href="#" id="rnfo">' + L.rc_nfo + '</a>' +
-		'<a href="#" id="rnfi">' + L.rc_nfi + '</a>'
+		'<a href="#" id="rnfo">' + (fun_tgl ? '📁 ' : '') + L.rc_nfo + '</a>' +
+		'<a href="#" id="rnfi">' + (fun_tgl ? '📄 ' : '') + L.rc_nfi + '</a>'
 	: '') +
 	'<div id="rs4" class="sep"></div>' +
-	'<a href="#" id="rsal">' + L.rc_sal + '</a>' +
-	'<a href="#" id="rsin">' + L.rc_sin + '</a>' +
+	'<a href="#" id="rsal">' + (fun_tgl ? '✅ ' : '') + L.rc_sal + '</a>' +
+	'<a href="#" id="rsin">' + (fun_tgl ? '🔛 ' : '') + L.rc_sin + '</a>' +
 	'<a id="rshr" href="#"></a>'
 );
@@ -1143,7 +1568,43 @@
 			ops[a].href = '#v=' + v;
 	}
+
+	ebi('op_acc').onclick = function (e){
+		e.stopPropagation();
+	};
 })();
 
+// fallback in case href change method somehow doesn't work
+var closebtns = QSA('.close');
+for(var a = 0; a < closebtns.length; a++){
+	closebtns[a].onclick = function(){
+		var modal = QS('.modal.vis');
+		if(modal)
+			modaltoggle(modal.id, false);
+	}
+}
+
+function modaltoggle(dest, show){
+	if(show == null)
+		show = 't';
+	if (show == false || show == 't' && QS('#' + dest + '.vis'))
+		dest = '';
+
+	var layoutchange = dest == 'music' || QS('#music.vis');
+
+	swrite('opmode', dest || null);
+
+	goto(dest);
+
+	var input = QS('.opview.act input:not([type="hidden"])')
+	if (input && !TOUCH) {
+		tt.skip = true;
+		input.focus();
+	}
 
+	if(layoutchange) {
+		onwidgetresize();
+		mpl.set_mu_cv();
+	}
+}
 function opclick(e) {
 	var dest = this.getAttribute('data-dest');
@@ -1151,5 +1612,6 @@
 		dest = '';
 
-	swrite('opmode', dest || null);
+	if(dest != 'acc')
+		swrite('opmode', dest || null);
 	if (ctrl(e))
 		return;
@@ -1159,4 +1621,6 @@
 
 	var input = QS('.opview.act input:not([type="hidden"])')
+	if(dest == 'search')
+		input = ebi('folder_search');
 	if (input && !TOUCH) {
 		tt.skip = true;
@@ -1175,7 +1639,11 @@
 		clmod(obj[a], 'act');
 
+	obj = QSA('.modal');
+	for (var a = obj.length - 1; a >= 0; a--)
+		clmod(obj[a], 'vis');
+
 	if (dest) {
 		var lnk = QS('#ops>a[data-dest=' + dest + ']'),
-			nps = lnk.getAttribute('data-perm');
+			nps = lnk && lnk.getAttribute('data-perm');
 
 		nps = nps && nps.length ? nps.split(' ') : [];
@@ -1189,13 +1657,39 @@
 			return;
 
-		clmod(ebi('op_' + dest), 'act', 1);
+		var modal = ebi(dest);
+		if(modal != null){
+			clmod(modal, 'vis', true);
+			if(location.hash.length <= 1)
+				location.hash = '#h_' + dest;
+		}
+
+		var opd = ebi('op_' + dest);
+		var page = opd != null ? opd : QS('#' + dest + ' .opview')
+		if(page != null)
+			clmod(page, 'act', 1);
 		clmod(lnk, 'act', 1);
 
+		if(dest == 'bup'){
+			clmod(ebi('up2k'), 'vis', true);
+			location.hash = '#h_bup';
+		}
+		if(dest == 'bup' || dest == 'up2k')
+			ebi('bup_tgl').open = dest == 'bup';
+
 		var fn = window['goto_' + dest];
 		if (fn)
 			fn();
 	}
+	else{
+		if(location.hash.startsWith('#h_'))
+			location.hash = '#';
+	}
+
+	ebi('srchfolder_div').style.display = dest == 'search' ? 'block' : 'none';
 
 	clmod(document.documentElement, 'op_open', dest);
+	// enables the use of keyboard page nav on modals
+	//var a_modal = QS('.modal.vis');
+	//clmod(document.documentElement, 'noscroll', a_modal && window.getComputedStyle(a_modal).position == 'fixed');
 
 	if (treectl)
@@ -1206,4 +1700,47 @@
 
 
+window.onhashchange = function() {
+	console.log('hash change: ' + location.hash)
+	// keep it clean
+	if(location.hash == '')
+		hist_replace(location.pathname + location.search);
+
+	var a_modal = QS('.modal.vis');
+	if(location.hash.length <= 1 && a_modal){
+		modaltoggle(a_modal.id);
+		console.log('closing modal due to hash');
+	}
+	if(a_modal && location.hash == '#h_' + a_modal.id){
+		if(a_modal.style.position != 'fixed')
+			return;
+		var m_header = QS('.modal.vis .modalheader');
+		if(m_header){
+			m_header.click();
+			console.log('going to top of modal');
+		}
+	}
+  	else if (location.hash.startsWith("#h_")){
+		var header = ebi(location.hash.slice(1));
+		if(!header)
+			return;
+		var p_modal = header.closest('.modal');
+		if(!p_modal)
+			return;
+		if(!clgot(p_modal, 'vis')){
+			console.log('forcing modal open due to subheader hash');
+			modaltoggle(p_modal.id, true);
+		}
+		ebi(location.hash.slice(1)).scrollIntoView();
+	}
+	else if (location.hash.startsWith('#gf-') && !baguetteBox.getUserAction()){
+		hash0 = location.hash;
+		eval_hash()
+	}
+	else if(location.hash == '' && QS('#bbox-overlay.visible')){
+		baguetteBox.hide();
+	}
+}
+
+
 var m = SPINNER.split(','),
 	SPINNER_CSS = SPINNER.slice(1 + m[0].length);
@@ -1246,6 +1783,6 @@
 
 var img_re = APPLE ?
-	/\.(a?png|avif|bmp|gif|hei[cf]s?|jpe?g|jfif|svg|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i :
-	/\.(a?png|avif|bmp|gif|jpe?g|jfif|svg|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i;
+	/\.(a?png|avif|bmp|gif|hei[cf]s?|jpe?g|jxl|jfif|svg|ico|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i :
+	/\.(a?png|avif|bmp|gif|jpe?g|jxl|jfif|svg|ico|webp|webm|mkv|mp4|m4v|mov)(\?|$)/i;
 
 var wopi_set = !window.have_wopi ? null :
@@ -1294,8 +1831,9 @@
 if (!fullui) {
 	if (window.ui_nombar || /[?&]nombar\b/.exec(sloc0)) x += '#ops,';
-	if (window.ui_noacci || /[?&]noacci\b/.exec(sloc0)) x += '#acc_info,';
-	if (window.ui_nosrvi || /[?&]nosrvi\b/.exec(sloc0)) x += '#srv_info,#srv_info2,';
+	if (window.ui_noacci || /[?&]noacci\b/.exec(sloc0)) x += '#acc_info,#opa_acc,';
+	if (window.ui_nosrvi || /[?&]nosrvi\b/.exec(sloc0)) x += '#srv_info,#srv_info2,#tree_footer';
 	if (window.ui_nocpla || /[?&]nocpla\b/.exec(sloc0)) x += '#goh,';
 	if (window.ui_nolbar || /[?&]nolbar\b/.exec(sloc0)) x += '#wfp,';
+	if (nonav) x += '#path,';
 	if (window.ui_noctxb || /[?&]noctxb\b/.exec(sloc0)) x += '#wtoggle,';
 	if (window.ui_norepl || /[?&]norepl\b/.exec(sloc0)) x += '#repl,';
@@ -1304,5 +1842,4 @@
 	document.head.appendChild(mknod('style', '', x.slice(0, -1) + '{display:none!important}'));
 
-
 if (location.pathname.indexOf('//') === 0)
 	hist_replace(location.pathname.replace(/^\/+/, '/'));
@@ -1349,59 +1886,4 @@
 	var have_mctl = 'mediaSession' in navigator && window.MediaMetadata;
 
-	ebi('op_player').innerHTML = (
-		'<div><h3>' + L.cl_opts + '</h3><div>' +
-		'<a href="#" class="tgl btn" id="au_loop" tt="' + L.mt_loop + '</a>' +
-		'<a href="#" class="tgl btn" id="au_one" tt="' + L.mt_one + '</a>' +
-		'<a href="#" class="tgl btn" id="au_shuf" tt="' + L.mt_shuf + '</a>' +
-		'<a href="#" class="tgl btn" id="au_aplay" tt="' + L.mt_aplay + '</a>' +
-		'<a href="#" class="tgl btn" id="au_preload" tt="' + L.mt_preload + '</a>' +
-		'<a href="#" class="tgl btn" id="au_prescan" tt="' + L.mt_prescan + '</a>' +
-		'<a href="#" class="tgl btn" id="au_fullpre" tt="' + L.mt_fullpre + '</a>' +
-		'<a href="#" class="tgl btn" id="au_fau" tt="' + L.mt_fau + '</a>' +
-		'<a href="#" class="tgl btn" id="au_waves" tt="' + L.mt_waves + '</a>' +
-		'<a href="#" class="tgl btn" id="au_npclip" tt="' + L.mt_npclip + '</a>' +
-		'<a href="#" class="tgl btn" id="au_m3u_c" tt="' + L.mt_m3u_c + '</a>' +
-		'<a href="#" class="tgl btn" id="au_os_ctl" tt="' + L.mt_octl + '</a>' +
-		'<a href="#" class="tgl btn" id="au_os_seek" tt="' + L.mt_oseek + '</a>' +
-		'<a href="#" class="tgl btn" id="au_osd_cv" tt="' + L.mt_oscv + '</a>' +
-		'<a href="#" class="tgl btn" id="au_follow" tt="' + L.mt_follow + '</a>' +
-		'<a href="#" class="tgl btn" id="au_compact" tt="' + L.mt_compact + '</a>' +
-		'</div></div>' +
-
-		'<div><h3>' + L.ml_btns + '</h3><div>' +
-		'<a href="#" class="btn" id="au_uncache" tt="' + L.mt_uncache + '</a>' +
-		'</div></div>' +
-
-		'<div><h3>' + L.ml_pmode + '</h3><div id="pb_mode">' +
-		'<a href="#" class="tgl btn" m="loop" tt="' + L.mt_mloop + '</a>' +
-		'<a href="#" class="tgl btn" m="next" tt="' + L.mt_mnext + '</a>' +
-		'<a href="#" class="tgl btn" m="stop" tt="' + L.mt_mstop + '</a>' +
-		'</div></div>' +
-
-		(have_acode ? (
-			'<div><h3>' + L.ml_tcode + '</h3><div>' +
-			'<a href="#" id="ac_flac" class="tgl btn" tt="' + L.mt_cflac + '</a>' +
-			'<a href="#" id="ac_aac" class="tgl btn" tt="' + L.mt_caac + '</a>' +
-			'<a href="#" id="ac_oth" class="tgl btn" tt="' + L.mt_coth + '</a>' +
-			'</div></div>' +
-			'<div><h3>' + L.ml_tcode2 + '</h3><div>' +
-			'<a href="#" id="ac2opus" class="tgl btn" tt="' + L.mt_c2opus + '</a>' +
-			'<a href="#" id="ac2owa" class="tgl btn" tt="' + L.mt_c2owa + '</a>' +
-			'<a href="#" id="ac2caf" class="tgl btn" tt="' + L.mt_c2caf + '</a>' +
-			'<a href="#" id="ac2mp3" class="tgl btn" tt="' + L.mt_c2mp3 + '</a>' +
-			'<a href="#" id="ac2flac" class="tgl btn" tt="' + L.mt_c2flac + '</a>' +
-			'<a href="#" id="ac2wav" class="tgl btn" tt="' + L.mt_c2wav + '</a>' +
-			'</div></div>'
-		) : '') +
-
-		'<div><h3>' + L.ml_tint + '</h3><div>' +
-		'<input type="text" id="pb_tint" value="0" ' + NOAC + ' style="width:2.4em" tt="' + L.mt_tint + '" />' +
-		'</div></div>' +
-
-		'<div><h3 id="h_drc">' + L.ml_drc + '</h3><div id="audio_drc"></div></div>' +
-		'<div><h3>' + L.ml_eq + '</h3><div id="audio_eq"></div></div>' +
-		'<div><h3>' + L.ml_ss + '</h3><div id="audio_ss"></div></div>' +
-		'');
-
 	var r = {
 		"pb_mode": (sread('pb_mode', ['loop', 'next', 'stop']) || 'next').split('-')[0],
@@ -1421,6 +1903,9 @@
 	bcfg_bind(r, 'shuf', 'au_shuf', false, function () {
 		mp.read_order();  // don't bind
+		ebi('au_shuf2').classList = ebi('au_shuf').classList;
 	});
+	ebi('au_shuf2').onclick = ebi('au_shuf').onclick;
 	bcfg_bind(r, 'aplay', 'au_aplay', true);
+	bcfg_bind(r, 'afade', 'au_afade', true);
 	bcfg_bind(r, 'preload', 'au_preload', true);
 	bcfg_bind(r, 'prescan', 'au_prescan', true);
@@ -1437,5 +1922,24 @@
 	bcfg_bind(r, 'waves', 'au_waves', true, function (v) {
 		if (!v) pbar.unwave();
+		r.selectprogbar();
 	});
+	r.selectprogbar = function(){
+		if (r.waves){
+			clmod(ebi('progbar'), 'vis', true)
+			clmod(ebi('altprogbar'), 'vis', false)
+			if(pbar){
+				pbar.unwave();
+				if (mpl.waves && mp && mp.au)
+					pbar.loadwaves(mp.au.src.replace(/\bth=(opus|mp3)&/, '') + '&th=p');
+				pbar.onresize();
+			}
+		}
+		else{
+			clmod(ebi('progbar'), 'vis', false)
+			clmod(ebi('altprogbar'), 'vis', true)
+		}
+	}
+	r.selectprogbar();
+
 	bcfg_bind(r, 'os_seek', 'au_os_seek', !IPHONE, announce);
 	bcfg_bind(r, 'osd_cv', 'au_osd_cv', true, announce);
@@ -1475,5 +1979,5 @@
 
 	function draw_pb_mode() {
-		var btns = QSA('#pb_mode>a');
+		var btns = QSA('#h_pbmode a');
 		for (var a = 0, aa = btns.length; a < aa; a++) {
 			clmod(btns[a], 'on', btns[a].getAttribute("m") == r.pb_mode);
@@ -1490,4 +1994,48 @@
 	}
 
+	ebi('cyclebtn_loopmode').onclick = function(){
+		if(r.pb_mode == 'next'){
+			// #4: folder loop was set, switch to none / next folder
+			r.pb_mode = 'stop';
+		}
+		else if(r.pb_mode == 'loop'){
+			// #3: folder loop was set, switch to none / next folder
+			r.pb_mode = 'next';
+		}
+		else if(clgot(ebi('au_loop'), 'on')) {
+			// #2: single loop was set, switch to folder loop
+			ebi('au_loop').click();
+			r.pb_mode = 'loop';
+		}
+		else {
+			// #1: no loop set, click single loop btn
+			ebi('au_loop').click();
+		}
+		swrite('pb_mode', r.pb_mode);
+		draw_pb_mode();
+		drawLoopStatus();
+	}
+
+	function drawLoopStatus(){
+		var btn = ebi('cyclebtn_loopmode');
+		if(r.pb_mode == 'next'){
+			btn.innerHTML = '⏭️';
+			clmod(btn, 'on', true);
+		}
+		else if(r.pb_mode == 'loop'){
+			btn.innerHTML = '🔁';
+			clmod(btn, 'on', true);
+		}
+		else if(clgot(ebi('au_loop'), 'on')) {
+			btn.innerHTML = '🔂';
+			clmod(btn, 'on', true);
+		}
+		else {
+			btn.innerHTML = '🔁';
+			clmod(btn, 'on', false);
+		}
+	}
+	drawLoopStatus();
+
 	function set_tint() {
 		var tint = icfg_get('pb_tint', 0);
@@ -1495,5 +2043,5 @@
 			ebi('barbuf').style.removeProperty('background');
 		else
-			ebi('barbuf').style.background = 'rgba(126,163,75,' + (tint / 100.0) + ')';
+			ebi('barbuf').style.background = 'rgba(126,126,126,' + (tint / 100.0) + ')';
 	}
 	ebi('pb_tint').oninput = function (e) {
@@ -1562,7 +2110,7 @@
 		}
 		if (!IPHONE)
-			btns[1].style.display = btns[2].style.display = 'none';
-		btns[4].style.display = have_c2flac ? '' : 'none';
-		btns[5].style.display = have_c2wav ? '' : 'none';
+			btns[1].parentElement.style.display = btns[2].parentElement.style.display = 'none';
+		btns[4].parentElement.style.display = have_c2flac ? '' : 'none';
+		btns[5].parentElement.style.display = have_c2wav ? '' : 'none';
 
 		if (v)
@@ -1576,7 +2124,7 @@
 
 		r.ac2 = v;
-		ebi('ac_flac').setAttribute('tt', L.mt_cflac.split('"')[0].format(v));
-		ebi('ac_aac').setAttribute('tt', L.mt_caac.split('"')[0].format(v));
-		ebi('ac_oth').setAttribute('tt', L.mt_coth.split('"')[0].format(v));
+		ebi('ac_flac').nextSibling.innerHTML = L.mt_cflac.split('"')[0].format(v);
+		ebi('ac_aac').nextSibling.innerHTML = L.mt_caac.split('"')[0].format(v);
+		ebi('ac_oth').nextSibling.innerHTML = L.mt_coth.split('"')[0].format(v);
 	};
 
@@ -1602,4 +2150,21 @@
 	setaufollow();
 
+	r.set_mu_cv = function() {
+		var o = QS('#music.vis #mu_th>img');
+		if (o && r.cover) {
+			var url = r.cover.slice(0, -1) + 'wf3';
+
+			set_loaded(o, false);
+			o.onload = th_onload;
+			o.setAttribute('src', url);
+
+			o = QS('html.b #music, html.f #music');
+			if (o)
+				o.style.background = "url('" + url + "') no-repeat center / cover";
+			else
+				ebi('music').style.background = "";
+		}
+	}
+
 	function announce() {
 		if (!r.os_ctl || !mp.au)
@@ -1620,5 +2185,5 @@
 
 		if (r.osd_cv) {
-			var files = QSA("#files tr>td:nth-child(2)>a[id]"),
+			var files = QSA("#files .flink"),
 				cover = null;
 
@@ -1630,7 +2195,8 @@
 			}
 
-			cover = addq(cover || mp.au.osrc, 'th=j');
+			cover = addq(cover || mp.au.src, 'th=j');
 			tags.artwork = [{ "src": cover, type: "image/jpeg" }];
 		}
+		r.cover = cover;
 
 		ebi('np_circle').textContent = np.circle || '';
@@ -1639,5 +2205,5 @@
 		ebi('np_artist').textContent = np.artist || (fns.length > 1 ? fns[0] : '');
 		ebi('np_title').textContent = np.title || '';
-		ebi('np_dur').textContent = np['.dur'] || '';
+		ebi('txtsongend').innerHTML = ebi('np_dur').textContent = np['.dur'] || '';
 		ebi('np_url').textContent = uricom_dec(get_evpath()) + np.file.split('?')[0];
 		if (!MOBILE && cover)
@@ -1654,4 +2220,5 @@
 		navigator.mediaSession.setActionHandler('nexttrack', next_song);
 		r.pp();
+		r.set_mu_cv();
 	}
 	r.announce = announce;
@@ -1731,5 +2298,5 @@
 	for (var a = 0, aa = trs.length; a < aa; a++) {
 		var tds = trs[a].getElementsByTagName('td'),
-			link = tds[1].getElementsByTagName('a');
+			link = trs[a].querySelectorAll('.flink');
 
 		link = link[link.length - 1];
@@ -1792,5 +2359,5 @@
 
 			tid = tid.slice(1);
-			if (r.tracks[tid]) 
+			if (r.tracks[tid])
 				order.push(tid);
 		}
@@ -1812,5 +2379,12 @@
 			r.au.play();
 			mpl.pp();
-			fader();
+			if(mpl.afade != false)
+				fader();
+			else{
+				// insta start
+				r.fvol = r.vol;
+				mpss.go();
+				r.au.volume = r.expvol(r.fvol);
+			}
 		}
 	};
@@ -1820,5 +2394,11 @@
 		r.fdir = -0.05 * r.vol * (CHROME ? 2 : 1);
 		r.ftid = r.au.tid;
-		fader();
+		if(mpl.afade != false)
+			fader();
+		else {
+			// insta stop
+			r.au.pause();
+			mpl.pp();
+		}
 	};
 	r.stopfade = function (hard) {
@@ -1944,5 +2524,5 @@
 	for (var a = 1, aa = th.length; a < aa; a++) {
 		var tv = tr.cells[a].textContent,
-			tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').pop().toLowerCase(),
+			tk = tr.cells[a].querySelector('.flink') ? 'file' : th[a].getAttribute('name').split('/').pop().toLowerCase(),
 			vis = th[a].className.indexOf('min') === -1;
 
@@ -1980,5 +2560,4 @@
 			return false;
 
-		clmod(document.documentElement, 'np_open', is_open);
 		clmod(widget, 'open', is_open);
 		bcfg_set('au_open', r.is_open = is_open);
@@ -1997,9 +2576,15 @@
 		if (was_paused != paused) {
 			was_paused = paused;
-			ebi('bplay').innerHTML = paused ? '▶' : '⏸';
+			ebi('bplay').innerHTML = ebi('bplay2').innerHTML = 
+				paused ? svg_play : svg_pause;
 		}
 	};
 	r.setvis = function () {
-		widget.style.display = !has(perms, "read") || showfile.abrt ? 'none' : '';
+		var display = !has(perms, "read") || showfile.abrt ? 'none' : '';
+		ebi('wtico').style.display =
+		ebi('wtoggle').style.display =
+		ebi('widgeti').style.display =
+		ebi('np_inf').style.display =
+			display;
 	};
 	wtico.onclick = function (e) {
@@ -2094,11 +2679,23 @@
 }
 
-
-function glossy_grad(can, h, s, l) {
+function auto_grad(can, color, color2) {
+	if(color2 == null)
+		color2 = '#000';
 	var g = can.ctx.createLinearGradient(0, 0, 0, can.h),
-		p = [0, 0.49, 0.50, 1];
+		p = [0, 0.49, 0.50, 1],
+		mix = [70, 100, 90, 70];
 
-	for (var a = 0; a < p.length; a++)
-		g.addColorStop(p[a], 'hsl(' + h + ',' + s[a] + '%,' + l[a] + '%)');
+	for (var a = 0; a < p.length; a++){
+		var c = 'color-mix(in oklab, ' + color + ' ' + mix[a] + '%, ' + color2 + ' ' + (100 - mix[a]) + '%)';
+		var pc = parseColor(c);
+        if (pc) {
+            try {
+				g.addColorStop(p[a], pc);
+            } catch (e) {
+				console.log(e);
+				g.addColorStop(p[a], color2);
+			}
+		}
+	}
 
 	return g;
@@ -2125,5 +2722,5 @@
 		r.buf.ctx.font = '.5em sans-serif';
 		r.pos.ctx.font = '.9em sans-serif';
-		r.pos.ctx.strokeStyle = 'rgba(24,56,0,0.5)';
+		r.pos.ctx.strokeStyle = 'rgba(40, 40, 40, 0.5)';
 		r.drawbuf();
 		r.drawpos();
@@ -2182,5 +2779,5 @@
 			apos, adur;
 
-		if (!widget.is_open)
+		if (!widget.is_open || !mpl.waves)
 			return;
 
@@ -2192,6 +2789,8 @@
 		bau = mp.au;
 
+		var accent = getComputedStyle(document.body).getPropertyValue('--a');
+
 		var sm = bc.w * 1.0 / mp.au.duration,
-			gk = bc.h + '/' + themen,
+			gk = bc.h + '/' + themen + '/' + accent,
 			dz = themen == 'dz',
 			dy = themen == 'dy';
@@ -2199,8 +2798,8 @@
 		if (gradh != gk) {
 			gradh = gk;
-			grad = glossy_grad(bc, dz ? 120 : 85,
-				dy ? [0, 0, 0, 0] : [35, 40, 37, 35],
-				dy ? [20, 24, 22, 20] : light ? [45, 56, 50, 45] : [42, 51, 47, 42]);
+			grad = auto_grad(bc, accent);
 		}
+		if(IE)
+			grad = 'rgb(85, 144, 255)';
 		bctx.fillStyle = grad;
 		for (var a = 0; a < mp.au.buffered.length; a++) {
@@ -2219,18 +2818,19 @@
 
 		var step = sm > 1 ? 1 : sm > 0.4 ? 3 : sm > 0.05 ? 30 : 720;
-		bctx.fillStyle = light && !dy ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)';
-		for (var p = step, mins = adur / 10; p <= mins; p += step)
-			bctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
 
-		step = sm > 0.15 ? 1 : sm > 0.05 ? 10 : 360;
-		bctx.fillStyle = light && !dy ? 'rgba(0,64,0,0.5)' : 'rgba(192,255,96,0.5)';
-		for (var p = step, mins = adur / 60; p <= mins; p += step)
-			bctx.fillRect(Math.floor(sm * p * 60), 0, 2, pc.h);
-
-		step = sm > 0.33 ? 1 : sm > 0.15 ? 5 : sm > 0.05 ? 10 : sm > 0.01 ? 60 : 720;
-		bctx.fillStyle = dz ? '#0f0' : dy ? '#999' : light ? 'rgba(0,64,0,0.9)' : 'rgba(192,255,96,1)';
-		for (var p = step, mins = adur / 60; p <= mins; p += step) {
-			bctx.fillText(p, Math.floor(sm * p * 60 + 3), pc.h / 3);
-		}
+		//bctx.fillStyle = light && !dy ? 'rgba(0,0,0,0.15)' : 'rgba(255, 255, 255, 0.15)';
+		//for (var p = step, mins = adur / 10; p <= mins; p += step)
+		//	bctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
+
+		//step = sm > 0.15 ? 1 : sm > 0.05 ? 10 : 360;
+		//bctx.fillStyle = light && !dy ? 'rgba(0, 0, 0, 0.5)' : 'rgba(255, 255, 255, 0.5)';
+		//for (var p = step, mins = adur / 60; p <= mins; p += step)
+		//	bctx.fillRect(Math.floor(sm * p * 60), 0, 2, pc.h);
+
+		//step = sm > 0.33 ? 1 : sm > 0.15 ? 5 : sm > 0.05 ? 10 : sm > 0.01 ? 60 : 720;
+		//bctx.fillStyle = dz ? '#0f0' : dy ? '#999' : light ? 'rgba(0, 0, 0, 0.9)' : 'rgb(255, 255, 255)';
+		//for (var p = step, mins = adur / 60; p <= mins; p += step) {
+		//	bctx.fillText(p, Math.floor(sm * p * 60 + 3), pc.h / 3);
+		//}
 
 		step = sm > 0.2 ? 10 : sm > 0.1 ? 30 : sm > 0.01 ? 60 : sm > 0.005 ? 720 : 1440;
@@ -2240,9 +2840,9 @@
 	};
 
-	r.drawpos = function () {
+	r.drawpos = function (force) {
 		var bc = r.buf,
 			pc = r.pos,
 			pctx = pc.ctx,
-			w = 8,
+			w = 2,
 			apos, adur;
 
@@ -2282,6 +2882,10 @@
 			x = sm * apos;
 
+		if(adur > 0)
+			ebi('sliderpos').value = apos / adur;
+
 		if (w && html_txt != t2) {
-			ebi('np_pos').textContent = html_txt = t2;
+			ebi('txtpos').innerHTML = ebi('np_pos').textContent = html_txt = t2;
+			ebi('txtsongend').innerHTML = t1;
 			if (mpl.os_ctl)
 				navigator.mediaSession.setPositionState({
@@ -2292,9 +2896,9 @@
 		}
 
-		if (!widget.is_open)
+		if (!widget.is_open || !mpl.waves && !force)
 			return;
 
-		pctx.fillStyle = '#573'; pctx.fillRect((x - w / 2) - 1, 0, w + 2, pc.h);
-		pctx.fillStyle = '#dfc'; pctx.fillRect((x - w / 2), 0, w, pc.h);
+		pctx.fillStyle = '#bbb'; pctx.fillRect((x - w / 2) - 1, 0, w + 2, pc.h);
+		pctx.fillStyle = '#fff'; pctx.fillRect((x - w / 2), 0, w, pc.h);
 
 		pctx.lineWidth = 2.5;
@@ -2304,5 +2908,5 @@
 			m1b = pctx.measureText(t1 + ":88"),
 			m2 = pctx.measureText(t2),
-			yt = pc.h * 0.94,
+			yt = pc.h * 0.65,
 			xt1 = pc.w - (m1.width + 12),
 			xt2 = x < m1.width * 1.4 ? (x + 12) : (Math.min(pc.w - m1b.width, x - 12) - m2.width);
@@ -2330,8 +2934,8 @@
 		lastv = -1,
 		untext = -1,
-		can, ctx, w, h, grad1, grad2;
+		can, ctx, w, h, style1, style2;
 
 	r.onresize = function () {
-		if (!widget.is_open && r.can)
+		if (!widget.is_open && r.can )
 			return;
 
@@ -2343,5 +2947,5 @@
 		w = r.can.w;
 		h = r.can.h;
-		r.draw();
+		setTimeout(r.draw, 1);
 	}
 
@@ -2350,5 +2954,8 @@
 			return;
 
-		var gh = h + '' + light,
+		var accent = getComputedStyle(document.body).getPropertyValue('--a');
+		var bg = getComputedStyle(document.body).getPropertyValue('--bg-u2');
+
+		var gh = h + '' + light  + '/' + accent,
 			dz = themen == 'dz',
 			dy = themen == 'dy';
@@ -2356,26 +2963,26 @@
 		if (gradh != gh) {
 			gradh = gh;
-			grad1 = glossy_grad(r.can, dz ? 120 : 50,
-				dy ? [0, 0, 0, 0] : light ? [50, 55, 52, 48] : [45, 52, 47, 43],
-				dy ? [20, 24, 22, 20] : light ? [54, 60, 52, 47] : [42, 51, 47, 42]);
-			grad2 = glossy_grad(r.can, dz ? 120 : 205,
-				dz ? [100, 100, 100, 100] : dy ? [0, 0, 0, 0] : [10, 15, 13, 10],
-				dz ? [10, 14, 12, 10] : dy ? [90, 90, 90, 90] : [16, 20, 18, 16]);
-		}
-		ctx.fillStyle = grad2; ctx.fillRect(0, 0, w, h);
-		ctx.fillStyle = grad1; ctx.fillRect(0, 0, w * mp.vol, h);
-
-		var vt = 'volume ' + Math.floor(mp.vol * 100),
-			tw = ctx.measureText(vt).width,
-			x = w * mp.vol - tw - 8,
-			li = dy;
-
-		if (mp.vol < 0.5) {
-			x += tw + 16;
-			li = !li;
+			style1 = auto_grad(r.can, accent, accent);
+			style2 = light ? 'color-mix(in oklab, ' + bg + ' 85%, #000 15%' : 'color-mix(in oklab, ' + bg + ' 85%, #fff 15%';
 		}
+		if(IE){
+			style1 = 'rgb(60, 126, 248)';
+			style2 = 'rgb(75, 78, 82)'
+		}
+		ctx.fillStyle = style2; ctx.fillRect(0, 0, w, h);
+		ctx.fillStyle = style1; ctx.fillRect(0, 0, w * mp.vol, h);
+
+		// var vt = Math.floor(mp.vol * 100) + '%',
+		// 	tw = ctx.measureText(vt).width,
+		// 	x = w * mp.vol - tw - 8,
+		// 	li = dy;
+
+		// if (mp.vol < 0.5) {
+		// 	x += tw + 16;
+		// 	li = !li;
+		// }
 
-		ctx.fillStyle = li ? '#fff' : '#210';
-		ctx.fillText(vt, x, h / 3 * 2);
+		// ctx.fillStyle = li ? '#fff' : '#210';
+		// ctx.fillText(vt, x, h / 3 * 2);
 
 		clearTimeout(untext);
@@ -2385,6 +2992,13 @@
 
 	var rect;
+	var cs;
+	var left, top, width, height;
 	function mousedown(e) {
 		rect = can.getBoundingClientRect();
+		cs = getComputedStyle(ebi('pvol'));
+		top = rect.top + parseFloat(cs.borderTopWidth);
+		left = rect.left + parseFloat(cs.borderLeftWidth);
+		height = rect.height - 2 * parseFloat(cs.borderTopWidth);
+		width = rect.width - 2 * parseFloat(cs.borderLeftWidth);
 		mousemove(e);
 	}
@@ -2398,6 +3012,12 @@
 		}
 
-		var x = e.clientX - rect.left,
-			mul = x * 1.0 / rect.width;
+		var x = e.clientX - left,
+			mul = x * 1.0 / width;
+
+		if(height > width) {
+			// vertical
+			var y = e.clientY - top;
+			mul = 1 - y * 1.0 / height;
+		}
 
 		if (mul > 0.98)
@@ -2413,18 +3033,28 @@
 		}, 1);
 	}
-	can.onmousedown = function (e) {
+	ebi('pvolbg').onmousedown = can.onmousedown = function (e) {
 		if (e.button !== 0)
 			return;
 
-		can.onmousemove = mousemove;
+		ebi('pvolbg').onmousemove = can.onmousemove = mousemove;
 		mousedown(e);
 	};
-	can.onmouseup = function (e) {
+	ebi('pvolbg').onmouseup = can.onmouseup = function (e) {
 		if (e.button === 0)
-			can.onmousemove = null;
+			ebi('pvolbg').onmousemove = can.onmousemove = null;
 	};
-	if (TOUCH) {
-		can.ontouchstart = mousedown;
-		can.ontouchmove = mousemove;
+	ebi('pvolbg').ontouchstart = can.ontouchstart = mousedown;
+	ebi('pvolbg').ontouchmove = can.ontouchmove = mousemove;
+	var mute = ebi('pvolbg').children[0];
+	mute.onclick = function (e) {
+		ev(e);
+		if(mp.vol > 0)
+			mp.setvol(0);
+		else
+			mp.setvol(1);
+		r.draw();
+	}
+	mute.onmousedown = function (e) {
+		ev(e);
 	}
 	return r;
@@ -2582,7 +3212,7 @@
 // hook up the widget buttons
 (function () {
-	ebi('bplay').onclick = playpause;
-	ebi('bprev').onclick = prev_song;
-	ebi('bnext').onclick = next_song;
+	ebi('bplay').onclick = ebi('bplay2').onclick = playpause;
+	ebi('bprev').onclick = ebi('bprev2').onclick = prev_song;
+	ebi('bnext').onclick = ebi('bnext2').onclick = next_song;
 
 	var bar = ebi('barpos');
@@ -2599,31 +3229,36 @@
 		seek_au_mul(x * 1.0 / rect.width);
 	};
+	ebi('sliderpos').oninput = function(){
+		if (!mp.au) {
+			play(0, true);
+			return mp.fade_in();
+		}
+		seek_au_mul(this.value);
+	}
 
-	if (!TOUCH) {
-		bar.onwheel = function (e) {
-			var dist = Math.sign(e.deltaY) * 10;
-			if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
-				dist = e.deltaY;
+	bar.onwheel = function (e) {
+		var dist = Math.sign(e.deltaY) * 10;
+		if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
+			dist = e.deltaY;
 
-			if (!dist || !mp.au)
-				return true;
+		if (!dist || !mp.au)
+			return true;
 
-			seek_au_rel(dist);
-			ev(e);
-		};
-		ebi('pvol').onwheel = function (e) {
-			var dist = Math.sign(e.deltaY) * 10;
-			if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
-				dist = e.deltaY;
+		seek_au_rel(dist);
+		ev(e);
+	};
+	ebi('pvolbg').onwheel = function (e) {
+		var dist = Math.sign(e.deltaY) * 10;
+		if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
+			dist = e.deltaY;
 
-			if (!dist || !mp.au)
-				return true;
+		if (!dist)
+			return true;
 
-			dist *= -1;
-			mp.setvol(Math.round((mp.vol + dist / 500) * 100) / 100 );
-			vbar.draw();
-			ev(e);
-		};
-	}
+		dist *= -1;
+		mp.setvol(Math.round((mp.vol + dist / 250) * 100) / 100 );
+		vbar.draw();
+		ev(e);
+	};
 })();
 
@@ -2880,10 +3515,12 @@
 		swrite('au_eq_chw', r.chw);
 
-		var txt = QSA('input.eq_gain');
+		var gains = QSA('input.eq_gain');
 		for (var a = 0; a < r.bands.length; a++)
-			txt[a].value = r.gains[a];
+			gains[a + gains.length / 2].value = gains[a].value = r.gains[a].toFixed(1);
 
-		QS('input.eq_gain[band="amp"]').value = r.amp;
-		QS('input.eq_gain[band="chw"]').value = r.chw;
+		var amps = QSA('input.eq_gain[band="amp"]');
+		amps[0].value = amps[1].value = r.amp;
+		var chws = QSA('input.eq_gain[band="chw"]');
+		chws[0].value = chws[1].value = r.chw;
 	};
 
@@ -3073,5 +3710,5 @@
 				v = parseFloat(vs);
 
-			if (!isNum(v) || v + '' != vs)
+			if (!isNum(v) || (v + '' != vs && v.toFixed(1) != vs))
 				throw new Error('inval band');
 
@@ -3157,7 +3794,9 @@
 	}
 
-	var html = ['<table><tr><td rowspan="4">',
-		'<a id="au_eq" class="tgl btn" href="#" tt="' + L.mt_eq + '">' + L.enable + '</a></td>'],
-		h2 = [], h3 = [], h4 = [];
+	ebi('audio_eq').innerHTML = '<a id="au_eq" class="tgl btn" href="#">' + L.enable +
+		'</a><p class="s_desc">' + tt.parse(L.mt_eq) + '</p><br/>';
+
+	var html = ['<table><tr>'],
+		h2 = [], h3 = [], h4 = [], h5 = [];
 
 	var vs = [];
@@ -3175,18 +3814,21 @@
 	for (var a = 0; a < vs.length; a++) {
 		var b = vs[a][0];
-		html.push('<td><a href="#" class="eq_step" step="0.5" band="' + b + '">+</a></td>');
-		h2.push('<td>' + vs[a][1] + '</td>');
+		html.push('<td>' + vs[a][1] + '</td>');
+		h2.push('<td><a href="#" class="eq_step" step="0.5" band="' + b + '">+</a></td>');
+		h3.push('<td><input class="eq_gain" type="range" min="-6" max="6" step="0.5" orient="vertical" band="' + b + '" value="' + vs[a][2] + '" /></td>');
 		h4.push('<td><a href="#" class="eq_step" step="-0.5" band="' + b + '">&ndash;</a></td>');
-		h3.push('<td><input type="text" class="eq_gain" ' + NOAC + ' band="' + b + '" value="' + vs[a][2] + '" /></td>');
+		h5.push('<td><input class="eq_gain" type="text" ' + NOAC + ' band="' + b + '" value="' + vs[a][2] + '" /></td>');
 	}
 	html = html.join('\n') + '</tr><tr>';
 	html += h2.join('\n') + '</tr><tr>';
 	html += h3.join('\n') + '</tr><tr>';
-	html += h4.join('\n') + '</tr><table>';
-	ebi('audio_eq').innerHTML = html;
+	html += h4.join('\n') + '</tr><tr>';
+	html += h5.join('\n') + '</tr><table>';
+	ebi('audio_eq').innerHTML += html;
 
+	ebi('audio_drc').innerHTML = '<a id="au_drc" class="tgl btn" href="#">' + L.enable +
+		'</a><p class="s_desc">' + tt.parse(L.mt_drc) + '</p><br/>';
 	h2 = [];
-	html = ['<table><tr><td rowspan="2">',
-		'<a id="au_drc" class="tgl btn" href="#" tt="' + L.mt_drc + '">' + L.enable + '</a></td>'];
+	html = ['<table><tr>'];
 
 	for (var a = 0; a < r.drch.length; a++) {
@@ -3196,9 +3838,10 @@
 	html = html.join('\n') + '</tr><tr>';
 	html += h2.join('\n') + '</tr><table>';
-	ebi('audio_drc').innerHTML = html;
+	ebi('audio_drc').innerHTML += html;
 
+	ebi('audio_ss').innerHTML = '<a id="au_ss" class="tgl btn" href="#">' + L.enable +
+		'</a><p class="s_desc">' + tt.parse(L.mt_ss) + '</p><br/>';
 	h2 = [];
-	html = ['<table><tr><td rowspan="2">',
-		'<a id="au_ss" class="tgl btn" href="#" tt="' + L.mt_ss + '">' + L.enable + '</a></td>'];
+	html = ['<table><tr>'];
 
 	for (var a = 0; a < r.sscl.length; a++) {
@@ -3209,5 +3852,5 @@
 	html = html.join('\n') + '</tr><tr>';
 	html += h2.join('\n') + '</tr><table>';
-	ebi('audio_ss').innerHTML = html;
+	ebi('audio_ss').innerHTML += html;
 
 	var stp = QSA('a.eq_step');
@@ -3319,6 +3962,20 @@
 
 	mp.au.osrc = mp.tracks[tid];
+
+	var tname = esc(uricom_dec(mp.au.osrc.split('/').pop()));
+	var el = ebi('trackname');
+	el.innerHTML = tname;
+	el.setAttribute('tt', tname);
+	tt.att(ebi('pctl'));
+
 	afilt.apply();
 
+	// popup player
+	ebi('mu_tn').innerHTML = tname.replace(/\.[0-9a-z]+$/i, '');
+	var m_ext = tname.match(/[^\.]*$/);
+	if(m_ext){
+		QS('#mu_th>svg').style.color = intToHSL(hashCode(m_ext[0]))
+		QS('#mu_th>span').innerHTML = m_ext[0];
+	}
 	setTimeout(function () {
 		mpl.unbuffer(url);
@@ -3342,4 +3999,5 @@
 	clmod(ebi('wtoggle'), 'np', mpl.clip);
 	clmod(ebi('wtoggle'), 'm3u', mpl.m3uen);
+
 	if (thegrid)
 		thegrid.loadsel();
@@ -3368,11 +4026,7 @@
 		mpl.np = ft2dict(t_tr, { 'up_ip': 1 });
 
-		pbar.unwave();
-		if (mpl.waves)
-			pbar.loadwaves(url.replace(/\bth=(opus|mp3)&/, '') + '&th=p');
-
 		mpss.go();
 		mpui.progress_updater();
-		pbar.onresize();
+		mpl.selectprogbar();
 		vbar.onresize();
 		mpl.announce();
@@ -3576,4 +4230,5 @@
 	var media = scan_hash(v);
 	if (media) {
+		console.log('got media in hash')
 		var mtype = media[0],
 			id = media[1],
@@ -3588,14 +4243,11 @@
 
 		if (mtype == 'g') {
-			if (!thegrid.en)
-				ebi('griden').click();
-
 			var t = setInterval(function () {
-				if (!thegrid.bbox)
+				if (!msel.bbox || !msel.ready)
 					return;
 
 				clearInterval(t);
 				baguetteBox.urltime(ts);
-				var im = QS('#ggrid a[ref="' + id + '"]');
+				var im = ebi(id);
 				if (!im)
 					return toast.warn(10, L.im_hnf);
@@ -3642,5 +4294,4 @@
 			d.setAttribute('class', 'ayjump');
 			d.innerHTML = a ? L.ay_path : L.ay_files;
-			document.body.insertBefore(d, ebi('ops'));
 			d.onclick = function (e) {
 				ev(e);
@@ -3654,11 +4305,14 @@
 		})(a);
 
-	// account-info label
-	var d = mknod('div', 'acc_info');
-	document.body.insertBefore(d, ebi('ops'));
-
 	// folder nav
-	ebi('goh').parentElement.appendChild(mknod('span', null,
-		'<a href="#" id="gop" tt="' + L.gop + '</a>/<a href="#" id="gou" tt="' + L.gou + '</a>/<a href="#" id="gon" tt="' + L.gon + '</a>'));
+	// from https://www.svgrepo.com/collection/iconcino-interface-icons/
+	var svg_left = svg_box + '<path d="M11 6L5 12M5 12L11 18M5 12H19" ' + svg_options +'/>';
+	var svg_right = svg_box + '<path d="M13 6L19 12M19 12L13 18M19 12H5" ' + svg_options +'/>';
+	var svg_up = svg_box + '<path d="M12 19V5M18 11L12 5L6 11" ' + svg_options +'/>';
+
+	ebi('gop').innerHTML = svg_left;
+	ebi('gon').innerHTML = svg_right;
+	ebi('gou').innerHTML = svg_up;
+	
 	ebi('gop').onclick = function () { tree_neigh(-1); }
 	ebi('gon').onclick = function () { tree_neigh(1); }
@@ -3672,4 +4326,6 @@
 	setsb();
 
+	bcfg_bind(props, 'enbg', 'enbg', true, function(){ setBg(props.enbg ? window.bg_img : '')});
+
 	// compact media player
 	function setacmp() {
@@ -3933,4 +4589,14 @@
 }
 
+if(navigator.serviceWorker && caches){
+	caches.open('/media').then(function(cache){
+		return cache.match('/shared-file-count')
+	}).then(function(r){
+		return r && r.text();
+	}).then(function(count){
+		window.shr_target = count > 0;
+		fileman.render();
+	});
+}
 
 var fileman = (function () {
@@ -3939,6 +4605,9 @@
 		bcut = ebi('fcut'),
 		bcpy = ebi('fcpy'),
+		bsav = ebi('fsav'),
 		bpst = ebi('fpst'),
 		bshr = ebi('fshr'),
+		bcpl = ebi('fcpl'),
+		bclr = ebi('fclr'),
 		t_paste,
 		r = {};
@@ -3967,9 +4636,12 @@
 			encpy = nsel,
 			enpst = r.clip && r.clip.length,
-			hren = !(have_mv && has(perms, 'write') && has(perms, 'move')),
-			hdel = !(have_del && has(perms, 'delete')),
-			hcut = !(have_mv && has(perms, 'move')),
-			hpst = !(have_mv && has(perms, 'write')),
-			hshr = !can_shr || !get_evpath().indexOf(have_shr);
+			hren = !(have_mv && has(perms, 'write') && has(perms, 'move')) || !nsel,
+			hdel = !(have_del && has(perms, 'delete')) || !nsel,
+			hcut = !(have_mv && has(perms, 'move')) || !nsel,
+			hpst = !(have_mv && has(perms, 'write')) || !enpst,
+			hshr = !can_shr || !get_evpath().indexOf(have_shr),
+			hcpl = !nsel,
+			hsav = !window.shr_target,
+			enclr = enpst || nsel || window.shr_target;
 
 		if (!(enren || endel || encut || enpst))
@@ -3981,11 +4653,18 @@
 		clmod(bcpy, 'en', encpy);
 		clmod(bpst, 'en', enpst);
+		clmod(bsav, 'en', 1);
 		clmod(bshr, 'en', 1);
+		clmod(bcpl, 'en', 1);
+		clmod(bclr, 'en', 1);
 
 		clmod(bren, 'hide', hren);
 		clmod(bdel, 'hide', hdel);
 		clmod(bcut, 'hide', hcut);
-		clmod(bpst, 'hide', hpst);
+		clmod(bcpy, 'hide', !encpy);
+		clmod(bpst, 'hide', !enpst);
+		clmod(bsav, 'hide', hsav);
 		clmod(bshr, 'hide', hshr);
+		clmod(bcpl, 'hide', hcpl);
+		clmod(bclr, 'hide', !enclr);
 
 		clmod(ebi('wfm'), 'act', QS('#wfm a.en:not(.hide)'));
@@ -4001,4 +4680,7 @@
 		bpst.setAttribute('tt', L.ft_paste.format(r.clip.length));
 		bshr.setAttribute('tt', nsel ? L.fs_ss : L.fs_sc);
+
+		ebi('spaceFree').innerHTML = volspace[0] + ' free of ' + volspace[1];
+		ebi('spaceUsed_bar').style.width = (volspace[2] / 100) + '%';
 	};
 
@@ -4038,4 +4720,31 @@
 	};
 
+	r.clear = function (e) {
+		ev(e);
+
+		if(window.shr_target){
+			window.shr_target = false;
+			caches.open('/media').then(function(cache){
+				cache.match('/shared-file-count').then(function(r){
+					return r && r.text()
+				}).then(function(count){
+					if(count > 0){
+						cache.delete('/shared-file-count');
+						for(var i = 0; i < count; i++){
+							cache.delete('/shared-file-' + i);
+							cache.delete('/shared-filename-' + i);
+						}
+					}
+				});
+			});
+		}
+
+		var stamp = Date.now();
+		msel.evsel();
+		jwrite('fman_clip', [stamp]);
+		r.clip = [];
+		r.tx(stamp);
+	}
+
 	r.share = function (e) {
 		ev(e);
@@ -4227,4 +4936,19 @@
 	};
 
+	r.link = function (e) {
+		var sel = msel.getsel(),
+			vps = [];
+
+		for (var a = 0; a < sel.length; a++)
+			vps.push(ebi(sel[a].id).href);
+
+		if (!sel.length)
+			return toast.err(3, L.fd_emore);
+
+		ev(e);
+
+		cliptxt(vps.join(' \n'), function () { toast.ok(2, L.clipped) }); 
+	}
+
 	r.rename = function (e) {
 		ev(e);
@@ -4256,5 +4980,5 @@
 			var vsp = vsplit(vp);
 			if (base != vsp[0])
-				return toast.err(0, esc('bug:\n' + base + '\n' + vsp[0]));
+				return toast.err(0, esc('batch rename not allowed for differing file paths:\n' + base + '\n' + vsp[0]));
 
 			var vars = ft2dict(ebi(sel[a].id).closest('tr'));
@@ -4638,41 +5362,51 @@
 
 	r.cut = function (e) {
+		var useClip = r.clip.length > 0;
+
 		var sel = msel.getsel(),
 			stamp = Date.now(),
 			vps = [stamp];
 
-		if (!sel.length)
+		if (!useClip && !sel.length)
 			return toast.err(3, L.fc_emore);
 
 		ev(e);
 
-		if (clgot(bcut, 'hide'))
+		if (!useClip && clgot(bcut, 'hide'))
 			return toast.err(3, L.fc_eperm);
 
-		var els = [], griden = thegrid.en;
-		for (var a = 0; a < sel.length; a++) {
-			vps.push(sel[a].vp);
-			if (sel.length < 100)
-				try {
-					if (griden)
-						els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]'));
-					else
-						els.push(ebi(sel[a].id).closest('tr'));
+		if(!useClip){
+			var els = [], griden = thegrid.en;
+			for (var a = 0; a < sel.length; a++) {
+				vps.push(sel[a].vp);
+				if (sel.length < 100)
+					try {
+						if (griden)
+							els.push(QS('#ggrid>a[ref="' + sel[a].id + '"]'));
+						else
+							els.push(ebi(sel[a].id).closest('tr'));
 
-					clmod(els[a], 'fcut');
+						clmod(els[a], 'fcut');
+					}
+					catch (ex) { }
+			}
+
+			setTimeout(function () {
+				try {
+					for (var a = 0; a < els.length; a++)
+						clmod(els[a], 'fcut', 1);
 				}
 				catch (ex) { }
-		}
+			}, 1);
 
-		setTimeout(function () {
-			try {
-				for (var a = 0; a < els.length; a++)
-					clmod(els[a], 'fcut', 1);
+			r.clip = vps.slice(1);
+		}
+		else {
+			for (var a = 0; a < r.clip.length; a++) {
+				vps.push(r.clip[a]);
 			}
-			catch (ex) { }
-		}, 1);
+		}
 
 		r.ccp = false;
-		r.clip = vps.slice(1);
 
 		try {
@@ -4689,4 +5423,7 @@
 			toast.warn(30, L.fc_warn.format(sel.length));
 		}
+
+		if(thegrid.tempsel)
+			msel.evsel();
 	};
 
@@ -4749,4 +5486,7 @@
 			toast.warn(30, L.fcc_warn.format(sel.length));
 		}
+
+		if(thegrid.tempsel)
+			msel.evsel();
 	};
 
@@ -4817,5 +5557,8 @@
 	};
 
-	r.paste = function () {
+	r.paste = function (justgo) {
+		if (ebi('unsearch'))
+			return toast.err(3, L.fp_esrch)
+
 		if (!r.clip.length)
 			return toast.err(5, L.fp_ecut);
@@ -4840,5 +5583,5 @@
 			indir = [],
 			srcdir = vsplit(r.clip[0])[0],
-			links = QSA('#files tbody td:nth-child(2) a');
+			links = QSA('#files .flink');
 
 		r.f = f;
@@ -4911,4 +5654,6 @@
 				src.push(f[a].src);
 
+			if (justgo)
+				return okgo();
 			return modal.confirm((r.ccp ? L.fcp_confirm : L.fp_confirm).format(f.length) + '<ul>' + uricom_adec(src, true).join('') + '</ul>', okgo, null);
 		}
@@ -5002,4 +5747,6 @@
 			}
 			r.render();
+			if(('' + msg).startsWith(window.location.origin))
+				msg = msg.replace(window.location.origin, "");
 			if (msg == get_evpath())
 				treectl.goto(msg);
@@ -5028,5 +5775,8 @@
 	bcpy.onclick = r.cpy;
 	bpst.onclick = r.paste;
+	if(up2k) bsav.onclick = up2k.save_here; // just in case up2k ever inits first
 	bshr.onclick = r.share;
+	bcpl.onclick = r.link;
+	bclr.onclick = r.clear;
 
 	return r;
@@ -5541,25 +6291,36 @@
 
 
-var thegrid = (function () {
+window.thegrid = (function () {
 	var lfiles = ebi('files'),
 		gfiles = mknod('div', 'gfiles');
 
+	var svg_list = svg_box + '<path fill-rule="evenodd" clip-rule="evenodd" d="M9 6C9 4.34315 7.65685 3 6 3H4C2.34315 3 1 4.34315 1 6V8C1 9.65685 2.34315 11 4 11H6C7.65685 11 9 9.65685 9 8V6ZM7 6C7 5.44772 6.55228 5 6 5H4C3.44772 5 3 5.44772 3 6V8C3 8.55228 3.44772 9 4 9H6C6.55228 9 7 8.55228 7 8V6Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M9 16C9 14.3431 7.65685 13 6 13H4C2.34315 13 1 14.3431 1 16V18C1 19.6569 2.34315 21 4 21H6C7.65685 21 9 19.6569 9 18V16ZM7 16C7 15.4477 6.55228 15 6 15H4C3.44772 15 3 15.4477 3 16V18C3 18.5523 3.44772 19 4 19H6C6.55228 19 7 18.5523 7 18V16Z" fill="currentColor"/><path d="M11 7C11 6.44772 11.4477 6 12 6H22C22.5523 6 23 6.44772 23 7C23 7.55228 22.5523 8 22 8H12C11.4477 8 11 7.55228 11 7Z" fill="currentColor"/><path d="M11 17C11 16.4477 11.4477 16 12 16H22C22.5523 16 23 16.4477 23 17C23 17.5523 22.5523 18 22 18H12C11.4477 18 11 17.5523 11 17Z" fill="currentColor"/></svg>'
+	var svg_grid = svg_box + '<rect x="4" y="4" width="7" height="7" rx="1" fill="currentColor"/><rect x="4" y="13" width="7" height="7" rx="1" fill="currentColor"/><rect x="13" y="4" width="7" height="7" rx="1" fill="currentColor"/><rect x="13" y="13" width="7" height="7" rx="1" fill="currentColor"/></svg>'
+	var svg_gallery = svg_box + '<path d="M4 17L7.58959 13.7694C8.38025 13.0578 9.58958 13.0896 10.3417 13.8417L11.5 15L15.0858 11.4142C15.8668 10.6332 17.1332 10.6332 17.9142 11.4142L20 13.5M11 9C11 9.55228 10.5523 10 10 10C9.44772 10 9 9.55228 9 9C9 8.44772 9.44772 8 10 8C10.5523 8 11 8.44772 11 9ZM6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20Z"' + svg_options + '/></svg>'
+	
 	gfiles.style.display = 'none';
 	gfiles.innerHTML = (
-		'<div id="ghead" class="ghead">' +
-		'<a href="#" class="tgl btn" id="gridvau" tt="' + L.gt_vau + '</a> ' +
-		'<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> ' +
-		'<a href="#" class="tgl btn" id="gridcrop" tt="' + L.gt_crop + '</a> ' +
-		'<a href="#" class="tgl btn" id="grid3x" tt="' + L.gt_3x + '</a> ' +
-		'<span>' + L.gt_zoom + ': ' +
-		'<a href="#" class="btn" z="-1.1" tt="Hotkey: shift-A">&ndash;</a> ' +
-		'<a href="#" class="btn" z="1.1" tt="Hotkey: shift-D">+</a></span> <span>' + L.gt_chop + ': ' +
-		'<a href="#" class="btn" l="-1" tt="' + L.gt_c1 + '">&ndash;</a> ' +
-		'<a href="#" class="btn" l="1" tt="' + L.gt_c2 + '">+</a></span> <span>' + L.gt_sort + ': ' +
-		'<a href="#" s="href">' + L.gt_name + '</a> ' +
-		'<a href="#" s="sz">' + L.gt_sz + '</a> ' +
-		'<a href="#" s="ts">' + L.gt_ts + '</a> ' +
-		'<a href="#" s="ext">' + L.gt_ext + '</a>' +
-		'</span></div>' +
+		'<div id="ghead" class="ghead normalrcm">' +
+			'<a href="#" id="listicon_template" class="grdbtn gb_lst svgIcon tgl btn" tt="' + L.wt_grid + '">' + svg_list + '</a>' +
+			'<a href="#" id="gridicon_template" class="grdbtn gb_grd svgIcon tgl btn on" tt="' + L.wt_grid + '">' + svg_grid + '</a>' +
+			'<a href="#" id="galleryicon_template" class="grdbtn gb_glr svgIcon tgl btn" tt="' + L.wt_gallery + '">' + svg_gallery + '</a>' +
+			'<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> ' +
+			'<a href="#" class="tgl btn" id="gridvau" tt="' + L.gt_vau + '</a> ' +
+			'<a href="#" class="tgl btn" id="gridcrop" tt="' + L.gt_crop + '</a> ' +
+			'<a href="#" class="tgl btn" id="gridupscale" tt="' + L.gt_upscale + '">↕</a> ' +
+			'<a href="#" class="tgl btn" id="grid3x" tt="' + L.gt_3x + '</a> ' +
+			'<span id="gridzoom">' + L.gt_zoom + ': ' +
+				'<a href="#" class="btn" z="-1.1" tt="Hotkey: shift-A">&ndash;</a> ' +
+				'<a href="#" class="btn" z="1.1" tt="Hotkey: shift-D">+</a>' +
+			'</span> <span id="gridchop">' + L.gt_chop + ': ' +
+				'<a href="#" class="btn" l="-1" tt="' + L.gt_c1 + '">&ndash;</a> ' +
+				'<a href="#" class="btn" l="1" tt="' + L.gt_c2 + '">+</a>' +
+			'</span> <span id="gridsort">' + L.gt_sort + ': ' +
+				'<a href="#" s="href">' + L.gt_name + '</a> ' +
+				'<a href="#" s="sz">' + L.gt_sz + '</a> ' +
+				'<a href="#" s="ts">' + L.gt_ts + '</a> ' +
+				'<a href="#" s="ext">' + L.gt_ext + '</a>' +
+			'</span>' +
+		'</div>' +
 		'<div id="ggrid"></div>'
 	);
@@ -5567,4 +6328,32 @@
 	var ggrid = ebi('ggrid');
 
+	var ico1 = ebi('listicon_template').cloneNode(true);
+	var ico2 = ebi('gridicon_template').cloneNode(true);
+	clmod(ico1, 'on', true);
+	clmod(ico2, 'on', false);
+
+	// file list header
+	ebi('wtc').innerHTML = (
+		ico1.outerHTML +
+		ico2.outerHTML +
+		'<details id="hcol_dd">' +
+			'<summary id="h_hidden">' + L.cl_hiddenc + '</summary>' +
+		'<div id="hcol_content" class="setting">' +
+			'<a class="btn tgl" href="#" id="hcolsh">' + L.cl_hidec + '</a>' +
+			'<a class="btn" id="hcolsr">' + L.cl_reset + '</a>' +
+			'<div class="divider_h"></div>' +
+			'<div id="hcols"></div>' +
+		'</div>' +
+		'</details>'
+	);
+
+	ebi('h_hidden').onclick = function () {
+		clmod(this.parentElement, 'open', 't');
+		if(!clgot(this.parentElement, 'open') && filecols.picking){
+			filecols.unpick();
+			clmod(ebi('hcolsh'), 'on', r.picking)
+		}
+	}
+
 	var r = {
 		'sz': clamp(fcfg_get('gridsz', 10), 4, 80),
@@ -5578,5 +6367,9 @@
 		var s = this.getAttribute('s'),
 			z = this.getAttribute('z'),
-			l = this.getAttribute('l');
+			l = this.getAttribute('l'),
+			grdbtn = clgot(this, 'grdbtn');
+
+		if(grdbtn)
+			return;
 
 		if (z)
@@ -5616,6 +6409,9 @@
 		gfiles.style.display = vis && r.en ? '' : 'none';
 		lfiles.style.display = vis && !r.en ? '' : 'none';
+		ebi('wtc').style.display = lfiles.style.display;
 		clmod(ggrid, 'crop', r.crop);
 		clmod(ggrid, 'nocrop', !r.crop);
+		clmod(ggrid, 'gallery', r.gallery);
+		clmod(ggrid, 'noupscale', !r.upscale);
 		ebi('pro').style.display = ebi('epi').style.display = ebi('lazy').style.display = ebi('treeul').style.display = ebi('treepar').style.display = '';
 		ebi('bdoc').style.display = 'none';
@@ -5625,8 +6421,10 @@
 			treectl.textmode(false);
 
-		if (filecols)
-			filecols.uivis();
+		var imgs = QSA('#ggrid>a img');
+		for (var a = 0, aa = imgs.length; a < aa; a++) {
+			set_loaded(imgs[a], r.thumbs && imgs[a].complete, true);
+		}
 
-		aligngriditems();
+		setTimeout(aligngriditems, 1);
 		restore_scroll();
 	};
@@ -5669,82 +6467,18 @@
 		}
 		setcvar('--grid-sz', r.sz + 'em');
-		aligngriditems();
+		setTimeout(aligngriditems, 1);
 	}
 	setsz();
 
-	function gclick1(e) {
-		if (ctrl(e) && !treectl.csel && !r.sel)
-			return true;
-
-		return gclick.call(this, e, false);
-	}
-
 	function gclick2(e) {
-		if (ctrl(e) || !r.sel)
+		if (ctrl(e) || !r.sel && !r.tempsel)
 			return true;
 
-		return gclick.call(this, e, true);
-	}
-
-	function gclick(e, dbl) {
-		var oth = ebi(this.getAttribute('ref')),
-			qhref = this.getAttribute('href'),
-			href = qhref.split('?')[0],
-			fid = oth.getAttribute('id'),
-			aplay = ebi('a' + fid),
-			atext = ebi('t' + fid),
-			is_txt = atext && !/\.ts$/.test(href) && showfile.getlang(href),
-			is_img = img_re.test(href),
-			is_dir = href.endsWith('/'),
-			is_srch = !!ebi('unsearch'),
-			in_tree = is_dir && treectl.find(oth.textContent.slice(0, -1)),
-			have_sel = QS('#files tr.sel'),
-			td = oth.closest('td').nextSibling,
-			tr = td.parentNode;
-
-		if (!is_srch && ((r.sel && !dbl && !ctrl(e)) || (treectl.csel && (e.shiftKey || ctrl(e))))) {
-			td.onclick.call(td, e);
-			if (e.shiftKey)
-				return r.loadsel();
-			clmod(this, 'sel', clgot(tr, 'sel'));
-		}
-		else if (in_tree && !have_sel)
-			in_tree.click();
-
-		else if (oth.hasAttribute('download'))
-			oth.click();
-
-		else if (aplay && (r.vau || !is_img))
-			aplay.click();
-
-		else if (is_dir && !have_sel)
-			treectl.reqls(qhref, true);
-
-		else if (is_txt && !has(['md', 'htm', 'html'], is_txt))
-			atext.click();
-
-		else if (!is_img && have_sel)
-			window.open(qhref, '_blank');
-
-		else {
-			if (!dbl)
-				return true;
-
-			setTimeout(function () {
-				r.sel = true;
-			}, 1);
-			r.sel = false;
-			this.click();
-		}
-		ev(e);
+		var td = ebi(this.getAttribute('ref'));
+		return fclick.call(td, e, true);
 	}
 
 	r.imshow = function (url) {
-		var sel = '#ggrid>a'
-		if (!thegrid.en) {
-			thegrid.bagit('#files');
-			sel = '#files a[id]';
-		}
-		var ims = QSA(sel);
+		var ims = QSA('#files a[id]');
 		for (var a = 0, aa = ims.length; a < aa; a++) {
 			var iu = ims[a].getAttribute('href').split('?')[0].split('/').slice(-1)[0];
@@ -5755,4 +6489,5 @@
 	};
 
+	r.tempsel = false;
 	r.loadsel = function () {
 		if (r.dirty)
@@ -5760,4 +6495,5 @@
 
 		var ths = QSA('#ggrid>a');
+		var anysel = false;
 
 		for (var a = 0, aa = ths.length; a < aa; a++) {
@@ -5766,9 +6502,11 @@
 				continue;
 			var cl = ebi(ref).closest('tr').className || '';
-
-			if (noq_href(ths[a]).endsWith('/'))
-				cl += ' dir';
-
 			ths[a].className = cl;
+
+			var chk = ths[a].getElementsByTagName('input')[0];
+			if(chk) {
+				chk.checked = cl.match(/\bsel\b/);
+				anysel = anysel || chk.checked;
+			}
 		}
 
@@ -5783,4 +6521,7 @@
 					};
 			})(a);
+
+		r.tempsel = TOUCH && anysel;
+		clmod(ebi('gridsel'), 'temp', r.tempsel);
 	};
 
@@ -5834,7 +6575,5 @@
 			svgs = new Set(),
 			max_svgs = CHROME ? 500 : 5000,
-			need_ext = !r.thumbs || !!ext_th,
-			use_ext_th = r.thumbs && ext_th,
-			files = QSA('#files>tbody>tr>td:nth-child(2) a[id]');
+			files = QSA('#files .flink');
 
 		for (var a = 0, aa = files.length; a < aa; a++) {
@@ -5842,69 +6581,17 @@
 				ohref = esc(ao.getAttribute('href')),
 				href = ohref.split('?')[0],
-				ext = '',
-				ext0 = '',
 				name = uricom_dec(vsplit(href)[1]),
 				ref = ao.getAttribute('id'),
-				isdir = href.endsWith('/'),
-				ac = isdir ? ' class="dir"' : '',
-				ihref = ohref;
-
-			if (need_ext && href != "#") {
-				var ar = href.split('.');
-				if (ar.length > 1)
-					ar.shift();
-
-				ar.reverse();
-				ext0 = ar[0];
-				for (var b = 0; b < Math.min(2, ar.length); b++) {
-					if (ar[b].length > 7)
-						break;
-
-					ext = ext ? (ar[b] + '.' + ext) : ar[b];
-				}
-				if (!ext)
-					ext = 'unk';
-			}
-
-			if (use_ext_th && (ext_th[ext] || ext_th[ext0])) {
-				ihref = ext_th[ext] || ext_th[ext0];
-			}
-			else if (r.thumbs) {
-				ihref = addq(ihref, 'th=' + (
-					have_jxl  ? 'x' :
-					have_webp ? 'w' :
-					'j'
-				));
-				if (!r.crop)
-					ihref += 'f';
-				if (r.x3)
-					ihref += '3';
-				if (href == "#")
-					ihref = SR + '/.cpr/ico/' + (ref == 'moar' ? '++' : 'exit');
-			}
-			else if (isdir) {
-				ihref = SR + '/.cpr/ico/folder';
-			}
-			else {
-				if (!svgs.has(ext)) {
-					if (svgs.size < max_svgs)
-						svgs.add(ext);
-					else
-						ext = "unk";
-				}
-				ihref = SR + '/.cpr/ico/' + ext;
-			}
-			ihref = addq(ihref, 'cache=i&_=' + ACB + TS);
-			if (CHROME)
-				ihref += "&raster";
+				isdir = clgot(ao.parentElement.parentElement, 'dir'),
+				ac = ao.parentElement.parentElement.classList;
 
 			html.push('<a href="' + ohref + '" ref="' + ref +
-				'"' + ac + ' ttt="' + esc(name) + '"><img style="height:' +
-				(r.sz / 1.25) + 'em" loading="lazy" fetchPriority="low" src="' +
-				ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
+				'" class="flink ' + ac + '" ttt="' + esc(name) + '">' +
+				get_thumb(ohref, ref, isdir, true) +
+				'<span class="' + ac + '">' + ao.innerHTML + '</span></a>');
 		}
 		ggrid.innerHTML = html.join('\n');
 
-		var ths = QSA('#ggrid>a>img');
+		var ths = QSA('#ggrid .imgcontainer>img');
 		for (var a = 0, aa = ths.length; a < aa; a++)
 			ths[a].onload = th_onload;
@@ -5913,58 +6600,34 @@
 		clmod(ggrid, 'nocrop', !r.crop);
 
-		var srch = ebi('unsearch'),
-			gsel = ebi('gridsel');
-
-		gsel.style.display = srch ? 'none' : '';
-		if (srch && r.sel)
-			gsel.click();
-
-		ths = QSA('#ggrid>a');
+		var ths = QSA('#ggrid>a');
 		for (var a = 0, aa = ths.length; a < aa; a++) {
 			ths[a].ondblclick = gclick2;
-			ths[a].onclick = gclick1;
+			ths[a].onclick = fclick1;
+		}
+
+		var imgs = QSA('#ggrid>a img');
+		for (var a = 0, aa = imgs.length; a < aa; a++) {
+			set_loaded(imgs[a], r.thumbs && imgs[a].complete, true);
+		}
+
+		var chks = QSA('.gselchk');
+		for (var a = 0, aa = chks.length; a < aa; a++) {
+			chks[a].onclick = function (e) {
+				var oth = ebi(this.closest('a').getAttribute('ref')),
+					td = oth.closest('td').nextSibling;
+				if(td && td.onclick)
+					td.onclick.call(td, e);
+				else
+					rcm.show(e);
+			}
 		}
 
 		r.dirty = false;
-		r.bagit('#ggrid');
 		r.loadsel();
-		aligngriditems();
+		setTimeout(aligngriditems, 1);
 		setTimeout(r.tippen, 20);
+		drag.initgrid();
 	}
 
-	r.bagit = function (isrc) {
-		if (!window.baguetteBox)
-			return;
-
-		if (r.bbox)
-			baguetteBox.destroy();
-
-		var br = baguetteBox.run(isrc, {
-			noScrollbars: true,
-			duringHide: r.onhide,
-			afterShow: function () {
-				r.bbox_opts.refocus = true;
-			},
-			captions: function (g, idx) {
-				var h = '' + g;
-
-				return '<a download href="' + h +
-					'">' + (idx + 1) + ' / ' + this.length + ' -- ' +
-					esc(uricom_dec(h.split('/').pop())) + '</a>';
-			},
-			onChange: function (i, maxIdx) {
-				if (this[i].imageElement) {
-					var h = 'g' + this[i].imageElement.getAttribute('ref');
-					if (this[i].page)
-						h += ',p' + this[i].page;
-					h += getsort();
-					sethash(h);
-				}
-			}
-		});
-		r.bbox = true;
-		r.bbox_opts = br[1];
-	};
-
 	r.onhide = function () {
 		afilt.apply();
@@ -6037,10 +6700,22 @@
 	}
 
-	bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
+	bcfg_bind(r, 'thumbs', 'thumbs', true, function(e){
+		r.setdirty(); 
+		msel.render();
+	});
 	bcfg_bind(r, 'ihop', 'ihop', true);
 	bcfg_bind(r, 'vau', 'gridvau', false);
 	bcfg_bind(r, 'crop', 'gridcrop', !dcrop.endsWith('n'), r.set_crop);
+	bcfg_bind(r, 'upscale', 'gridupscale', true, function (v) {
+		clmod(ggrid, 'noupscale', !v)
+	});
 	bcfg_bind(r, 'x3', 'grid3x', dth3x.endsWith('y'), r.set_x3);
-	bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel);
+	bcfg_bind(r, 'sel', 'gridsel', false, function (e) {
+		if (r.tempsel) {
+			r.tempsel = false;
+			clmod(ebi('gridsel'), 'temp', false);
+		}
+		r.loadsel();
+	});
 	bcfg_bind(r, 'en', 'griden', dgrid, function (v) {
 		v ? loadgrid() : r.setvis(true);
@@ -6048,5 +6723,5 @@
 		vbar.onresize();
 	});
-	bcfg_bind(r, 'gaen', 'gauto', !!dgauto, function(v) {
+	bcfg_bind(r, 'gaen', 'gauto', !!dgauto, function (v) {
 		if (r.en && sread("griden") != 1) {
 			r.en = false;
@@ -6059,14 +6734,153 @@
 		swrite('ga_thresh', r.gathr = (isNum(n) ? n : 0) || 70);
 	};
-	ebi('wtgrid').onclick = ebi('griden').onclick;
+
+	var gbtn = QSA('.gb_grd');
+	for(var a = 0; a < gbtn.length; a++)
+		gbtn[a].onclick = function(){
+			if(!thegrid.en)
+				ebi('griden').click();
+		}
+
+	var lsbtn = QSA('.gb_lst');
+	for(var a = 0; a < lsbtn.length; a++)
+		lsbtn[a].onclick = function(){
+			if(thegrid.en)
+				ebi('griden').click();
+		}
+
+	bcfg_bind(r, 'gallery', 'galleryicon_template', false, function (v) {
+		clmod(ebi('ggrid'), 'gallery', v);
+	});
 
 	return r;
 })();
 
+function get_thumb(ohref, ref, isdir, grid){
+	var href = ohref.split('?')[0],
+		ext = '',
+		ext0 = '',
+		need_ext = !thegrid.thumbs || !!ext_th,
+		use_ext_th = thegrid.thumbs && ext_th,
+		ihref = ohref;
+
+	if (need_ext && href != "#") {
+		var ar = href.split('.');
+		if (ar.length > 1)
+			ar.shift();
+
+		ar.reverse();
+		ext0 = ar[0];
+		for (var b = 0; b < Math.min(2, ar.length); b++) {
+			ext = ext ? (ar[b] + '.' + ext) : ar[b];
+			if (ar[b].length > 2)
+				break;
+		}
+		if (!ext)
+			ext = 'unk';
+	}
+
+	if (use_ext_th && (ext_th[ext] || ext_th[ext0])) {
+		ihref = ext_th[ext] || ext_th[ext0];
+	}
+	else if (thegrid.thumbs) {
+		ihref = addq(ihref, 'th=' + (
+			have_jxl ? 'x' :
+			have_webp ? 'w' :
+			'j'
+		));
+		if (grid && !thegrid.crop)
+			ihref += 'f';
+		if (grid && thegrid.x3)
+			ihref += '3';
+		if (href == "#")
+			ihref = SR + '/.cpr/ico/' + (ref == 'moar' ? '++' : 'exit');
+	}
+	else {
+		ihref = '';
+	}
+
+	if(ihref){
+		ihref = addq(ihref, 'cache=i&_=' + ACB + TS);
+
+		var accent = getComputedStyle(document.body).getPropertyValue('--a');
+		if (!accent)
+			accent = '#07c';
+		ihref += '&a=' + parseColor(accent).replace(/ /g, '');
+	}
+
+	var svg = '<use href="#' +
+		(isdir ? 'folder' : 'file') + '-icon" color="' +
+		(ext == 'unk' || ext.startsWith('/') ? '#0000' : intToHSL(hashCode(ext))) + '"/>';
+	svg = '<svg class="thumb" width="100%" height="100%">' + svg + '</svg>';
+
+	if (N3DS){
+		// 3DS / unsupported: use fallback
+		svg = '<span class="thumb">' + (isdir ? '📁[FOLDER]' : '📄[FILE:' + ext + ']') + '</span>';
+		ext = 'unk';
+	}
+
+	var container = '<div class="imgcontainer" ref="' + ref + '">' +
+		(grid ? '<input type="checkbox" class="gselchk"></input>' : '') +
+		svg +
+		(isdir || ext == 'unk' || ext.startsWith('/') ? '' :
+			'<span class="th_ext" style="font-size: ' + 
+			(grid ? 
+				(thegrid.sz / 5) +'em; font-size:calc((var(--grid-sz) - 2.5em) / 3 * ' + (ext.length > 3 ? 1 / (3 + ext.length * .4) * 3 : 1) + ')' :
+				'.8em') + 
+			'"><span class="inner">' + ext + '</span></span>') +
+		'<img loading="lazy" fetchPriority="low" src="' +
+		ihref + '" /></div>'
+		// onload needs to be set afterwards due to nonces
+	return container;
+}
 
 function th_onload() {
-	this.style.height = '';
+	set_loaded(this, true, false)
+}
+
+function set_loaded(el, state, dblcheck) {
+	if(state && dblcheck) {
+		testImage(el);
+		return;
+	}
+	if(el.parentElement == null || el.parentElement.parentElement == null)
+		return;
+	var p = el.parentElement.parentElement
+	clmod(p, 'thumbed', state)
+	var a = p.getAttribute('ref')
+	if(a){
+		var b = ebi(a)
+		if(b){
+			var c = b.closest('tr')
+			if(c)
+				clmod(c, 'thumbed', state)
+		}
+	}
+}
+
+function testImage(el) {
+	var URL = el.src
+    var tester=new Image();
+    tester.onload=
+		function imageFound() {
+			set_loaded(el, true, false)
+		};
+    tester.onerror=
+		function imageFound() {
+			set_loaded(el, false, false)
+		};
+    tester.src=URL;
 }
 
+function hashCode (str) {
+	var hash = 0;
+	for(var i = 0; i < str.length; i++){
+		hash = str.charCodeAt(i) + 130 * ((hash << 5) - hash);
+	}
+	return hash
+}
+function intToHSL(i){
+	return 'hsl(' + i % 360 + 'deg 100% 50%)'
+}
 
 function tree_scrollto(e) {
@@ -6218,5 +7032,5 @@
 	selfun();
 }
-var konmai = 0, konmak = (function() {
+var konmai = 0, konmak = (function () {
 	var u = "arrowup",
 		d = "arrowdown",
@@ -6235,5 +7049,6 @@
 		sh = e.shiftKey,
 		ae = document.activeElement,
-		aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '';
+		aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '',
+		aett = aet ? ae.type : '';
 
 	if (k.startsWith('Key'))
@@ -6250,5 +7065,5 @@
 		noop();
 	else if (konmak[konmai] != kl)
-		konmai = konmai && kl == konmak[0] ? (konmai<3?konmai:1):0;
+		konmai = konmai && kl == konmak[0] ? (konmai < 3 ? konmai : 1) : 0;
 	else if (++konmai >= konmak.length) {
 		konmai = -1;
@@ -6257,6 +7072,7 @@
 		start_actx();
 		sfx_nice();
+		treectl.entree();
 		toast.inf(9, 'omega clearance granted', null, 'top');
-		setTimeout(function() {
+		setTimeout(function () {
 			apply_perms(treectl.lsc);
 			fileman.render();
@@ -6284,4 +7100,10 @@
 			return ebi('sh_abrt').click();
 
+		if (QS('#music.vis'))
+			return ebi('cl_mu').click();
+
+		if (!clgot(ebi('fclr'), 'hide'))
+			return fileman.clear();
+
 		if (QS('.opview.act'))
 			return QS('#ops>a').click();
@@ -6303,5 +7125,5 @@
 	}
 
-	if (aet == 'input')
+	if (aet == 'input' && aett != 'checkbox')
 		return;
 
@@ -6356,5 +7178,5 @@
 		return ev(e) && ae.click() || true;
 
-	if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre')
+	if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre' && aett != 'checkbox')
 		return;
 
@@ -6362,5 +7184,5 @@
 		return hkhelp();
 
-	if (!sh && ctrl(e)) {
+	if (!sh && ctrl(e) || k.endsWith('Delete')) {
 		var sel = window.getSelection && window.getSelection() || {};
 		sel = sel && !sel.isCollapsed && sel.direction != 'none';
@@ -6375,5 +7197,5 @@
 			return fileman.d_paste(e);
 
-		if (kl == 'k')
+		if (kl == 'k' || (k.endsWith('Delete') && !ctrl(e)))
 			return fileman.delete(e);
 
@@ -6469,33 +7291,33 @@
 	var sconf = [
 		[
-			L.s_sz,
-			["szl", "sz_min", L.s_s1, "14", ""],
-			["szu", "sz_max", L.s_s2, "14", ""]
+			L.s_fn,
+			["name", "name", L.s_f1, "td_s", ".exe$"]
 		],
 		[
-			L.s_dt,
-			["dtl", "dt_min", L.s_d1, "14", "1997-08-15, 01:00"],
-			["dtu", "dt_max", L.s_d2, "14", "2020"]
+			L.s_rd,
+			["path", "path", L.s_r1, "td_w", "windows  -system32"]
 		],
 		[
-			L.s_rd,
-			["path", "path", L.s_r1, "30", "windows  -system32"]
+			L.s_sz,
+			["szl", "sz_min", L.s_s1, "td_s", ""],
+			["szu", "sz_max", L.s_s2, "td_s", ""]
 		],
 		[
-			L.s_fn,
-			["name", "name", L.s_f1, "30", ".exe$"]
+			L.s_dt,
+			["dtl", "dt_min", L.s_d1, "td_s", "1997-08-15, 01:00", "date"],
+			["dtu", "dt_max", L.s_d2, "td_s", "2020", "date"]
 		],
 		[
 			L.s_ta,
-			["tags", "tags", L.s_t1, "30", "^irui$"]
+			["tags", "tags", L.s_t1, "td_w", "^irui$"]
 		],
 		[
 			L.s_ad,
-			["adv", "adv", L.s_a1, "30", "key>=1A  key<=2B  .bpm>165"]
+			["adv", "adv", L.s_a1, "td_w", "key>=1A  key<=2B  .bpm>165"]
 		],
 		[
 			L.s_ua,
-			["utl", "ut_min", L.s_u1, "14", "2007-04-08"],
-			["utu", "ut_max", L.s_u2, "14", "2038-01-19"]
+			["utl", "ut_min", L.s_u1, "td_s", "2007-04-08", "date"],
+			["utu", "ut_max", L.s_u2, "td_s", "2038-01-19", "date"]
 		]
 	];
@@ -6511,11 +7333,12 @@
 		for (var b = 1; b < 3; b++) {
 			var hn = "srch_" + sconf[a][b][0],
-				csp = (sconf[a].length == 2) ? 2 : 1;
+				csp = (sconf[a].length == 2) ? 2 : 1,
+				type = sconf[a][b].length >= 6 ? sconf[a][b][5] : "text";
 
 			html.push(
 				'<td colspan="' + csp + '"><input id="' + hn + 'c" type="checkbox">\n' +
 				'<label for="' + hn + 'c">' + sconf[a][b][2] + '</label>\n' +
-				'<br /><input id="' + hn + 'v" type="text" style="width:' + sconf[a][b][3] +
-				'em" name="' + sconf[a][b][1] + '" placeholder="' + sconf[a][b][4] + '" /></td>');
+				'<br /><input id="' + hn + 'v" type="' + type + '" class="' + sconf[a][b][3] +
+				'" name="' + sconf[a][b][1] + '" placeholder="' + sconf[a][b][4] + '" /></td>');
 			if (csp == 2)
 				break;
@@ -6537,4 +7360,56 @@
 	}
 
+	var folderSearch = ebi('folder_search');
+	folderSearch.placeholder = L.s_dir;
+	folderSearch.onfocus = function(){
+		ebi('srch_pathc').checked = true;
+		var path = decodeURI(get_evpath());
+		ebi('srch_pathv').value = path.slice(1) || '/';
+	}
+	if(IE)
+		folderSearch.onclick = function() {
+			clmod(ebi('srch_quickopts'), 'act', 't');
+		}
+	folderSearch.oninput = function(){
+		var v = unsmart(this.value);
+		if(IE && !v)
+			return;
+		var nsearch = ebi('srch_namev');
+		nsearch.value = v;
+		nsearch.oninput();
+	}
+	folderSearch.onkeydown = ev_search_keydown;
+
+	var expand = sread('s_ex') == 'true';
+	exp_search(expand);
+	function exp_search (e) {
+		clmod(ebi('op_search'), 'vis', e);
+
+		var ms = ebi('moresearch');
+		if(e){
+			window.scrollTo(0, 0);
+			ms.innerHTML = '<span>▲</span>';
+		}
+		else{
+			ms.innerHTML = '<span>▼</span>';
+		}
+	}
+	ebi('moresearch').onclick = function () {
+		expand = !expand;
+		swrite('s_ex', expand);
+		exp_search(expand);
+	};
+
+	ebi('closesearch').onclick = function (e) {
+		ebi('opa_srch').click();
+		folderSearch.value = '';
+		if(ebi('unsearch'))
+			ebi('unsearch').click();
+	}
+
+	var recur_c = ebi('srch_recursivec');
+	recur_c.oninput = ev_search_input;
+	ebi('srch_recursivelbl').innerHTML = L.s_rec;
+
 	function srch_msg(err, txt) {
 		var o = ebi('srch_q');
@@ -6596,5 +7471,10 @@
 	}
 
+	function onlyUnique(value, index, array) {
+		return array.indexOf(value) == index && value.length > 0;
+	}
+
 	function encode_query() {
+		var recursive = ebi('srch_recursivec').checked;
 		var q = '';
 		for (var a = 0; a < sconf.length; a++) {
@@ -6605,6 +7485,21 @@
 					tvs = [];
 
-				if (a == 1)
-					vs = vs.trim().replace(/ +/, 'T');
+				if(k == 'path' && vs.endsWith('/') && !vs.match('"')){
+					vs = vs.slice(0, -1);
+					vs = '"' + vs + (recursive ? '*' : '') + '"';
+					tvs.push(vs);
+					vs = '';
+				}
+
+				var match = vs.match(/\B("(?:[^"]|\\")*[^\\]")\B/g);
+				if(match != null){
+					var immune = match.filter(onlyUnique)
+					for(var i = 0; i < immune.length; i++){
+						if(immune[i].length > 0){
+							tvs.push(immune[i]);
+							vs = vs.replace(immune[i], '')
+						}
+					}
+				}
 
 				while (vs) {
@@ -6625,5 +7520,6 @@
 					}
 					else {
-						var vp = vs.split(/ +(.*)/);
+						// split at spaces
+						var vp = vs.split(/\s+(.*)/);
 						v = vp[0].replace(/\\"/g, '"');
 						vs = vp[1] || '';
@@ -6635,4 +7531,8 @@
 					continue;
 
+				if(q.length > 0)
+					q += ' and ';
+
+				q += ' ( ';
 				for (var c = 0; c < tvs.length; c++) {
 					var tv = tvs[c];
@@ -6640,5 +7540,6 @@
 						break;
 
-					q += ' and ';
+					if(c > 0)
+						q += ' or ';
 
 					if (k == 'adv') {
@@ -6662,5 +7563,5 @@
 							tv = tv.slice(1);
 						}
-						else {
+						else if (!tv.match(' ') && !tv.startsWith('"')){
 							tv = '*' + tv;
 						}
@@ -6669,18 +7570,29 @@
 							tv = tv.slice(0, -1);
 						}
-						else {
+						else if(!tv.match(' ') && !tv.endsWith('"')){
 							tv += '*';
 						}
 
-						if (tv.indexOf(' ') + 1) {
+						if (tv.match(' ') && !tv.match('"')) {
 							tv = '"' + tv + '"';
 						}
 
+						var quote = tv.match('"') ? '"' : '';
+						var match;
+						while ( (match = tv.match(/[^\*\"\s]\*[^\*\"\s]/)) && match[0]){
+							console.log(match)
+							tv = tv.replace(
+								match[0],
+								match[0].replace('*', '*' + quote + ' and ' + k + ' like ' + quote + '*')
+							)
+						}
+
 						q += not + k + ' like ' + tv;
 					}
 				}
+				q += ' ) ';
 			}
 		}
-		ebi('q_raw').value = q.slice(5);
+		ebi('q_raw').value = q.trim();
 	}
 
@@ -6729,5 +7641,5 @@
 		var html = mk_files_header(tagord), seen = {};
 		html.push('<tbody>');
-		html.push('<tr class="srch_hdr"><td>-</td><td><a href="#" id="unsearch"><big style="font-weight:bold">[❌] ' + L.sl_close + '</big></a> -- ' + L.sl_hits.format(res.hits.length) + (res.trunc ? ' -- <a href="#" id="moar">' + L.sl_moar + '</a>' : '') + '</td></tr>');
+		html.push('<tr class="srch_hdr"><td>-</td><td></td><td><a href="#" id="unsearch" class="flink"><big style="font-weight:bold">[❌] ' + L.sl_close + '</big></a> -- ' + L.sl_hits.format(res.hits.length) + (res.trunc ? ' -- <a href="#" id="moar" class="flink">' + L.sl_moar + '</a>' : '') + '</td></tr>');
 
 		for (var a = 0; a < res.hits.length; a++) {
@@ -6747,6 +7659,6 @@
 				ext = '%';
 
-			var links = linksplit(r.rp + '', null, id).join('<span>/</span>'),
-				nodes = ['<tr><td>-</td><td><div>' + links +
+			var links = linksplit(r.rp + '', null, id, true).join('<span>/</span>'),
+				nodes = ['<tr class="' + (img_re.exec(r.rp) ? 'img ' : '') + '"><td>-</td><td></td><td><div>' + links +
 					'</div></td><td sortv="' + sz + '">' + hsz];
 
@@ -6847,5 +7759,5 @@
 		artist = '',
 		title = '',
-		ret = {'hits': [], 'tag_order': ['artist', 'title', '.dur'], 'trunc': false};
+		ret = { 'hits': [], 'tag_order': ['artist', 'title', '.dur'], 'trunc': false };
 
 	for (var a = 0; a < lines.length; a++) {
@@ -6883,5 +7795,5 @@
 			"sz": 100000 + n,
 			"rp": url,
-			"tags": {".dur": dur, "artist": artist, "title": title}
+			"tags": { ".dur": dur, "artist": artist, "title": title }
 		});
 		dur = 1;
@@ -6898,5 +7810,4 @@
 }
 
-
 function aligngriditems() {
 	if (!treectl)
@@ -6922,8 +7833,10 @@
 		val = 'left';
 	} else {
-		val = treectl.hidden ? 'center' : 'space-between';
+		val = 'center';// treectl.hidden ? 'center' : 'space-between';
 	}
 	if (st.justifyContent != val)
 		st.justifyContent = val;
+
+	onwidgetresize();
 }
 onresize100.add(aligngriditems);
@@ -6946,4 +7859,77 @@
 onresize100.add(filecolwidth, true);
 
+function onwidgetresize(){
+	var mumodal = QS('#music.vis');
+	var widget = ebi('widget');
+	var bar = ebi('pctl');
+	var pbarthinpos = ebi('pbarthinpos');
+
+	if(mumodal){
+		var pb_container = ebi('mu_pbb');
+		pb_container.appendChild(ebi('progbar'));
+		pb_container.appendChild(ebi('altprogbar'));
+		ebi('mu_toggles').appendChild(ebi('cyclebtn_loopmode'));
+		ebi('mu_toggles').appendChild(ebi('au_shuf'));
+		ebi('altprogbar').maxWidth = '';
+		ebi('mu_vol').appendChild(ebi('pvolbg'));
+		pbar.onresize();
+		return;
+	}
+
+	var width = widget.offsetWidth;
+
+	var thin = width < 800; //px
+
+	clmod(ebi('pathBar'), 'thin', thin);
+	clmod(ebi('wrap'), 'thin', thin);
+
+	thin = thin || IE;
+
+	var gtc = 'max-content max-content max-content ' + (thin ? '' : '20%') + ' auto max-content max-content max-content';
+	if(!thin && bar.children.length < gtc.split(' ').length){
+		try{
+			//wide
+			ebi('trackname').after(ebi('progbar'));
+			ebi('trackname').after(ebi('altprogbar'));
+			
+			bar.style.paddingTop = '0';
+		}
+		catch (e) {
+			// unnacounted old browser fallback
+			console.log(e);
+			thin = true;
+		}
+	}
+	if(thin && pbarthinpos.children.length < 2){
+		//thin
+		pbarthinpos.appendChild(ebi('progbar'));
+		pbarthinpos.appendChild(ebi('altprogbar'));
+		bar.style.paddingTop = '.3em';
+	}
+	ebi('altprogbar').maxWidth = thin ? '' : '40vw'
+	bar.style.gridTemplateColumns = gtc;
+
+	clmod(widget, 'thin', thin);
+
+	if(ebi('mu_vol').children.length > 0){
+		bar.appendChild(ebi('pvolbg'));
+	}
+	var inmu = ebi('mu_toggles').children.length
+	if(!thin && inmu){
+		ebi('au_tgls').appendChild(ebi('cyclebtn_loopmode'));
+		ebi('au_tgls').appendChild(ebi('au_shuf'));
+	}
+	else if(thin && !inmu){
+		ebi('mu_toggles').appendChild(ebi('cyclebtn_loopmode'));
+		ebi('mu_toggles').appendChild(ebi('au_shuf'));
+	}
+
+	pbar.onresize();
+
+	// keep path scrolled right
+	ebi('path').onresize = keep_right(ebi('path'));
+}
+window.addEventListener('resize', onwidgetresize);
+onwidgetresize();
 
 var treectl = (function () {
@@ -6971,5 +7957,5 @@
 	bcfg_bind(r, 'ireadme', 'ireadme', true);
 	bcfg_bind(r, 'idxh', 'idxh', idxh, setidxh);
-	bcfg_bind(r, 'dyn', 'dyntree', true, onresize);
+	bcfg_bind(r, 'dyn', 'dyntree', false, onresize); // dyntree size offset needs to be considered when drag-resizing before considering to re-enable this default
 	bcfg_bind(r, 'csel', 'csel', dgsel);
 	bcfg_bind(r, 'dsel', 'dsel', !MOBILE);
@@ -6986,5 +7972,5 @@
 	bcfg_bind(r, 'dir1st', 'dir1st', true, resort);
 	setwrap(bcfg_bind(r, 'wtree', 'wraptree', true, setwrap));
-	setwrap(bcfg_bind(r, 'parpane', 'parpane', true, onscroll));
+	setwrap(bcfg_bind(r, 'parpane', 'parpane', !N3DS, onscroll));
 	bcfg_bind(r, 'htree', 'hovertree', false, reload_tree);
 	bcfg_bind(r, 'ask', 'bd_ask', MOBILE && FIREFOX);
@@ -7025,20 +8011,37 @@
 
 		get_tree("", get_evpath(), true);
-		r.show();
+		r.show(nostore);
+
+		clmod(ebi('treeToggleBtn'), 'on', true)
 	}
 
-	r.show = function () {
+	r.show = function (instant) {
 		r.hidden = false;
 		if (!entreed) {
-			ebi('path').style.display = nonav ? 'none' : 'inline-block';
 			return;
 		}
 
-		ebi('path').style.display = 'none';
-		ebi('tree').style.display = 'block';
+		var tree = ebi('tree')
+		tree.style.display = 'block';
+		tree.style.width = 0;
+		clmod(ebi('pathBar'), 'shifted', false);
 		window.addEventListener('scroll', onscroll);
 		window.addEventListener('resize', onresize);
-		onresize();
-		aligngriditems();
+
+		if(!instant){
+			// makes animation work by waiting for next frame
+			setTimeout(function () {
+				onresize();
+				aligngriditems();
+			}, 10);
+		}
+		else {
+			tree.style.transition = 'none';
+			onresize();
+			setTimeout(function(){
+				aligngriditems();
+				tree.style.transition = '';
+			}, 1)
+		}
 	};
 
@@ -7052,14 +8055,32 @@
 		if (!nonav)
 			ebi('path').style.display = '';
+
+		clmod(ebi('treeToggleBtn'), 'on', false)
+	};
+
+	r.toggleTree = function (e, nostore) {
+		if(entreed){
+			r.detree(e, nostore);
+		}
+		else{
+			r.entree(e, nostore);
+		}
 	};
 
 	r.hide = function () {
 		r.hidden = true;
-		ebi('path').style.display = 'none';
-		ebi('tree').style.display = 'none';
+		ebi('tree').style.width = ebi('tree_footer').style.width = '0';
+		ebi('tree_footer').style.display = 'none';
+		ebi('widget').style.marginLeft = '0';
 		ebi('wrap').style.marginLeft = '';
+		clmod(ebi('pathBar'), 'shifted', true);
 		window.removeEventListener('resize', onresize);
 		window.removeEventListener('scroll', onscroll);
 		aligngriditems();
+
+		// wait for animation to finish (.15s), then set display to none if width still 0
+		setTimeout(function () {
+			onresize();
+		}, 170);
 	};
 
@@ -7112,5 +8133,5 @@
 		}
 
-		if (!r.parpane || !r.pdir.length || y >= r.pdir.slice(-1)[0][0] || y <= r.pdir[0][0]) {
+		if (!r.parpane || !r.pdir.length) {
 			clmod(parp, 'off', 1);
 			r.pdirh = null;
@@ -7136,46 +8157,19 @@
 					els[a].onclick = bad_proxy;
 			}
-			y = ebi('treeh').offsetHeight;
-			if (!fixedpos)
-				y += tree.offsetTop - yscroll();
-
-			y = (y - 3) + 'px';
+			y = ebi('treesuperh').offsetHeight + 'px';
 			if (parp.style.top != y)
 				parp.style.top = y;
 		}
-
-		if (wraptop === null)
-			return;
-
-		prev_atop = atop;
-		prev_winh = winh;
-
-		if (fixedpos && atop >= 0) {
-			tree.style.position = 'absolute';
-			tree.style.bottom = '';
-			fixedpos = false;
-		}
-		else if (!fixedpos && atop < 0) {
-			tree.style.position = 'fixed';
-			tree.style.height = 'auto';
-			fixedpos = true;
-		}
-
-		if (fixedpos) {
-			tree.style.top = Math.max(0, parseInt(atop)) + 'px';
-		}
-		else {
-			var top = Math.max(0, wraptop),
-				treeh = winh - atop;
-
-			tree.style.top = top + 'px';
-			tree.style.height = treeh < 10 ? '' : Math.floor(treeh) + 'px';
-		}
 	}
 	timer.add(onscroll2, true);
 
 	function onresize(e) {
-		if (!entreed || r.hidden)
+		if (!entreed || r.hidden){
+			if(ebi('tree').style.width == '0px')
+				ebi('tree').style.display = 'none';
+			ebi('reszbar').style.left = '2px';
+			setcvar('--nav-sz', 0);
 			return;
+		}
 
 		var q = '#tree',
@@ -7188,13 +8182,19 @@
 				break;
 		}
-		nq = Math.max(nq, get_evpath().split('/').length - 2);
+		if(r.dyn)
+			nq = Math.max(nq, get_evpath().split('/').length - 2);
 		var iw = (treesz + Math.max(0, nq)),
 			w = iw + 'em',
-			w2 = (iw + 2) + 'em';
+			w2 = (iw + 1) + 'em';
 
 		setcvar('--nav-sz', w);
 		ebi('tree').style.width = w;
-		ebi('wrap').style.marginLeft = w2;
-		onscroll();
+		ebi('tree_footer').style.width = (iw - 2) + 'em';
+		ebi('tree_footer').style.display = 'block';
+		ebi('wrap').style.marginLeft = w;
+		ebi('reszbar').style.left = w;
+		ebi('widget').style.marginLeft = (iw /1.4) + 'em';
+		onwidgetresize();
+		setTimeout(onscroll, 1);
 	}
 
@@ -7318,4 +8318,8 @@
 			}
 		}
+
+		var dirs = QSA("#treeul li a:nth-child(2)");
+		for (var i = 0; i < dirs.length; i++)
+			drag.mktarget(dirs[i]);
 	};
 
@@ -7352,4 +8356,5 @@
 		for (var a = 0, aa = links.length; a < aa; a++) {
 			links[a].setAttribute('dst', links[a].nextSibling.getAttribute('href'));
+			clmod(links[a], 'ld', false);
 			links[a].onclick = treegrow;
 		}
@@ -7382,14 +8387,19 @@
 		var p = this.offsetParent,
 			pp = p.offsetParent,
-			ppy = pp.offsetTop,
-			y = this.offsetTop + p.offsetTop + ppy - p.scrollTop - pp.scrollTop - (ppy ? document.documentElement.scrollTop : 0);
+			ppy = pp ? pp.offsetTop : 0,
+			y = this.offsetTop + p.offsetTop + ppy - p.scrollTop - (pp ? pp.scrollTop : 0) - (ppy ? document.documentElement.scrollTop : 0);
 
 		this.style.top = y + 'px';
 		this.style.position = 'fixed';
+		this.style.width = 'auto';
+		this.style.minWidth = ebi('tree').offsetWidth + p.offsetLeft - 5 + 'px';
 		mentered = this;
 	}
 
 	function mleave(e) {
+		this.style.top = '';
 		this.style.position = '';
+		this.style.minWidth = '';
+		this.style.width = '';
 		mentered = null;
 	}
@@ -7424,5 +8434,5 @@
 		r.dir_cb = tree_scrollto;
 		thegrid.setvis(true);
-		clmod(this, 'ld', 1);
+		clmod(this.previousSibling, 'ld', 1);
 	}
 
@@ -7535,8 +8545,14 @@
 			clmod(ebi('griden'), 'on', thegrid.en = dgrid);
 
+		var accent = parseColor(sread('paccent'))
+		if(!accent && (res.tcolor || tcolor))
+			setColor(parseColor(res.tcolor ? res.tcolor : tcolor), true);
+
 		srvinf = res.srvinf;
-		if (rtt !== null)
+		volspace = res.volspace;
+		if (rtt !== null){
 			srvinf += (srvinf ? '</span> // <span>rtt: ' : 'rtt: ') + rtt;
-
+			ebi('rtt_latency').innerHTML = 'latency: ' + rtt + 'ms';
+		}
 		var o = ebi('srv_info2');
 		if (o)
@@ -7546,4 +8562,5 @@
 			while (qsr('head>link[rel~="icon"]')) { }
 			document.head.insertAdjacentHTML('beforeend', res.ufavico);
+            ebi('favico_onpage').innerHTML = res.ufavico;
 		}
 
@@ -7583,5 +8600,5 @@
 			lg1 = L.f_empty;
 
-		sandbox(ebi('pro'), sb_lg, sba_lg,'', lg0);
+		sandbox(ebi('pro'), sb_lg, sba_lg, '', lg0);
 		if (dirchg)
 			sandbox(ebi('epi'), sb_lg, sba_lg, '', lg1);
@@ -7611,6 +8628,18 @@
 		}
 
+		if (drag.is_dragging) {
+			fileman.paste(true);
+			drag.is_dragging = false;
+		}
+		drag.initfiles();
+
 		if (can_shr && in_shr && QS('#op_unpost.act'))
 			goto('unpost');
+
+		setBg(res.bg_img);
+
+		if(!override_thm && res.dtheme){
+			settheme.init(res.dtheme)
+		}
 	}
 
@@ -7628,4 +8657,7 @@
 	r.gentab = function (top, res) {
 		showfile.untail();
+		for (var a = 0; a < res.dirs.length; a++)
+			res.dirs[a].cls = 'dir';
+
 		var nodes = res.dirs.concat(res.files),
 			html = mk_files_header(res.taglist),
@@ -7700,7 +8732,8 @@
 					'" hl="' + id + '" name="' + hname + '">-txt-</a>';
 
-			var cl = /\.PARTIAL$/.exec(fname) ? ' class="fade"' : '',
-				ln = ['<tr' + cl + '><td>' + tn.lead + '</td><td><a href="' +
-					top + tn.href + '" id="' + id + '">' + hname +
+			var cl = (/\.PARTIAL$/.exec(fname) ? 'fade ' : '') + (img_re.exec(fname) ? 'img ' : '') + (tn.cls || ''),
+				ln = ['<tr class="' + cl + '"><td>' + tn.lead + '</td><td>' + 
+					get_thumb(tn.href, id, (/\bdir\b/i).test(cl), false) + 
+					'</td><td><a href="' + top + tn.href + '" id="' + id + '" class="flink">' + hname +
 					'</a></td><td sortv="' + tn.sz + '">' + filesizefun(tn.sz)];
 
@@ -7726,4 +8759,9 @@
 		html = html.join('\n');
 		set_files_html(html);
+
+		var ths = QSA('#files .imgcontainer>img');
+		for (var a = 0, aa = ths.length; a < aa; a++)
+			ths[a].onload = th_onload;
+
 		if (r.dlni) {
 			var o = QSA('#files a[id]');
@@ -7805,4 +8843,9 @@
 		vbar.onresize();
 		showfile.addlinks();
+		drag.initfiles();
+			
+		var media = scan_hash(location.hash);
+		if(media && media[0] == 'g')
+			clmod(ebi('wrap'), 'waiting', true);
 		setTimeout(eval_hash, 1);
 	};
@@ -7938,14 +8981,22 @@
 		ev(e);
 		treesz += parseInt(this.getAttribute("step"));
+		r.settreesize(treesz);
+	}
+
+	r.settreesize = function(width){
+		treesz = width;
 		if (!isNum(treesz))
 			treesz = 16;
 
-		treesz = clamp(treesz, 2, 120);
+		treesz = clamp(treesz, 6, 120);
 		swrite('treesz', treesz);
 		onresize();
 	}
 
-	ebi('entree').onclick = r.entree;
-	ebi('detree').onclick = r.detree;
+	var svg_hamburger = svg_box + '<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Menu"><rect id="Rectangle" fill-rule="nonzero" x="0" y="0" width="24" height="24"></rect><line x1="5" y1="7" x2="19" y2="7" id="Path" ' + svg_options + '></line><line x1="5" y1="17" x2="19" y2="17" id="Path" ' + svg_options + '></line><line x1="5" y1="12" x2="19" y2="12" id="Path" ' + svg_options + '></line></g></g></svg>'
+	ebi('treeToggleBtn').innerHTML = svg_hamburger;
+	ebi('treeToggleBtn').setAttribute('tt', L.tt_entree);
+
+	ebi('treeToggleBtn').onclick = r.toggleTree;
 	ebi('visdir').onclick = tree_scrollto;
 	ebi('twig').onclick = scaletree;
@@ -7958,4 +9009,6 @@
 		cs = 'na';
 		r.detree(null, 1);
+		ebi('treeToggleBtn').style.display = ebi('reszbar').style.display = 'none';
+		clmod(ebi('pathBar'), 'shifted', false);
 	}
 
@@ -7975,4 +9028,14 @@
 			mdk = /[?&](k=[^&#]+)/.exec('' + url);
 
+		if(location.hash == '' && QS('#bbox-overlay.visible')){
+			baguetteBox.hide();
+			baguetteBox.resetUserAction();
+			return;
+		}
+		if(baguetteBox.getUserAction()){
+			baguetteBox.resetUserAction();
+			return;
+		}
+
 		if (mdoc && hbase == cbase)
 			return showfile.show(hbase + showfile.sname(url.search), true);
@@ -8014,5 +9077,5 @@
 			clearTimeout(r.t);
 			r.t = setTimeout(r.reset, 300);
-			ebi('wfp').style.opacity = 0.1;
+			//ebi('wfp').style.opacity = 0.1;
 		}
 	};
@@ -8044,5 +9107,6 @@
 	if (konmai < 0) {
 		acct = 'Ted Faro';
-		srvinf = 'FAS Nexus</span> // <span>57.3 EiB free of 127 EiB';
+		ebi('srv_name').innerHTML = 'FAS Nexus';
+		volspace = ['57.3 EiB', '127 EiB', 4512];
 		res.shr_who = 'auth';
 		perms = res.perms = chk;
@@ -8084,22 +9148,44 @@
 		dst = idp_login.replace(/\{dst\}/g, get_evpath());
 
-	ebi('acc_info').innerHTML = '<span id="srv_info2"><span>' + srvinf +
-		'</span></span><span' + aclass + axs + L.access + '</span>' + (acct != '*' ?
-			'<form id="flogout" method="post" enctype="multipart/form-data"><input type="hidden" name="act" value="logout" /><input id="blogout" type="submit" value="' + L.logout + acct + '"></form>' :
-			'<a href="' + dst + '">' + L.login + '</a>');
+	function goHome(){
+		window.location.href = dst;
+	}
+
+	if(acct != '*'){
+		ebi('acc_name').innerHTML = acct;
+		ebi('acc_pfp').innerHTML = acct.substring(0, 1).toUpperCase();
+		ebi('accessType').innerHTML = '<span' + aclass + axs + L.access + '</span>';
+		ebi('blogout').value = L.logout;
+	}
+	else{
+		ebi('blogout').style.display = 'none';
+		ebi('acc_name').innerHTML = L.login;
+		ebi('acc_pfp').innerHTML = fun_tgl && EMOJI ? '👤' : 'acc';
+	}
+	clmod(ebi('acc_pfp'), 'placeholder', acct == '*');
 
-	var o = QSA('#ops>a[data-perm]');
+	var o = QSA('#ops>a');
 	for (var a = 0; a < o.length; a++) {
+		if(!o[a].hasAttribute('data-perm')){
+			clmod(o[a], 'disabled', false);
+			continue;
+		}
 		var display = '';
+		var enabled = true;
 		var needed = o[a].getAttribute('data-perm').split(' ');
 		for (var b = 0; b < needed.length; b++) {
 			if (!has(perms, needed[b])) {
-				display = 'none';
+				enabled = false;
+				if(needed[b] != 'write')
+					display = 'none';
 			}
 		}
+		clmod(o[a], 'disabled', !enabled);
 		o[a].style.display = display;
 	}
+	clmod(ebi('up_quick'), 'disabled', !has(perms, 'write'));
+	ebi('bup_tgl').style.display = has(perms, 'write') ? '' : 'none';
 
-	var o = QSA('#ops>a[data-dep], #u2conf td[data-dep]');
+	var o = QSA('#ops>a[data-dep], #u2conf div[data-dep]');
 	for (var a = 0; a < o.length; a++)
 		o[a].style.display = (
@@ -8118,5 +9204,5 @@
 	var have_write = has(perms, "write"),
 		de = document.documentElement,
-		tds = QSA('#u2conf td');
+		tds = QSA('#u2conf div');
 
 	shr_who = res.shr_who || shr_who;
@@ -8139,6 +9225,7 @@
 
 	u2ts = res.u2ts;
-	if (up2k)
-		up2k.set_fsearch();
+
+	if(up2k)
+		wait_set_fsearch();
 
 	if (res.cfg)
@@ -8149,6 +9236,27 @@
 	widget.setvis();
 	thegrid.setvis();
-	if (!have_read && have_write)
+
+	var up_only = !have_read && have_write;
+	if (up_only){
 		goto('up2k');
+		clmod(document.documentElement, 'noscroll', false);
+	}
+	if(up_only && !up2k_lgcy){
+		up2k_lgcy = !up2k_lgcy;
+		ebi('reloc_up').innerHTML = up2k_lgcy ? '<span>▲</span>' : '<span>▼</span>';
+		clmod(ebi('up2k'), 'unmodal', up2k_lgcy);
+	}
+	if(EMOJI)
+		clmod(ebi('opa_mkd'), 'vis', up_only);
+}
+
+function wait_set_fsearch(){
+	var t = setInterval(function () {
+		if (!up2k || !up2k.set_fsearch)
+			return;
+
+		clearInterval(t);
+		up2k.set_fsearch();
+	}, 50);
 }
 
@@ -8156,5 +9264,5 @@
 function tr2id(tr) {
 	try {
-		return tr.cells[1].querySelector('a[id]').getAttribute('id');
+		return tr.querySelector('.flink').getAttribute('id');
 	}
 	catch (ex) {
@@ -8188,5 +9296,6 @@
 	var html = [
 		'<thead><tr>',
-		'<th name="lead"><span>c</span></th>',
+		'<th name="lead"><span>!</span></th>',
+		'<th name="thumb"><span>th</span></th>',
 		'<th name="href"><span>File Name</span></th>',
 		'<th name="sz" sort="int"><span>Size</span></th>'
@@ -8205,5 +9314,5 @@
 	}
 	html = html.concat([
-		'<th name="ext"><span>T</span></th>',
+		'<th name="ext"><span>Ext</span></th>',
 		'<th name="ts"><span>Date</span></th>',
 		'</tr></thead>',
@@ -8246,9 +9355,4 @@
 	}
 
-	r.uivis = function () {
-		var hcols = ebi('hcols');
-		hcols.previousSibling.style.display = hcols.style.display = ((!thegrid || !thegrid.en) && (hidden.length || MOBILE)) ? 'block' : 'none';
-	};
-
 	r.set_style = function (unhide) {
 		hidden.sort();
@@ -8268,5 +9372,4 @@
 		hcols.innerHTML = html.join('\n');
 		hcols.onclick = hcols_click;
-		r.uivis();
 		r.add_btns();
 
@@ -8327,24 +9430,26 @@
 	};
 
-	if (MOBILE)
-		ebi('hcolsh').onclick = function (e) {
-			ev(e);
-			if (r.picking)
-				return r.unpick();
+	ebi('hcolsh').onclick = function (e) {
+		clmod(this, 'on', !r.picking)
+		if (r.picking)
+			return r.unpick();
 
-			var lbs = QSA('#files>thead th');
-			for (var a = 0; a < lbs.length; a++) {
-				lbs[a].onclick = function (e) {
-					ev(e);
-					if (toast.tag == 'pickhide')
-						toast.hide();
+		var lbs = QSA('#files>thead th');
+		for (var a = 0; a < lbs.length; a++) {
+			lbs[a].onclick = function (e) {
+				ev(e);
+				if (toast.tag == 'pickhide')
+					toast.hide();
 
+				if(e.target.lastChild)
+					r.hide(e.target.lastChild.textContent)
+				else
 					r.hide(e.target.textContent);
-				};
 			};
-			r.picking = true;
-			clmod(ebi('files'), 'hhpick', 1);
-			toast.inf(0, L.cl_hpick, 'pickhide');
 		};
+		r.picking = true;
+		clmod(ebi('files'), 'hhpick', 1);
+		toast.inf(0, L.cl_hpick, 'pickhide');
+	};
 
 	r.unpick = function () {
@@ -8503,5 +9608,5 @@
 
 
-var light, theme, themen;
+var light, theme, themen, override_thm = false;
 var settheme = (function () {
 	var r = {},
@@ -8511,16 +9616,32 @@
 
 	r.ldr = {
-		'4':['🌴'],
-		'5':['🌭', 'padding:0 0 .7em .7em;filter:saturate(3)'],
-		'6':['📞', 'padding:0;filter:brightness(2) sepia(1) saturate(3) hue-rotate(60deg)'],
-		'7':['▲', 'font-size:3em'], //cp437
+		'4': ['🌴'],
+		'5': ['🌭', 'padding:0 0 .7em .7em;filter:saturate(3)'],
+		'6': ['📞', 'padding:0;filter:brightness(2) sepia(1) saturate(3) hue-rotate(60deg)'],
+		'7': ['▲', 'font-size:3em'], //cp437
 	};
 
-	theme = sread('cpp_thm') || 'a';
-	if (!/^[a-x][yz]/.exec(theme))
-		theme = dtheme;
+	r.init = function(t){
+		theme = sread('cpp_thm');
+		if(!theme)
+			theme = 'a';
+		else
+			override_thm = true;
+		if (!/^[a-x][yz]/.exec(theme))
+			theme = dtheme;
+		if(t)
+			theme = t;
 
-	themen = theme.split(/ /)[0];
-	light = !!(theme.indexOf('y') + 1);
+		console.log('theme: ' + theme)
+
+		themen = theme.split(/ /)[0];
+		light = !!(theme.indexOf('y') + 1);
+
+		var m = /[?&]theme=([0-9]+)/.exec(sloc0);
+		if (m)
+			r.go(parseInt(m[1]));
+		else
+			freshen();
+	}
 
 	function freshen() {
@@ -8538,10 +9659,10 @@
 			cb = ebi('themes'),
 			itheme = ax.indexOf(theme[0]) * 2 + (light ? 1 : 0),
-			names = ['classic dark', 'classic light', 'pm-monokai', 'flat light', 'vice', 'hotdog stand', 'hacker', 'hi-con', 'phi95 dark', 'phi95'];
+			names = ['flat dark', 'flat light', 'frutiger dark', 'frutiger light', 'vice', 'hotdog stand', 'hacker', 'hi-con', 'phi95 dark', 'phi95', 'poison', 'wing', ];
 
 		for (var a = 0; a < themes; a++)
 			html.push('<option value="{0}">{0} ┃ {1}</option>'.format(a, names[a] || 'custom'));
 
-		ebi('themes').innerHTML = html.join('');
+		cb.innerHTML = html.join('');
 		cb.value = itheme;
 		cb.onchange = r.onsel;
@@ -8557,5 +9678,8 @@
 
 	r.onsel = function () {
-		r.go(parseInt(ebi('themes').value));
+		if(!override_thm)
+			ebi('override_thm').click();
+		else
+			r.go(parseInt(ebi('themes').value));
 	};
 
@@ -8570,9 +9694,15 @@
 	};
 
-	var m = /[?&]theme=([0-9]+)/.exec(sloc0);
-	if (m)
-		r.go(parseInt(m[1]));
-	else
-		freshen();
+	r.init();
+
+	ebi('override_thm').onclick = function(){
+		clmod(this, 'on', 't');
+		override_thm = !override_thm;
+		if(override_thm)
+			r.onsel();
+		else
+			sdrop('cpp_thm');
+	}
+	clmod(ebi('override_thm'), 'on', override_thm)
 
 	return r;
@@ -8678,10 +9808,10 @@
 			o.textContent = fmt.split('_')[0];
 		}
-		ebi('selzip').textContent = fmt.split('_')[0];
+		ebi('selzip').innerHTML = (fun_tgl ? '📦<br />' : '') + fmt.split('_')[0];
 		ebi('selzip').setAttribute('fmt', arg);
 
 		QS('#zip1 span').textContent = fmt.split('_')[0];
 		ebi('zip1').setAttribute("href",
-			get_evpath() + (dk ? '?k=' + dk + '&': '?') + arg);
+			get_evpath() + (dk ? '?k=' + dk + '&' : '?') + arg);
 
 		if (!have_zip) {
@@ -8743,5 +9873,5 @@
 
 		r.all = [];
-		var links = QSA('#files tbody td:nth-child(2) a:last-child'),
+		var links = QSA('#files .flink'),
 			is_srch = !!ebi('unsearch'),
 			vbase = get_evpath();
@@ -8770,6 +9900,5 @@
 				r.sel.push(item);
 
-			if (!is_srch)
-				links[a].closest('tr').setAttribute('tabindex', '0');
+			links[a].closest('tr').setAttribute('tabindex', '0');
 		}
 	};
@@ -8811,4 +9940,5 @@
 
 		clmod(ebi('wtoggle'), 'sel', r.getsel().length);
+		clmod(ebi('wstack'), 'sel', r.getsel().length);
 		thegrid.loadsel();
 		fileman.render();
@@ -8821,9 +9951,10 @@
 	};
 	r.seltgl = function (e) {
-		ev(e);
+		e.stopPropagation();
+
 		var tr = this.parentNode,
 			id = tr2id(tr);
 
-		if ((treectl.csel || !thegrid.en || thegrid.sel) && e.shiftKey && r.so && id && r.so != id) {
+		if ((treectl.csel || !thegrid.en || thegrid.sel || thegrid.tempsel) && e.shiftKey && r.so && id && r.so != id) {
 			var o1 = -1, o2 = -1;
 			for (a = 0; a < r.all.length; a++) {
@@ -8861,4 +9992,9 @@
 		}
 		r.selui();
+
+		if (thegrid.tempsel && msel.getsel().length == 0){
+			thegrid.tempsel = false;
+			clmod(ebi('gridsel'), 'temp', false);
+		}
 	};
 	r.origin_tr = function (tr) {
@@ -8873,5 +10009,5 @@
 		ev(e);
 		r.so = r.pr = null;
-		var trs = QSA('#files tbody tr');
+		var trs = QSA('#files tbody tr:not(.srch_hdr)');
 		for (var a = 0, aa = trs.length; a < aa; a++)
 			clmod(trs[a], 'sel', fun);
@@ -8924,11 +10060,26 @@
 	};
 	r.render = function () {
-		var tds = QSA('#files tbody td+td+td'),
+		var tds = QSA('#files tbody td+td+td+td'),
 			is_srch = !!ebi('unsearch');
 
-		if (!is_srch)
-			for (var a = 0, aa = tds.length; a < aa; a++)
-				tds[a].onclick = r.seltgl;
+		for (var a = 0, aa = tds.length; a < aa; a++)
+			tds[a].onclick = r.seltgl;
+
+		var links = QSA('#files .flink')
+		for (var a = 0, aa = links.length; a < aa; a++)
+			links[a].onclick = fclick;
+
+		var thumbs = QSA('#files .imgcontainer')
+		for (var a = 0, aa = thumbs.length; a < aa; a++)
+			thumbs[a].onclick = fclick1;
+
+		var imgs = QSA('#files img');
+		for (var a = 0, aa = imgs.length; a < aa; a++) {
+			set_loaded(imgs[a], thegrid.thumbs && imgs[a].complete, true);
+		}
 
+		if(window.baguetteBox != undefined)
+			r.bagit();
+		
 		r.selui(true);
 		arcfmt.render();
@@ -8938,8 +10089,117 @@
 		ebi('selzip').style.display = zipvis;
 		ebi('zip1').style.display = zipvis;
+
+		r.ready = true;
 	}
+
+	r.bagit = function (isrc) {
+		console.log('init image viewer');
+		
+		if (r.bbox)
+			baguetteBox.destroy();
+
+		if(!isrc)
+			isrc = '#files'
+
+		var br = baguetteBox.run(isrc, {
+			noScrollbars: true,
+			duringHide: r.onhide,
+			afterShow: function () {
+				r.bbox_opts.refocus = true;
+			},
+			captions: function (g, idx) {
+				var h = '' + g;
+
+				return '<a download href="' + h +
+					'">' + (idx + 1) + ' / ' + this.length + ' -- ' +
+					esc(uricom_dec(h.split('/').pop())) + '</a>';
+			},
+			onChange: function (i, maxIdx) {
+				if (this[i].imageElement) {
+					sethash('g' + this[i].imageElement.getAttribute('id') + getsort());
+				}
+			}
+		});
+		r.bbox_opts = br[1];
+		r.bbox = true;
+	};
+
 	return r;
 })();
 
+function fclick1(e) {
+	e.preventDefault ? e.preventDefault() : e.returnValue = false;
+
+	// pass copy of click event to list entry (can trigger bbox handler)
+	var td = ebi(this.getAttribute('ref'));
+	td.dispatchEvent(new MouseEvent('click', {
+        bubbles: true,
+        cancelable: true,
+        clientX: e.clientX,
+        clientY: e.clientY,
+		ctrlKey: e.ctrlKey,
+        shiftKey: e.shiftKey,
+        altKey: e.altKey,
+        metaKey: e.metaKey,
+    }));
+}
+
+function fclick(e, dbl) {
+	if (e.altKey){
+		ev(e);
+		return;
+	}
+	var qhref = this.getAttribute('href'),
+		href = qhref.split('?')[0],
+		fid = this.getAttribute('id'),
+		aplay = ebi('a' + fid),
+		atext = ebi('t' + fid),
+		is_txt = atext && !/\.ts$/.test(href) && showfile.getlang(href),
+		is_img = img_re.test(href),
+		is_dir = href.endsWith('/'),
+		is_srch = !!ebi('unsearch'),
+		in_tree = is_dir && treectl.find(this.textContent.slice(0, -1)),
+		have_sel = QS('#files tr.sel'),
+		td = this.closest('td').nextSibling,
+		tr = td.parentNode;
+
+	if (thegrid.en && (thegrid.sel || thegrid.tempsel) && !dbl && !ctrl(e) || treectl.csel && (e.shiftKey || ctrl(e))) {
+		ev(e);
+		msel.seltgl.call(td, e);
+		if (e.shiftKey)
+			return thegrid.loadsel();
+		clmod(this, 'sel', clgot(tr, 'sel'));
+	}
+	else if (in_tree)
+		in_tree.click();
+
+	else if (this.hasAttribute('download'))
+		this.click();
+
+	else if (aplay && (thegrid.vau || !is_img))
+		aplay.click();
+
+	else if (is_dir)
+		treectl.reqls(qhref, true);
+
+	else if (!is_img && is_txt && !has(['md', 'htm', 'html'], is_txt))
+		atext.click();
+
+	else if (!is_img && !is_dir && have_sel)
+		window.open(qhref, '_blank');
+
+	else {
+		if (!dbl)
+			return true;
+
+		setTimeout(function () {
+			thegrid.sel = true;
+		}, 1);
+		thegrid.sel = false;
+		this.click();
+	}
+	ev(e);
+}
+
 
 (function () {
@@ -9180,5 +10440,5 @@
 			clmod(div, 'raw', 1);
 			div.textContent = "--[ " + name + " ]---------\r\n" + md;
-			return toast.warn(10, errmsg + (WebAssembly ? 'failed to load marked.js' : 'your browser is too old'));
+			return toast.warn(3, errmsg + (WebAssembly ? 'failed to load marked.js' : 'your browser is too old'));
 		}
 
@@ -9393,5 +10653,5 @@
 				ao = td.firstChild,
 				href = noq_href(ao),
-				isdir = href.endsWith('/'),
+				isdir = clgot(tr[a], 'dir'),
 				txt = ao.textContent;
 
@@ -9655,4 +10915,32 @@
 };
 
+function scroll_v_to_h (e, o) {
+  ev(e);
+  var delta = e.deltaY || e.deltaX;
+  var maxScrollLeft = o.scrollWidth - o.clientWidth;
+
+  o.scrollLeft = Math.max(0, Math.min(maxScrollLeft, o.scrollLeft + delta));
+}
+function keep_right (o) {
+	o.scrollLeft = o.scrollWidth - o.clientWidth;
+}
+
+ebi('path').addEventListener('wheel', function (e) {
+	scroll_v_to_h(e, this);
+});
+
+ebi('ghead').addEventListener('wheel', function (e) {
+	scroll_v_to_h(e, this);
+});
+
+ebi('treeh').addEventListener('wheel', function (e) {
+	scroll_v_to_h(e, this);
+});
+
+if(FIREFOX && MOBILE){
+	// disable glitchy ::after gradient (scrollhint) (2026-04-26)
+	clmod(ebi('ghead'), 'noa', true);
+	clmod(ebi('treeh'), 'noa', true);
+}
 
 var scroll_y = -1;
@@ -9721,6 +11009,7 @@
 			fun();
 		else
-			modal.confirm(L.f_bigtxt.format(f2f(sz / 1024 / 1024, 1)), fun, function() {
-				modal.confirm(L.f_bigtxt2, tfun, null)});
+			modal.confirm(L.f_bigtxt.format(f2f(sz / 1024 / 1024, 1)), fun, function () {
+				modal.confirm(L.f_bigtxt2, tfun, null)
+			});
 
 		return ev(e);
@@ -9738,10 +11027,7 @@
 
 var rcm = (function () {
-	if (MOBILE)
-		return {enabled: false}
-
 	var r = {};
-	bcfg_bind(r, 'enabled', 'rcm_en', drcm.charAt(0)=='y');
-	bcfg_bind(r, 'double', 'rcm_db', drcm.charAt(1)=='y');
+	bcfg_bind(r, 'enabled', 'rcm_en', drcm.charAt(0) == 'y');
+	bcfg_bind(r, 'double', 'rcm_db', drcm.charAt(1) == 'y');
 
 	var menu = ebi('rcm');
@@ -9758,62 +11044,7 @@
 	var selFile = jcp(nsFile);
 
-	function mktemp(is_dir) {
-		qsr('#rcm_tmp');
-		if (!thegrid.en) {
-			var row = mknod('tr', 'rcm_tmp',
-				'<td>-new-</td><td colspan="' + (QSA("#files thead th").length - 1) + '"><input id="tempname" class="i" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></td>');
-			QS("#files tbody").appendChild(row);
-		}
-		else {
-			var row = mknod('a', 'rcm_tmp',
-				'<span class="dir" style="align-self:end"><input id="tempname" class="dir" type="text" placeholder="' + (is_dir ? 'Folder' : 'File') + ' Name"></span>');
-			if (is_dir)
-				row.className = 'dir';
-			row.style.display = 'flex';
-			QS("#ggrid").appendChild(row);
-		}
-
-		function sendit(name) {
-			name = ('' + name).trim();
-			if (!name)
-				return;
-			var data = new FormData();
-			data.set("act", is_dir ? "mkdir" : "new_md");
-			data.set("name", name);
-
-			var req = new XHR();
-			req.open("POST", get_evpath());
-			req.onload = req.onerror = function() {
-				if (req.status == 405 || req.status == 500)
-					return toast.err(3, "a " + (is_dir ? "folder" : "file") + " with that name already exists.");
-				if (req.status < 200 || req.status > 399)
-					return toast.err(3, "couldn't create " + (is_dir ? "folder" : "file") + ": <br><code>" + esc(req.responseText) + '</code>');
-				treectl.goto();
-			};
-			req.send(data);
-		}
-
-		var input = ebi("tempname");
-		input.onblur = function() {
-			sendit(input.value);
-			// Chrome blurs elements when calling remove for some reason
-			input.onblur = null;
-			row.remove();
-		};
-		input.onkeydown = function(e) {
-			if (e.key == "Enter")
-				sendit(input.value);
-			if (e.key == "Enter" || e.key == "Escape") {
-				input.onblur = null;
-				row.remove();
-				ev(e);
-			}
-		};
-		input.focus();
-	}
-
 	var opts = QSA('#rcm a');
 	for (var i = 0; i < opts.length; i++) {
-		opts[i].onclick = function(e) {
+		opts[i].onclick = function (e) {
 			ev(e);
 			switch(e.target.id.slice(1)) {
@@ -9828,5 +11059,5 @@
 				case 'txt': showfile.show(selFile.name); break;
 				case 'md': location = selFile.path + (has(selFile.path, '?') ? '&v' : '?v'); break;
-				case 'cpl': cliptxt(selFile.url, function() {toast.ok(2, L.clipped)}); break;
+				case 'cpl': fileman.link(); break;
 				case 'dl': ebi('seldl').click(); break;
 				case 'zip': ebi('selzip').click(); break;
@@ -9860,7 +11091,7 @@
 				file = ref && ref.closest('#files tbody tr');
 			}
-			var fa = file && file.children[1].querySelector('a[id]');
+			var fa = file && file.querySelector('.flink');
 			if (fa && fa.id != 'unsearch') {
-				selFile.no_dsel = clgot(file, "sel");
+				selFile.no_dsel = TOUCH || clgot(file, "sel");
 				clmod(file, "sel", true);
 				selFile.elem = file;
@@ -9910,14 +11141,42 @@
 		shr.innerHTML = has_sel ? L.rc_shs : L.rc_shf;
 
-		menu.style.left = x + 5 + 'px';
-		menu.style.top = y + 5 + 'px';
+		clmod(menu, 'large', TOUCH);
+		if (TOUCH && target && !forceopen && !thegrid.sel){
+			thegrid.tempsel = true;
+			clmod(ebi('gridsel'), 'temp', true);
+			return;
+		}
+		var vh = document.documentElement.clientHeight;
+		var vw = document.documentElement.clientWidth;
+
+		var maxh = Math.min(y, vh / 2);
+		menu.style.maxHeight = 'calc(100% - ' + maxh + 'px - 2em)';
+
+		if(y > vh / 2){
+			// low click => menu upwards
+			menu.style.top = '';
+			menu.style.bottom = (vh - y) + 'px';
+		}
+		else{
+			menu.style.top = y + 'px';
+			menu.style.bottom = '';
+		}
+		if(x > vw / 2){
+			// far right click => menu leftwards
+			menu.style.left = '';
+			menu.style.right = (vw - x) + 'px';
+		}
+		else{
+			menu.style.left = x + 'px';
+			menu.style.right = '';
+		}
 		menu.style.display = 'block';
 		menu.focus();
 	}
 
-	r.hide = function(force) {
+	r.hide = function (force) {
 		if (!menu.style.display || (!force && menu.contains(document.activeElement)))
 			return;
-		if (selFile.elem && !selFile.no_dsel) {
+		if (selFile.elem && !selFile.no_dsel && !thegrid.tempsel) {
 			clmod(selFile.elem, "sel", false);
 			msel.selui();
@@ -9927,10 +11186,16 @@
 	}
 
-	ebi('wrap').oncontextmenu = function(e) {
+	var forceopen;
+	r.show = function (e) {
 		if (!r.enabled || e.shiftKey || (r.double && menu.style.display) || /doc=/.exec(location.search)) {
 			r.hide(true);
 			return true;
 		}
+		var gfile = thegrid.en && e.target && e.target.closest('#ggrid > a');
+		var target = gfile || e.target;
+
 		r.hide(true);
+		if (TOUCH)
+			forceopen = !gfile || clgot(gfile, 'sel');
 		if (selFile.elem && !selFile.no_dsel) {
 			clmod(selFile.elem, "sel", false);
@@ -9938,9 +11203,154 @@
 		}
 		ev(e);
-		var gfile = thegrid.en && e.target && e.target.closest('#ggrid > a');
-		show(xscroll() + e.clientX, yscroll() + e.clientY, gfile || e.target, gfile);
+		show(e.clientX, e.clientY, target, gfile);
 		return false;
+	}
+
+	ebi('wrap').oncontextmenu = function (e) {
+		if (!e.target.closest('.normalrcm'))
+			r.show(e);
 	};
-	menu.onblur = function() {setTimeout(r.hide)};
+	menu.onblur = function () { setTimeout(r.hide) };
+
+	return r;
+})();
+
+var drag = (function() {
+	var r = {is_dragging: false, no_warn: false};
+	bcfg_bind(r, "enabled", "den", true, function() {setTimeout(reload_browser, 1)});
+	var current = null, currLink = null;
+
+	r.mktarget = function(elem) {
+		if (!r.enabled) return;
+
+		elem.ondragenter = elem.ondragleave = elem.ondragover = function(e) {
+			if (!r.enabled) return;
+			var elemHref = basenames((elem.tagName == "A" ? elem : elem.querySelector(".flink")).href.split("?")[0])
+			if (current == elem || elemHref == get_evpath() || currLink == elemHref) // Prevent folders being dragged into themselves
+				return;
+			ev(e);
+			clmod(elem, "dtarget", e.type != "dragleave");
+		};
+
+		elem.ondrop = function(e) {
+			if (!r.enabled) return;
+			ev(e);
+			if (e.dataTransfer && e.dataTransfer.getData("text") && e.dataTransfer.getData("text").startsWith(window.location.origin)){
+				currLink = e.dataTransfer.getData("text");
+				console.log("elem.ondrop: " + currLink);
+				fileman.clip = currLink.split("\n");
+
+				fileman.cut();
+				msel.evsel();
+
+				if (fileman.clip.length) {
+					r.is_dragging = true;
+					(elem.tagName == "TR" ? elem.querySelector("a[id]") : elem).click();
+				}
+			}
+			else{
+				// wrong source location
+				return
+			}
+		};
+	}
+
+	r.initfiles = function() {
+		if (!r.enabled) return;
+
+		var files = QSA("#files tbody tr");
+		for (var i = 0; i < files.length; i++) {
+			var f = files[i];
+			f.draggable = true;
+			f.ondragstart = function(e) {
+				try{
+					currLink = basenames(e.target.querySelector(".flink").href.split("?"));
+					current = e.target;
+					r.no_warn = true;
+
+					if(!clgot(current, "sel"))
+					{
+						msel.so = msel.pr = null;
+						var trs = QSA('#files tbody tr');
+						for (var a = 0, aa = trs.length; a < aa; a++)
+							clmod(trs[a], 'sel', false);
+						clmod(current, "sel", true);
+						msel.selui();
+					}
+					r.setDataForTransfer(e);
+				}
+				catch(ex){
+					console.log(e.target)
+					console.log(ex)
+				}
+			};
+			f.ondragend = function() {
+				r.no_warn = false;
+			};
+
+			var links = f.querySelectorAll("a");
+			for (var j = 0; j < links.length; j++)
+				links[j].draggable = false;
+
+			if (clgot(f, 'dir'))
+				r.mktarget(f);
+		}
+	};
+
+	r.initgrid = function() {
+		if (!r.enabled) return;
+
+		var files = QSA("#ggrid a");
+		for (var i = 0; i < files.length; i++) {
+			var f = files[i];
+			f.draggable = true;
+			f.ondragstart = function(e) {
+				try{
+					var a = ebi(e.target.getAttribute("ref"));
+					current = a.closest("tr");
+					currLink = basenames(a.href.split("?"));
+					r.no_warn = true;
+
+					if(!clgot(current, "sel"))
+					{
+						msel.so = msel.pr = null;
+						var trs = QSA('#files tbody tr');
+						for (var a = 0, aa = trs.length; a < aa; a++)
+							clmod(trs[a], 'sel', false);
+						clmod(current, "sel", true);
+						msel.selui();
+					}
+					r.setDataForTransfer(e);
+				}
+				catch(ex){
+					console.log(e.target)
+					console.log(ex)
+				}
+			}
+			f.ondragend = function() {
+				r.no_warn = false;
+			};
+
+			if (clgot(f, 'dir'))
+				r.mktarget(f);
+		}
+	};
+	r.setDataForTransfer = function(e){
+		var sel = msel.getsel(),
+			vps = [];
+
+		for (var a = 0; a < sel.length; a++) {
+			var link = sel[a].vp;
+			if(!link.startsWith(window.location.origin))
+				link = window.location.origin + link;
+			vps.push(link);
+		}
+
+		var payload = vps.join('\n')
+		e.dataTransfer.setData("text", payload);
+		e.dataTransfer.setData("text/plain", payload);
+		e.dataTransfer.setData("text/uri-list", payload);
+		e.dataTransfer.setData("application/copyparty", true);
+	}
 
 	return r;
@@ -9979,10 +11389,8 @@
 
 	var parts = get_evpath().split('/'),
-		rm = ebi('entree'),
 		ftab = ebi('files'),
 		link = '', o;
 
-	while (rm.nextSibling)
-		rm.parentNode.removeChild(rm.nextSibling);
+	ebi('path').innerHTML = "";
 
 	for (var a = 0; a < parts.length - 1; a++) {
@@ -9992,9 +11400,14 @@
 		o = mknod('a');
 		o.setAttribute('href', link2);
-		o.textContent = uricom_dec(parts[a]) || '/';
-		ebi('path').appendChild(mknod('i'));
+		o.textContent = uricom_dec(parts[a]) || (fun_tgl && EMOJI ? '🏠' : 'home');
 		ebi('path').appendChild(o);
+		ebi('path').appendChild(mknod('i'));
+		drag.mktarget(o);
 	}
 
+	setTimeout(function () {
+		ebi('path').scrollLeft = ebi('path').scrollWidth - ebi('path').clientWidth;
+	}, 100);
+
 	reload_mp();
 	try { showsort(ftab); } catch (ex) { }
@@ -10010,6 +11423,6 @@
 		clmod(ebi(ns[a]), 'hidden', ebi('unsearch'));
 
-	if (up2k)
-		up2k.set_fsearch();
+	if(up2k)
+		wait_set_fsearch();
 
 	thegrid.setdirty();
@@ -10017,5 +11430,5 @@
 }
 
-(function() {
+(function () {
 	var is_selma = false;
 	var dragging = false;
@@ -10026,5 +11439,5 @@
 	var ttimer = null;
 
-	var lpdelay = 250; 
+	var lpdelay = 250;
 	var mvthresh = 44;
 
@@ -10033,10 +11446,10 @@
 		ebi('gfiles').style.removeProperty('pointer-events')
 		ebi('wrap').style.removeProperty('user-select')
-		
+
 		if (selbox) {
 			console.log(selbox)
 			window.getSelection().removeAllRanges();
 		}
-		
+
 		is_selma = false;
 		dragging = false;
@@ -10059,4 +11472,7 @@
 				var tr = ehidden.closest('tr');
 				if (tr) clmod(tr, 'sel', m);
+
+				var chk = el.getElementsByTagName('input')[0];
+				if(chk) chk.checked = m;
 			}
 		}
@@ -10068,8 +11484,24 @@
 	}
 
+	var resizing = false;
 	function sel_start(e) {
-		if (e.button !== 0 && e.type !== 'touchstart') return;
-		if (!thegrid.en || !treectl.dsel) return;
-		if (e.target.closest('#widget,#ops,.opview,.doc')) return;
+		try{
+			if(e.target.id == "reszbar"){
+				if(sread('entreed') != "tree"){
+					treectl.entree();
+					return;
+				}
+				resizing = true;
+				clmod(ebi('tree'), 'resz', true);
+				return;
+			}
+			if (e.button !== 0 && e.type !== 'touchstart') return;
+			if (!thegrid.en || !treectl.dsel) return;
+			if (e.target.closest('#widget,#ops,.opview,.doc,#ggrid>a,.modal,.normalrcm,#tree')) return;
+		}
+		catch(ex){
+			console.log(ex);
+			return;
+		}
 
 		if (e.target.closest('#gfiles'))
@@ -10081,7 +11513,7 @@
 		is_selma = true;
 		ttimer = null;
-		
+
 		if (e.type === 'touchstart') {
-			ttimer = setTimeout(function() {
+			ttimer = setTimeout(function () {
 				ttimer = null;
 				start_drag();
@@ -10089,5 +11521,5 @@
 		}
 	}
-	
+
 	function start_drag() {
 		if (dragging) return;
@@ -10100,6 +11532,11 @@
 		ebi('gfiles').style.pointerEvents = 'none';
 	}
-	
+
 	function sel_move(e) {
+		if(resizing){
+			var em2px = parseFloat(getComputedStyle(document.body).fontSize);
+			treectl.settreesize(getpp(e).x / em2px);
+			return;
+		}
 		if (!is_selma) return;
 		var pos = getpp(e);
@@ -10117,5 +11554,5 @@
 			if (e.target instanceof Element)
 				fwrap = e.target.closest('#wrap');
-			if (fwrap) 
+			if (fwrap)
 				fwrap.style.userSelect = 'none';
 			else return;
@@ -10136,4 +11573,8 @@
 
 	function sel_end(e) {
+		if(resizing){
+			resizing = false;
+			clmod(ebi('tree'), 'resz', false);
+		}
 		clearTimeout(ttimer);
 		if (dragging && selbox) {
@@ -10159,5 +11600,5 @@
 		window.addEventListener('touchend', sel_end, { passive: true });
 
-		window.addEventListener('dragstart', function(e) {
+		window.addEventListener('dragstart', function (e) {
 			if (treectl.dsel && (is_selma || dragging)) {
 				if (!QS('body.bbox-open'))
@@ -10166,10 +11607,10 @@
 		});
 	}
-	
+
 	dsel_init();
 })();
 
 
-var mpss = (function() {
+var mpss = (function () {
 	var r = {}, config, ssint, npaint = 0;
 
@@ -10209,5 +11650,5 @@
 		var gain = afilt.ssg.gain;
 		var duration = ae.duration || 0;
-	
+
 		var slimit = duration * (config.sthresh / 100);
 		var elimit = duration * (1 - (config.etresh / 100));
@@ -10258,3 +11699,41 @@
 treectl.hydrate();
 
+// https://stackoverflow.com/questions/49084718/how-exactly-add-service-worker-allowed-to-register-service-worker-scope-in-upp
+if(PWA){
+	import_mjs('sw');
+	window.addEventListener('load', function() {
+		try {
+			var registration = navigator.serviceWorker.register("/.cpr/w/sw.js", {
+				scope: "/",
+			});
+			if (registration.installing) {
+				console.log("Service worker installing");
+			} else if (registration.waiting) {
+				console.log("Service worker installed");
+			} else if (registration.active) {
+				console.log("Service worker active");
+			}
+		} catch (error) {
+			console.error("Registration failed with " + error);
+		}
+	});
+}
+else if (!PWA && navigator.serviceWorker && caches){
+	var installPrompt = null;
+	window.addEventListener("beforeinstallprompt", function(e) {
+		e.preventDefault();
+		installPrompt = e;
+		ebi('inst').removeAttribute("hidden");
+	});
+	ebi('inst').onclick = function(){
+		if (!installPrompt) {
+			return;
+		}
+		var result = installPrompt.prompt();
+		console.log("Install prompt was: " + result.outcome);
+		installPrompt = null;
+		ebi('inst').setAttribute("hidden", "");
+	}
+}
+
 J_BRW = 2;
diff -NarU2 a/copyparty/web/config.css b/copyparty/web/config.css
--- a/copyparty/web/config.css	1970-01-01 00:00:00.000000000 +0000
+++ b/copyparty/web/config.css	2026-07-28 23:09:10.623480436 +0000
@@ -0,0 +1,334 @@
+
+html{
+    font-family: var(--font-main);
+}
+body{
+    padding-top: 3em;
+    margin: 0;
+}
+
+#disclaimer{
+    background-color: rgb(240, 38, 28);
+    color: black;
+    padding: .5em;
+    text-align: center;
+}
+
+button{
+    margin: .1em;
+    font-size: 1em;
+    cursor: pointer;
+}
+button:not(.delBtn){
+    min-height: 2.2em;
+    min-width: 2.2em;
+    margin: .3em;
+}
+#copyBtn{
+    position: fixed;
+    font-size: 1.5em;
+    bottom: .6em;
+    right: .6em;
+    z-index: 10;
+    box-shadow: 1px 15px 20px #0009;
+}
+button,
+.abtn{
+    background: var(--a);
+    border: var(--bg) solid 1px;
+    border-radius: 7px;
+    color: var(--bg);
+    padding: 0 .5em;
+}
+input{
+    font-size: 1em;
+    margin: .2em;
+    outline: none !important;
+    border-color: var(--a-gray);
+    border-width: 0 0 1px 0;
+    line-height: 2em;
+    border-radius: .3em;
+    max-width: calc(40vw - 2em);
+    padding-left: .5em;
+}
+input:focus-visible{
+    border-color: var(--a);
+}
+input[type=number]{
+    width: 5em;
+}
+input[type=checkbox]{
+    width: 1.25em;
+    height: 1.25em;
+}
+input[type="checkbox"] + label {
+    vertical-align: top;
+    margin-top: .1em;
+    display: inline-block;
+}
+h2{
+    background: var(--bg-u2);
+    padding: .5em;
+    font-size: 1.2em;
+    text-align: center;
+    border-radius: 5px;
+    border: var(--a) solid 1px;
+    margin-bottom: .3em;
+}
+
+#header{
+    display: flex;
+    height: 3em;
+    color: var(--bg);
+    background: var(--a);
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 10;
+}
+#header>*{
+    display: inline-block;
+    font-size: 1.1em;
+    margin: .2em;
+    padding: .3em;
+}
+#header h1{
+    font-size: 1.2em;
+    margin-right: auto;
+}
+#header a{
+    padding: .4em;
+}
+a{
+    border-radius: 5px;
+}
+a:not(:hover){
+    text-decoration: none;
+}
+.abtn:hover,
+button:hover{
+    background: color-mix(in srgb, var(--a-dark), var(--a));
+}
+a:hover{
+    background: color-mix(in srgb, var(--a-dark), transparent);
+}
+
+#wrap{
+    padding-bottom: 100px;
+    max-width: 600px;
+    margin: auto;
+}
+.autocomplete:focus{
+    position: fixed;
+    top: .2em;
+    z-index: 50;
+    width: 30vw;
+}
+.autocomplete-list{
+    max-height: calc(100% - 5em);
+    max-width: 600px;
+    overflow-y: auto;
+    position: fixed;
+    top: 3.5em;
+    z-index: 50;
+    background: var(--bg);
+    border: currentColor solid 1px;
+    border-color: var(--bg-u5);
+    border-radius: 7px;
+    box-shadow: 0 5px 10px color-mix(in srgb, currentColor 30%, transparent);
+}
+#perm-list>div,
+#volumes>div,
+#flags>div,
+.autocomplete-list>div{
+    position: relative;
+    margin: .4em;
+    border: currentColor solid 1px;
+    border-color: var(--bg-u5);
+    border-radius: 5px;
+    padding: .2em .2em .2em .5em;
+}
+.autocomplete-list>div{
+    cursor: pointer;
+}
+.autocomplete-active,
+.autocomplete-list>div:hover{
+    background: #8888;
+    border-color: var(--a-dark);
+}
+#flags a,
+strong {
+    font-weight: bold;
+    color: var(--a);
+    margin-right: .5em;
+}
+#flags a {
+    padding: .6em 0;
+}
+h3>input{
+    display: block;
+}
+
+#users{
+    padding: 0 .2em;
+}
+#uopt>*{
+    display: inline-block;
+    margin-bottom: .5em;
+    margin-right: .5em;
+    vertical-align: baseline;
+}
+#uopt>input[type=checkbox]{
+    margin-right: auto;
+    margin-bottom: 0;
+    vertical-align: center;
+}
+.uinp {
+    margin-top: 1em;
+    display: inline-block;
+}
+.inplbl{
+    position: absolute;
+    font-size: .8em;
+    margin-left: .3em;
+    opacity: .7;
+}
+.inlinePrompt{
+    display: inline-block;
+    margin-right: 5px;
+    min-width: 170px;
+}
+#users form{
+    border: transparent solid 1px;
+    border-radius: 5px;
+}
+.dhandle{
+    cursor: move;
+    display: inline-block;
+    margin: auto 0;
+    padding: .5em;
+}
+#users form:has(.dhandle:hover){
+    background: var(--bg-u5);
+}
+
+#flags>div{
+    cursor: default;
+    max-width: max-content;
+}
+strong,
+#flags a{
+    min-width: 100px;
+    display: inline-block;
+}
+.delBtn{
+    padding: .05em .1em;
+    line-height: .8em;
+    vertical-align: bottom;
+    font-size: 2.5em;
+    min-width: .9em;
+    font-family: initial;
+}
+.binary input{
+    opacity: 0;
+}
+
+#volumes h3{
+    display: inline-block;
+    font-size: .9em;
+    margin: 1em 0 0 0;
+}
+#volumes .h_vcfg{
+    display: block;
+}
+#volumes>div>.delBtn{
+    position: absolute;
+    top: 2px;
+    right: 2px;
+}
+#volumes p{
+    margin: 5px 0;
+}
+
+#perms-modal{
+    display: none;
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 20;
+
+    padding: 20px;
+    background-color: #8885;
+}
+#perms-content{
+    max-width: 500px;
+    margin: auto;
+    overflow-y: auto;
+    background: var(--bg);
+    border: var(--bg-u5) solid 1px;
+    border-radius: 5px;
+    position: relative;
+    padding: 10px;
+    max-height: calc(100% - 40px);
+}
+#perms-content p,
+#perms-content h2{
+    margin-top: 1px;
+    font-weight: bold;
+}
+#perms-modal .delBtn{
+    /* display: none; */
+    position: absolute;
+    top: 12px;
+    right: 12px;
+}
+#m_accept{
+    display: none;
+    float: right;
+}
+.vis{
+    display: block !important;
+}
+#m_closeArea{
+    position: absolute;
+    inset: 0;
+}
+#perms-content label{
+    margin-right: 15px;
+}
+
+
+/* mobile layout */
+@media (max-width: 650px){
+    html{
+        font-size: 1.2em;
+    }
+    #wrap{
+        margin: 0 1em;
+    }
+    .autocomplete-list{
+        max-width: calc(100% - 2.9em);
+        left: 1.5em;
+    }
+    strong{
+        padding: .6em 0;
+    }
+    .autocomplete:focus{
+        left: 10em;
+        width: calc(100% - 13em);
+    }
+    .dhandle{
+        display: none;
+    }
+}
+@media (min-width: 950px){
+    .autocomplete:focus{
+        width: 585px;
+        left: calc(50% - 300px);
+        right: auto;
+        max-width: none;
+    }
+}
diff -NarU2 a/copyparty/web/config.html b/copyparty/web/config.html
--- a/copyparty/web/config.html	1970-01-01 00:00:00.000000000 +0000
+++ b/copyparty/web/config.html	2026-07-28 23:09:10.623565066 +0000
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html lang="en" id="ht_shr">
+
+<head>
+	<meta charset="utf-8">
+	<title>{{ s_doctitle }}</title>
+	<meta http-equiv="X-UA-Compatible" content="IE=edge">
+	<meta name="viewport" content="width=device-width, initial-scale=0.8">
+	<meta name="robots" content="noindex, nofollow">
+	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
+	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/config.css?_={{ ts }}">
+	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
+{{ html_head }}
+</head>
+
+<video id="bg_vid" autoplay muted loop playsinline></video>
+
+<body>
+    <div id="header">
+        <a href="/?h">⬅️</a>
+        <h1>guest list</h1>
+        <a href="https://github.com/9001/copyparty#quickstart">ℹ️</a>
+    </div>
+    <button id="copyBtn" class="abtn" tt="export config / copy to clipboard">📋</button>
+    <div id="disclaimer">
+        ⚠️ this page is a preview feature! this is only a config builder and doesn't use your actual config atm. feedback is welcome in the copyparty discord. ⚠️
+    </div>
+	<div id="wrap" class="main">
+
+        <div tt="feel free to add anything here. this will be the at the start of your run command one-to-one">
+            <p class="inlinePrompt">run prefix</p>
+            <input id="rpre" type="text" placeholder="python -m copyparty"/>
+        </div>
+
+        <div>
+            <p class="inlinePrompt">server name</p>
+            <input data-flag="--name" type="text" placeholder="copyparty"/>
+        </div>
+
+        <div>
+            <p class="inlinePrompt">port</p>
+            <input data-flag="-p" type="text" placeholder="3923"/>
+        </div>
+
+        <div>
+            <h2>global settings</h2>
+            <div id="flags"></div>
+            <p class="inlinePrompt">+ add launch argument</p>
+            <input id="flagSearch" class="autocomplete" type="text" placeholder="🔎 arguments"/>
+        </div>
+
+        <h2>shared volumes</h2>
+        <div id="volumes"></div>
+        <button id="addVol">+ add volume</button>
+
+        <h2>users</h2>
+        <div id="uopt">
+            <input type="checkbox" id="unames" data-flag="--usernames"/>
+            <label for="unames">require usernames</label>
+
+            <input type="checkbox" id="uchpw" data-flag="--chpw"/>
+            <label for="uchpw">allow password change</label>
+
+            <div>
+                <label for="uminpw" class="inplbl">min pw length</label>
+                <input type="number" class="uinp" id="uminpw" data-flag="--chpw-len" placeholder="8"/>
+            </div>
+        </div>
+        <div id="users"></div>
+        <button id="addUser">+ add user</button>
+
+        <h2>groups</h2>
+        <div id="groups"></div>
+        <button id="addGrp">+ add group</button>
+
+        <h2>display defaults</h2>
+        <div id="dopt">
+            <input type="checkbox" id="gview" data-flag="--grid"/>
+            <label for="gview" tt="--grid: enable or disable the grid by default. can be set per folder via volflag: grid">grid view</label>
+
+            <div tt="--theme: 0=flat dark, 1=flat light... look at the file browser's settings dialog for reference (can be set per volume with volflag: theme)">
+                <label for="dtheme" class="inplbl">default theme no.</label>
+                <input type="number" class="uinp" id="dtheme" data-flag="--theme" placeholder="0-11"/>
+            </div>
+
+            <div tt="--tcolor: default theme color. users can change this in the settings. this can be set per volume with volflag: tcolor">
+                <label for="tcolor" class="inplbl">accent color</label>
+                <input type="text" class="uinp" id="tcolor" data-flag="--tcolor" placeholder="fc5 / ffcc55"/>
+                <input type="color" class="uinp" id="tcolor+" data-flag="--tcolor"/>
+            </div>
+
+            <div style="display: inline-block;" tt="--bg-img: the url may not contain any commas or spaces. supports relative paths. also supports video. (can be set per volume with volflag: bg_img)">
+                <label for="bg-url" class="inplbl">background image url</label>
+                <input type="text" class="uinp" id="bg-url" data-flag="--bg-img" placeholder="/folder/image.jpg"/>
+            </div>
+            <div style="display: inline-block;">
+                <label for="bg-opa" class="inplbl">opacity</label>
+                <input type="number" class="uinp" id="bg-opa" data-prefix="opacity=" data-flag="--bg-img+" placeholder="0-1"/>
+            </div>
+            <div style="display: inline-block;" tt="it's recommended to blur the image itself instead of using this setting for performance reasons">
+                <label for="bg-blur" class="inplbl">blur in px</label>
+                <input type="number" class="uinp" id="bg-blur" data-prefix="blur=" data-flag="--bg-img++" placeholder="0-50"/>
+            </div>
+        </div>
+
+        <div>
+            <h2>database options</h2>
+            <input type="checkbox" name="e2d" id="e2dsa" data-flag="-e2dsa"/>
+            <label for="e2dsa">enable file search</label>
+            <br/>
+            <input type="checkbox" id="e2ts" data-flag="-e2ts"/>
+            <label for="e2ts">metadata indexing (file properties become searchable)</label>
+            <p>you can find more related options and explanations by searching for "e2" in the global settings</p>
+        </div>
+
+        <h2>save / load</h2>
+        <p>your changes are saved automatically</p>
+        <p>saving / loading the real server config is currently not implemented, but you can import an existing config command below</p>
+        <input id="importTxt" placeholder="paste command here" tt="format is more strict than normal. supports importing configs generated on this page."/>
+        <button id="importBtn">import</button>
+        <br/>
+        <button id="resetBtn">reset</button>
+
+    </div>
+    <div id="perms-modal">
+        <div id="m_closeArea"></div>
+        <div id="perms-content">
+            <h2 id="h_perms">permissions for volume</h2>
+            <div id="perm-list"></div>
+            <button id="m_close" class="delBtn">×</button>
+            <button id="m_accept">OK</button>
+        </div>
+    </div>
+    
+	<script nonce="{{ js_nonce }}">
+        var SR="{{ r }}",
+            lang="{{ lang }}",
+			bg_img = "{{ bg_img }}",
+			JS_NONCE = "{{ js_nonce }}",
+            dfavico="{{ favico }}";
+
+        var STG = window.localStorage;
+        document.documentElement.className = (STG && STG.cpp_thm) || "{{ this.args.theme }}";
+    </script>
+    <script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/util.js?_={{ ts }}"></script>
+    <script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/config.js?_={{ ts }}"></script>
+    {%- if js %}
+    <script nonce="{{ js_nonce }}" src="{{ js }}_={{ ts }}"></script>
+    {%- endif %}
+    <script nonce="{{ js_nonce }}">
+        Date.now();function jsldp(a,b){2!=window[a]&&alert("FATAL ERROR: cannot load "+b+".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R")}
+        jsldp("J_UTL","util");
+        jsldp("J_CFG","config");
+    </script>
+</body>
+</html>
+
diff -NarU2 a/copyparty/web/config.js b/copyparty/web/config.js
--- a/copyparty/web/config.js	1970-01-01 00:00:00.000000000 +0000
+++ b/copyparty/web/config.js	2026-07-28 23:09:10.623907873 +0000
@@ -0,0 +1,1794 @@
+/* ToDo: 
+get searchable commands on demand by searching via xhr, like file search,
+OR hand over this list generated from within httpcli.py or something
+(remove the hard-coded arg lists in this file afterwards)
+
+read current config and hand over from httpcli.py on page load or on button press
+
+import from command string(?)
+
+export: better indication / user guidance
+
+add config for user groups
+
+indicate that users without pw are usable for external identity providers, possibly linking it to the setting that enables idps
+
+require usernames checkbox setting
+
+finally, remove the disclaimer banner
+*/
+
+"use strict";
+
+var J_CFG = 1;
+
+class Argument {
+    constructor(cmd, help) {
+        this.cmd = cmd;
+        this.help = help;
+    }
+}
+
+/* FLAGS update instructions
+
+to update manually from the documentation:
+copy what makes sense from the cli help page
+https://copyparty.eu/cli/
+
+open VS code search
+filter for file: flags*
+
+replace
+^[a-z].*
+with nothing
+
+replace (careful there are spaces after the \n that you need to copy too)
+\n                   
+with nothing
+
+replace (optional)
+\n\n
+with
+\n
+(twice)
+
+replace
+\\
+with
+\\\\
+
+replace
+\$
+with
+\\\$
+
+replace
+"
+with
+\\"
+
+replace
+^\s*(-\S+).*?\s{2,}(.*)$
+with
+  new Argument("$1", "$2"),
+
+*/
+
+var flags = [
+    new Argument("-c", "REPEATABLE: add config file (default: [])"),
+    new Argument("-nc", "max num clients (default: 1024)"),
+    new Argument("-a", "REPEATABLE: add account, USER:PASS; example [ed:wark] (default: None)"),
+    new Argument("-v", "REPEATABLE: add volume, SRC:DST:FLAG; examples [.::r], [/mnt/nas/music:/music:r:aed], see --help-accounts (default: None)"),
+    new Argument("--grp", "REPEATABLE: add group, NAME:USER1,USER2,...; example [admins:ed,foo,bar] (default: None)"),
+    new Argument("--usernames", "require username and password for login; default is just password (default: False)"),
+    new Argument("--chdir", "change working-directory to PATH before mapping volumes (default: None)"),
+    new Argument("-ed", "enable the ?dots url parameter / client option which allows clients to see dotfiles / hidden files (volflag=dots) (default: False)"),
+    new Argument("--urlform", "how to handle url-form POSTs; see --help-urlform (default: print,xm)"),
+    new Argument("--wintitle", "server terminal title, for example [\$ip-10.1.2.] or [\$ip-] (default: cpp @ \$pub)"),
+    new Argument("--name", "server name (displayed topleft in browser and in mDNS) (default: hostname)"),
+    new Argument("--name-url", "URL for server name hyperlink (displayed topleft in browser) (default: None)"),
+    new Argument("--site", "public URL to assume when creating links; example: [https://example.com/] (default: )"),
+    new Argument("--env-expand", "expand environment-variables in config-files? [0]=no, [1]=\$VAR (old scary syntax), [2]=\${VAR} (new recommended syntax); default is new-syntax with panic if old-syntax is seen (default: -1)"),
+    new Argument("--mime", "REPEATABLE: map file EXTension to MIMEtype, for example [jpg=image/jpeg] (default: None)"),
+    new Argument("--mimes", "list default mimetype mapping and exit (default: False)"),
+    new Argument("--rmagic", "do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic) (default: False)"),
+    new Argument("-j", "num cpu-cores for uploads/downloads (0=all); keeping the default is almost always best (default: 1)"),
+    new Argument("--reload-sig", "reload server config when unix-signal S is received; examples: [SIGUSR1], [USR1], [10] (default: USR1)"),
+    new Argument("--vc-url", "URL to check for vulnerable versions (default-disabled) (default: )"),
+    new Argument("--vc-age", "how many hours to wait between vulnerability checks (default: 3)"),
+    new Argument("--vc-sev", "minimum severity to care about; one of these: low medium high critical (default: low)"),
+    new Argument("--vc-exit", "panic and exit if current version is vulnerable (default: False)"),
+    new Argument("--license", "show licenses and exit (default: False)"),
+    new Argument("--version", "show versions and exit (default: False)"),
+    new Argument("--versionb", "show version and exit (default: False)"),
+    new Argument("-i", "IPs and/or unix-sockets to listen on (comma-separated list; see --help-bind). Default: all IPv4 and IPv6 (default: ::)"),
+    new Argument("-p", "ports to listen on (comma/range); ignored for unix-sockets (default: 3923)"),
+    new Argument("--ll", "include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients) (default: False)"),
+    new Argument("--rproxy", "which ip to associate clients with; [0]=tcp, [1]=origin (first x-fwd, unsafe), [-1]=closest-proxy, [-2]=second-hop, [-3]=third-hop (default: 9999999)"),
+    new Argument("--xff-hdr", "if reverse-proxied, which http header to read the client's real ip from (default: x-forwarded-for)"),
+    new Argument("--xf-host", "if reverse-proxied, which http header to read the correct Host value from; this header must contain the server's external domain name (default: x-forwarded-host)"),
+    new Argument("--xf-proto", "if reverse-proxied, which http header to read the correct protocol value from; this header must contain either 'http' or 'https' (default: x-forwarded-proto)"),
+    new Argument("--xf-proto-fb", "protocol to assume if the X-Forwarded-Proto header (--xf-proto) is not provided by the reverseproxy; either 'http' or 'https' (default: )"),
+    new Argument("--xff-src", "list of trusted reverse-proxy CIDRs (comma-separated); only accept the real-ip header (--xff-hdr) and IdP headers if the incoming connection is from an IP within either of these subnets. Specify [lan] to allow all LAN / private / non-internet IPs. Can be disabled with [any] if you are behind cloudflare (or similar) and are using --xff-hdr=cf-connecting-ip (or similar) (default: 127.0.0.0/8, ::1/128)"),
+    new Argument("--ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] └─for performance and security, this only looks at the TCP/Network-level IP, and will NOT work behind a reverseproxy  (default: )"),
+    new Argument("--ipar", "only accept connections from IP-addresses inside CIDR (comma-separated). └─this is reverseproxy-compatible; reads client-IP from 'X-Forwarded-For' if possible, with TCP/Network IP as  fallback (default: )"),
+    new Argument("--rp-loc", "if reverse-proxying on a location instead of a dedicated domain/subdomain, provide the base location here; example: [/foo/bar] (default: )"),
+    new Argument("--cachectl", "default-value of the 'Cache-Control' response-header (controls caching in webbrowsers). Default prevents repeated downloading of the same file unless necessary (browser will ask copyparty if the file has changed). Examples: [max-age=604869] will cache for 7 days, [no-store, max-age=0] will always redownload. (volflag=cachectl) (default: no-cache)"),
+    new Argument("--http-vary", "value of the 'Vary' response-header; a hint for caching proxies (default: Origin, PW, Cookie)"),
+    new Argument("--http-no-tcp", "do not listen on TCP/IP for http/https; only listen on unix-domain-sockets (default: False)"),
+    new Argument("--freebind", "allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind) (default: False)"),
+    new Argument("--wr-h-eps", "write list of listening-on ip:port to textfile at PATH when http-servers have started (default: )"),
+    new Argument("--wr-h-aon", "write list of accessible-on ip:port to textfile at PATH when http-servers have started (default: )"),
+    new Argument("--s-thead", "socket timeout (read request header) (default: 120)"),
+    new Argument("--s-tbody", "socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2% speed boost (default: 128.0)"),
+    new Argument("--s-rd-sz", "socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than --iobuf) (default: 262144)"),
+    new Argument("--s-wr-sz", "socket write size in bytes (default: 262144)"),
+    new Argument("--s-wr-slp", "debug: socket write delay in seconds (default: 0.0)"),
+    new Argument("--rsp-slp", "debug: response delay in seconds (default: 0.0)"),
+    new Argument("--rsp-jtr", "debug: response delay, random duration 0..SEC (default: 0.0)"),
+    new Argument("--list-nics", "debug: list detected network adapters (default: False)"),
+    new Argument("--list-ips", "debug: list detected LAN IPs (default: False)"),
+    new Argument("--http-only", "disable ssl/tls -- force plaintext (default: False)"),
+    new Argument("--https-only", "disable plaintext -- force tls (default: False)"),
+    new Argument("--cert", "path to file containing a concatenation of TLS key and certificate chain (if --certkey is not set), or just the certificate chain (if --certkey is set) (default: ~/.config/copyparty/cert.pem)"),
+    new Argument("--certkey", "path to file containing just the certificate key; if this is set, then --cert should only contain the certificate chain (default: )"),
+    new Argument("--ssl-ver", "set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe (default: )"),
+    new Argument("--ciphers", "set allowed ssl/tls ciphers; [help] shows available ciphers (default: )"),
+    new Argument("--ssl-dbg", "dump some tls info (default: False)"),
+    new Argument("--ssl-log", "log master secrets for later decryption in wireshark (default: )"),
+    new Argument("--no-crt", "disable automatic certificate creation (default: False)"),
+    new Argument("--crt-ns", "comma-separated list of FQDNs (domains) to add into the certificate (default: )"),
+    new Argument("--crt-exact", "do not add wildcard entries for each --crt-ns (default: False)"),
+    new Argument("--crt-noip", "do not add autodetected IP addresses into cert (default: False)"),
+    new Argument("--crt-nolo", "do not add 127.0.0.1 / localhost into cert (default: False)"),
+    new Argument("--crt-nohn", "do not add mDNS names / hostname into cert (default: False)"),
+    new Argument("--crt-dir", "where to save the CA cert (default: ~/.config/copyparty)"),
+    new Argument("--crt-cdays", "ca-certificate expiration time in days (default: 3650.0)"),
+    new Argument("--crt-sdays", "server-cert expiration time in days (default: 365.0)"),
+    new Argument("--crt-cn", "CA/server-cert common-name (default: partyco)"),
+    new Argument("--crt-cnc", "override CA name (default: --crt-cn)"),
+    new Argument("--crt-cns", "override server-cert name (default: --crt-cn cpp)"),
+    new Argument("--crt-back", "backdate in hours (default: 72.0)"),
+    new Argument("--crt-alg", "algorithm and keysize; one of these: ecdsa-256 rsa-4096 rsa-2048 (default: ecdsa-256)"),
+    new Argument("--idp-h-usr", "REPEATABLE: bypass the copyparty authentication checks if the request-header HN contains a username to associate the request with (for use with authentik/oauth/...)WARNING: if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy (default: None)"),
+    new Argument("--idp-hm-usr", "REPEATABLE: bypass the copyparty authentication checks if the request-header T is provided, and its value exists in a mapping defined by this option; see --help-idp (default: None)"),
+    new Argument("--idp-h-grp", "assume the request-header HN contains the groupname of the requesting user; can be referenced in config files for group-based access control (default: )"),
+    new Argument("--idp-h-key", "optional but recommended safeguard; your reverse-proxy will insert a secret header named HN into all requests, and the other IdP headers will be ignored if this header is not present (default: )"),
+    new Argument("--idp-gsep", "if there are multiple groups in --idp-h-grp, they are separated by one of the characters in RE (default: |:;+,)"),
+    new Argument("--idp-chsub", "characters to replace in usernames/groupnames; a list of pairs of characters separated by | so for example | _| will replace spaces with _ to make configuration easier, or |%_|^_|@_| will replace %/^/@ with _ (default: )"),
+    new Argument("--idp-db", "where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs) (default: ~/.config/copyparty/idp.db)"),
+    new Argument("--idp-store", "how to use --idp-db; [0] = entirely disable, [1] = write-only (effectively disabled), [2] = remember users, [3] = remember users and groups.NOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP (default: 1)"),
+    new Argument("--idp-adm", "comma-separated list of users allowed to use /?idp (the cache management UI) (default: )"),
+    new Argument("--idp-cookie", "generate a session-token for IdP users which is written to cookie cppws (or cppwd if plaintext), to reduce the load on the IdP server, lifetime S seconds. └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next  restart with --ses-db wiped) (default: 0)"),
+    new Argument("--idp-login", "replace all login-buttons with a link to URL L (unless pw is in --auth-ord then both will be shown); [{dst}] expands to url of current page (default: )"),
+    new Argument("--idp-login-t", "the label/text for the idp-login button (default: Login with SSO)"),
+    new Argument("--idp-logout", "replace all logout-buttons with a link to URL L (default: )"),
+    new Argument("--auth-ord", "controls auth precedence; examples: [pw,idp,ipu], [ipu,pw,idp], see --help-auth-ord (default: idp,ipu)"),
+    new Argument("--pw-hdr", "lowercase name of password-header (NAME: foo); WARNING: Changing this will break support for many clients (default: pw)"),
+    new Argument("--pw-urlp", "lowercase name of password url-param (?NAME=foo); WARNING: Changing this will break support for many clients (default: pw)"),
+    new Argument("--no-bauth", "disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app (default: False)"),
+    new Argument("--bauth-last", "keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins (default: False)"),
+    new Argument("--ses-db", "where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs) (default: ~/.config/copyparty/sessions.db)"),
+    new Argument("--ses-len", "session key length; default is 120 bits ((20//4)*4*6) (default: 20)"),
+    new Argument("--no-ses", "disable sessions; use plaintext passwords in cookies (default: False)"),
+    new Argument("--grp-all", "the name of the auto-generated group which contains every username which is known (default: acct)"),
+    new Argument("--ipu", "REPEATABLE: users with IP matching CIDR are auto-authenticated as username USR; example: [172.16.24.0/24=dave] (default: None)"),
+    new Argument("--ipr", "REPEATABLE: username USR can only connect from an IP matching one or more CIDR (comma-sep.); example: [192.168.123.0/24,172.16.0.0/16=dave] (default: None)"),
+    new Argument("--chpw", "allow users to change their own passwords (default: False)"),
+    new Argument("--chpw-no", "REPEATABLE: do not allow password-changes for this comma-separated list of usernames (default: None)"),
+    new Argument("--chpw-db", "where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs) (default: ~/.config/copyparty/chpw.json)"),
+    new Argument("--chpw-len", "minimum password length (default: 8)"),
+    new Argument("--chpw-v", "verbosity of summary on config load [0] = nothing at all, [1] = number of users, [2] = list users with default-pw, [3] = list all users (default: 2)"),
+    new Argument("--qr", "show QR-code on startup (default: False)"),
+    new Argument("--qrs", "change the QR-code URL to https:// (default: False)"),
+    new Argument("--qrl", "location to include in the url, for example [priv/?pw=hunter2] (default: )"),
+    new Argument("--qri", "select IP which starts with PREFIX; [.] to force default IP when mDNS URL would have been used instead (default: )"),
+    new Argument("--qr-fg", "foreground; try [0] or [-1] if the qr-code is unreadable (default: 16)"),
+    new Argument("--qr-bg", "background (white=255) (default: 229)"),
+    new Argument("--qrp", "padding (spec says 4 or more, but 1 is usually fine) (default: 4)"),
+    new Argument("--qrz", "[1]=1x, [2]=2x, [0]=auto (try [2] on broken fonts) (default: 0)"),
+    new Argument("--qr-pin", "sticky/pin the qr-code to always stay on-screen; [0]=disabled, [1]=with-url, [2]=just-qr (default: 0)"),
+    new Argument("--qr-wait", "wait SEC before printing the qr-code to the log (default: 0)"),
+    new Argument("--qr-every", "print the qr-code every SEC (try this with/without --qr-pin in case of issues) (default: 0)"),
+    new Argument("--qr-winch", "when --qr-pin is enabled, check for terminal size change every SEC (default: 0)"),
+    new Argument("--qr-file", "REPEATABLE: write qr-code to file. └─To create txt or svg, TXT is Filepath:Zoom:Pad, for example [qr.txt:1:2] └─To create png or gif, TXT is Filepath:Zoom:Pad:Foreground:Background, for example [qr.png:8:2:333333:ffcc55], or  [qr.png:8:2::ffcc55] for transparent (default: None)"),
+    new Argument("--qr-stdout", "always display the QR-code on STDOUT in the terminal, even if -q (default: False)"),
+    new Argument("--qr-stderr", "always display the QR-code on STDERR in the terminal, even if -q (default: False)"),
+    new Argument("-z", "enable all zeroconf backends (mdns, ssdp) (default: False)"),
+    new Argument("--z-on", "enable zeroconf ONLY on the comma-separated list of subnets and/or interface names/indexes └─example: eth0, wlo1, virhost0, 192.168.123.0/24, fd00:fda::/96 (default: )"),
+    new Argument("--z-off", "disable zeroconf on the comma-separated list of subnets and/or interface names/indexes (default: )"),
+    new Argument("--z-chk", "check for network changes every SEC seconds (0=disable) (default: 10)"),
+    new Argument("-zv", "verbose all zeroconf backends (default: False)"),
+    new Argument("--mc-hop", "rejoin multicast groups every SEC seconds (workaround for some switches/routers which cause mDNS to suddenly stop working after some time); try [300] or [180] └─note: can be due to firewalls; make sure UDP port 5353 is open in both directions (on clients too) (default: 0)"),
+    new Argument("--zm", "announce the enabled protocols over mDNS (multicast DNS-SD) -- compatible with KDE, gnome, macOS, ... (default: False)"),
+    new Argument("--zm-on", "enable mDNS ONLY on the comma-separated list of subnets and/or interface names/indexes (default: )"),
+    new Argument("--zm-off", "disable mDNS on the comma-separated list of subnets and/or interface names/indexes (default: )"),
+    new Argument("--zm4", "IPv4 only -- try this if some clients can't connect (default: False)"),
+    new Argument("--zm6", "IPv6 only (default: False)"),
+    new Argument("--zmv", "verbose mdns (default: False)"),
+    new Argument("--zmvv", "verboser mdns (default: False)"),
+    new Argument("--zm-http", "port to announce for http/webdav; [-1] = auto, [0] = disabled, [4649] = port 4649 (default: -1)"),
+    new Argument("--zm-https", "port to announce for https/webdavs; [-1] = auto, [0] = disabled, [4649] = port 4649 (default: -1)"),
+    new Argument("--zm-no-pe", "mute parser errors (invalid incoming MDNS packets) (default: False)"),
+    new Argument("--zm-nwa-1", "disable workaround for avahi-bug #379 (corruption in Avahi's mDNS reflection feature) (default: False)"),
+    new Argument("--zms", "list of services to announce -- d=webdav h=http f=ftp s=smb -- lowercase=plaintext uppercase=TLS -- default: all enabled services except http/https (Ddfs if --ftp and --smb is set, Dd otherwise) (default: )"),
+    new Argument("--zm-ld", "link a specific folder for webdav shares (default: )"),
+    new Argument("--zm-lh", "link a specific folder for http shares (default: )"),
+    new Argument("--zm-lf", "link a specific folder for ftp shares (default: )"),
+    new Argument("--zm-ls", "link a specific folder for smb shares (default: )"),
+    new Argument("--zm-fqdn", "the domain to announce; NOTE: using anything other than .local is nonstandard and could cause problems (default: --name.local)"),
+    new Argument("--zm-mnic", "merge NICs which share subnets; assume that same subnet means same network (default: False)"),
+    new Argument("--zm-msub", "merge subnets on each NIC -- always enabled for ipv6 -- reduces network load, but gnome-gvfs clients may stop working, and clients cannot be in subnets that the server is not (default: False)"),
+    new Argument("--zm-noneg", "disable NSEC replies -- try this if some clients don't see copyparty (default: False)"),
+    new Argument("--zm-spam", "send unsolicited announce every SEC; useful if clients have IPs in a subnet which doesn't overlap with the server, or to avoid some firewall issues (default: 0.0)"),
+    new Argument("--zs", "announce the enabled protocols over SSDP -- compatible with Windows (default: False)"),
+    new Argument("--zs-on", "enable SSDP ONLY on the comma-separated list of subnets and/or interface names/indexes (default: )"),
+    new Argument("--zs-off", "disable SSDP on the comma-separated list of subnets and/or interface names/indexes (default: )"),
+    new Argument("--zsv", "verbose SSDP (default: False)"),
+    new Argument("--zsl", "location to include in the url (or a complete external URL), for example [priv/?pw=hunter2] (goes directly to /priv/ with password hunter2) or [?hc=priv&pw=hunter2] (shows mounting options for /priv/ with password) (default: /?hc)"),
+    new Argument("--zsid", "USN (device identifier) to announce (default: autogenerated)"),
+    new Argument("--casechk", "detect and prevent CI (case-insensitive) behavior if the underlying filesystem is CI? [y] = detect and prevent, [n] = ignore and allow, [auto] = y if CI fs detected. NOTE: y is very slow but necessary for correct WebDAV behavior on Windows/Macos (volflag=casechk) (default: auto)"),
+    new Argument("--fsnt", "which characters to allow in file/folder names; [win] = windows (not <>:|?*\"\\/), [mac] = macos (not :), [lin] = linux (anything goes) (volflag=fsnt) (default: auto)"),
+    new Argument("--rm-retry", "if a file cannot be deleted because it is busy, continue trying for T seconds, retry every R seconds; disable with 0/0 (volflag=rm_retry) (default: 0/0)"),
+    new Argument("--mv-retry", "if a file cannot be renamed because it is busy, continue trying for T seconds, retry every R seconds; disable with 0/0 (volflag=mv_retry) (default: 0/0)"),
+    new Argument("--iobuf", "file I/O buffer-size; if your volumes are on a network drive, try increasing to 524288 or even 4194304 (and let me know if that improves your performance) (default: 262144)"),
+    new Argument("--mtab-age", "rebuild mountpoint cache every SEC to keep track of sparse-files support; keep low on servers with removable media (default: 60)"),
+    new Argument("--shr", "toplevel virtual folder for shared files/folders, for example [/share] (default: )"),
+    new Argument("--shr-db", "database to store shares in (default: ~/.config/copyparty/shares.db)"),
+    new Argument("--shr-who", "who can create a share? [no]=nobody, [a]=admin-permission, [auth]=authenticated (volflag=shr_who) (default: auth)"),
+    new Argument("--shr-adm", "comma-separated list of users allowed to view/delete any share (default: )"),
+    new Argument("--shr-rt", "shares can be revived by their owner if they expired less than MIN minutes ago; [60]=hour, [1440]=day, [10080]=week (default: 1440)"),
+    new Argument("--shr-site", "public URL to assume when creating share-links; example: [https://example.com/] (default: --site)"),
+    new Argument("--shr-v", "debug (default: False)"),
+    new Argument("--dotpart", "dotfile incomplete uploads, hiding them from clients unless -ed (default: False)"),
+    new Argument("--plain-ip", "when avoiding filename collisions by appending the uploader's ip to the filename: append the plaintext ip instead of salting and hashing the ip (default: False)"),
+    new Argument("--up-site", "public URL to assume when creating links to uploaded files; example: [https://example.com/] (default: --site)"),
+    new Argument("--put-name", "filename for nameless uploads (when uploader doesn't provide a name); default is [put-UNIXTIME-IP.bin] (the .6f means six decimal places) (volflag=put_name) (default: put-{now.6f}-{cip}.bin)"),
+    new Argument("--put-ck", "default checksum-hasher for PUT/WebDAV uploads: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=put_ck) (default: sha512)"),
+    new Argument("--bup-ck", "default checksum-hasher for bup/basic-uploader: no / md5 / sha1 / sha256 / sha512 / b2 / blake2 / b2s / blake2s (volflag=bup_ck) (default: sha512)"),
+    new Argument("--unpost", "grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled, default=12h (default: 43200)"),
+    new Argument("--unp-who", "clients can undo recent uploads by using the unpost tab (requires -e2d). [0] = never allowed (disable feature), [1] = allow if client has the same IP as the upload AND is using the same account, [2] = just check the IP, [3] = just check account-name (volflag=unp_who) (default: 1)"),
+    new Argument("--apnd-who", "who can append to existing files? [no]=nobody, [aw]=admin+write, [dw]=delete+write, [w]=write (volflag=apnd_who) (default: dw)"),
+    new Argument("--u2abort", "clients can abort incomplete uploads by using the unpost tab (requires -e2d). [0] = never allowed (disable feature), [1] = allow if client has the same IP as the upload AND is using the same account, [2] = just check the IP, [3] = just check account-name (volflag=u2abort) (default: 1)"),
+    new Argument("--blank-wt", "file write grace period (any client can write to a blank file last-modified more recently than SEC seconds ago) (default: 300)"),
+    new Argument("--reg-cap", "max number of uploads to keep in memory when running without -e2d; roughly 1 MiB RAM per 600 (default: 38400)"),
+    new Argument("--no-fpool", "disable file-handle pooling -- instead, repeatedly close and reopen files during upload (bad idea to enable this on windows and/or cow filesystems) (default: False)"),
+    new Argument("--use-fpool", "force file-handle pooling, even when it might be dangerous (multiprocessing, filesystems lacking sparse-files support, ...) (default: False)"),
+    new Argument("--chmod-f", "unix file permissions to use when creating files; default is probably 644 (OS-decided), see --help-chmod. Examples: [644] = owner-RW + all-R, [755] = owner-RWX + all-RX, [777] = full-yolo (volflag=chmod_f) (default: )"),
+    new Argument("--chmod-d", "unix file permissions to use when creating directories; see --help-chmod. Examples: [755] = owner-RW + all-R, [2750] = setgid + owner-RW + group-R, [777] = full-yolo (volflag=chmod_d) (default: 755)"),
+    new Argument("--uid", "unix user-id to chown new files/folders to; default = -1 = do-not-change (volflag=uid) (default: -1)"),
+    new Argument("--gid", "unix group-id to chown new files/folders to; default = -1 = do-not-change (volflag=gid) (default: -1)"),
+    new Argument("--wram", "allow uploading even if a volume is inside a ramdisk, meaning that all data will be lost on the next server reboot (volflag=wram) (default: False)"),
+    new Argument("--dedup", "enable symlink-based upload deduplication (volflag=dedup) (default: False)"),
+    new Argument("--safe-dedup", "how careful to be when deduplicating files; [1] = just verify the filesize, [50] = verify file contents have not been altered (volflag=safededup) (default: 50)"),
+    new Argument("--hardlink", "enable hardlink-based dedup; will fallback on symlinks when that is impossible (across filesystems) (volflag=hardlink) (default: False)"),
+    new Argument("--hardlink-only", "do not fallback to symlinks when a hardlink cannot be made (volflag=hardlinkonly) (default: False)"),
+    new Argument("--reflink", "enable reflink-based dedup; will fallback on full copies when that is impossible (non-CoW filesystem) (volflag=reflink) (default: False)"),
+    new Argument("--no-dupe", "reject duplicate files during upload; only matches within the same volume (volflag=nodupe) (default: False)"),
+    new Argument("--no-dupe-m", "also reject dupes when moving a file into another volume (volflag=nodupem) (default: False)"),
+    new Argument("--no-clone", "do not use existing data on disk to satisfy dupe uploads; reduces server HDD reads in exchange for much more network load (volflag=noclone) (default: False)"),
+    new Argument("--no-snap", "disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually (default: False)"),
+    new Argument("--snap-wri", "write upload state to ./hist/up2k.snap every SEC seconds; allows resuming incomplete uploads after a server crash (default: 300)"),
+    new Argument("--snap-drop", "forget unfinished uploads after MIN minutes; impossible to resume them after that (360=6h, 1440=24h) (default: 1440.0)"),
+    new Argument("--rm-partial", "delete the .PARTIAL file when an unfinished upload expires after --snap-drop (volflag=rm_partial) (default: False)"),
+    new Argument("--u2ts", "how to timestamp uploaded files; [c]=client-last-modified, [u]=upload-time, [fc]=force-c, [fu]=force-u (volflag=u2ts) (default: c)"),
+    new Argument("--rotf-tz", "default timezone for the rotf upload rule; examples: [Europe/Oslo], [America/Toronto], [Antarctica/South_Pole] (volflag=rotf_tz) (default: UTC)"),
+    new Argument("--rand", "force randomized filenames, --nrand chars long (volflag=rand) (default: False)"),
+    new Argument("--nrand", "randomized filenames length (volflag=nrand) (default: 9)"),
+    new Argument("--magic", "enable filetype detection on nameless uploads (volflag=magic) (default: False)"),
+    new Argument("--df", "ensure GiB free disk space by rejecting upload requests; assumes gigabytes unless a unit suffix is given: [256m], [4], [2T] (volflag=df) (default: 0)"),
+    new Argument("--sparse", "windows-only: minimum size of incoming uploads through up2k before they are made into sparse files (default: 4)"),
+    new Argument("--turbo", "configure turbo-mode in up2k client; [-1] = forbidden/always-off, [0] = default-off and warn if enabled, [1] = default-off, [2] = on, [3] = on and disable datecheck (default: 0)"),
+    new Argument("--nosubtle", "when to use a wasm-hasher instead of the browser's builtin; faster on chrome, but buggy in older chrome versions. [0] = only when necessary (non-https), [1] = always (all browsers), [2] = always on chrome/firefox, [3] = always on chrome, [N] = chrome-version N and newer (recommendation: 137) (default: 0)"),
+    new Argument("--u2j", "web-client: number of file chunks to upload in parallel; 1 or 2 is good when latency is low (same-country), 2~4 for android-clients, 2~6 for cross-atlantic. Max is 6 in most browsers. Big values increase network-speed but may reduce HDD-speed (default: 2)"),
+    new Argument("--u2sz", "web-client: default upload chunksize (MiB); sets min,default,max in the settings gui. Each HTTP POST will aim for default, and never exceed max. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [1,1,1] (default: 1,64,96)"),
+    new Argument("--u2ow", "web-client: default setting for when to replace/overwrite existing files; [0]=never, [1]=if-client-newer, [2]=always (volflag=u2ow) (default: 0)"),
+    new Argument("--u2sort", "upload order; [s]=smallest-first, [n]=alphabetical, [fs]=force-s, [fn]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine (default: s)"),
+    new Argument("--write-uplog", "write POST reports to textfiles in working-directory (default: False)"),
+    new Argument("-e2d", "enable up2k database; this enables file search, upload-undo, improves deduplication (default: False)"),
+    new Argument("-e2ds", "scan writable folders for new files on startup; sets -e2d (default: False)"),
+    new Argument("-e2dsa", "scans all folders on startup; sets -e2ds (default: False)"),
+    new Argument("-e2v", "verify file integrity; rehash all files and compare with db (default: False)"),
+    new Argument("-e2vu", "on hash mismatch: update the database with the new hash (default: False)"),
+    new Argument("-e2vp", "on hash mismatch: panic and quit copyparty (default: False)"),
+    new Argument("--hist", "where to store volume data (db, thumbs); default is a folder named \".hist\" inside each volume (volflag=hist) (default: )"),
+    new Argument("--dbpath", "override where the volume databases are to be placed; default is the same as --hist (volflag=dbpath) (default: )"),
+    new Argument("--fika", "list of user-actions to allow while filesystem-indexer is still busy; set blank to never interrupt indexing (old default). [u]=uploads, [c]=filecopy, [m]=move/rename, [d]=delete. NOTE: [m] is untested/scary, and blank is recommended if dedup enabled (default: ucd)"),
+    new Argument("--no-hash", "regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (must be specified as one big regex, not multiple times) (volflag=nohash) (default: )"),
+    new Argument("--no-idx", "regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scan (must be specified as one big regex, not multiple times) (volflag=noidx) (default: )"),
+    new Argument("--no-dirsz", "do not show total recursive size of folders in listings, show inode size instead; slightly faster (volflag=nodirsz) (default: False)"),
+    new Argument("--re-dirsz", "if the directory-sizes in the UI are bonkers, use this along with -e2dsa to rebuild the index from scratch (default: False)"),
+    new Argument("--no-dhash", "disable rescan acceleration; do full database integrity check -- makes the db ~5% smaller and bootup/rescans 3~10x slower (default: False)"),
+    new Argument("--re-dhash", "force a cache rebuild on startup; enable this once if it gets out of sync (should never be necessary) (default: False)"),
+    new Argument("--no-forget", "never forget indexed files, even when deleted from disk -- makes it impossible to ever upload the same file twice -- only useful for offloading uploads to a cloud service or something (volflag=noforget) (default: False)"),
+    new Argument("--forget-ip", "remove uploader-IP from database (and make unpost impossible) MIN minutes after upload, for GDPR reasons. Default [0] is never-forget. [1440]=day, [10080]=week, [43200]=month. (volflag=forget_ip) (default: 0)"),
+    new Argument("--dbd", "database durability profile; sets the tradeoff between robustness and speed, see --help-dbd (volflag=dbd) (default: wal)"),
+    new Argument("--xlink", "on upload: check all volumes for dupes, not just the target volume (probably buggy, not recommended) (volflag=xlink) (default: False)"),
+    new Argument("--hash-mt", "num cpu cores to use for file hashing; set 0 or 1 for single-core hashing (default: numCores if 5 or less)"),
+    new Argument("--re-maxage", "rescan filesystem for changes every SEC seconds; 0=off (volflag=scan) (default: 0)"),
+    new Argument("--db-act", "defer any scheduled volume reindexing until SEC seconds after last db write (uploads, renames, ...) (default: 10.0)"),
+    new Argument("--srch-icase", "case-insensitive search for all unicode characters (the default is icase for just ascii). NOTE: will make searches much slower (around 4x), and NOTE: only applies to filenames/paths, not tags (default: False)"),
+    new Argument("--srch-time", "search deadline -- terminate searches running for more than SEC seconds (default: 45)"),
+    new Argument("--srch-hits", "max search results to allow clients to fetch; 125 results will be shown initially (default: 7999)"),
+    new Argument("--srch-excl", "regex: exclude files from search results if the file-URL matches PTN (case-sensitive). Example: [password|logs/[0-9]] any URL containing 'password' or 'logs/DIGIT' (volflag=srch_excl) (default: )"),
+    new Argument("--dotsrch", "show dotfiles in search results (volflags: dotsrch | nodotsrch) (default: False)"),
+    new Argument("-e2t", "enable metadata indexing; makes it possible to search for artist/title/codec/resolution/... (default: False)"),
+    new Argument("-e2ts", "scan newly discovered files for metadata on startup; sets -e2t (default: False)"),
+    new Argument("-e2tsr", "delete all metadata from DB and do a full rescan; sets -e2ts (default: False)"),
+    new Argument("--no-mutagen", "use FFprobe for tags instead; will detect more tags (default: False)"),
+    new Argument("--no-mtag-ff", "never use FFprobe as tag reader; is probably safer (default: False)"),
+    new Argument("--mtag-to", "timeout for FFprobe tag-scan (default: 60)"),
+    new Argument("--mtag-mt", "num cpu cores to use for tag scanning (default: numCores)"),
+    new Argument("--mtag-v", "verbose tag scanning; print errors from mtp subprocesses and such (default: False)"),
+    new Argument("--mtag-vv", "debug mtp settings and mutagen/FFprobe parsers (default: False)"),
+    new Argument("--db-xattr", "read file xattrs as metadata tags; [a,b] reads keys a and b as tags a and b, [a=foo,b=bar] reads keys a and b as tags foo and bar, [~~a,b] does everything except a and b, [~~] does everything. NOTE: Each tag must also be enabled with -mte (volflag=db_xattr) (default: )"),
+    new Argument("-mtm", "REPEATABLE: add/replace metadata mapping (default: None)"),
+    new Argument("-mte", "tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar (default: .files,circle,album,.tn,artist,title,tdate,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash, vhash)"),
+    new Argument("-mth", "tags to hide by default (comma-sep.); assign/add/remove same as -mte (default: tdate,.vq,.aq,vc,ac,fmt,res,.fps)"),
+    new Argument("-mtp", "REPEATABLE: read tag M using program BIN to parse the file (default: None)"),
+    new Argument("--no-thumb", "disable all thumbnails (volflag=dthumb) (default: False)"),
+    new Argument("--no-vthumb", "disable video thumbnails (volflag=dvthumb) (default: False)"),
+    new Argument("--no-athumb", "disable audio thumbnails (spectrograms) (volflag=dathumb) (default: False)"),
+    new Argument("--th-size", "thumbnail res (volflag=thsize) (default: 320x256)"),
+    new Argument("--th-mt", "num cpu cores to use for generating thumbnails (default: numCores)"),
+    new Argument("--th-convt", "convert-to-image timeout in seconds (volflag=convt) (default: 60.0)"),
+    new Argument("--ac-convt", "convert-to-audio timeout in seconds (volflag=aconvt) (default: 150.0)"),
+    new Argument("--th-ram-max", "max memory usage (GiB) permitted by thumbnailer; not very accurate (default: dynamic)"),
+    new Argument("--th-crop", "crop thumbnails to 4:3 or keep dynamic height; client can override in UI unless force. [y]=crop, [n]=nocrop, [fy]=force-y, [fn]=force-n (volflag=crop) (default: y)"),
+    new Argument("--th-x3", "show thumbs at 3x resolution; client can override in UI unless force. [y]=yes, [n]=no, [fy]=force-yes, [fn]=force-no (volflag=th3x) (default: n)"),
+    new Argument("--th-qv", "webp/jpg thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qv) (default: 40)"),
+    new Argument("--th-qvx", "jxl thumbnail quality (10~90); higher is larger filesize and better quality (volflag=th_qvx) (default: 64)"),
+    new Argument("--th-dec", "image decoders, in order of preference (default: vips,raw,pil,ff)"),
+    new Argument("--th-no-jpg", "disable jpg output (default: False)"),
+    new Argument("--th-no-webp", "disable webp output (default: False)"),
+    new Argument("--th-no-jxl", "disable jpeg-xl output (default: False)"),
+    new Argument("--th-ff-jpg", "force jpg output for video thumbs (avoids issues on some FFmpeg builds) (default: False)"),
+    new Argument("--th-ff-swr", "use swresample instead of soxr for audio thumbs (faster, lower accuracy, avoids issues on some FFmpeg builds) (default: False)"),
+    new Argument("--th-vips-jxl", "when to allow generating jxl thumbnails with libvips; 0=never, 1=musl-mallocng, 2=always (default: 1)"),
+    new Argument("--th-poke", "activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than SEC seconds (default: 300)"),
+    new Argument("--th-clean", "cleanup interval; 0=disabled (default: 43200)"),
+    new Argument("--th-maxage", "max folder age -- folders which haven't been poked for longer than --th-poke seconds will get deleted every --th-clean seconds (default: 604800)"),
+    new Argument("--th-pregen", "pregenerate thumbnails on startup; F,F is comma-separated list of formats; example: [j,jf,w,w3,wf,wf3,x,xf] NOTE: remember to set --th-maxage 123456789 (volflag=th_pregen) (default: )"),
+    new Argument("--th-pre-rl", "while pregen is running, ratelimit the thumbnailer logger to one message every SEC seconds (only works with -j1); set 0 to disable ratelimit (default: 30)"),
+    new Argument("--th-covers", "folder thumbnails to stat/look for; enabling -e2d will make these case-insensitive, and try them as dotfiles (.folder.jpg), and also automatically select thumbnails for all folders that contain pics, even if none match this pattern (default: folder.png,folder.jpg,cover.png,cover.jpg)"),
+    new Argument("--th-spec-p", "for music, do spectrograms or embedded coverart? [0]=only-art, [1]=prefer-art, [2]=only-spec (default: 1)"),
+    new Argument("--th-spec-fl", "generate spectrograms with logarithmic frequency scale instead of linear (default: False)"),
+    new Argument("--th-r-pil", "image formats to decode using pillow (default: avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif, heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff, webp,wmf,xbm,xpm)"),
+    new Argument("--th-r-vips", "image formats to decode using pyvips (default: 3fr,arw,avif,cr2,cr3,crw,dcr,dng,erf,exr,fit,fits,fts,gif,hdr,heic, heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,k25,kdc,mdc,mef,mrw,nef,nii,nrw,orf,pfm,pgm,png,ppm,raf,raw,rw2,sr2,srf,srw,svg, tif,tiff,webp,x3f)"),
+    new Argument("--th-r-raw", "image formats to decode using rawpy (if available) or libraw's dcraw_emu (default: 3fr,arw,cr2,cr3,crw,dcr,dng,erf,k25, kdc,mdc,mef,mos,mrw,nef,nrw,orf,pef,raf,raw,rw2,sr2,srf,srw,x3f)"),
+    new Argument("--th-r-ffi", "image formats to decode using ffmpeg (default: apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics, heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm)"),
+    new Argument("--th-r-ffv", "video formats to decode using ffmpeg (default: 3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg, mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv)"),
+    new Argument("--th-r-ffa", "audio formats to decode using ffmpeg (default: aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bcstm,bfstm,brstm,bonk,dfpwm, dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,m4b,m4r,mdgz,mdxz,mdz,mka,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus, ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk)"),
+    new Argument("--th-spec-cnv", "audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms) (default: it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk)"),
+    new Argument("--au-unpk", "audio/image formats to decompress before passing to ffmpeg (default: mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub)"),
+    new Argument("--q-opus", "target bitrate for transcoding to opus; set 0 to disable (default: 128)"),
+    new Argument("--q-mp3", "target quality for transcoding to mp3, for example [192k] (CBR) or [q0] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable (default: q2)"),
+    new Argument("--allow-wav", "allow transcoding to wav (lossless, uncompressed) (default: False)"),
+    new Argument("--allow-flac", "allow transcoding to flac (lossless, compressed) (default: False)"),
+    new Argument("--no-caf", "disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead (default: False)"),
+    new Argument("--no-owa", "disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead (default: False)"),
+    new Argument("--no-acode", "disable audio transcoding (default: False)"),
+    new Argument("--no-bacode", "disable batch audio transcoding by folder download (zip/tar) (default: False)"),
+    new Argument("--ac-maxage", "delete cached transcode output after SEC seconds (default: 86400)"),
+    new Argument("--rss", "enable RSS output (experimental) (volflag=rss) (default: False)"),
+    new Argument("--rss-nf", "default number of files to return (url-param 'nf') (default: 250)"),
+    new Argument("--rss-fext", "default list of file extensions to include (url-param 'fext'); blank=all (default: )"),
+    new Argument("--rss-sort", "default sort order (url-param 'sort'); [m]=last-modified [u]=upload-time [n]=filename [s]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files (volflag=rss_sort) (default: m)"),
+    new Argument("--rss-fmt-t", "title format (url-param 'rss_fmt_t') (volflag=rss_fmt_t) (default: {fname})"),
+    new Argument("--rss-fmt-d", "description format (url-param 'rss_fmt_d') (volflag=rss_fmt_d) (default: {artist} - {title})"),
+    new Argument("--sftp", "enable SFTP server on PORT, for example 3922 (default: 0)"),
+    new Argument("--sftpv", "verbose (default: False)"),
+    new Argument("--sftpvv", "verboser (default: False)"),
+    new Argument("--sftp-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"),
+    new Argument("--sftp4", "only listen on IPv4 (default: False)"),
+    new Argument("--sftp-key", "REPEATABLE: add ssh-key K for user U (username, space, key-type, space, base64); if user has multiple keys, then repeat this option for each key └─commandline example: --sftp-key 'david ssh-ed25519 AAAAC3NzaC...' └─config-file example: sftp-key: david ssh-ed25519 AAAAC3NzaC... (default: None)"),
+    new Argument("--sftp-pw", "allow password-authentication with sftp (not just ssh-keys) (default: False)"),
+    new Argument("--sftp-anon", "allow anonymous/unauthenticated connections with TXT as username (default: )"),
+    new Argument("--sftp-hostk", "path to folder with hostkeys, for example 'ssh_host_rsa_key'; missing keys will be generated (default: ~/.config/copyparty)"),
+    new Argument("--sftp-banner", "bannertext to send when someone connects; can be @filepath (default: )"),
+    new Argument("--sftp-ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa / --ipar. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] (default: )"),
+    new Argument("--ftp", "enable FTP server on PORT, for example 3921 (default: 0)"),
+    new Argument("--ftps", "enable FTPS server on PORT, for example 3990 (default: 0)"),
+    new Argument("--ftpv", "verbose (default: False)"),
+    new Argument("--ftp-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"),
+    new Argument("--ftp4", "only listen on IPv4 (default: False)"),
+    new Argument("--ftp-ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa / --ipar. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] (default: )"),
+    new Argument("--ftp-no-ow", "if target file exists, reject upload instead of overwrite (default: False)"),
+    new Argument("--ftp-wt", "grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than SEC seconds ago) (default: 7)"),
+    new Argument("--ftp-nat", "the NAT address to use for passive connections (default: )"),
+    new Argument("--ftp-pr", "the range of TCP ports to use for passive connections, for example 12000-13000 (default: )"),
+    new Argument("--daw", "enable full write support, even if client may not be webdav. Some webdav clients need this option for editing existing files; not necessary for clients that send the 'x-oc-mtime' header. Regardless, the delete-permission must always be given. WARNING: This has side-effects -- PUT-operations will now OVERWRITE existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client) (default: False)"),
+    new Argument("--dav-inf", "allow depth:infinite requests (recursive file listing); extremely server-heavy but required for spec compliance -- luckily few clients rely on this (default: False)"),
+    new Argument("--dav-mac", "disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS) (default: False)"),
+    new Argument("--dav-rt", "show symlink-destination's lastmodified instead of the link itself; always enabled for recursive listings (volflag=davrt) (default: False)"),
+    new Argument("--dav-auth", "force auth for all folders (required by davfs2 when only some folders are world-readable) (volflag=davauth) (default: False)"),
+    new Argument("--dav-ua1", "regex of user-agents which ARE webdav-clients, and expect 401 from GET requests; disable with [no] or blank (default: kioworker/)"),
+    new Argument("--dav-port", "additional port to listen on for misbehaving webdav clients which pretend they are graphical browsers; an alternative/supplement to dav-ua1 (default: 0)"),
+    new Argument("--ua-nodav", "regex of user-agents which are NOT webdav-clients (default: ^(Mozilla/|NetworkingExtension/|com\\.apple\\.WebKit))"),
+    new Argument("--p-nodav", "server-ports (comma-sep.) which are NOT webdav-clients; an alternative/supplement to ua-nodav (default: )"),
+    new Argument("--tftp", "enable TFTP server on PORT, for example 69 or 3969 (default: 0)"),
+    new Argument("--tftp-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"),
+    new Argument("--tftp4", "only listen on IPv4 (default: False)"),
+    new Argument("--tftpv", "verbose (default: False)"),
+    new Argument("--tftpvv", "verboser (default: False)"),
+    new Argument("--tftp-no-fast", "debug: disable optimizations (default: False)"),
+    new Argument("--tftp-lsf", "return a directory listing if a file with this name is requested and it does not exist; defaults matches .ls, dir, .dir.txt, ls.txt, ... (default: \\.?(dir|ls)(\\.txt)?)"),
+    new Argument("--tftp-nols", "if someone tries to download a directory, return an error instead of showing its directory listing (default: False)"),
+    new Argument("--tftp-ipa", "only accept connections from IP-addresses inside CIDR (comma-separated); specify [any] to disable inheriting --ipa / --ipar. Examples: [lan] or [10.89.0.0/16, 192.168.33.0/24] (default: )"),
+    new Argument("--tftp-pr", "the range of UDP ports to use for data transfer, for example 12000-13000 (default: )"),
+    new Argument("--smb", "enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port is set above 1024 and your OS does port-forwarding from 445 to that.WARNING: this protocol is DANGEROUS and buggy! Never expose to the internet! (default: False)"),
+    new Argument("--smb-i", "IPs to listen on (comma-separated list). Set this to override -i for this protocol (default: -i)"),
+    new Argument("--smbw", "enable write support (please dont) (default: False)"),
+    new Argument("--smb1", "disable SMBv2, only enable SMBv1 (CIFS) (default: False)"),
+    new Argument("--smb-port", "port to listen on -- if you change this value, you must NAT from TCP:445 to this port using iptables or similar (default: 445)"),
+    new Argument("--smb-nwa-1", "truncate directory listings to 64kB (~400 files); avoids impacket-0.11 bug, fixes impacket-0.12 performance (default: False)"),
+    new Argument("--smb-nwa-2", "disable impacket workaround for filecopy globs (default: False)"),
+    new Argument("--smba", "small performance boost: disable per-account permissions, enables account coalescing instead (if one user has write/delete-access, then everyone does) (default: False)"),
+    new Argument("--smb6", "enable IPv6 (default: False)"),
+    new Argument("--smbv", "verbose (default: False)"),
+    new Argument("--smbvv", "verboser (default: False)"),
+    new Argument("--smbvvv", "verbosest (default: False)"),
+    new Argument("--opds", "enable opds -- allows e-book readers to browse and download files (volflag=opds) (default: False)"),
+    new Argument("--opds-exts", "file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_exts) (default: epub,cbz,pdf)"),
+    new Argument("-s", "increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers. └─Alias of --dotpart --no-thumb --no-mtag-ff --no-robots --force-js (default: 0)"),
+    new Argument("-ss", "further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav requires login, 404 on 403, ban on excessive 404s. └─Alias of -s --no-html --no-readme --no-logues --unpost=0 --no-del --no-mv --reflink --dav-auth --vague-403 -nih  (default: False)"),
+    new Argument("-sss", "further increase safety: Enable logging to disk, scan for dangerous symlinks. └─Alias of -ss --no-dav --no-logues --no-readme -lo=cpp-%Y-%m%d-%H%M%S.txt.xz --ls=**,*,ln,p,r (default: False)"),
+    new Argument("--ls", "do a sanity/safety check of all volumes on startup; new Arguments USER,VOL,FLAGS (see --help-ls); example [**,*,ln,p,r] (default: )"),
+    new Argument("--xvol", "never follow symlinks leaving the volume root, unless the link is into another volume where the user also has access (keeps permissions from the outer/initial volume) (volflag=xvol) (default: False)"),
+    new Argument("--xdev", "stay within the filesystem of the volume root; do not descend into other devices (symlink or bind-mount to another HDD,  ...) (volflag=xdev) (default: False)"),
+    new Argument("--vol-nospawn", "if a volume's folder does not exist on the HDD, then do not create it (continue with warning) (volflag=nospawn) (default: False)"),
+    new Argument("--vol-or-crash", "if a volume's folder does not exist on the HDD, then burst into flames (volflag=assert_root) (default: False)"),
+    new Argument("--no-dot-mv", "disallow moving dotfiles; makes it impossible to move folders containing dotfiles (default: False)"),
+    new Argument("--no-dot-ren", "disallow renaming dotfiles; makes it impossible to turn something into a dotfile (default: False)"),
+    new Argument("--no-logues", "disable rendering .prologue/.epilogue.html into directory listings (default: False)"),
+    new Argument("--no-readme", "disable rendering readme/preadme.md into directory listings (default: False)"),
+    new Argument("--no-script", "disables javascript in html files; helps prevent XSS but kills interactive websites (volflag=noscript) (default: False)"),
+    new Argument("--no-html", "show html-files as plain text; helps prevent XSS but kills websites/blogs, also enables --no-script (volflag=nohtml) (default: False)"),
+    new Argument("--vague-403", "send 404 instead of 403 (security through ambiguity, very enterprise). WARNING: Not compatible with WebDAV (default: False)"),
+    new Argument("--force-js", "don't send folder listings as HTML, force clients to use the embedded json instead -- slight protection against misbehaving search engines which ignore --no-robots (default: False)"),
+    new Argument("--no-robots", "adds http and html headers asking search engines to not index anything (volflag=norobots) (default: False)"),
+    new Argument("--logout", "logout clients after H hours of inactivity; [0.0028]=10sec, [0.1]=6min, [24]=day, [168]=week, [720]=month, [8760]=year) (default: 8086.0)"),
+    new Argument("--dont-ban", "anyone at this accesslevel or above will not get banned: [av]=admin-in-volume, [aa]=has-admin-anywhere, [rw]=read-write, [auth]=authenticated, [any]=disable-all-bans, [no]=anyone-can-get-banned (default: no)"),
+    new Argument("--banmsg", "the response to send to banned users; can be @ban.html to send the contents of ban.html (default: thank you for playing   (see fileserver log and readme))"),
+    new Argument("--ban-pw", "more than N wrong passwords in W minutes = ban for B minutes; disable with [no] (default: 9,60,1440)"),
+    new Argument("--ban-pwc", "more than N password-changes in W minutes = ban for B minutes; disable with [no] (default: 5,60,1440)"),
+    new Argument("--ban-404", "hitting more than N 404's in W minutes = ban for B minutes; only affects users who cannot see directory listings because their access is either g/G/h (default: 50,60,1440)"),
+    new Argument("--ban-403", "hitting more than N 403's in W minutes = ban for B minutes; [1440]=day, [10080]=week, [43200]=month (default: 9,2,1440)"),
+    new Argument("--ban-422", "hitting more than N 422's in W minutes = ban for B minutes (invalid requests, attempted exploits ++) (default: 9,2, 1440)"),
+    new Argument("--ban-url", "hitting more than N sus URL's in W minutes = ban for B minutes; applies only to permissions g/G/h (decent replacement for --ban-404 if that can't be used) (default: 9,2,1440)"),
+    new Argument("--sus-urls", "URLs which are considered sus / eligible for banning; disable with blank or [no] (default: \\.php\\$|(^|/)wp-(admin|content|includes)/)"),
+    new Argument("--nonsus-urls", "harmless URLs ignored from 403/404-bans; disable with blank or [no] (default: ^(favicon\\..{3}|robots\\.txt)\\$|^apple-touch-icon|^\\.well-known)"),
+    new Argument("--early-ban", "if a client is banned, reject its connection as soon as possible; not a good idea to enable when proxied behind cloudflare since it could ban your reverse-proxy (default: False)"),
+    new Argument("--cookie-nmax", "reject HTTP-request from client if they send more than N cookies (default: 50)"),
+    new Argument("--cookie-cmax", "reject HTTP-request from client if more than N characters in Cookie header (default: 8192)"),
+    new Argument("--aclose", "if a client maxes out the server connection limit, downgrade it from connection:keep-alive to connection:close for MIN minutes (and also kill its active connections) -- disable with 0 (default: 10)"),
+    new Argument("--loris", "if a client maxes out the server connection limit without sending headers, ban it for B minutes; disable with [0] (default: 60)"),
+    new Argument("--acao", "Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [https://1.2.3.4]. Default [*] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives (default: *)"),
+    new Argument("--acam", "Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like --acao's description) (default: GET,HEAD)"),
+    new Argument("--ah-alg", "account-pw hashing algorithm; one of these, best to worst: argon2 scrypt sha2 none (each optionally followed by alg-specific comma-sep. config) (default: none)"),
+    new Argument("--ah-salt", "account-pw salt; ignored if --ah-alg is none (default) (default: 24-character-autogenerated)"),
+    new Argument("--ah-gen", "generate hashed password for PW, or read passwords from STDIN if PW is [-] (default: )"),
+    new Argument("--ah-cli", "launch an interactive shell which hashes passwords without ever storing or displaying the original passwords (default: False)"),
+    new Argument("--fk-salt", "per-file accesskey salt; used to generate unpredictable URLs for hidden files (default: 24-character-autogenerated)"),
+    new Argument("--dk-salt", "per-directory accesskey salt; used to generate unpredictable URLs to share folders with users who only have the 'get' permission (default: 40-character-autogenerated)"),
+    new Argument("--warksalt", "up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do) (default: hunter2)"),
+    new Argument("--show-ah-salt", "on startup, print the effective value of --ah-salt (the autogenerated value in \$XDG_CONFIG_HOME unless otherwise specified) (default: False)"),
+    new Argument("--show-fk-salt", "on startup, print the effective value of --fk-salt (the autogenerated value in \$XDG_CONFIG_HOME unless otherwise specified) (default: False)"),
+    new Argument("--show-dk-salt", "on startup, print the effective value of --dk-salt (the autogenerated value in \$XDG_CONFIG_HOME unless otherwise specified) (default: False)"),
+    new Argument("-nw", "never write anything to disk (debug/benchmark) (default: False)"),
+    new Argument("--keep-qem", "do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection in the terminal window, as this would pause execution) (default: False)"),
+    new Argument("--no-dav", "disable webdav support (default: False)"),
+    new Argument("--no-del", "disable delete operations (default: False)"),
+    new Argument("--no-mv", "disable move/rename operations (default: False)"),
+    new Argument("--no-cp", "disable copy operations (default: False)"),
+    new Argument("--no-fs-abrt", "disable ability to abort ongoing copy/move (default: False)"),
+    new Argument("-nih", "no info hostname -- removes it from the UI corner, but the value of --bname still shows in the browsertab title (default: False)"),
+    new Argument("-nid", "no info disk-usage -- don't show in UI. This is the same as --du-who no (default: False)"),
+    new Argument("-nb", "no powered-by-copyparty branding in UI (default: False)"),
+    new Argument("--smsg", "HTTP-methods to allow ?smsg for; will execute xm hooks like urlform / message-to-serverlog; dangerous example: [GET, POST]. WARNING: The default (POST) is safe, but GET is dangerous; security/CSRF hazard (default: POST)"),
+    new Argument("--zipmaxn", "reject download-as-zip if more than N files in total; optionally takes a unit suffix: [256], [9K], [4G] (volflag=zipmaxn) (default: 0)"),
+    new Argument("--zipmaxs", "reject download-as-zip if total download size exceeds SZ bytes; optionally takes a unit suffix: [256M], [4G], [2T] (volflag=zipmaxs) (default: 0)"),
+    new Argument("--zipmaxt", "custom errormessage when download size exceeds max (volflag=zipmaxt) (default: )"),
+    new Argument("--zipmaxu", "authenticated users bypass the zip size limit (volflag=zipmaxu) (default: False)"),
+    new Argument("--zip-who", "who can download as zip/tar? [0]=nobody, [1]=admins, [2]=authenticated-with-read-access, [3]=everyone-with-read-access (volflag=zip_who)WARNING: if a nested volume has a more restrictive value than a parent volume, then this will be ignored if the download is initiated from the parent, more lenient volume (default: 3)"),
+    new Argument("--ua-nozip", "regex of user-agents to reject from download-as-zip/tar; disable with [no] or blank (default: Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot)"),
+    new Argument("--no-zip", "disable download as zip/tar; same as --zip-who=0 (default: False)"),
+    new Argument("--no-tarcmp", "disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...) (default: False)"),
+    new Argument("--no-lifetime", "do not allow clients (or server config) to schedule an upload to be deleted after a given time (default: False)"),
+    new Argument("--no-pipe", "disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe) (default: False)"),
+    new Argument("--no-tail", "disable streaming a growing files with ?tail (volflag=notail) (default: False)"),
+    new Argument("--no-db-ip", "do not write uploader-IP into the database; will also disable unpost, you may want --forget-ip instead (volflag=no_db_ip) (default: False)"),
+    new Argument("--no-zls", "disable browsing the contents of zip/cbz files, does not affect thumbnails (default: False)"),
+    new Argument("--ign-ebind", "continue running even if it's impossible to listen on some of the requested endpoints (default: False)"),
+    new Argument("--ign-ebind-all", "continue running even if it's impossible to receive connections at all (default: False)"),
+    new Argument("--exit", "shutdown after WHEN has finished; [cfg] config parsing, [idx] volscan + multimedia indexing, [thgen] thumbnail-pregen (default: )"),
+    new Argument("--allow-csrf", "disable csrf protections; let other domains/sites impersonate you through cross-site requests; DANGEROUS / LAN-only (default: False)"),
+    new Argument("--cookie-lax", "allow cookies from other domains (if you follow a link from another website into your server, you will arrive logged-in); this reduces protection against CSRF (default: False)"),
+    new Argument("--no-fnugg", "disable the smoketest for caching-related issues in the web-UI (default: False)"),
+    new Argument("--getmod", "permit ?move=[...] and ?delete as GET -- DANGEROUS, removes csrf protection (default: False)"),
+    new Argument("--wo-up-readme", "allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme) (default: False)"),
+    new Argument("--unsafe-state", "when one of the emergency fallback locations are used for runtime state (\$TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets (default: False)"),
+    new Argument("--on404", "REPEATABLE: handle 404s by executing PY file (default: None)"),
+    new Argument("--on403", "REPEATABLE: handle 403s by executing PY file (default: None)"),
+    new Argument("--hot-handlers", "recompile handlers on each request -- expensive but convenient when hacking on stuff (default: False)"),
+    new Argument("--xbu", "REPEATABLE: execute CMD before a file upload starts (default: None)"),
+    new Argument("--xau", "REPEATABLE: execute CMD after  a file upload finishes (default: None)"),
+    new Argument("--xiu", "REPEATABLE: execute CMD after  all uploads finish and volume is idle (default: None)"),
+    new Argument("--xbc", "REPEATABLE: execute CMD before a file copy (default: None)"),
+    new Argument("--xac", "REPEATABLE: execute CMD after  a file copy (default: None)"),
+    new Argument("--xbr", "REPEATABLE: execute CMD before a file move/rename (default: None)"),
+    new Argument("--xar", "REPEATABLE: execute CMD after  a file move/rename (default: None)"),
+    new Argument("--xbd", "REPEATABLE: execute CMD before a file delete (default: None)"),
+    new Argument("--xad", "REPEATABLE: execute CMD after  a file delete (default: None)"),
+    new Argument("--xm", "REPEATABLE: execute CMD on message (default: None)"),
+    new Argument("--xban", "REPEATABLE: execute CMD if someone gets banned (pw/404/403/url) (default: None)"),
+    new Argument("--hook-v", "verbose hooks (default: False)"),
+    new Argument("--stats", "enable openmetrics at /.cpr/metrics for admin accounts (default: False)"),
+    new Argument("--stats-u", "comma-separated list of users allowed to access /.cpr/metrics even if they aren't admin (default: )"),
+    new Argument("--nos-hdd", "disable disk-space metrics (used/free space) (default: False)"),
+    new Argument("--nos-vol", "disable volume size metrics (num files, total bytes, vmaxb/vmaxn) (default: False)"),
+    new Argument("--nos-vst", "disable volume state metrics (indexing, analyzing, activity) (default: False)"),
+    new Argument("--nos-dup", "disable dupe-files metrics (good idea; very slow) (default: False)"),
+    new Argument("--nos-unf", "disable unfinished-uploads metrics (default: False)"),
+    new Argument("--rw-edit", "comma-sep. list of file-extensions to allow editing with permissions read+write; all others require read+write+delete (volflag=rw_edit) (default: md)"),
+    new Argument("--md-no-br", "markdown: disable newline-is-newline; will only render a newline into the html given two trailing spaces or a double-newline (volflag=md_no_br) (default: False)"),
+    new Argument("--md-hist", "where to store old version of markdown files; [s]=subfolder, [v]=volume-histpath, [n]=nope/disabled (volflag=md_hist) (default: s)"),
+    new Argument("--txt-eol", "enable EOL conversion when writing documents; supported: CRLF, LF (volflag=txt_eol) (default: )"),
+    new Argument("-mcr", "the textfile editor will check for serverside changes every SEC seconds (default: 60)"),
+    new Argument("-emp", "enable markdown plugins -- neat but dangerous, big XSS risk (default: False)"),
+    new Argument("--exp", "enable textfile expansion -- replace {{self.ip}} and such; see --help-exp (volflag=exp) (default: False)"),
+    new Argument("--exp-md", "comma/space-separated list of placeholders to expand in markdown files; add/remove stuff on the default list with +hdr_foo or /vf.scan (volflag=exp_md) (default: self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf-ipcountry srv.itime srv.htime)"),
+    new Argument("--exp-lg", "comma/space-separated list of placeholders to expand in prologue/epilogue files (volflag=exp_lg) (default: self.ip self.ua self.uname self.host cfg.name cfg.logout vf.scan vf.thsize hdr.cf-ipcountry srv.itime srv.htime)"),
+    new Argument("--ua-nodoc", "regex of user-agents to reject from viewing documents through ?doc=[...]; disable with [no] or blank (default: Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot)"),
+    new Argument("--tail-who", "who can tail? [0]=nobody, [1]=admins, [2]=authenticated-with-read-access, [3]=everyone-with-read-access (volflag=tail_who) (default: 2)"),
+    new Argument("--tail-cmax", "do not allow starting a new tail if more than N active downloads (default: 64)"),
+    new Argument("--tail-tmax", "terminate connection after SEC seconds; [0]=never (volflag=tail_tmax) (default: 0)"),
+    new Argument("--tail-rate", "check for new data every SEC seconds (volflag=tail_rate) (default: 0.2)"),
+    new Argument("--tail-ka", "send a zerobyte if connection is idle for SEC seconds to prevent disconnect (default: 3.0)"),
+    new Argument("--tail-fd", "check if file was replaced (new fd) if idle for SEC seconds (volflag=tail_fd) (default: 1.0)"),
+    new Argument("--og", "disable hotlinking and return an html document instead; this is required by open-graph, but can also be useful on its own (volflag=og) (default: False)"),
+    new Argument("--og-ua", "only disable hotlinking / engage OG behavior if the useragent matches regex RE (volflag=og_ua) (default: )"),
+    new Argument("--og-tpl", "do not return the regular copyparty html, but instead load the jinja2 template at PATH (if path contains 'EXT' then EXT will be replaced with the requested file's extension) (volflag=og_tpl) (default: )"),
+    new Argument("--og-no-head", "do not automatically add OG entries into <head> (useful if you're doing this yourself in a template or such) (volflag=og_no_head) (default: False)"),
+    new Argument("--og-th", "thumbnail format; j=jpeg, jf=jpeg-uncropped, jf3=jpeg-uncropped-large, w=webm, ... (volflag=og_th) (default: jf3)"),
+    new Argument("--og-title", "fallback title if there is nothing in the -e2t database (volflag=og_title) (default: )"),
+    new Argument("--og-title-a", "audio title format; takes any metadata key (volflag=og_title_a) (default: 🎵 {{ artist }} - {{ title }})"),
+    new Argument("--og-title-v", "video title format; takes any metadata key (volflag=og_title_v) (default: {{ title }})"),
+    new Argument("--og-title-i", "image title format; takes any metadata key (volflag=og_title_i) (default: {{ title }})"),
+    new Argument("--og-s-title", "force default title; do not read from tags (volflag=og_s_title) (default: False)"),
+    new Argument("--og-desc", "description text; same for all files, disable with [-] (volflag=og_desc) (default: )"),
+    new Argument("--og-site", "sitename; defaults to --name, disable with [-] (volflag=og_site) (default: )"),
+    new Argument("--tcolor", "accent color (3 or 6 hex digits); may also affect safari and/or android-chrome (volflag=tcolor) (default: 333)"),
+    new Argument("--uqe", "query-string parceling; translate a request for /foo/.uqe/BASE64 into /foo?TEXT, or /foo/?TEXT if the first character in TEXT is a slash. Automatically enabled for --og (default: False)"),
+    new Argument("--grid", "show grid/thumbnails by default (volflag=grid) (default: False)"),
+    new Argument("--gsel", "select files in grid by ctrl-click (volflag=gsel) (default: False)"),
+    new Argument("--localtime", "default to local timezone instead of UTC (default: False)"),
+    new Argument("--ui-filesz", "default filesize format; one of these: 0, 1, 2, 2c, 3, 3c, 4, 4c, 5, 5c, 6, 6c, 7, 7c, fuzzy (see UI) (default: 1)"),
+    new Argument("--gauto", "switch to gridview if more than PERCENT of files are pics/vids; 0=disabled (default: 0)"),
+    new Argument("--rcm", "rightclick-menu; two yes/no options: 1st y/n is enable-custom-menu, 2nd y/n is enable-double (default: yy)"),
+    new Argument("--lang", "language, for example eng / nor / ... (default: eng)"),
+    new Argument("--glang", "guess the browser's default language, otherwise fall back to --lang (default: False)"),
+    new Argument("--theme", "default theme to use (0..9) (default: 0)"),
+    new Argument("--themes", "number of themes installed (default: 10)"),
+    new Argument("--au-vol", "default audio/video volume percent (default: 50)"),
+    new Argument("--sort", "default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: href -href ext sz ts tags/Album tags/.tn (volflag=sort) (default: href)"),
+    new Argument("--nsort", "default-enable natural sort of filenames with leading numbers (volflag=nsort) (default: False)"),
+    new Argument("--hsortn", "number of sorting rules to include in media URLs by default (volflag=hsortn) (default: 2)"),
+    new Argument("--see-dots", "default-enable seeing dotfiles; only takes effect if user has the necessary permissions (default: False)"),
+    new Argument("--qdel", "number of confirmations to show when deleting files (2/1/0) (default: 2)"),
+    new Argument("--dlni", "force download (don't show inline) when files are clicked (volflag:dlni) (default: False)"),
+    new Argument("--unlist", "don't show files/folders matching REGEX in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\\.(js|css)\\$] (volflag=unlist) (default: )"),
+    new Argument("--dothidden", "hide specific files in a folder by listing them in a file named .hidden -- WARNING: Mostly cosmetic! Download-as-zip/tar will still download them. Do not rely on this for security (volflag=dothidden) (default: False)"),
+    new Argument("--favico", "favicon-text [ foreground [ background ] ], set blank to disable (default: 🎉 000 none)"),
+    new Argument("--ufavico", "URL to .ico/png/gif/svg file; --favico takes precedence unless disabled (volflag=ufavico) (default: )"),
+    new Argument("--bg-img", "URL to .jpg/png/gif/svg file (volflag=bg_img)"),
+    new Argument("--ext-th", "REPEATABLE: use thumbnail-image VP for file-extension E, example: [exe=/.res/exe.png] (volflag=ext_th) (default: None)"),
+    new Argument("--notooltips", "tooltips disabled as default (default: False)"),
+    new Argument("--spinner", "emoji or emoji,css Example: [🥖,padding:0] (default: 🌲)"),
+    new Argument("--css-browser", "URL to additional CSS to include in the filebrowser html (default: )"),
+    new Argument("--js-browser", "URL to additional JS to include in the filebrowser html (default: )"),
+    new Argument("--js-other", "URL to additional JS to include in all other pages (default: )"),
+    new Argument("--html-head", "text to append to the <head> of all HTML pages (except for basic-browser); can be @PATH to send the contents of a file at PATH, and/or begin with % to render as jinja2 template (volflag=html_head) (default: )"),
+    new Argument("--html-head-s", "text to append to the <head> of all HTML pages (except for basic-browser); similar to (and can be combined with) --html-head but only accepts static text (volflag=html_head_s) (default: )"),
+    new Argument("--ih", "if a folder contains index.html, show that instead of the directory listing by default (can be changed in the client settings UI, or add ?v to URL for override) (default: False)"),
+    new Argument("--textfiles", "file extensions to present as plaintext (default: txt,nfo,diz,cue,readme)"),
+    new Argument("--txt-max", "max size of embedded textfiles on ?doc= (anything bigger will be lazy-loaded by JS) (default: 64)"),
+    new Argument("--prologues", "comma-sep. list of filenames to scan for and use as prologues (embed above/before directory listing) (volflag=prologues) (default: .prologue.html)"),
+    new Argument("--epilogues", "comma-sep. list of filenames to scan for and use as epilogues (embed below/after directory listing) (volflag=epilogues) (default: .epilogue.html)"),
+    new Argument("--preadmes", "comma-sep. list of filenames to scan for and use as preadmes (embed above/before directory listing) (volflag=preadmes) (default: preadme.md,PREADME.md)"),
+    new Argument("--readmes", "comma-sep. list of filenames to scan for and use as readmes (embed below/after directory listing) (volflag=readmes) (default: readme.md,README.md)"),
+    new Argument("--doctitle", "title / service-name to show in html documents (default: copyparty @ --name)"),
+    new Argument("--bname", "server name (displayed in filebrowser document title) (default: --name)"),
+    new Argument("--pb-url", "powered-by link; disable with -nb (default: https://github.com/9001/copyparty)"),
+    new Argument("--ver", "show version on the control panel (incompatible with -nb). This is the same as --ver-who all (default: False)"),
+    new Argument("--ver-who", "only show version for: [a]=admin-permission-anywhere, [auth]=authenticated, [all]=anyone (default: no)"),
+    new Argument("--du-who", "only show disk usage for: [no]=nobody, [a]=admin-permission, [rw]=read-write, [w]=write, [auth]=authenticated, [all]=anyone (volflag=du_who) (default: all)"),
+    new Argument("--k304", "configure the option to enable/disable k304 on the controlpanel (workaround for buggy reverse-proxies); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on (default: 0)"),
+    new Argument("--no304", "configure the option to enable/disable no304 on the controlpanel (workaround for buggy caching in browsers); [0] = hidden and default-off, [1] = visible and default-off, [2] = visible and default-on (default: 0)"),
+    new Argument("--ctl-re", "the controlpanel Refresh-button will autorefresh every SEC; [0] = just once (default: 1)"),
+    new Argument("--md-sbf", "list of capabilities to allow in the iframe 'sandbox' attribute for README.md docs (volflag=md_sbf); see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox (default: downloads forms popups scripts top-navigation-by-user-activation)"),
+    new Argument("--lg-sbf", "list of capabilities to allow in the iframe 'sandbox' attribute for prologue/epilogue docs (volflag=lg_sbf) (default: downloads forms popups scripts top-navigation-by-user-activation)"),
+    new Argument("--md-sba", "the value of the iframe 'allow' attribute for README.md docs, for example [fullscreen] (volflag=md_sba) (default: )"),
+    new Argument("--lg-sba", "the value of the iframe 'allow' attribute for prologue/epilogue docs (volflag=lg_sba); see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#iframes (default: )"),
+    new Argument("--no-sb-md", "don't sandbox README/PREADME.md documents (volflags: no_sb_md | sb_md) (default: False)"),
+    new Argument("--no-sb-lg", "don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support (default: False)"),
+    new Argument("--ui-nombar", "hide top-menu in the UI (volflag=ui_nombar) (default: False)"),
+    new Argument("--ui-noacci", "hide account-info in the UI (volflag=ui_noacci) (default: False)"),
+    new Argument("--ui-nosrvi", "hide server-info in the UI (volflag=ui_nosrvi) (default: False)"),
+    new Argument("--ui-nonav", "hide navpane+breadcrumbs (volflag=ui_nonav) (default: False)"),
+    new Argument("--ui-notree", "hide navpane in the UI (volflag=ui_notree) (default: False)"),
+    new Argument("--ui-nocpla", "hide cpanel-link in the UI (volflag=ui_nocpla) (default: False)"),
+    new Argument("--ui-nolbar", "hide link-bar in the UI (volflag=ui_nolbar) (default: False)"),
+    new Argument("--ui-noctxb", "hide context-buttons in the UI (volflag=ui_noctxb) (default: False)"),
+    new Argument("--ui-norepl", "hide repl-button in the UI (volflag=ui_norepl) (default: False)"),
+    new Argument("--unfun", "hides many of the emojis in the UI (default: False)"),
+    new Argument("--no-reload", "disable ?reload=cfg (reload users/volumes/volflags from config file) (default: False)"),
+    new Argument("--no-rescan", "disable ?scan (volume reindexing) (default: False)"),
+    new Argument("--no-stack", "disable ?stack (list all stacks); same as --stack-who=no (default: False)"),
+    new Argument("--no-ups-page", "disable ?ru (list of recent uploads) (default: False)"),
+    new Argument("--no-up-list", "don't show list of incoming files in controlpanel (default: False)"),
+    new Argument("--dl-list", "who can see active downloads in the controlpanel? [0]=nobody, [1]=admins, [2]=everyone (default: 2)"),
+    new Argument("--ups-who", "who can see recent uploads on the ?ru page? [0]=nobody, [1]=admins, [2]=everyone (volflag=ups_who) (default: 2)"),
+    new Argument("--ups-when", "let everyone see upload timestamps on the ?ru page, not just admins (default: False)"),
+    new Argument("--stack-who", "who can see the ?stack page (list of threads)? [no]=nobody, [a]=admins, [rw]=read+write, [all]=everyone (default: a)"),
+    new Argument("--stack-v", "verbose ?stack (default: False)"),
+    new Argument("-q", "quiet; disable most STDOUT messages (default: False)"),
+    new Argument("-lo", "logfile; use .txt for plaintext or .xz for compressed. Example: cpp-%Y-%m%d-%H%M%S.txt.xz (NB: some errors may appear on STDOUT only) (default: )"),
+    new Argument("--flo", "log format for -lo; [1]=classic/colors, [2]=no-color (default: 1)"),
+    new Argument("--rlo", "logrotate counter format; see --help-rlo (default: .1)"),
+    new Argument("--logrot-sig", "immediately logrotate when unix-signal S is received; examples: [SIGHUP], [HUP], [1] (default: )"),
+    new Argument("--no-ansi", "disable colors; same as environment-variable NO_COLOR (default: False)"),
+    new Argument("--ansi", "force colors; overrides environment-variable NO_COLOR (default: False)"),
+    new Argument("--no-logflush", "don't flush the logfile after each write; tiny bit faster (default: False)"),
+    new Argument("--no-voldump", "do not list volumes and permissions on startup (default: False)"),
+    new Argument("--log-utc", "do not use local timezone; assume the TZ env-var is UTC (tiny bit faster) (default: False)"),
+    new Argument("--log-tdec", "timestamp resolution / number of timestamp decimals (default: 3)"),
+    new Argument("--log-date", "date-format, for example [%Y-%m-%d] (default is disabled; no date, just HH:MM:SS) (default: )"),
+    new Argument("--log-badpwd", "log failed login attempt passwords: 0=terse, 1=plaintext, 2=hashed (default: 2)"),
+    new Argument("--log-badxml", "log any invalid XML received from a client (default: False)"),
+    new Argument("--log-conn", "debug: print tcp-server msgs (default: False)"),
+    new Argument("--log-htp", "debug: print http-server threadpool scaling (default: False)"),
+    new Argument("--ihead", "print request HEADER; [*]=all (default: None)"),
+    new Argument("--ohead", "print response HEADER; [*]=all (default: None)"),
+    new Argument("--lf-url", "dont log URLs matching regex RE (default: ^/\\.cpr/|[?&]th=[xwjp]|/\\.(_|ql_|DS_Store\\$|localized\\$))"),
+    new Argument("--scan-st-r", "fs-indexing: wait SEC between each status-message (default: 0.1)"),
+    new Argument("--scan-pr-r", "fs-indexing: wait SEC between each 'progress:' message (default: 10)"),
+    new Argument("--scan-pr-s", "fs-indexing: say 'file: <name>' when a file larger than MiB is about to be hashed (default: 1)"),
+    new Argument("--vc", "verbose config file parser (explain config) (default: False)"),
+    new Argument("--cgen", "generate config file from current config (best-effort; probably buggy) (default: False)"),
+    new Argument("--deps", "list information about detected optional dependencies (default: False)"),
+    new Argument("--no-poll", "kernel-bug workaround: disable poll; use select instead (limits max num clients to ~700) (default: False)"),
+    new Argument("--no-sendfile", "kernel-bug workaround: disable sendfile; do a safe and slow read-send-loop instead (default: False)"),
+    new Argument("--no-scandir", "kernel-bug workaround: disable scandir; do a listdir + stat on each file instead (default: False)"),
+    new Argument("--no-fastboot", "wait for initial filesystem indexing before accepting client requests (default: False)"),
+    new Argument("--no-htp", "disable httpserver threadpool, create threads as-needed instead (default: False)"),
+    new Argument("--sig-thr", "start separate thread for OS-signals (try this if CTRL-C is busted) (default: False)"),
+    new Argument("--rm-sck", "when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind (default: False)"),
+    new Argument("--srch-dbg", "explain search processing, and do some extra expensive sanity checks (default: False)"),
+    new Argument("--rclone-mdns", "use mdns-domain instead of server-ip on /?hc (default: False)"),
+    new Argument("--stackmon", "write stacktrace to Path every S second, for example --stackmon=./st/%Y-%m/%d/%H%M.xz,60 (default: )"),
+    new Argument("--stack-sig", "show stacktrace when unix-signal S is received; examples: [SIGUSR2], [USR2], [12] (default: )"),
+    new Argument("--log-thrs", "list active threads every SEC (default: 0.0)"),
+    new Argument("--log-fk", "log filekey params for files where path matches REGEX; [.] (a single dot) = all files (default: )"),
+    new Argument("--bak-flips", "[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to --bf-nc and --bf-dir (default: False)"),
+    new Argument("--bf-nc", "bak-flips: stop if there's more than NUM files at --kf-dir already; default: 6.3 GiB max (200*32M) (default: 200)"),
+    new Argument("--bf-dir", "bak-flips: store corrupted chunks at PATH; default: folder named 'bf' wherever copyparty was started (default: bf)"),
+    new Argument("--bf-log", "bak-flips: log corruption info to a textfile at PATH (default: )"),
+    new Argument("--help-bind", "configure listening (default: False)"),
+    new Argument("--help-accounts", "accounts and volumes (default: False)"),
+    new Argument("--help-auth", "how to login from a client (default: False)"),
+    new Argument("--help-auth-ord", "authentication precedence (default: False)"),
+    new Argument("--help-flags", "list of volflags (default: False)"),
+    new Argument("--help-handlers", "use plugins to handle certain events (default: False)"),
+    new Argument("--help-hooks", "execute commands before/after various events (default: False)"),
+    new Argument("--help-idp", "replacing the login system with fancy middleware (default: False)"),
+    new Argument("--help-urlform", "how to handle url-form POSTs (default: False)"),
+    new Argument("--help-exp", "text expansion (default: False)"),
+    new Argument("--help-rlo", "logrotate format (default: False)"),
+    new Argument("--help-ls", "volume inspection (default: False)"),
+    new Argument("--help-dbd", "database durability profiles (default: False)"),
+    new Argument("--help-chmod", "file/folder permissions (default: False)"),
+    new Argument("--help-pwhash", "password hashing (default: False)"),
+    new Argument("--help-zm", "mDNS debugging (default: False)"),
+];
+
+// ^\s*([^\s=]+)(?:\s|=)(.*)$
+var volflags = [
+    new Argument("dedup", "enable symlink-based file deduplication"),
+    new Argument("hardlink", "enable hardlink-based file deduplication,with fallback on symlinks when that is impossible"),
+    new Argument("hardlinkonly", "dedup with hardlink only, never symlink;make a full copy if hardlink is impossible"),
+    new Argument("reflink", "enable reflink-based file deduplication,with fallback on full copy when that is impossible"),
+    new Argument("safededup", "verify on-disk data before using it for dedup"),
+    new Argument("noclone", "take dupe data from clients, even if available on HDD"),
+    new Argument("nodupe", "rejects existing files (instead of linking/cloning them)"),
+    new Argument("nodupem", "rejects existing files during moves as well"),
+    new Argument("casechk", "auto actively prevent case-insensitive filesystem? y/n"),
+    new Argument("chmod_d", "755 unix-permission for new dirs/folders"),
+    new Argument("chmod_f", "644 unix-permission for new files"),
+    new Argument("uid", "573 change owner of new files/folders to unix-user 573"),
+    new Argument("gid", "999 change owner of new files/folders to unix-group 999"),
+    new Argument("fsnt", "auto filesystem filename traits (lin/win/mac/auto)"),
+    new Argument("wram", "allow uploading into ramdisks"),
+    new Argument("sparse", "force use of sparse files, mainly for s3-backed storage"),
+    new Argument("nosparse", "deny use of sparse files, mainly for slow storage"),
+    new Argument("rm_partial", "delete unfinished uploads from HDD when they timeout"),
+    new Argument("daw", "enable full WebDAV write support (dangerous);PUT-operations will now OVERWRITE existing files"),
+    new Argument("nosub", "forces all uploads into the top folder of the vfs"),
+    new Argument("magic", "enables filetype detection for nameless uploads"),
+    new Argument("put_name", "fallback filename for nameless uploads"),
+    new Argument("put_ck", "default checksum-hasher for PUT/WebDAV uploads"),
+    new Argument("bup_ck", "default checksum-hasher for bup/basic uploads"),
+    new Argument("gz", "allows server-side gzip compression of uploads with ?gz"),
+    new Argument("xz", "allows server-side lzma compression of uploads with ?xz"),
+    new Argument("pk", "forces server-side compression, optional arg: xz,9"),
+    new Argument("apnd_who", "dw who can append? (aw/dw/w/no)"),
+    new Argument("maxn", "250,600 max 250 uploads over 15min"),
+    new Argument("maxb", "1g,300 max 1 GiB over 5min (suffixes: b, k, m, g, t)"),
+    new Argument("vmaxb", "1g total volume size max 1 GiB (suffixes: b, k, m, g, t)"),
+    new Argument("vmaxn", "4k max 4096 files in volume (suffixes: b, k, m, g, t)"),
+    new Argument("medialinks", "return medialinks for non-up2k uploads (not hotlinks)"),
+    new Argument("wo_up_readme", "write-only users can upload logues without getting renamed"),
+    new Argument("rand", "force randomized filenames, 9 chars long by default"),
+    new Argument("nrand", "N randomized filenames are N chars long"),
+    new Argument("u2ow", "N overwrite existing files? 0=no 1=if-older 2=always"),
+    new Argument("u2ts", "fc [f]orce [c]lient-last-modified or [u]pload-time"),
+    new Argument("u2abort", "1 allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk"),
+    new Argument("sz", "1k-3m allow filesizes between 1 KiB and 3MiB"),
+    new Argument("df", "1g ensure 1 GiB free disk space"),
+    new Argument("rotn", "100,3 3 levels of subfolders with 100 entries in each"),
+    new Argument("rotf", "%Y-%m/%d-%H date-formatted organizing"),
+    new Argument("rotf_tz", "Europe/Oslo timezone (default=UTC)"),
+    new Argument("lifetime", "3600 uploads are deleted after 1 hour"),
+    new Argument("e2d", "enable database; makes files searchable + enables upload-undo"),
+    new Argument("e2ds", "scan writable folders for new files on startup; also sets -e2d"),
+    new Argument("e2dsa", "scans all folders for new files on startup; also sets -e2d"),
+    new Argument("e2t", "enable multimedia indexing; makes it possible to search for tags"),
+    new Argument("e2ts", "scan existing files for tags on startup; also sets -e2t"),
+    new Argument("e2tsr", "delete all metadata from DB (full rescan); also sets -e2ts"),
+    new Argument("d2ts", "disables metadata collection for existing files"),
+    new Argument("e2v", "verify integrity on startup by hashing files and comparing to db"),
+    new Argument("e2vu", "when e2v fails, update the db (assume on-disk files are good)"),
+    new Argument("e2vp", "when e2v fails, panic and quit copyparty"),
+    new Argument("d2ds", "disables onboot indexing, overrides -e2ds*"),
+    new Argument("d2t", "disables metadata collection, overrides -e2t*"),
+    new Argument("d2v", "disables file verification, overrides -e2v*"),
+    new Argument("d2d", "disables all database stuff, overrides -e2*"),
+    new Argument("hist", "/tmp/cdb puts thumbnails and indexes at that location"),
+    new Argument("dbpath", "/tmp/cdb puts indexes at that location"),
+    new Argument("landmark", "foo disable db if file foo doesn't exist"),
+    new Argument("scan", "60 scan for new files every 60sec, same as --re-maxage"),
+    new Argument("nohash", "\\.iso\$ skips hashing file contents if path matches *.iso"),
+    new Argument("noidx", "\\.iso\$ fully ignores the contents at paths matching *.iso"),
+    new Argument("noforget", "don't forget files when deleted from disk"),
+    new Argument("forget_ip", "43200 forget uploader-IP after 30 days (GDPR)"),
+    new Argument("no_db_ip", "never store uploader-IP in the db; disables unpost"),
+    new Argument("fat32", "avoid excessive reindexing on android sdcardfs"),
+    new Argument("dbd", "[acid|swal|wal|yolo] database speed-durability tradeoff"),
+    new Argument("xlink", "cross-volume dupe detection / linking (dangerous)"),
+    new Argument("xdev", "do not descend into other filesystems"),
+    new Argument("xvol", "do not follow symlinks leaving the volume root"),
+    new Argument("dotsrch", "show dotfiles in search results"),
+    new Argument("nodotsrch", "hide dotfiles in search results (default)"),
+    new Argument("srch_excl", "exclude search results with URL matching this regex"),
+    new Argument("db_xattr", "user.foo,user.bar index file xattrs as media-tags"),
+    new Argument("mte", "artist,title media-tags to index/display"),
+    new Argument("mth", "fmt,res,ac media-tags to hide by default"),
+    new Argument("mtp", ".bpm=f,audio-bpm.py uses the \"audio-bpm.py\" program togenerate \".bpm\" tags from uploads (f = overwrite tags)"),
+    new Argument("mtp", "ahash,vhash=media-hash.py collects two tags at once"),
+    new Argument("dthumb", "disables all thumbnails"),
+    new Argument("dvthumb", "disables video thumbnails"),
+    new Argument("dathumb", "disables audio thumbnails (spectrograms)"),
+    new Argument("dithumb", "disables image thumbnails"),
+    new Argument("pngquant", "compress audio waveforms 33% better"),
+    new Argument("thsize", "thumbnail res; WxH"),
+    new Argument("crop", "center-cropping (y/n/fy/fn)"),
+    new Argument("th3x", "3x resolution (y/n/fy/fn)"),
+    new Argument("th_qv", "40 webp/jpg thumbnail quality (10~90)"),
+    new Argument("th_qvx", "40 jxl thumbnail quality (10~90)"),
+    new Argument("convt", "convert-to-image timeout in seconds"),
+    new Argument("aconvt", "convert-to-audio timeout in seconds"),
+    new Argument("th_spec_p", "1 make spectrograms? 0=never 1=fallback 2=always"),
+    new Argument("ext_th", "s=/b.png use /b.png as thumbnail for file-extension s"),
+    new Argument("th_pregen", "w,wf pregenerate thumbs for these formats"),
+    new Argument("on404", "PY handle 404s by executing PY file"),
+    new Argument("on403", "PY handle 403s by executing PY file"),
+    new Argument("xbu", "CMD execute CMD before a file upload starts"),
+    new Argument("xau", "CMD execute CMD after  a file upload finishes"),
+    new Argument("xiu", "CMD execute CMD after  all uploads finish and volume is idle"),
+    new Argument("xbc", "CMD execute CMD before a file copy"),
+    new Argument("xac", "CMD execute CMD after  a file copy"),
+    new Argument("xbr", "CMD execute CMD before a file rename/move"),
+    new Argument("xar", "CMD execute CMD after  a file rename/move"),
+    new Argument("xbd", "CMD execute CMD before a file delete"),
+    new Argument("xad", "CMD execute CMD after  a file delete"),
+    new Argument("xm", "CMD execute CMD on message"),
+    new Argument("xban", "CMD execute CMD if someone gets banned"),
+    new Argument("grid", "show grid/thumbnails by default"),
+    new Argument("gsel", "select files in grid by ctrl-click"),
+    new Argument("sort", "default sort order"),
+    new Argument("nsort", "natural-sort of leading digits in filenames"),
+    new Argument("hsortn", "number of sort-rules to add to media URLs"),
+    new Argument("ufavico", "URL per-volume favicon (.ico/png/gif/svg)"),
+    new Argument("bg_img", "background image url"),
+    new Argument("theme", "default theme"),
+    new Argument("unlist", "dont list files matching REGEX"),
+    new Argument("dothidden", "enable support for .hidden files"),
+    new Argument("dlni", "force-download (no-inline) files on click"),
+    new Argument("html_head", "TXT includes TXT in the <head>, or @PATH for file at PATH"),
+    new Argument("html_head_s", "TXT additional static text in the html <head>"),
+    new Argument("tcolor", "#fc0 theme color (a hint for webbrowsers, discord, etc.)"),
+    new Argument("nodirsz", "don't show total folder size"),
+    new Argument("du_who", "all show disk-usage info to everyone"),
+    new Argument("robots", "allows indexing by search engines (default)"),
+    new Argument("norobots", "kindly asks search engines to leave"),
+    new Argument("unlistcr", "don't list read-access in controlpanel"),
+    new Argument("unlistcw", "don't list write-access in controlpanel"),
+    new Argument("prologues", ".prologue.html files to embed above/before files"),
+    new Argument("epilogues", ".epilogue.html files to embed below/after files"),
+    new Argument("readmes", "readme.md,README.md files to embed as readmes"),
+    new Argument("preadmes", "preadme.md,PREADME.md files to embed as preadmes"),
+    new Argument("no_sb_md", "disable js sandbox for markdown files"),
+    new Argument("no_sb_lg", "disable js sandbox for prologue/epilogue"),
+    new Argument("sb_md", "enable js sandbox for markdown files (default)"),
+    new Argument("sb_lg", "enable js sandbox for prologue/epilogue (default)"),
+    new Argument("md_sbf", "list of markdown-sandbox safeguards to disable"),
+    new Argument("lg_sbf", "list of *logue-sandbox safeguards to disable"),
+    new Argument("md_sba", "value of iframe allow-prop for markdown-sandbox"),
+    new Argument("lg_sba", "value of iframe allow-prop for *logue-sandbox"),
+    new Argument("nohtml", "return html and markdown as text/html"),
+    new Argument("noscript", "disable most javascript by use of CSP"),
+    new Argument("ui_noacci", "hide account-info in the UI"),
+    new Argument("ui_nocpla", "hide cpanel-link in the UI"),
+    new Argument("ui_nolbar", "hide link-bar in the UI"),
+    new Argument("ui_nombar", "hide top-menu in the UI"),
+    new Argument("ui_nonav", "hide navpane+breadcrumbs in the UI"),
+    new Argument("ui_notree", "hide navpane in the UI"),
+    new Argument("ui_norepl", "hide repl-button in the UI"),
+    new Argument("ui_nosrvi", "hide server-info in the UI"),
+    new Argument("ui_noctxb", "hide context-buttons in the UI"),
+    new Argument("og", "enable OG (disables hotlinking)"),
+    new Argument("og_site", "sitename; defaults to --name, disable with '-'"),
+    new Argument("og_desc", "description text for all files; disable with '-'"),
+    new Argument("og_th", "jf thumbnail format; j / jf / jf3 / w / w3 / ..."),
+    new Argument("og_title_a", "audio title format; default: {{ artist }} - {{ title }}"),
+    new Argument("og_title_v", "video title format; default: {{ title }}"),
+    new Argument("og_title_i", "image title format; default: {{ title }}"),
+    new Argument("og_title", "foo fallback title if there's nothing in the db"),
+    new Argument("og_s_title", "force default title; do not read from tags"),
+    new Argument("og_tpl", "custom html; see --og-tpl in --help"),
+    new Argument("og_no_head", "you want to add tags manually with og_tpl"),
+    new Argument("og_ua", "if defined: only send OG html if useragent matches this regex"),
+    new Argument("opds", "enable OPDS"),
+    new Argument("opds_exts", "file formats to list in OPDS feeds; leave empty to show everything"),
+    new Argument("rw_edit", "md,txt only require read+write to edit .md and .txt"),
+    new Argument("md_no_br", "newline only on double-newline or two tailing spaces"),
+    new Argument("md_hist", "where to put markdown backups; s=subfolder, v=volHist, n=nope"),
+    new Argument("exp", "enable textfile expansion; see --help-exp"),
+    new Argument("exp_md", "placeholders to expand in markdown files; see --help"),
+    new Argument("exp_lg", "placeholders to expand in prologue/epilogue; see --help"),
+    new Argument("txt_eol", "lf enable EOL conversion when writing docs (LF or CRLF)"),
+    new Argument("notail", "disable ?tail (download a growing file continuously)"),
+    new Argument("tail_fd", "1 check if file was replaced (new fd) every 1 sec"),
+    new Argument("tail_rate", "0.2 check for new data every 0.2 sec"),
+    new Argument("tail_tmax", "30 kill connection after 30 sec"),
+    new Argument("tail_who", "2 restrict ?tail access (1=admins,2=authed,3=everyone)"),
+    new Argument("rss", "allow '?rss' URL suffix (experimental)"),
+    new Argument("rss_sort", "m default sort-order (m/u/n/s)"),
+    new Argument("rss_fmt_t", "{fname} default title-format"),
+    new Argument("rss_fmt_d", "{album},{.tn} default description-format"),
+    new Argument("dots", "allow all users with read-access toenable the option to show dotfiles in listings"),
+    new Argument("fk", "8 generates per-file accesskeys,which are then required at the \"g\" permission;keys are invalidated if filesize or inode changes"),
+    new Argument("fka", "8 generates slightly weaker per-file accesskeys,which are then required at the \"g\" permission;not affected by filesize or inode numbers"),
+    new Argument("dk", "8 generates per-directory accesskeys,which are then required at the \"g\" permission;keys are invalidated if filesize or inode changes"),
+    new Argument("dks", "per-directory accesskeys allow browsing into subdirs"),
+    new Argument("dky", "allow seeing files (not folders) inside a specific folderwith \"g\" perm, and does not require a valid dirkey to do so"),
+    new Argument("rmagic", "expensive analysis for mimetype accuracy"),
+    new Argument("shr_who", "auth who can create shares? no/auth/a"),
+    new Argument("unp_who", "2 unpost only if same... 1=ip+name, 2=ip, 3=name"),
+    new Argument("ups_who", "2 restrict viewing the list of recent uploads"),
+    new Argument("zip_who", "2 restrict access to download-as-zip/tar"),
+    new Argument("zipmaxn", "9k reject download-as-zip if more than 9000 files"),
+    new Argument("zipmaxs", "2g reject download-as-zip if size over 2 GiB"),
+    new Argument("zipmaxt", "no reply with 'no' if download-as-zip exceeds max"),
+    new Argument("zipmaxu", "zip-size-limit does not apply to authenticated users"),
+    new Argument("nopipe", "disable race-the-beam (download unfinished uploads)"),
+    new Argument("cachectl", "no-cache controls caching in webbrowsers"),
+    new Argument("mv_retry", "ms-windows: timeout for renaming busy files"),
+    new Argument("rm_retry", "ms-windows: timeout for deleting busy files"),
+    new Argument("nospawn", "don't create volume's folder if not exist"),
+    new Argument("assert_root", "crash on startup if volume's folder not exist"),
+    new Argument("davauth", "ask webdav clients to login for all folders"),
+    new Argument("davrt", "show lastmod time of symlink destination, not the link itself(note: this option is always enabled for recursive listings)"),
+]
+
+
+// main code ________________________________________________________________________________________________
+
+
+ebi('rpre').value = sread('rprefix') ?? 'python -m copyparty'
+ebi('rpre').oninput = function(){
+    swrite('rprefix', this.value)
+}
+
+// get flags
+var flagsConf = jread("flagsConf", []);
+
+// these flags have a better, feature-complete way to be configured
+var uiOnlyFlags = QSA('input[data-flag]')
+function loadUiFlags(){
+    for(var i = 0; i < uiOnlyFlags.length; i++){
+        var elem = uiOnlyFlags[i]
+        var cmd = elem.getAttribute('data-flag')
+        var val = flagsConf.filter((f) => f.cmd == cmd)[0]?.value;
+        if(elem){
+            var n = cmd + '+'
+            var o = QS('input[data-flag="' + n + '"]');
+            if(o){
+                var arr = val ? val.split(',') : val
+                elem.value = arr ? arr[0] : '';
+                var j = 1
+                while(o){
+                    var val2 = arr ? arr[j] : ''
+                    if(val2){
+                        var prefix = o.getAttribute('data-prefix');
+                        if(prefix) val2 = val2.replace(prefix, '')
+                        o.value = val2
+                    }
+                    else o.value = ''
+                    n += '+'
+                    j++;
+                    o = QS('input[data-flag="' + n + '"]');
+                }
+            }
+            else {
+                if (elem.getAttribute('type') == 'checkbox' || elem.getAttribute('type') == 'radio')
+                    elem.checked = val ? true : false;
+                else
+                    elem.value = val != undefined ? val : ''
+            }
+        }
+        elem.oninput = uiOptInput;
+    }
+};
+var synching = false;
+var uiOptInput = function(){
+    if(synching) return;
+
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=2035033
+    if(FIREFOX && this.value == '#000000') return;
+
+    var cmd = this.getAttribute('data-flag');
+    if(!cmd) return;
+    var e1 = this;
+    if(cmd.endsWith('+')){
+        cmd = cmd.replaceAll('+', '');
+        e1 = QS('input[data-flag="' + cmd + '"]')
+    }
+    var val = e1.value
+    
+    // collect all parts of cmd
+    var n = cmd + '+'
+    var o = QS('input[data-flag="' + n + '"]');
+    if(o){
+        while(o){
+            if(o.value){
+                var prefix = o.getAttribute('data-prefix');
+                val += ',' + (prefix ? prefix : '') + o.value;
+            }
+            n += '+'
+            o = QS('input[data-flag="' + n + '"]');
+        }
+    }
+    console.log(val)
+    if (e1.getAttribute('type') == 'checkbox' || e1.getAttribute('type') == 'radio')
+        val = e1.checked ? 'on' : '';
+    if(val){
+        var f = flagsConf.filter((f) => f.cmd == cmd)[0]
+        if(!f)
+            flagsConf.push({cmd: cmd, value: val});
+        else
+            f.value = val;
+    }
+    else{
+        var f = flagsConf.filter((f) => f.cmd == cmd)[0]
+        if(!f) return;
+        var index = flagsConf.indexOf(f)
+        console.log("removing arg at index " + index)
+        flagsConf.splice(index, 1)
+    }
+    jwrite("flagsConf", flagsConf);
+
+    n = this.id.replaceAll('+', '')
+    if(!n) return;
+    o = ebi(n)
+    synching = true;
+    while(o){
+        if(o.value != this.value)
+            o.value = this.value;
+        n += '+'
+        o = ebi(n)
+    }
+    synching = false;
+}
+loadUiFlags();
+
+function loadFlags(){
+    var container = ebi('flags');
+    container.innerHTML = ""
+    for (var i = 0; i < flagsConf.length; i++) {
+        if(QS('input[data-flag="' + flagsConf[i].cmd + '"]')) continue;
+        var a = mknod("div");
+        var flag = flags.filter((f) => f.cmd == flagsConf[i].cmd)[0];
+        a.setAttribute("tt", flag?.help);
+        a.setAttribute("ref", i);
+        a.innerHTML = '<a href="' + 'https://copyparty.eu/cli/#g' + flagsConf[i].cmd.replace("--", "-") + '">' + flagsConf[i].cmd + "</a>\n";
+        var inp = mknod("input");
+        inp.setAttribute("value", flagsConf[i].value);
+        if(flag?.help.match(/\(default:/)){
+            var ph = flag.help.match(/(?:\(default:)(.*)(?:\))/)[1]
+            ph = ph.trim()
+            inp.setAttribute("placeholder", ph);
+            if(ph.toLowerCase() == "false" || ph.toLowerCase() == "true"){
+                inp.setAttribute("disabled", "")
+                clmod(a, "binary", true)
+            }
+        }
+        inp.oninput = function(e){
+            flagsConf[this.parentNode.getAttribute("ref")].value = this.value;
+            jwrite("flagsConf", flagsConf);
+        }
+        a.appendChild(inp);
+        var b = mknod("button", "", "×");
+        clmod(b, "delBtn", true);
+        b.value = i
+        b.onclick = function(){
+            console.log("removing arg at index " + this.value)
+            flagsConf.splice(this.value, 1)
+            loadFlags();
+            jwrite("flagsConf", flagsConf);
+        }
+        a.appendChild(b)
+        container.appendChild(a)
+    }
+}
+console.log(flagsConf);
+loadFlags();
+
+// based on https://www.w3schools.com/howto/howto_js_autocomplete.asp
+var currentFocus;
+function addActive(x) {
+    if (!x) return false;
+    removeActive(x);
+    if (currentFocus >= x.length) currentFocus = 0;
+    if (currentFocus < 0) currentFocus = (x.length - 1);
+    x[currentFocus].classList.add("autocomplete-active");
+}
+function removeActive(x) {
+    for (var i = 0; i < x.length; i++) {
+        x[i].classList.remove("autocomplete-active");
+    }
+}
+function closeAllLists(elmnt) {
+    var x = document.getElementsByClassName("autocomplete-list");
+    for (var i = 0; i < x.length; i++) {
+        if (elmnt != x[i]) {
+            x[i].parentNode.removeChild(x[i]);
+        }
+    }
+}
+document.addEventListener("click", function (e) {
+    if(clgot(e.target, 'autocomplete'))
+        closeAllLists(ebi(e.target.id + '-list'))
+    else
+        closeAllLists(e.target);
+});
+document.onkeyup = function(e){
+    if(e.key.match("Esc")){
+        closeAllLists();
+        var o = QS('.autocomplete:focus')
+        if(o) o.blur();
+    }
+}
+function autocompleteFlags(inp, arr) {
+    inp.onclick = 
+    inp.oninput = function(e) {
+        var a, b, i, val = this.value.trim().split(" ")[this.value.split(" ").length - 1];
+        closeAllLists();
+        currentFocus = -1;
+        if(!val) return;
+        a = mknod("DIV", this.id + "-list");
+        clmod(a, "autocomplete-list", true)
+        this.parentNode.appendChild(a);
+        
+        function iClick(e){
+            var val = this.getElementsByTagName("input")[0].value;
+            inp.value = "";
+            closeAllLists();
+            var exel = QS('input[data-flag="' + val + '"]')
+            if (exel) {
+                exel.focus();
+                return
+            }
+            flagsConf.push({cmd: val, value: ""});
+
+            console.log(flagsConf);
+            loadFlags();
+            jwrite("flagsConf", flagsConf);
+
+            inp.focus();
+        }
+
+        var leftovers = [];
+        for (i = 0; i < arr.length; i++) {
+            // check if cmd contains query
+            if (arr[i].cmd.toUpperCase().match(val.toUpperCase())) {
+                b = mknod("DIV");
+                b.innerHTML = "<strong>" + arr[i].cmd + "</strong>\n";
+                b.innerHTML += arr[i].help;
+                b.innerHTML += "<input type='hidden' value='" + arr[i].cmd + "'>";
+                b.onclick = iClick;
+                a.appendChild(b);
+            }
+            else{
+                leftovers.push(arr[i])
+            }
+        }
+        // achieves basic sorting prio by appending help text matches to the dropdown later
+        for (i = 0; i < leftovers.length; i++) {
+            // check if help text contains query
+            if (leftovers[i].help.toUpperCase().match(val.toUpperCase())) {
+                b = mknod("DIV");
+                b.innerHTML = "<strong>" + leftovers[i].cmd + "</strong>\n";
+                b.innerHTML += leftovers[i].help;
+                b.innerHTML += "<input type='hidden' value='" + leftovers[i].cmd + "'>";
+                b.onclick = iClick;
+                a.appendChild(b);
+            }
+        }
+        
+    };
+    inp.addEventListener("keydown", function(e) {
+        var x = ebi(this.id + "-list");
+        if (x) x = x.getElementsByTagName("div");
+        console.log(e.key)
+        if (e.key === 'ArrowDown' || e.key === 'Tab') {
+            ev(e);
+            currentFocus++;
+            addActive(x);
+        } else if (e.key === 'ArrowUp') {
+            ev(e);
+            currentFocus--;
+            addActive(x);
+        } else if (e.key === 'Enter') {
+            ev(e);
+            if (currentFocus > -1) {
+                if (x) x[currentFocus].click();
+            }
+            else
+                if(x && x.length > 0) x[0].click();
+        }
+    });
+}
+function autocompleteVolFlags(inp, arr, index) {
+    inp.onclick = 
+    inp.oninput = function(e) {
+        var a, b, i, val = this.value.trim().split(" ")[this.value.split(" ").length - 1];
+        closeAllLists();
+        currentFocus = -1;
+        if(!val) return;
+        a = mknod("DIV", this.id + "-list");
+        clmod(a, "autocomplete-list", true)
+        this.parentNode.appendChild(a);
+        
+        function iClick(e){
+            var val = this.getElementsByTagName("input")[0].value;
+            inp.value = "";
+            closeAllLists();
+            volsConf[index].flags.push({cmd: val, value: ""});
+
+            console.log(volsConf[index].flags);
+            loadVolumes();
+            jwrite("volsConf", volsConf);
+
+            inp.focus();
+        }
+
+        var leftovers = [];
+        for (i = 0; i < arr.length; i++) {
+            // check if cmd contains query
+            if (arr[i].cmd.toUpperCase().match(val.toUpperCase())) {
+                b = mknod("DIV");
+                b.innerHTML = "<strong>" + arr[i].cmd + "</strong>\n";
+                b.innerHTML += arr[i].help;
+                b.innerHTML += "<input type='hidden' value='" + arr[i].cmd + "'>";
+                b.onclick = iClick;
+                a.appendChild(b);
+            }
+            else{
+                leftovers.push(arr[i])
+            }
+        }
+        // achieves basic sorting prio by appending help text matches to the dropdown later
+        for (i = 0; i < leftovers.length; i++) {
+            // check if help text contains query
+            if (leftovers[i].help.toUpperCase().match(val.toUpperCase())) {
+                b = mknod("DIV");
+                b.innerHTML = "<strong>" + leftovers[i].cmd + "</strong>\n";
+                b.innerHTML += leftovers[i].help;
+                b.innerHTML += "<input type='hidden' value='" + leftovers[i].cmd + "'>";
+                b.onclick = iClick;
+                a.appendChild(b);
+            }
+        }
+        
+    };
+    inp.addEventListener("keydown", function(e) {
+        var x = ebi(this.id + "-list");
+        if (x) x = x.getElementsByTagName("div");
+        if (e.key === 'ArrowDown' || e.key === 'Tab') {
+            ev(e);
+            currentFocus++;
+            addActive(x);
+        } else if (e.key === 'Up') {
+            ev(e);
+            currentFocus--;
+            addActive(x);
+        } else if (e.key === 'Enter') {
+            ev(e);
+            if (currentFocus > -1) {
+                if (x) x[currentFocus].click();
+            }
+            else
+                if(x && x.length > 0) x[0].click();
+        }
+    });
+}
+autocompleteFlags(ebi('flagSearch'), flags);
+
+// alternative call that hides flags that have a better UI method
+//autocompleteFlags(ebi('flagSearch'), flags.filter((arg) => !QS('input[data-flag="' + arg.cmd + '"]')));
+
+
+// users
+var usrsConf = jread("usrsConf", []);
+function loadUsers(){
+    var container = ebi('users');
+    container.innerHTML = ""
+    for (var i = 0; i < usrsConf.length; i++) {
+        var a = mknod("form");
+        a.setAttribute("ref", i);
+        
+        var handle = mknod('p', '', '::::')
+        clmod(handle, 'dhandle', true)
+        handle.setAttribute('draggable', true)
+        handle.ondragstart = function(e){
+		    e.dataTransfer.setData("text", ', ' + usrsConf[this.parentNode.getAttribute("ref")].name);
+        }
+        a.appendChild(handle)
+
+        var inp = mknod("input");
+        inp.setAttribute("value", usrsConf[i].name)
+        inp.setAttribute("placeholder", "username")
+        inp.setAttribute("autocomplete", "new-password")
+        clmod(inp, "name", true)
+        inp.oninput = function(e){
+            usrsConf[this.parentNode.getAttribute("ref")].name = this.value;
+            jwrite("usrsConf", usrsConf);
+        }
+        a.appendChild(inp);
+
+        var inp2 = mknod("input");
+        inp2.setAttribute("value", usrsConf[i].pw)
+        inp2.setAttribute("type", "password")
+        inp2.setAttribute("placeholder", "password")
+        inp2.setAttribute("autocomplete", "new-password")
+        clmod(inp2, "pw", true)
+        inp2.oninput = function(e){
+            usrsConf[this.parentNode.getAttribute("ref")].pw = this.value;
+            jwrite("usrsConf", usrsConf);
+        }
+        a.appendChild(inp2);
+
+        var b = mknod("button", "", "×");
+        clmod(b, "delBtn", true);
+        b.value = i
+        b.onclick = function(){
+            console.log("removing user at index " + this.value)
+            usrsConf.splice(this.value, 1)
+            loadUsers();
+            jwrite("usrsConf", usrsConf);
+        }
+        a.appendChild(b)
+        container.appendChild(a)
+    }
+}
+loadUsers();
+
+ebi('addUser').onclick = function(){
+    usrsConf.push({name: "", pw: "", grp: ""});
+
+    console.log(usrsConf);
+    loadUsers();
+    
+    var ins = QSA('#users .name')
+    ins[ins.length - 1].focus();
+}
+
+var groups = jread("groupsConf", []);
+function loadGroups(){
+    var container = ebi('groups');
+    container.innerHTML = ""
+    for (var i = 0; i < groups.length; i++) {
+        var a = mknod("div");
+        a.setAttribute("ref", i);
+
+        var inp = mknod("input");
+        inp.setAttribute("value", groups[i].name)
+        inp.setAttribute("placeholder", "group name")
+        clmod(inp, "name", true)
+        inp.oninput = function(e){
+            groups[this.parentNode.getAttribute("ref")].name = this.value;
+            jwrite("groupsConf", groups);
+        }
+        a.appendChild(inp);
+
+        var inp2 = mknod("input");
+        inp2.setAttribute("value", groups[i].users)
+        inp2.setAttribute("placeholder", "(drag users here, or type)")
+        clmod(inp2, "users", true)
+        inp2.oninput = function(e){
+            while(this.value.startsWith(',') || this.value.startsWith(' ')) this.value = this.value.slice(1);
+            groups[this.parentNode.getAttribute("ref")].users = this.value;
+            jwrite("groupsConf", groups);
+        }
+        a.appendChild(inp2);
+
+        var b = mknod("button", "", "×");
+        clmod(b, "delBtn", true);
+        b.value = i
+        b.onclick = function(){
+            console.log("removing group at index " + this.value)
+            groups.splice(this.value, 1)
+            loadGroups();
+            jwrite("groupsConf", groups);
+        }
+        a.appendChild(b)
+        container.appendChild(a)
+    }
+}
+loadGroups();
+ebi('addGrp').onclick = function(){
+    groups.push({name: "", users: ""});
+
+    loadGroups();
+    
+    var ins = QSA('#groups .name')
+    ins[ins.length - 1].focus();
+}
+
+// volumes
+var volsConf = jread("volsConf", []);
+function loadVolumes(){
+    var container = ebi('volumes');
+    container.innerHTML = ""
+
+    var srcInput = function(){
+        volsConf[this.parentNode.parentNode.getAttribute("ref")].src = this.value;
+        jwrite("volsConf", volsConf);
+    }
+    var dstInput = function(){
+        volsConf[this.parentNode.parentNode.getAttribute("ref")].dst = this.value;
+        jwrite("volsConf", volsConf);
+    }
+    for (var i = 0; i < volsConf.length; i++) {
+        var a = mknod("div");
+        a.setAttribute("ref", i);
+
+        var h = mknod("p", "", "Volume #" + i)
+        a.appendChild(h);
+
+        var h3_1 = mknod("h3", "", "real path");
+        var inp = mknod("input", "rp" + i, volsConf[i].src);
+        inp.setAttribute("value", volsConf[i].src);
+        inp.setAttribute("placeholder", "/storage/emulated/0/files");
+        clmod(inp, "src", true);
+        inp.oninput = srcInput;
+        h3_1.appendChild(inp);
+        a.appendChild(h3_1);
+
+        a.appendChild(document.createTextNode("➡️"));
+
+        var h3_2 = mknod("h3", "", "virtual path");
+        var inp2 = mknod("input", "vp" + i, volsConf[i].dst);
+        inp2.setAttribute("value", volsConf[i].dst);
+        inp2.setAttribute("placeholder", "/");
+        clmod(inp2, "dst", true);
+        inp2.oninput = dstInput;
+        h3_2.appendChild(inp2);
+        a.appendChild(h3_2);
+
+        var pbtn = mknod("button", "", "⚙️ permissions")
+        pbtn.setAttribute("value", i)
+        pbtn.onclick = showPermConf;
+        a.appendChild(pbtn)
+
+        var h3_3 = mknod("h3", "", "volume settings")
+        clmod(h3_3, "h_vcfg", true)
+        a.appendChild(h3_3)
+
+        if(volsConf[i].flags == undefined)
+            volsConf[i].flags = []
+
+        var vfc = mknod("div", "vfc" + i)
+        for(var j = 0; j < volsConf[i].flags.length; j++){
+            var a2 = mknod("div");
+            var flag = volflags.filter((f) => f.cmd == volsConf[i].flags[j].cmd)[0];
+            a2.setAttribute("tt", flag?.help);
+            a2.setAttribute("vol", i);
+            a2.setAttribute("ref", j);
+            a2.innerHTML = (
+                "<strong>" + volsConf[i].flags[j].cmd + "</strong>"
+            );
+            var inp3 = mknod("input");
+            inp3.setAttribute("value", volsConf[i].flags[j].value);
+            if(flag?.help.match(/\(default:/)){
+                var ph = flag.help.match(/(?:\(default:)(.*)(?:\))/)[1]
+                ph = ph.trim()
+                inp3.setAttribute("placeholder", ph);
+                if(ph.toLowerCase() == "false" || ph.toLowerCase() == "true"){
+                    inp.setAttribute("disabled", "")
+                    clmod(a, "binary", true)
+                }
+            }
+            inp3.oninput = function(e){
+                volsConf[this.parentNode.getAttribute("vol")]
+                    .flags[this.parentNode.getAttribute("ref")]
+                    .value = this.value;
+                jwrite("volsConf", volsConf);
+                if(this.value.match(':'))
+                    toast.err(30, 'volflags cannot contain ":" in commandline arguments. if you really need to use ":", consider making a configuration file.')
+            }
+            a2.appendChild(inp3);
+            var b2 = mknod("button", "", "×");
+            clmod(b2, "delBtn", true);
+            b2.value = j
+            b2.onclick = function(){
+                console.log("removing volflag at index " + this.value)
+                volsConf[this.parentNode.getAttribute("vol")]
+                    .flags.splice(this.value, 1)
+                loadVolumes();
+                jwrite("volsConf", volsConf);
+            }
+            a2.appendChild(b2)
+            vfc.appendChild(a2)
+        }
+        a.appendChild(vfc)
+
+        var pr = mknod("p", "", "+ add volume setting")
+        clmod(pr, "inlinePrompt", true)
+        a.appendChild(pr)
+
+        var vf = mknod("input", "vf" + i)
+        clmod(vf, "autocomplete", true)
+        vf.setAttribute("placeholder", "🔎 volflags");
+        autocompleteVolFlags(vf, volflags, i)
+        a.appendChild(vf)
+
+        var b = mknod("button", "", "×");
+        clmod(b, "delBtn", true);
+        b.value = i
+        b.onclick = function(){
+            console.log("removing volume at index " + this.value)
+            volsConf.splice(this.value, 1)
+            loadVolumes();
+            jwrite("volsConf", volsConf);
+        }
+        a.appendChild(b)
+        container.appendChild(a)
+    }
+}
+loadVolumes();
+
+var perms = [
+    {letter: "r", desc: "read: list folder contents, download files"},
+    {letter: "w", desc: 'write: upload files; need "r" to see the uploads'},
+    {letter: "m", desc: 'move: move files and folders; need "w" at destination'},
+    {letter: "d", desc: "delete: permanently delete files and folders"},
+    {letter: "g", desc: "get: download files, but cannot see folder contents"},
+    {letter: "G", desc: 'upget: "get", but can see filekeys of their own uploads'},
+    {letter: "h", desc: 'html: "get", but folders return their index.html'},
+    {letter: ".", desc: "dots: user can ask to show dotfiles in listings"},
+    {letter: "a", desc: "admin: can see uploader IPs, config-reload"},
+    {letter: "A", desc: 'all: same as "rwmda." (read/write/move/delete/admin/dotfiles)'},
+]
+function loadPerms(vol){
+    var container = ebi('perm-list')
+    container.innerHTML = ""
+    var volume = volsConf[vol]
+    ebi('h_perms').innerHTML = "permissions for " + (volume.dst.length > 0 ? volume.dst : "home")
+    var users = []
+    for(var i = 0; i < groups.length; i++){
+        users.push({name: '@' + groups[i].name})
+    }
+    users = users.concat(usrsConf)
+    for(var i = -1; i < users.length; i++){
+        var usr = i > -1 ? users[i] : {name: "everyone"};
+        var aperms = volume.perms ?? []
+        var uperms = aperms.filter((o) => o.user == usr.name)[0]
+        var a = mknod("div")
+        a.setAttribute("ref", i);
+        
+        var h = mknod("p", "", usr.name)
+        a.appendChild(h)
+
+        var b = mknod("div")
+        for(var j = 0; j < perms.length; j++){
+            var cid = "upc" + i.toString() + j.toString()
+            var c = mknod("input", cid)
+            c.setAttribute("type", "checkbox")
+            c.setAttribute("uid", i);
+            c.setAttribute("p", perms[j].letter);
+            if(uperms?.perms.includes(perms[j].letter))
+                c.setAttribute("checked", "")
+            c.setAttribute("tt", perms[j].desc)
+            c.onclick = function(){
+                var p = this.getAttribute("p")
+                var uid = this.getAttribute("uid")
+                var usr = uid > -1 ? users[uid] : {name: "everyone"}
+                var o = aperms.filter((o) => o.user == usr.name)[0]
+                if(!o){
+                    o = {user: usr.name, perms: ""}
+                    aperms.push(o)
+                }
+                if(!o.perms.includes(p))
+                    o.perms += p
+                else
+                    o.perms = o.perms.replace(p, "")
+                if(o.perms.length == 0)
+                    aperms.splice(o, 1)
+                volume.perms = aperms
+            }
+            b.appendChild(c)
+
+            var l = mknod("label", "", perms[j].letter)
+            l.setAttribute("for", cid)
+            l.setAttribute("tt", perms[j].desc)
+            b.appendChild(l)
+        }
+        a.appendChild(b)
+        container.appendChild(a)
+    }
+
+    tt.init();
+}
+function showPermConf(){
+    var index = this.value
+    if(index != undefined && index !== ""){
+        loadPerms(index)
+    }
+    else{
+        jwrite("volsConf", volsConf);
+        console.log(volsConf)
+    }
+    clmod(ebi('perms-modal'), 'vis', 't');
+}
+ebi('m_close').onclick =
+ebi('m_closeArea').onclick =
+    showPermConf;
+
+ebi('addVol').onclick = function(){
+    volsConf.push({src: "", dst: "", perms: []});
+
+    console.log(volsConf);
+    loadVolumes();
+    
+    var ins = QSA('#volumes .src')
+    ins[ins.length - 1].focus();
+}
+
+
+// config generation
+function esc(s){
+    if(s.match(' ') && !s.startsWith('"'))
+        s = '"' + s + '"';
+    return s;
+}
+function getConfig(){
+    var conf = ebi('rpre').value + ' ';
+
+    for(var i = 0; i < flagsConf.length; i++){
+        var val = flagsConf[i].value;
+        // i think none of the options want a leading # for colors
+        if(val.startsWith('#')) val = val.slice(1);
+        // checkboxes set text value to 'on'
+        conf += flagsConf[i].cmd + ((val !== '' && val !== 'on') ? ('=' + esc(val)) : '') + " ";
+    }
+
+    for(var i = 0; i < usrsConf.length; i++){
+        if(usrsConf[i].pw != ""){
+            conf += "-a=" + esc(usrsConf[i].name) + ':' + esc(usrsConf[i].pw) + " "
+        }
+    }
+
+    for(var i = 0; i < groups.length; i++){
+        if(groups[i].name != "" && groups[i].users != "")
+            conf += "--grp=" + esc(groups[i].name) + ':' + groups[i].users.replaceAll(' ', ',').replaceAll(',,', ',') + " "
+    }
+
+    for(var i = 0; i < volsConf.length; i++){
+        conf += "-v=" + esc(volsConf[i].src) + ":" + esc(volsConf[i].dst)
+        var a = volsConf[i].perms.filter((u) => u.user === 'everyone')
+        var b = volsConf[i].perms.filter((u) => u.user !== 'everyone')
+        volsConf[i].perms = [...a, ...b]
+        for(var j = 0; j < volsConf[i].perms.length; j++){
+            var uname = volsConf[i].perms[j].user
+            if(uname)
+                conf += ":" + volsConf[i].perms[j].perms + 
+                    (uname != "everyone" ? "," + uname : "")
+        }
+        for(var j = 0; j < volsConf[i].flags.length; j++){
+            conf += ":c," + volsConf[i].flags[j].cmd + (volsConf[i].flags[j].value ? '="' + volsConf[i].flags[j].value + '"' : '')
+        }
+        conf += ": "
+    }
+
+    return conf;
+}
+ebi('copyBtn').onclick = function(){
+    cliptxt(getConfig(), function(){
+        var a = ebi('copyBtn').innerHTML;
+        ebi('copyBtn').innerHTML = "✅";
+        setTimeout(function(){ebi('copyBtn').innerHTML = a;}, 1000);
+    });
+}
+
+ebi('resetBtn').onclick = function(){
+    modal.confirm("this will delete your entire configuration on this page! (not your actual server config) continue?", reset, null);
+}
+function reset(){
+    flagsConf = []
+    volsConf = []
+    usrsConf = []
+    groups = []
+
+    swrite('rprefix', 'python -m copyparty')
+    jwrite('flagsConf', flagsConf)
+    jwrite('volsConf', volsConf)
+    jwrite('usrsConf', usrsConf)
+    jwrite('groups', groups)
+
+    ebi('rpre').value = 'python -m copyparty'
+    loadUiFlags()
+    loadFlags()
+    loadVolumes()
+    loadUsers()
+    loadGroups()
+}
+ebi('importBtn').onclick = function(){
+    modal.confirm("this will overwrite your configuration on this page! (not your actual server config) continue?", importConfig, null);
+}
+function importConfig(txt){
+    if(!txt || txt === true)
+        txt = ebi('importTxt').value.trim()
+
+    flagsConf = []
+    volsConf = []
+    usrsConf = []
+    groups = []
+
+    var first = true;
+    var buffer = "";
+    while(txt.length > 0){
+        var cmd = txt.match(/^.*?\s-/)
+        if(!cmd)
+            cmd = txt
+        else
+            cmd = cmd[0]
+        txt = txt.slice(cmd.length)
+        if(cmd.match(/"/g)?.length % 2 > 1){
+            var m = txt.match(/"\S*\s-/)[0]
+            cmd += m
+            txt.slice(m.length)
+        }
+        var c = (first ? '' : '-') + cmd.split(/(\s|=)/)[0]
+        first = false
+        var val = cmd.slice(c.length + first)
+        val = val.replace(/(^|\s)-$/, '')
+        if(val === '') val = 'on'
+        console.log(c)
+        console.log(val)
+        var flag = flags.filter((f) => f.cmd == c)[0];
+        if(!flag){
+            buffer += cmd
+            continue
+        }
+        else if(buffer){
+            buffer = buffer.slice(0, buffer.length - 2)
+            swrite('rprefix', buffer)
+            ebi('rpre').value = buffer
+        }
+        buffer = ''
+
+        switch(c){
+            case "-v":
+                volsConf.push(getVolFromStr(val))
+                break;
+            case "-a":
+                var name = val.split(':')[0]
+                var pw = val.slice(name.length + 1)
+                var user = usrsConf.filter((u) => u.name == name)[0]
+                if(!user)
+                    usrsConf.push({name: name, pw: pw})
+                else
+                    user.pw = pw
+                break;
+            case "--grp":
+                var name = val.split(':')[0]
+                var u = val.slice(name.length + 1)
+                groups.push({name: name, users: u})
+                
+                var n = u.split(',')
+                for(var j = 0; j < n.length; j++){
+                    if(n[j] && !usrsConf.filter((u) => u.name === n[j])[0])
+                        usrsConf.push({name: n[j], pw: ''})
+                }
+                break;
+            default:
+                if(val.startsWith('"') && val.endsWith('"'))
+                    val = val.slice(1, val.length - 1)
+                flagsConf.push({cmd: c, value: val});
+                break;
+        }
+    }
+
+    console.log(flagsConf)
+    console.log(volsConf)
+    console.log(usrsConf)
+    console.log(groups)
+
+    jwrite('flagsConf', flagsConf)
+    jwrite('volsConf', volsConf)
+    jwrite('usrsConf', usrsConf)
+    jwrite('groups', groups)
+
+    loadUiFlags()
+    loadFlags()
+    loadVolumes()
+    loadUsers()
+    loadGroups()
+}
+function getVolFromStr(s){
+    var parts = s.split(':')
+    var src = parts[0]
+    var dst = parts[1]
+    var rest = parts.slice(2)
+    var perms = []
+    var flags = []
+    for(var i = 0; i < rest.length; i++){
+        var o = rest[i]
+        if(o.startsWith('c,')){
+            o = o.slice(2)
+            var c = o.split(/(\s|=)/)[0]
+            var val = o.slice(c.length + 1)
+            if(val.startsWith('"') && val.endsWith('"'))
+                val = val.slice(1, val.length - 1)
+            flags.push({cmd: c, value: val})
+        }
+        else{
+            var p = o.split(',')[0]
+            var n = o.slice(p.length + 1)
+            for(var j = 0; j < n.length; j++){
+                console.log('user: ' + n[j] + ' gets perms: ' + p)
+                perms.push({user: n[j], perms: p})
+
+                if(n[j] && !usrsConf.filter((u) => u.name === n[j])[0])
+                    usrsConf.push({name: n[j], pw: ''})
+            }
+        }
+    }
+    return {src: src, dst: dst, perms: perms, flags: flags}
+}
+
+tt.init();
+
+J_CFG = 2;
diff -NarU2 a/copyparty/web/copyparty.svg b/copyparty/web/copyparty.svg
--- a/copyparty/web/copyparty.svg	1970-01-01 00:00:00.000000000 +0000
+++ b/copyparty/web/copyparty.svg	2026-07-08 15:11:40.609175034 +0000
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="256mm" height="256mm" viewBox="0 0 256 256"><defs><linearGradient id="a" x1="4.859" x2="4.859" y1="14.819" y2="212.404" gradientTransform="matrix(1.21265 0 0 .57836 2.307 33.663)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fc5" style="stop-color:#ffd34d;stop-opacity:1"/><stop offset=".2" stop-color="#fc0" style="stop-color:#ffc500;stop-opacity:1"/><stop offset="1" stop-color="#f80"/></linearGradient></defs><rect width="251.387" height="183.488" x="2.307" y="28.025" fill="#333" rx="11.784" ry="11.482" style="stroke:#acacac;stroke-width:3.39476;stroke-dasharray:none;stroke-opacity:1"/><rect width="239.603" height="114.276" x="8.199" y="42.233" fill="url(#a)" rx="7.856" ry="7.772" style="fill:url(#a);stroke-width:.837468"/><rect width="153.189" height="51.063" x="51.406" y="81.923" fill="#333" rx="25.531" ry="25.531" style="stroke-width:.981978"/><circle cx="76.937" cy="107.454" r="15.712" fill="#ccc" style="fill:#acacac;fill-opacity:1;stroke-width:.981978"/><circle cx="179.063" cy="107.454" r="15.712" fill="#ccc" style="fill:#acacac;fill-opacity:1;stroke-width:.981978"/><path fill="#737373" d="m34.712 211.512 9.82-33.864c1.964-5.53 4.91-6.912 10.802-6.912h147.39c5.892 0 6.78 1.383 8.744 6.912l9.82 33.864z" style="fill:#acacac;fill-opacity:1;stroke-width:.823811"/></svg>
\ No newline at end of file
diff -NarU2 a/copyparty/web/idp.html b/copyparty/web/idp.html
--- a/copyparty/web/idp.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/idp.html	2026-07-08 15:11:40.609175034 +0000
@@ -9,4 +9,5 @@
     <meta name="robots" content="noindex, nofollow">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/shares.css?_={{ ts }}">
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
diff -NarU2 a/copyparty/web/manifest.json b/copyparty/web/manifest.json
--- a/copyparty/web/manifest.json	1970-01-01 00:00:00.000000000 +0000
+++ b/copyparty/web/manifest.json	2026-07-08 15:11:40.609175034 +0000
@@ -0,0 +1,32 @@
+{
+  "short_name": "copyparty",
+  "name": "copyparty",
+  "description": "the all in one file server",
+  "icons": [
+    {
+      "src": "/.cpr/copyparty.svg",
+      "sizes": "512x512",
+      "type": "image/svg+xml"
+    }
+  ],
+  "start_url": "/?utm_medium=PWA&utm_source=launcher",
+  "display": "minimal-ui",
+  "theme_color": "#222",
+  "background_color": "#222",
+  "share_target": {
+    "action": "/?utm_medium=PWA&utm_source=share-target&share-target",
+    "method": "POST",
+    "enctype": "multipart/form-data",
+    "params": {
+      "title": "share",
+      "text": "upload",
+      "url": "/",
+      "files": [
+        {
+          "name": "files",
+          "accept": ["*/*"]
+        }
+      ]
+    }
+  }
+ }
\ No newline at end of file
diff -NarU2 a/copyparty/web/md.css b/copyparty/web/md.css
--- a/copyparty/web/md.css	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/md.css	2026-07-08 15:11:40.609175034 +0000
@@ -141,5 +141,5 @@
 	}
 	#mn {
-		padding: 1.3em 0 .7em 1em;
+		padding: .8em 0 .2em 1em;
 		border-bottom: 1px solid #ccc;
 		background: #eee;
@@ -148,8 +148,9 @@
 	}
 	#mn a {
+		display: inline-block;
 		color: #444;
 		background: none;
 		margin: 0 0 0 -.2em;
-		padding: .3em 0 .3em .4em;
+		padding: .5em .4em .5em 0;
 		text-decoration: none;
 		border: none;
@@ -321,5 +322,5 @@
 	html.z #toc,
 	html.z #mw {
-		scrollbar-color: #b80 #282828;
+		scrollbar-color: rgba(203, 203, 203, 0.634) #282828;
 	}
 	html.z #toc::-webkit-scrollbar-track {
diff -NarU2 a/copyparty/web/mde.css b/copyparty/web/mde.css
--- a/copyparty/web/mde.css	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/mde.css	2026-07-08 15:11:40.610174059 +0000
@@ -10,4 +10,7 @@
 
 
+:root {
+	background: none;
+}
 html {
 	line-height: 1.5em;
@@ -40,10 +43,11 @@
 #mn {
 	font-weight: normal;
-	margin: 1.3em 0 .7em 1em;
+	margin: .8em 0 .2em 1em;
 }
 #mn a {
+	display: inline-block;
 	color: #444;
 	margin: 0 0 0 -.2em;
-	padding: 0 0 0 .4em;
+	padding: .5em .4em .5em 0;
 	text-decoration: none;
 	/* ie: */
diff -NarU2 a/copyparty/web/mde.html b/copyparty/web/mde.html
--- a/copyparty/web/mde.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/mde.html	2026-07-08 15:11:40.610174059 +0000
@@ -8,4 +8,5 @@
 	<meta name="viewport" content="width=device-width, initial-scale=0.7">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
 	<link rel="stylesheet" href="{{ r }}/.cpr/w/mde.css?_={{ ts }}">
diff -NarU2 a/copyparty/web/mde.js b/copyparty/web/mde.js
--- a/copyparty/web/mde.js	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/mde.js	2026-05-22 21:41:25.295039355 +0000
@@ -10,5 +10,5 @@
     var n = location + '';
     n = (n.slice(n.indexOf('//') + 2).split('?')[0] + '?v').split('/');
-    n[0] = 'top';
+    n[0] = '🏠';
     var loc = [];
     var nav = [];
diff -NarU2 a/copyparty/web/md.html b/copyparty/web/md.html
--- a/copyparty/web/md.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/md.html	2026-07-08 15:11:40.609175034 +0000
@@ -8,4 +8,5 @@
 	<meta name="viewport" content="width=device-width, initial-scale=0.7">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
 	<link rel="stylesheet" href="{{ r }}/.cpr/w/md.css?_={{ ts }}">
diff -NarU2 a/copyparty/web/md.js b/copyparty/web/md.js
--- a/copyparty/web/md.js	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/md.js	2026-05-22 21:41:25.294836617 +0000
@@ -42,5 +42,5 @@
         o = mknod('a');
         o.setAttribute('href', link);
-        o.textContent = uricom_dec(parts[a].split('?')[0]) || 'top';
+        o.textContent = uricom_dec(parts[a].split('?')[0]) || '🏠';
         dom_nav.appendChild(o);
     }
diff -NarU2 a/copyparty/web/msg.html b/copyparty/web/msg.html
--- a/copyparty/web/msg.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/msg.html	2026-07-08 15:11:40.610174059 +0000
@@ -8,4 +8,5 @@
 	<meta name="viewport" content="width=device-width, initial-scale=0.8">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 <style>:root{--font-main:sans-serif;--font-mono:monospace}
 html,body,a{margin:0;padding:0;border:none;color:#ccc;background:none;font-family:sans-serif;font-family:var(--font-main),sans-serif}
diff -NarU2 a/copyparty/web/rups.html b/copyparty/web/rups.html
--- a/copyparty/web/rups.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/rups.html	2026-07-08 15:11:40.610174059 +0000
@@ -9,4 +9,5 @@
 	<meta name="robots" content="noindex, nofollow">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/rups.css?_={{ ts }}">
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
diff -NarU2 a/copyparty/web/shares.css b/copyparty/web/shares.css
--- a/copyparty/web/shares.css	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/shares.css	2026-07-28 23:09:10.624009535 +0000
@@ -30,4 +30,10 @@
 #wrap td a {
 	margin: 0;
+	max-width: 40vw;
+	display: inline-block;
+	white-space: wrap;
+	word-wrap: break-word;
+	vertical-align: top;
+	line-height: 1.5em;
 }
 #w {
@@ -62,5 +68,5 @@
 	white-space: nowrap;
 }
-#wrap td+td+td+td+td+td+td+td {
+#wrap td+td+td+td+td+td+td+td+td {
 	font-family: var(--font-mono), monospace, monospace;
 }
diff -NarU2 a/copyparty/web/shares.html b/copyparty/web/shares.html
--- a/copyparty/web/shares.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/shares.html	2026-07-08 15:11:40.610174059 +0000
@@ -9,4 +9,5 @@
 	<meta name="robots" content="noindex, nofollow">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/shares.css?_={{ ts }}">
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
diff -NarU2 a/copyparty/web/splash.css b/copyparty/web/splash.css
--- a/copyparty/web/splash.css	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/splash.css	2026-07-13 09:49:10.692382672 +0000
@@ -6,9 +6,58 @@
 	touch-action: manipulation;
 }
+
+#homebtn {
+	font-size: x-large; 
+	top: 1em;
+	left: 1em;
+	margin: 0 0 .3em 0;
+	padding: .4em .5em;
+	border-radius: .3em; 
+	background: var(--bg-u2);
+	border-left: var(--bg-u5) solid 1px;
+	border-top: var(--bg-u5) solid 1px;
+	border-right: var(--bg-u5) solid 1px;
+	border-bottom: 2px solid rgb(7, 130, 212);
+	border-bottom-color: var(--a);
+	position: fixed;
+	z-index: 10;
+	box-shadow: 0 0 1em color-mix(in oklab, var(--bg-max) 60%, transparent 40%);
+}
+#homebtn:hover, #homebtn:focus-visible {
+	background: var(--bg-u5);
+}
+
+#outer {
+	top: 0;
+	left: 0;
+	bottom: 0;
+	right: 0;
+	position: absolute;
+	display: flex;
+	flex-direction: column;
+	height: 100%;
+	width: 100%;
+}
+#middle {
+	display: flex;
+	margin: auto;
+	justify-content: center;
+}
 #wrap {
-	max-width: 40em;
-	margin: 2em auto;
-	padding: 0 1em 3em 1em;
+	position: relative;
+	max-width: 80vw;
+	margin: 1.5em 2em;
+	margin-bottom: 3em;
+	padding: 1.5em;
+	padding-bottom: 0em;
 	line-height: 1.3em;
+	background: #282827;
+	background: color-mix(in oklab, var(--a-gray) 18%, var(--bg-max) 40%);
+	border-radius: .5em;
+	border: var(--bg-u5) solid 1px;
+	box-shadow: 0 0 2em color-mix(in oklab, var(--bg-max) 60%, transparent 40%);
+}
+html.y #wrap {
+	background: color-mix(in oklab, var(--a-gray) 18%, var(--bg-max) 40%);
 }
 #wrap.w {
@@ -16,23 +65,54 @@
 }
 h1 {
-	border-bottom: 1px solid #ccc;
-	margin: 2em 0 .4em 0;
+	margin: 1.5em 0 .4em 0;
 	padding: 0;
 	line-height: 1em;
+	font-size: x-large;
 	font-weight: normal;
 }
+#top_L {
+	display: flex;
+}
+#l {
+	margin-top: 0;
+	text-align: center;
+}
+#login {
+	border: var(--bg-u5) solid 1px;
+	border-radius: .3em;
+	padding: 1em;
+	margin: 2em auto 0 auto;
+	max-width: 25em;
+}
 li {
 	margin: 1em 0;
 }
+p {
+  	margin: 0 .2em;
+}
 #lo,
 a {
-	color: #047;
-	background: #fff;
+	color: #fff;
+	color: var(--bg);
+	background: rgba(5, 92, 184, 0.69);
+	background: var(--a);
 	text-decoration: none;
 	white-space: nowrap;
-	border-bottom: 1px solid #8ab;
+	border-bottom: 2px solid rgb(3, 50, 112);
+	border-bottom: 2px solid var(--a-dark);
 	border-radius: .2em;
 	padding: .2em .6em;
-	margin: 0 .3em;
+	margin: 0 .6em 0 0;
+}
+#lo {
+	padding: 0 .6em;
+	line-height: 1.8em;
+	border-width: 0 0 2px 0;
+	max-height: 30px;
+}
+#lo:hover, #lo:focus-visible,
+a:hover, a:focus-visible {
+	background: rgb(10, 134, 206);
+	background: var(--a-hil);
 }
 td a {
@@ -42,6 +122,10 @@
 #w {
 	color: #fff;
-	background: #940;
-	border-color: #b70;
+	background: rgba(50, 158, 4, 0.69);
+	border-color: rgb(20, 90, 3);
+}
+#wb:hover, #wb:focus-visible,
+#w:hover, #w:focus-visible {
+	background: rgb(93, 180, 12);
 }
 .af,
@@ -50,14 +134,35 @@
 	margin: -.2em 0 0 .8em;
 }
+
+#a {
+	color: #fff;
+	background: transparent;
+	border: var(--bg-u5) solid 1px;
+	font-weight: bold;
+	padding: .2em .5em;
+}
+#a:hover, #a:focus-visible {
+	background: var(--bg-u5);
+}
+html.y #a {
+	color: #222;
+}
 #lo,
 .logout,
 a.r {
-	color: #c04;
-	border-color: #c7a;
+	color: #fff;
+	background: rgba(230, 3, 3, 0.61);
+	border-color: rgb(178, 5, 66);
 }
-a.g {
-	color: #0a0;
-	border-color: #3a0;
-	box-shadow: 0 .3em 1em #4c0;
+#lo:hover, #lo:focus-visible,
+.logout:hover, .logout:focus-visible,
+a.r:hover, a.r:focus-visible {
+	background: rgb(209, 4, 4);
+}
+#a.g {
+	background: rgb(254, 207, 20);
+	border-color: rgb(224, 177, 7);
+	box-shadow: 0 .3em 1em rgba(240, 149, 3, 0.584);
+	color: #000;
 }
 #repl,
@@ -70,15 +175,17 @@
 #repl {
 	position: fixed;
-	bottom: .25em;
-	left: .2em;
+	bottom: .5em;
+	left: .5em;
 }
 #pb {
 	opacity: .5;
 	position: fixed;
-	bottom: .25em;
-	right: .3em;
+	white-space: nowrap;
+	bottom: .5em;
+	right: .5em;
 }
 #pb span {
 	opacity: .6;
+	pointer-events: none;
 }
 #pb a {
@@ -86,5 +193,8 @@
 }
 table {
-	border-collapse: collapse;
+	overflow-x: auto;
+	display: block;
+	overflow-y: hidden;
+	margin-bottom: .5em;
 }
 .vols td,
@@ -113,29 +223,14 @@
 	text-align: center;
 }
-.btns {
-	margin: 1em 0;
-}
-.btns>a:first-child {
-	margin-left: 0;
+.btns a, .btns form {
+  display: inline-block;
+  margin-bottom: .3em;
 }
 .agr br {
 	display: none;
 }
-#lo,
 .agr a,
 .agr form {
-	margin: 0 .5em 0 0;
-	line-height: 4em;
-}
-.agr form,
-.agr input {
-	display: inline;
-	padding: 0;
-	margin: 0;
-}
-#lo,
-.agr input {
-	line-height: 1em;
-	font-weight: normal;
+	line-height: 1.3em;
 }
 #msg {
@@ -200,50 +295,56 @@
 	border-color: #777;
 }
-html.z #lo,
-html.z a {
-	color: #fff;
-	background: #057;
-	border-color: #37a;
-}
-html.z .logout,
-html.z #lo,
-html.z a.r {
-	background: #804;
-	border-color: #c28;
-}
-html.z a.g {
-	background: #470;
-	border-color: #af4;
-	box-shadow: 0 .3em 1em #7d0;
-}
 form {
 	line-height: 2.5em;
+	display: flex;
+	flex-direction: column;
+	margin: auto;
 }
 #x,
 input {
-	color: #a50;
-	background: #fff;
-	border: 1px solid #a50;
+	margin: 1em 0 0 0;
+	background: #ccc;
+	background: var(--transparent);
+	padding: .5em;
+	border-color: rgb(72, 182, 255);
+	border-color: var(--a-hil);
 	border-radius: .3em;
-	padding: .25em .6em;
-	margin: 0 .3em 0 0;
-	font-size: 1em;
+	text-align: center;
 }
 input::placeholder {
 	font-size: 1.2em;
-	font-style: italic;
 	letter-spacing: .04em;
 	opacity: 0.64;
-	color: #930;
+	color: #000;
 }
-#x,
-html.z input {
-	color: #fff;
-	background: #626;
-	border-color: #c2c;
+input:autofill {
+  box-shadow: 0 0 0 40px #1f3b67 inset;
+  box-shadow: 0 0 0 40px color-mix(in srgb, var(--a) 25%, var(--bg-max)) inset;
+}
+html.y input:autofill {
+  box-shadow: 0 0 0 40px #a2c9e6 inset;
+  box-shadow: 0 0 0 40px color-mix(in srgb, var(--a) 25%, var(--bg-max)) inset;
 }
 html.z input::placeholder {
 	color: #fff;
 }
+#x,
+input[type=submit] {
+	color: #fff;
+	color: var(--bg);
+	background: rgb(39, 130, 204);
+	border-color: rgb(72, 182, 255);
+	background: var(--a);
+	border-color: var(--a-hil);
+	border-radius: .3em;
+	padding: .4em .6em;
+	font-size: 1em;
+	line-height: 1.35em;
+}
+#x:hover, #x:focus-visible,
+input[type=submit]:hover, input[type=submit]:focus-visible {
+	background: linear-gradient(-10deg,  rgb(10, 77, 145), rgb(39, 130, 204));
+	background: linear-gradient(-10deg,  var(--a-dark), var(--a));
+}
 html.z .num {
 	border-color: #777;
@@ -251,9 +352,15 @@
 
 
-html.bz {
-	color: #bbd;
-	background: #11121d;
-}
 html.bz .vols img {
 	filter: sepia(0.8) hue-rotate(180deg);
 }
+
+@media (max-width: 55em){
+	#homebtn {
+		position: initial;
+		display: inline-block;
+	}
+	#login {
+		margin-top: .7em;
+	}
+}
diff -NarU2 a/copyparty/web/splash.html b/copyparty/web/splash.html
--- a/copyparty/web/splash.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/splash.html	2026-07-13 09:49:10.693382031 +0000
@@ -8,4 +8,5 @@
 	<meta name="viewport" content="width=device-width, initial-scale=0.8">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/splash.css?_={{ ts }}">
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
@@ -13,195 +14,203 @@
 </head>
 
+<video id="bg_vid" autoplay muted loop playsinline></video>
+
 <body>
-	<div id="wrap">
-		{%- if not in_shr %}
-		<a id="a" href="{{ r }}/?h{{ re }}" class="af">refresh</a>
-		<a id="v" href="{{ r }}/?hc" class="af">connect</a>
-
-			{%- if this.uname == '*' %}
-		<p id="b">howdy stranger &nbsp; <small>(you're not logged in)</small></p>
-			{%- else %}
-				{%- if this.args.idp_logout %}
-		<a id="c" href="{{ this.args.idp_logout }}" class="logout">logout</a>
-				{%- else %}
-		<a id="c" href="{{ r }}/?pw=x" class="logout">logout</a>
-				{%- endif %}
-		<p><span id="m">welcome back,</span> <strong id="un">{{ this.uname|e }}</strong></p>
-			{%- endif %}
-		{%- endif %}
-
-		{%- if msg %}
-		<div id="msg">
-			{{ msg }}
-		</div>
-		{%- endif %}
+	<div id="outer">
+		<div id="middle">
+			<div id="wrap">
+				<a id="homebtn" href="/">🏠</a>
+
+				{%- if not in_shr %}
+				<a id="a" href="{{ r }}/?h{{ re }}" class="af" tt="toggle auto-refresh">↻</a>
+				<a id="v" href="{{ r }}/?hc" class="af">Connect</a>
+
+					{%- if this.uname == '*' %}
+				<p id="b">howdy stranger &nbsp; <small>(you're not logged in)</small></p>
+					{%- else %}
+						{%- if this.args.idp_logout %}
+				<a id="c" href="{{ this.args.idp_logout }}" class="logout">Logout</a>
+						{%- else %}
+				<a id="c" href="{{ r }}/?pw=x" class="logout">Logout</a>
+						{%- endif %}
+				<p><span id="m">welcome back,</span> <strong id="un">{{ this.uname|e }}</strong></p>
+					{%- endif %}
+				{%- endif %}
 
-		{%- if ups %}
-		<h1 id="aa">incoming files:</h1>
-			<table class="vols">
-				<thead><tr><th>%</th><th>speed</th><th>eta</th><th>idle</th><th>dir</th><th>file</th></tr></thead>
-				<tbody>
-					{%- for u in ups %}
-					<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td><a href="{{ r }}{{ u[4] }}">{{ u[5]|e }}</a></td><td>{{ u[6]|e }}</td></tr>
-					{%- endfor %}
-				</tbody>
-			</table>
-		{%- endif %}
-
-		{%- if dls %}
-		<h1 id="ae">active downloads:</h1>
-			<table class="vols">
-				<thead><tr><th>%</th><th>sent</th><th>speed</th><th>eta</th><th>idle</th><th></th><th>dir</th><th>file</th></tr></thead>
-				<tbody>
-					{%- for u in dls %}
-					<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td>{{ u[4] }}</td><td>{{ u[5] }}</td><td><a href="{{ r }}{{ u[6] }}">{{ u[7]|e }}</a></td><td>{{ u[8] }}</td></tr>
-					{%- endfor %}
-				</tbody>
-			</table>
-		{%- endif %}
-
-		{%- if avol %}
-		<h1>admin panel:</h1>
-		<table><tr><td> <!-- hehehe -->
-			<table class="num">
-				<tr><td>scanning</td><td>{{ scanning }}</td></tr>
-				<tr><td>hash-q</td><td>{{ hashq }}</td></tr>
-				<tr><td>tag-q</td><td>{{ tagq }}</td></tr>
-				<tr><td>mtp-q</td><td>{{ mtpq }}</td></tr>
-				<tr><td>db-act</td><td id="u">{{ dbwt }}</td></tr>
-			</table>
-		</td><td>
-			<table class="vols">
-				<thead><tr><th>vol</th><th id="t">action</th><th>status</th></tr></thead>
-				<tbody>
-					{%- for mp in avol %}
-					{%- if mp in vstate and vstate[mp] %}
-					<tr><td><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></td><td><a class="s" href="{{ r }}{{ mp }}?scan">rescan</a></td><td>{{ vstate[mp] }}</td></tr>
+				<div id="login">
+					{%- if in_shr %}
+					<h1 id="z">Unlock this share</h1>
+					<div>
+						<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
+							<input type="hidden" id="la" name="act" value="login" />
+							<input type="password" id="lp" name="cppwd" placeholder="Password" />
+							<input type="hidden" name="uhash" id="uhash" value="x" />
+							<input type="submit" id="ls" value="Unlock" />
+							{%- if ahttps %}
+							<a id="w" href="{{ ahttps }}">Switch to https</a>
+							{%- endif %}
+							<div id="lm"></div>
+						</form>
+					</div>
+					{%- else %}
+						{%- if this.uname == '*' %}
+					<h1 id="l">Login for more</h1>
+						{%- else %}
+					<h1 id="l">Change account</h1>
+						{%- endif %}
+					<div>
+						{%- if this.args.idp_login %}
+						<ul><li>
+							<a href="{{ this.args.idp_login | replace("{dst}",r+"/"+qvpath) }}">{{ this.args.idp_login_t }}</a>
+							{%- if this.args.ao_have_pw %}or alternatively{%- endif %}
+						</li></ul>
+						{%- endif %}
+						{%- if this.args.ao_have_pw %}
+						<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
+							<input type="hidden" id="la" name="act" value="login" />
+							{%- if this.args.usernames %}
+							<input type="text" id="lu" name="uname" placeholder="Username" size="12" />
+							<input type="password" id="lp" name="cppwd" placeholder="Password" size="12" />
+							{%- else %}
+							<input type="password" id="lp" name="cppwd" placeholder="Password" />
+							{%- endif %}
+							<input type="hidden" name="uhash" id="uhash" value="x" />
+							<input type="submit" id="ls" value="Login" />
+							{%- if chpw %}
+							<a id="x" href="#">Change password</a>
+							{%- endif %}
+							<div id="lm"></div>
+						</form>
+						{%- endif %}
+					</div>
 					{%- endif %}
+				</div>
+
+				{%- if msg %}
+				<div id="msg">
+					{{ msg }}
+				</div>
+				{%- endif %}
+
+				{%- if ups %}
+				<h1 id="aa">Incoming files</h1>
+					<table class="vols">
+						<thead><tr><th>%</th><th>Speed</th><th>Eta</th><th>Idle</th><th>Dir</th><th>File</th></tr></thead>
+						<tbody>
+							{%- for u in ups %}
+							<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td><a href="{{ r }}{{ u[4] }}">{{ u[5]|e }}</a></td><td>{{ u[6]|e }}</td></tr>
+							{%- endfor %}
+						</tbody>
+					</table>
+				{%- endif %}
+
+				{%- if dls %}
+				<h1 id="ae">Active downloads</h1>
+					<table class="vols">
+						<thead><tr><th>%</th><th>Sent</th><th>Speed</th><th>Eta</th><th>Idle</th><th></th><th>Dir</th><th>File</th></tr></thead>
+						<tbody>
+							{%- for u in dls %}
+							<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td>{{ u[4] }}</td><td>{{ u[5] }}</td><td><a href="{{ r }}{{ u[6] }}">{{ u[7]|e }}</a></td><td>{{ u[8] }}</td></tr>
+							{%- endfor %}
+						</tbody>
+					</table>
+				{%- endif %}
+
+				{%- if avol %}
+				<h1>Admin panel</h1>
+				<table><tr><td> <!-- hehehe -->
+					<table class="num">
+						<tr><td>scanning</td><td>{{ scanning }}</td></tr>
+						<tr><td>hash-q</td><td>{{ hashq }}</td></tr>
+						<tr><td>tag-q</td><td>{{ tagq }}</td></tr>
+						<tr><td>mtp-q</td><td>{{ mtpq }}</td></tr>
+						<tr><td>db-act</td><td id="u">{{ dbwt }}</td></tr>
+					</table>
+				</td><td>
+					<table class="vols">
+						<thead><tr><th>vol</th><th id="t">action</th><th>status</th></tr></thead>
+						<tbody>
+							{%- for mp in avol %}
+							{%- if mp in vstate and vstate[mp] %}
+							<tr><td><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></td><td><a class="s" href="{{ r }}{{ mp }}?scan">rescan</a></td><td>{{ vstate[mp] }}</td></tr>
+							{%- endif %}
+							{%- endfor %}
+						</tbody>
+					</table>
+				</td></tr></table>
+				<div class="btns">
+					<a id="d" href="{{ r }}/?stack">📃 Dump stack</a>
+					<a id="cfg" href="{{ r }}/?config">✏️ Edit config</a>
+					<a id="e" href="{{ r }}/?reload=cfg">🔁 Reload config</a>
+				</div>
+				{%- endif %}
+
+				{%- if rvol %}
+				<h1 id="f">You can browse</h1>
+				<ul>
+					{%- for mp in rvol %}
+					<li><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
 					{%- endfor %}
-				</tbody>
-			</table>
-		</td></tr></table>
-		<div class="btns">
-			<a id="d" href="{{ r }}/?stack">dump stack</a>
-			<a id="e" href="{{ r }}/?reload=cfg">reload cfg</a>
-		</div>
-		{%- endif %}
+				</ul>
+				{%- endif %}
 
-		{%- if rvol %}
-		<h1 id="f">you can browse:</h1>
-		<ul>
-			{%- for mp in rvol %}
-			<li><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
-			{%- endfor %}
-		</ul>
-		{%- endif %}
-
-		{%- if wvol %}
-		<h1 id="g">you can upload to:</h1>
-		<ul>
-			{%- for mp in wvol %}
-			<li><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
-			{%- endfor %}
-		</ul>
-		{%- endif %}
-
-		{%- if in_shr %}
-		<h1 id="z">unlock this share:</h1>
-		<div>
-			<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
-				<input type="hidden" id="la" name="act" value="login" />
-				<input type="password" id="lp" name="cppwd" placeholder=" password" />
-				<input type="hidden" name="uhash" id="uhash" value="x" />
-				<input type="submit" id="ls" value="Unlock" />
-				{%- if ahttps %}
-				<a id="w" href="{{ ahttps }}">switch to https</a>
+				{%- if wvol %}
+				<h1 id="g">You can upload to</h1>
+				<ul>
+					{%- for mp in wvol %}
+					<li><a href="{{ r }}{{ mp }}{{ url_suf }}">{{ mp }}</a></li>
+					{%- endfor %}
+				</ul>
 				{%- endif %}
-				<div id="lm"></div>
-			</form>
-		</div>
-		{%- else %}
-			{%- if this.uname == '*' %}
-		<h1 id="l">login for more:</h1>
-			{%- else %}
-		<h1 id="l">change account:</h1>
-			{%- endif %}
-		<div>
-			{%- if this.args.idp_login %}
-			<ul><li>
-				<a href="{{ this.args.idp_login | replace("{dst}",r+"/"+qvpath) }}">{{ this.args.idp_login_t }}</a>
-				{%- if this.args.ao_have_pw %}or alternatively:{%- endif %}
-			</li></ul>
-			{%- endif %}
-			{%- if this.args.ao_have_pw %}
-			<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
-				<input type="hidden" id="la" name="act" value="login" />
-				{%- if this.args.usernames %}
-				<input type="text" id="lu" name="uname" placeholder=" username" size="12" />
-				<input type="password" id="lp" name="cppwd" placeholder=" password" size="12" />
-				{%- else %}
-				<input type="password" id="lp" name="cppwd" placeholder=" password" />
-				{%- endif %}
-				<input type="hidden" name="uhash" id="uhash" value="x" />
-				<input type="submit" id="ls" value="login" />
-				{%- if chpw %}
-				<a id="x" href="#">change password</a>
-				{%- endif %}
-				{%- if ahttps %}
-				<a id="w" href="{{ ahttps }}">switch to https</a>
-				{%- endif %}
-				<div id="lm"></div>
-			</form>
-			{%- endif %}
-		</div>
-		{%- endif %}
 
-		<h1 id="cc">other stuff:</h1>
-		<div class="agr">
-			{%- if ahttps %}
-			<a id="wb" href="{{ ahttps }}">switch to https</a><br />
-			{%- endif %}
-
-			<a id="af" href="{{ r }}/?ru">show recent uploads</a><br />
-
-			{%- if this.uname != '*' and this.args.shr %}
-			<a id="y" href="{{ r }}/?shares">edit shares</a><br />
-			{%- endif %}
-
-            {%- if this.uname in this.args.idp_adm_set %}
-			<a id="ag" href="{{ r }}/?idp">view idp cache</a><br />
-			{%- endif %}
-
-			<a id="k" href="{{ r }}/?reset" class="r">reset client settings</a><br />
-
-			{%- if this.uname != '*' and not in_shr %}
-			<form method="post" enctype="multipart/form-data">
-				<input type="hidden" name="act" value="logout" />
-				<input type="submit" id="lo" value="logout “{{ this.uname|e }}” everywhere" />
-			</form>
-			{%- endif %}
-		</div>
-		<ul>
-			{%- if k304 or k304vis %}
-			{%- if k304 %}
-			<li><a id="h" href="{{ r }}/?cc&setck=k304=n">disable k304</a> (currently enabled)
-			{%- else %}
-			<li><a id="i" href="{{ r }}/?cc&setck=k304=y" class="r">enable k304</a> (currently disabled)
-			{%- endif %}
-			<blockquote id="j">enabling k304 will disconnect your client on every HTTP 304, which can prevent some buggy proxies from getting stuck (suddenly not loading pages), <em>but</em> it will also make things slower in general</blockquote></li>
-			{%- endif %}
-
-			{%- if no304 or no304vis %}
-			{%- if no304 %}
-			<li><a id="ab" href="{{ r }}/?cc&setck=no304=n">disable no304</a> (currently enabled)
-			{%- else %}
-			<li><a id="ac" href="{{ r }}/?cc&setck=no304=y" class="r">enable no304</a> (currently disabled)
-			{%- endif %}
-			<blockquote id="ad">enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!</blockquote></li>
-			{%- endif %}
-		</ul>
+				<h1 id="cc">Other stuff</h1>
+				<div class="agr btns">
+					{%- if ahttps %}
+					<a id="wb" href="{{ ahttps }}">Switch to https</a><br />
+					{%- endif %}
+
+					<a id="af" href="{{ r }}/?ru">Show recent uploads</a><br />
+
+					{%- if this.uname != '*' and this.args.shr %}
+					<a id="y" href="{{ r }}/?shares">Edit shares</a><br />
+					{%- endif %}
+
+					{%- if this.uname in this.args.idp_adm_set %}
+					<a id="ag" href="{{ r }}/?idp">View idp cache</a><br />
+					{%- endif %}
 
+					<a id="k" href="{{ r }}/?reset" class="r">Reset client settings</a><br />
+
+					{%- if this.uname != '*' and not in_shr %}
+					<form method="post" enctype="multipart/form-data">
+						<input type="hidden" name="act" value="logout" />
+						<input type="submit" id="lo" value="Logout “{{ this.uname|e }}” everywhere" />
+					</form>
+					{%- endif %}
+				</div>
+				<ul>
+					{%- if k304 or k304vis %}
+					{%- if k304 %}
+					<li><a id="h" href="{{ r }}/?cc&setck=k304=n">disable k304</a> (currently enabled)
+					{%- else %}
+					<li><a id="i" href="{{ r }}/?cc&setck=k304=y" class="r">enable k304</a> (currently disabled)
+					{%- endif %}
+					<blockquote id="j">enabling k304 will disconnect your client on every HTTP 304, which can prevent some buggy proxies from getting stuck (suddenly not loading pages), <em>but</em> it will also make things slower in general</blockquote></li>
+					{%- endif %}
+
+					{%- if no304 or no304vis %}
+					{%- if no304 %}
+					<li><a id="ab" href="{{ r }}/?cc&setck=no304=n">disable no304</a> (currently enabled)
+					{%- else %}
+					<li><a id="ac" href="{{ r }}/?cc&setck=no304=y" class="r">enable no304</a> (currently disabled)
+					{%- endif %}
+					<blockquote id="ad">enabling no304 will disable all caching; try this if k304 wasn't enough. This will waste a huge amount of network traffic!</blockquote></li>
+					{%- endif %}
+				</ul>
+
+			</div>
+		</div>
 	</div>
-	<a href="#" id="repl">π</a>
+	<a href="#" id="repl" style="opacity: .4;">π</a>
 	{%- if not this.args.nb %}
 	<span id="pb"><span>powered by</span> <a href="{{ this.args.pb_url }}">copyparty {{ver}}</a></span>
@@ -212,4 +221,5 @@
 	JS_NONCE = "{{ js_nonce }}",
 	lang="{{ lang }}",
+	bg_img = "{{ this.args.bg_img }}",
 	dfavico="{{ favico }}";
 
diff -NarU2 a/copyparty/web/svcs.html b/copyparty/web/svcs.html
--- a/copyparty/web/svcs.html	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/svcs.html	2026-07-08 15:11:40.610174059 +0000
@@ -8,4 +8,5 @@
 	<meta name="viewport" content="width=device-width, initial-scale=0.8">
 	<meta name="theme-color" content="#{{ tcolor }}">
+	<link rel="manifest" href="{{ r }}/.cpr/w/manifest.json?_={{ ts }}" />
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/splash.css?_={{ ts }}">
 	<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
@@ -15,4 +16,6 @@
 
 <body>
+    <a id="homebtn" href="/?h">⬅️</a>
+
 	<div id="wrap" class="w">
         <div class="cn">
diff -NarU2 a/copyparty/web/sw.js b/copyparty/web/sw.js
--- a/copyparty/web/sw.js	1970-01-01 00:00:00.000000000 +0000
+++ b/copyparty/web/sw.js	2026-07-08 15:11:40.610174059 +0000
@@ -0,0 +1,52 @@
+// service worker. required for PWAs
+// https://www.digitalapplied.com/blog/progressive-web-apps-2026-pwa-performance-guide
+// Register service worker on page load
+// modern syntax allowed here, only supported browsers load this file
+console.log('sw load')
+self.addEventListener("fetch", (event) => {
+    // Regular requests not related to Web Share Target.
+    var [baseurl, query] = event.request.url.split('?');
+    if (event.request.method !== "POST" || !query.match("share-target")) {
+        var toplevel = baseurl == self.location.origin;
+        if(query?.match("utm_source=launcher") && toplevel){
+            // prevent cors restriction
+            event.respondWith(
+                fetch(new Request("/", { mode: 'same-origin' }))
+            );
+            return;
+        }
+        console.log('normal response')
+        event.respondWith(fetch(event.request));
+        return;
+    }
+
+    // Requests related to Web Share Target.
+    event.respondWith(
+        (async () => {
+            try{
+                const formData = await event.request.formData();
+                const files = formData.getAll('files');
+
+                // store in cache for retrieval in up2k.js.
+                // leading "/" is necessary, because cache 
+                // operates relative to path of current file
+                const cache = await caches.open('/media');
+                for(var i = 0; i < files.length; i++){
+                    await cache.put('/shared-file-' + i, new Response(files[i]))
+                    await cache.put('/shared-filename-' + i, new Response(files[i].name))
+                }
+
+                await cache.put('/shared-file-count', new Response(files.length))
+
+                return Response.redirect('/?share-target', 303);
+            }
+            catch(e){
+                alert(e)
+            }
+        })(),
+    );
+});
+self.addEventListener('install', (event) => {
+    console.log('sw wait skip')
+    self.skipWaiting(); // insta replace old service workers (helpful for dev)
+});
\ No newline at end of file
diff -NarU2 a/copyparty/web/tl/chi.js b/copyparty/web/tl/chi.js
--- a/copyparty/web/tl/chi.js	2026-07-08 15:13:32.822003171 +0000
+++ b/copyparty/web/tl/chi.js	2026-07-28 23:09:10.624190177 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "操作按钮",
+		"!": "操作按钮",
 		"dur": "时长",
 		"q": "质量 / 比特率",
@@ -15,5 +15,5 @@
 		"Vhash": "视频校验和",
 		"Res": "分辨率",
-		"T": "文件类型",
+		"Ext": "文件类型",
 		"aq": "音频质量 / 比特率",
 		"vq": "视频质量 / 比特率",
@@ -34,5 +34,5 @@
 			["T", "切换缩略图 / 图标"],
 			["⇧ A/D", "缩略图大小"],
-			["ctrl-K", "删除选中项"],
+			["ctrl-K/Del", "删除选中项"],
 			["ctrl-X", "剪切选中项到剪贴板"],
 			["ctrl-C", "复制选中项到剪贴板"],
@@ -108,7 +108,7 @@
 
 	"goh": "控制面板",
-	"gop": '上一个同级文件夹">前',
-	"gou": '上一级文件夹">上',
-	"gon": '下一个文件夹">后',
+	"gop": '上一个同级文件夹',
+	"gou": '上一级文件夹',
+	"gon": '下一个文件夹',
 	"logout": "登出 ",
 	"login": "登录",
@@ -158,5 +158,5 @@
 	"ut_ow": "覆盖服务器上的现有文件？$N🛡️：不要覆盖（会生成新文件名）$N🕒：如果服务器文件较旧则覆盖$N♻️：只要文件内容不同就覆盖$N⏭️：无条件跳过所有已有文件",
 	"ut_mt": "在上传时继续哈希其他文件$N$N如果你的 CPU 或硬盘是瓶颈，可能需要禁用",
-	"ut_ask": '上传开始前询问确认">💭',
+	"ut_ask": '上传开始前询问确认',
 	"ut_pot": "通过简化界面来$N提高慢设备上的上传速度",
 	"ut_srch": "不会真的上传，而是检查文件是否$N已经存在于服务器上（将扫描你可以读取的所有文件夹）",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 网格',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ 提示',
-	"ct_thumb": '在网格视图中，切换图标或缩略图$N快捷键: T">🖼️ 缩略',
+	"ct_thumb": '切换图标或缩略图$N快捷键: T">🖼️ 缩略',
 	"ct_csel": '在网格视图中，允许使用 CTRL 和 SHIFT 进行文件选择">选择',
 	"ct_dsel": '在网格视图中，允许拖动选择">拖选',
@@ -289,7 +289,7 @@
 	"ml_ss": "无声段自动快进",
 
-	"mt_loop": "单曲循环\">🔁",
+	"mt_loop": "单曲循环",
 	"mt_one": "播完一首歌曲后停止\">1️⃣",
-	"mt_shuf": "随机播放各文件夹中的歌曲\">🔀",
+	"mt_shuf": "随机播放各文件夹中的歌曲",
 	"mt_aplay": "如果链接中有歌曲 ID，则自动播放$N$N禁用此选项将不再在播放音乐时更新页面 URL 中的歌曲 ID，以防止设置丢失但 URL 保留时又自动播放起来\">自▶",
 	"mt_preload": "在歌曲快结束时开始加载下一首歌，以实现无缝播放\">预载",
@@ -306,6 +306,6 @@
 	"mt_compact": "紧凑的控制按钮\">⟎",
 	"mt_uncache": "清除缓存&nbsp;$N（如果你的浏览器因缓存歌曲损坏而无法播放，请尝试此操作）\">清缓",
-	"mt_mloop": "循环播放当前播放中的文件夹\">🔁 循环",
-	"mt_mnext": "加载下一个文件夹并继续播放\">📂 继续",
+	"mt_mloop": "循环播放当前播放中的文件夹",
+	"mt_mnext": "加载下一个文件夹并继续播放",
 	"mt_mstop": "停止播放\">⏸ 停止",
 	"mt_cflac": "将 flac / wav 转换为 {0}\">flac",
diff -NarU2 a/copyparty/web/tl/cze.js b/copyparty/web/tl/cze.js
--- a/copyparty/web/tl/cze.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/cze.js	2026-07-28 23:09:10.624348025 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "tlačítka akcí",
+		"!": "tlačítka akcí",
 		"dur": "doba trvání",
 		"q": "kvalita / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "kontrolní součet videa",
 		"Res": "rozlišení",
-		"T": "typ souboru",
+		"Ext": "typ souboru",
 		"aq": "kvalita zvuku / bitrate",
 		"vq": "kvalita videa / bitrate",
@@ -34,5 +34,5 @@
 			["T", "přepnout náhledy / ikony"],
 			["⇧ A/D", "velikost náhledů"],
-			["ctrl-K", "smazat vybrané"],
+			["ctrl-K/Del", "smazat vybrané"],
 			["ctrl-X", "vyjmout výběr do schránky"],
 			["ctrl-C", "kopírovat výběr do schránky"],
@@ -112,7 +112,7 @@
 
 	"goh": "ovládací panel",
-	"gop": 'předchozí sourozenec">předchozí',
-	"gou": 'nadřazená složka">nahoru',
-	"gon": 'následující složka">následující',
+	"gop": 'předchozí sourozenec',
+	"gou": 'nadřazená složka',
+	"gon": 'následující složka',
 	"logout": "Odhlásit ",
 	"login": "Přihlásit se", //m
@@ -162,5 +162,5 @@
 	"ut_ow": "přepsat existující soubory na serveru?$N🛡️: nikdy (místo toho vytvoří nový název souboru)$N🕒: přepsat pokud je soubor na serveru starší než váš$N♻️: vždy přepsat pokud se soubory liší$N⏭️: bezpodmínečně přeskočit všechny existující soubory", //m
 	"ut_mt": "pokračovat v hashování ostatních souborů během nahrávání$N$Nmožná zakázat pokud je vaše CPU nebo HDD bottleneckem",
-	"ut_ask": 'požádat o potvrzení před zahájením nahrávání">💭',
+	"ut_ask": 'požádat o potvrzení před zahájením nahrávání',
 	"ut_pot": "zlepšit rychlost nahrávání na pomalých zařízeních$Nzjednodušením UI",
 	"ut_srch": "skutečně nenahrávat, místo toho zkontrolovat zda soubory již $N existují na serveru (prohledá všechny složky které můžete číst)",
@@ -230,5 +230,5 @@
 	"ct_grid": '田 mřížka',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ nápovědy',
-	"ct_thumb": 'v zobrazení mřížky přepnout ikony nebo náhledy$NKlávesová zkratka: T">🖼️ náhledy',
+	"ct_thumb": 'přepnout ikony nebo náhledy$NKlávesová zkratka: T">🖼️ náhledy',
 	"ct_csel": 'použít CTRL a SHIFT pro výběr souborů v zobrazení mřížky">výběr',
 	"ct_dsel": 'použít tažený výběr v zobrazení mřížky">tažení', //m
@@ -293,7 +293,7 @@
 	"ml_ss": "přeskočit ticho", //m
 
-	"mt_loop": "smyčka/opakovat jednu skladbu\">🔁",
+	"mt_loop": "smyčka/opakovat jednu skladbu",
 	"mt_one": "zastavit po jedné skladbě\">1️⃣",
-	"mt_shuf": "zamíchat skladby v každé složce\">🔀",
+	"mt_shuf": "zamíchat skladby v každé složce",
 	"mt_aplay": "automatické přehrávání pokud je ID skladby v odkazu kterým jste přišli na server$N$Nzakázání toho také zastaví aktualizaci URL stránky s ID skladby při přehrávání hudby, aby se zabránilo automatickému přehrávání pokud se tato nastavení ztratí ale URL zůstane\">a▶",
 	"mt_preload": "začít načítat následující skladbu před koncem pro plynulé přehrávání\">přednahrání",
@@ -310,6 +310,6 @@
 	"mt_compact": "kompaktní ovládání\">⟎",
 	"mt_uncache": "vymazat cache &nbsp;(zkuste to, pokud váš prohlížeč uložil$Nporušenou kopii písně a odmítá ji přehrát)\">uncache",
-	"mt_mloop": "opakovat otevřenou složku\">🔁 loop",
-	"mt_mnext": "načíst další složku a pokračovat\">📂 next",
+	"mt_mloop": "opakovat otevřenou složku",
+	"mt_mnext": "načíst další složku a pokračovat",
 	"mt_mstop": "zastavit přehrávání\">⏸ stop",
 	"mt_cflac": "převést flac / wav na {0}\">flac",
diff -NarU2 a/copyparty/web/tl/deu.js b/copyparty/web/tl/deu.js
--- a/copyparty/web/tl/deu.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/deu.js	2026-07-28 23:09:10.624520481 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "Aktionen",
+		"!": "Aktionen",
 		"dur": "Dauer",
 		"q": "Qualität / Bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "Video Checksumme",
 		"Res": "Auflösung",
-		"T": "Dateityp",
+		"Ext": "Dateityp",
 		"aq": "Audioqualität / Bitrate",
 		"vq": "Videoqualität / Bitrate",
@@ -108,7 +108,7 @@
 
 	"goh": "Einstellungen",
-	"gop": 'zum vorherigen Ordner springen">vorh.',
-	"gou": 'zum übergeordneter Ordner springen">hoch',
-	"gon": 'zum nächsten Ordner springen">nächst.',
+	"gop": 'zum vorherigen Ordner springen',
+	"gou": 'zum übergeordneter Ordner springen',
+	"gon": 'zum nächsten Ordner springen',
 	"logout": "Abmelden ",
 	"login": "Anmelden",
@@ -158,5 +158,5 @@
 	"ut_ow": "Existierende Dateien auf dem Server überschreiben?$N🛡️: Nie (generiert einen neuen Dateinamen)$N🕒: Überschreiben, wenn Server-Datei älter ist als meine$N♻️: Überschreiben, wenn der Dateiinhalt anders ist$N⏭️: Vorhandene Dateien immer überspringen",
 	"ut_mt": "Andere Dateien während des Uploads hashen$N$Nsolltest du deaktivieren, falls deine CPU oder Festplatte zum Flaschenhals werden könnte",
-	"ut_ask": 'Vor dem Upload nach Bestätigung fragen">💭',
+	"ut_ask": 'Vor dem Upload nach Bestätigung fragen',
 	"ut_pot": "Verbessert Upload-Geschwindigkeit$Nindem das UI weniger komplex gemacht wird",
 	"ut_srch": "nicht wirklich hochladen, stattdessen prüfen ob Datei bereits auf dem Server existiert (scannt alle Ordner, die du lesen kannst)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 Das Raster&trade;',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ Tooltips',
-	"ct_thumb": 'In Raster-Ansicht, zwischen Icons und Vorschau wechseln$NHotkey: T">🖼️ Vorschaubilder',
+	"ct_thumb": 'Zwischen Icons und Vorschau wechseln$NHotkey: T">🖼️ Vorschaubilder',
 	"ct_csel": 'Benutze STRG und UMSCHALT für Dateiauswahl in Raster-Ansicht">sel',
 	"ct_dsel": 'Ziehauswahl in Raster-Ansicht verwenden">ziehen', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "Stille Überspringen", //m
 
-	"mt_loop": "Song wiederholen\">🔁",
+	"mt_loop": "Song wiederholen",
 	"mt_one": "Wiedergabe nach diesem Song beenden\">1️⃣",
-	"mt_shuf": "Zufällige Wiedergabe im Ordner\">🔀",
+	"mt_shuf": "Zufällige Wiedergabe im Ordner",
 	"mt_aplay": "automatisch abspielen, wenn der Link, mit dem du auf den Server zugreifst, eine Titel-ID enthält$N$NDeaktivieren verhindert auch, dass die Seiten-URL bei Musikwiedergabe mit Titel-IDs aktualisiert wird, um Autoplay zu verhindern, falls diese Einstellungen verloren gehen, die URL aber bestehen bleibt\">a▶",
 	"mt_preload": "nächsten Titel gegen Ende vorladen für nahtlose Wiedergabe\">Vorladen",
@@ -306,6 +306,6 @@
 	"mt_compact": "kompakte Steuerelemente\">⟎",
 	"mt_uncache": "Cache leeren &nbsp;(probier das, wenn dein Browser$Neine defekte Kopie eines Titels zwischenspeichert und sich weigert, ihn abzuspielen)\">Cache leeren",
-	"mt_mloop": "offenen Ordner wiederholen\">🔁 Schleife",
-	"mt_mnext": "nächsten Ordner laden und fortfahren\">📂 nächster",
+	"mt_mloop": "offenen Ordner wiederholen",
+	"mt_mnext": "nächsten Ordner laden und fortfahren",
 	"mt_mstop": "Wiedergabe beenden\">⏸ Stop",
 	"mt_cflac": "FLAC / WAV zu {0} konvertierebn\">flac",
diff -NarU2 a/copyparty/web/tl/epo.js b/copyparty/web/tl/epo.js
--- a/copyparty/web/tl/epo.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/epo.js	2026-07-28 23:09:10.624685573 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "ago-butonoj",
+		"!": "ago-butonoj",
 		"dur": "daŭro",
 		"q": "kvalito / bitrapido",
@@ -15,5 +15,5 @@
 		"Vhash": "kontrolsumo de video",
 		"Res": "distingivo",
-		"T": "dosiertipo",
+		"Ext": "dosiertipo",
 		"aq": "kvalito / bitrapido de aŭdio",
 		"vq": "kvalito / bitrapido de video",
@@ -108,7 +108,7 @@
 
 	"goh": "stirpanelo",
-	"gop": 'malsekva dosierujo">malsekva',
-	"gou": 'supra dosierujo">supren',
-	"gon": 'sekva dosierujo">sekva',
+	"gop": 'malsekva dosierujo',
+	"gou": 'supra dosierujo',
+	"gon": 'sekva dosierujo',
 	"logout": "Adiaŭi kiel ",
 	"login": "Ensaluti",
@@ -158,5 +158,5 @@
 	"ut_ow": "ĉu anstataŭigi dosierojn ĉe la servilo?$N🛡️: neniam (dosiero estos alŝutita kun nova dosiernomo)$N🕒: anstataŭigi, se servila dosiero estas pli malnova ol via$N♻️: ĉiam anstataŭigi, se dosieroj estas malsamaj$N⏭️: senkondiĉe preterlasi ĉiujn ekzistantajn dosierojn",
 	"ut_mt": "daŭri kalkuladon de kontrolsumoj por aliaj dosieroj dum alŝutado$N$Nmalŝaltinda, se via procesoro aŭ disko ne estas sufiĉe rapidaj",
-	"ut_ask": 'peti konfirmon antaŭ komenco de alŝutado">💭',
+	"ut_ask": 'peti konfirmon antaŭ komenco de alŝutado',
 	"ut_pot": "plirapidigi alŝutadon por malrapidaj komputiloj$Nper malkomplikado de fasado",
 	"ut_srch": "ne alŝuti ion ajn, nur kontroli, ke la dosieroj $N jam ekzistas ĉe la servilo (ĉiuj dosierujoj, kiuj vi povas legi, estos skanitaj)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 krado',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ ŝpruchelpiloj',
-	"ct_thumb": 'dum krado-vido, baskuli montradon de simboloj aŭ bildetoj$NFulmoklavo: T">🖼️ bildetoj',
+	"ct_thumb": 'baskuli montradon de simboloj aŭ bildetoj$NFulmoklavo: T">🖼️ bildetoj',
 	"ct_csel": 'uzi STIR kaj MAJ por elekti dosierojn en krado-vido">elekto',
 	"ct_dsel": 'uzi tren-elekton en krado-vido">treni',
@@ -289,7 +289,7 @@
 	"ml_ss": "preterpasi silenton",
 
-	"mt_loop": "ripeti unu kanton\">🔁",
+	"mt_loop": "ripeti unu kanton",
 	"mt_one": "haltigi post unu kanto\">1️⃣",
-	"mt_shuf": "ludi ĉiu dosierujo en hazarda ordo\">🔀",
+	"mt_shuf": "ludi ĉiu dosierujo en hazarda ordo",
 	"mt_aplay": "ludi aŭtomate, se ligilo enhavas identigilon de kanto$N$Nmalŝaltado de ĉi tiu agordo ankaŭ malŝaltas ĝisdatigadon de paĝ-adreso, por ke ludado ne rekomenciĝas, se la paĝo estos poste malfermita sen aliaj agordoj\">a▶",
 	"mt_preload": "komenci ŝargadon de sekva kanto antaŭ la fino de la nuna, por kontinua ludado\">antaŭŝarg.",
@@ -306,6 +306,6 @@
 	"mt_compact": "kompaktaj ruliloj\">⟎",
 	"mt_uncache": "malplenigi kaŝmemoron &nbsp;(uzinda, se via retumilo kaŝmemoris$Ndifektitan kopion de kanto, kaj ne povas ludi ĝin)\">🗑️ kaŝmem.",
-	"mt_mloop": "ripeti la nunan dosierujon\">🔁 ripeti",
-	"mt_mnext": "ŝargi la sekvan dosierujon kaj daŭrigi\">📂 sekva",
+	"mt_mloop": "ripeti la nunan dosierujon",
+	"mt_mnext": "ŝargi la sekvan dosierujon kaj daŭrigi",
 	"mt_mstop": "haltigi ludadon\">⏸ haltigi",
 	"mt_cflac": "konverti el flac / wav al {0}\">flac",
diff -NarU2 a/copyparty/web/tl/fin.js b/copyparty/web/tl/fin.js
--- a/copyparty/web/tl/fin.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/fin.js	2026-07-28 23:09:10.624925587 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "toimintopainikkeet",
+		"!": "toimintopainikkeet",
 		"dur": "kesto",
 		"q": "laatu / bittinopeus",
@@ -15,5 +15,5 @@
 		"Vhash": "videon tarkistussumma",
 		"Res": "resoluutio",
-		"T": "tiedostotyyppi",
+		"Ext": "tiedostotyyppi",
 		"aq": "äänenlaatu / bittinopeus",
 		"vq": "kuvalaatu / bittinopeus",
@@ -34,5 +34,5 @@
 			["T", "vaihda pienoiskuviin/kuvakkeisiin"],
 			["⇧ A/D", "pienoiskuvien koko"],
-			["ctrl-K", "poista valitut"],
+			["ctrl-K/Del", "poista valitut"],
 			["ctrl-X", "siirrä valitut leikepöydälle"],
 			["ctrl-C", "kopioi valitut leikepöydälle"],
@@ -108,7 +108,7 @@
 
 	"goh": "hallintapaneeli",
-	"gop": 'viereinen hakemisto">edell',
-	"gou": 'ylempi hakemisto">ylös',
-	"gon": 'seuraava hakemisto">seur',
+	"gop": 'viereinen hakemisto',
+	"gou": 'ylempi hakemisto',
+	"gon": 'seuraava hakemisto',
 	"logout": "Kirjaudu ulos ",
 	"login": "Kirjaudu sisään",
@@ -158,5 +158,5 @@
 	"ut_ow": "korvaa olemassa olevat tiedostot palvelimella?$N🛡️: ei koskaan (luo sen sijaan uuden tiedostonimen)$N🕒: korvaa jos palvelintiedosto on vanhempi kuin omasi$N♻️: korvaa aina jos tiedostot ovat erilaisia$N⏭️: ohita kaikki olemassa olevat tiedostot ehdottomasti",
 	"ut_mt": "jatka muiden tiedostojen tiivisteiden laskemista latauksen aikana$N$Nkannattanee poistaa käytöstä, mikäli prosessori tai kovalevy on vanhempaa mallia",
-	"ut_ask": 'kysy vahvistusta ennen latauksen aloittamista">💭',
+	"ut_ask": 'kysy vahvistusta ennen latauksen aloittamista',
 	"ut_pot": "paranna latausnopeutta hitailla laitteilla$Nvähentämällä käyttöliittymän monimutkaisuutta",
 	"ut_srch": "lataamisen sijaan tarkista, ovatko tiedostot jo $N olemassa palvelimella (käy läpi kaikki hakemistot, joihin sinulla on read-oikeudet)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 kuvanäkymä',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ vihjelaatikot',
-	"ct_thumb": 'valitse kuvakkeiden / pienoiskuvien välillä kuvanäkymässä $NPikanäppäin: T">🖼️ pienoiskuvat',
+	"ct_thumb": 'valitse kuvakkeiden / pienoiskuvien välillä $NPikanäppäin: T">🖼️ pienoiskuvat',
 	"ct_csel": 'käytä CTRL ja SHIFT tiedostojen valintaan kuvanäkymässä">valitse',
 	"ct_dsel": 'käytä aluevalintaa tiedostojen valintaan kuvanäkymässä">aluevalinta',
@@ -289,7 +289,7 @@
 	"ml_ss": "ohita hiljaiset kohdat",
 
-	"mt_loop": "toista samaa kappaletta\">🔁",
+	"mt_loop": "toista samaa kappaletta",
 	"mt_one": "lopeta yhden toiston jälkeen\">1️⃣",
-	"mt_shuf": "aktivoi satunnaistoisto\">🔀",
+	"mt_shuf": "aktivoi satunnaistoisto",
 	"mt_aplay": "automaattitoisto jos linkissä jolla pääsit palvelimelle oli kappale-ID$N$Ntämän poistaminen käytöstä pysäyttää myös sivun URL:n päivittämisen kappale-ID:lla musiikkia toistettaessa, estääksesi automaattitoiston jos nämä asetukset menetetään mutta URL säilyy\">a▶",
 	"mt_preload": "aloita seuraavan kappaleen lataaminen lähellä loppua, mahdollistaen saumattoman toiston\">esilataus",
@@ -306,6 +306,6 @@
 	"mt_compact": "kompaktit säätimet\">⟎",
 	"mt_uncache": "tyhjennä välimuisti &nbsp;(kokeile tätä jos selaimesi välimuistissa on$Nrikkinäinen kopio kappaleesta)\">uncache",
-	"mt_mloop": "toista avoinna olevaa hakemistoa loputtomasti\">🔁 alkuun",
-	"mt_mnext": "lataa seuraava hakemisto ja jatka\">📂 seuraava",
+	"mt_mloop": "toista avoinna olevaa hakemistoa loputtomasti",
+	"mt_mnext": "lataa seuraava hakemisto ja jatka",
 	"mt_mstop": "pysäytä toisto\">⏸ pysäytä",
 	"mt_cflac": "muunna flac / wav {0}-muotoon\">flac",
diff -NarU2 a/copyparty/web/tl/fra.js b/copyparty/web/tl/fra.js
--- a/copyparty/web/tl/fra.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/fra.js	2026-07-28 23:09:10.625112510 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "bouton d'action",
+		"!": "bouton d'action",
 		"dur": "durée",
 		"q": "qualité / débit binaire",
@@ -15,5 +15,5 @@
 		"Vhash": "somme de contrôle vidéo",
 		"Res": "résolution",
-		"T": "type de fichier",
+		"Ext": "type de fichier",
 		"aq": "qualité audio / débit binaire",
 		"vq": "qualité vidéo / débit binaire",
@@ -34,5 +34,5 @@
 			["T", "activer les miniatures / icônes"],
 			["⇧ A/D", "taille des miniatures"],
-			["ctrl-K", "suprimer la sélection"],
+			["ctrl-K/Del", "suprimer la sélection"],
 			["ctrl-X", "couper la sélection au presse-papier"],
 			["ctrl-C", "copier la sélection au presse-papier"],
@@ -108,7 +108,7 @@
 
 	"goh": "panneau-de-commande",
-	"gop": 'élément "frère" précédent">précédent',
-	"gou": 'dossier parent">haut',
-	"gon": 'dossier suivant">suivant',
+	"gop": 'élément "frère" précédent',
+	"gou": 'dossier parent',
+	"gon": 'dossier suivant',
 	"logout": "Déconnexion ",
 	"login": "Se connecter", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "écraser les fichiers existants sur le serveur?$N🛡️: jamais (générera un nouveau nom de fichier à la place)$N🕒: écraser si le fichier sur le serveur est plus ancien que le vôtre$N♻️: toujours écraser si les fichiers sont différents$N⏭️: ignorer systématiquement tous les fichiers existants", //m
 	"ut_mt": "continuer à calculer la somme de contrôle d'autres fichiers pendant le téléversement$N$Npeut-être désactiver si votre CPU ou HDD est la cause de perte de performances",
-	"ut_ask": 'demander confirmation avant le début du téléversement">💭',
+	"ut_ask": 'demander confirmation avant le début du téléversement',
 	"ut_pot": "améliorer la vitesse de téléversement sur les appareils lents$Nen simplifiant l'interface utilisateur",
 	"ut_srch": "ne pas réellement téléverser, mais vérifier si les fichiers existent déjà$N sur le serveur (scannera tous les dossiers que vous pouvez lire)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 grille',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ infobulles',
-	"ct_thumb": 'vue en grille, activer les icônes ou les miniatures$NHotkey: T">🖼️ minia',
+	"ct_thumb": 'activer les icônes ou les miniatures$NHotkey: T">🖼️ minia',
 	"ct_csel": 'utiliser CTRL et MAJ pour selectioner des fichiers en vue en grille">sel',
 	"ct_dsel": 'utiliser la sélection par glisser en vue en grille">glisser', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "ignorer les silences", //m
 
-	"mt_loop": "répéter en boucle une musique\">🔁",
+	"mt_loop": "répéter en boucle une musique",
 	"mt_one": "stopper après une musique\">1️⃣",
-	"mt_shuf": "mélanger les musiques dans chaque dossiers\">🔀",
+	"mt_shuf": "mélanger les musiques dans chaque dossiers",
 	"mt_aplay": "jouer automatiquement si le lien utilisé pour accéder au serveur a un song-ID $N$N, désactiver cela arrêtera également la mise à jour de l'URL de la page avec les song-IDs lors de la lecture de la musique, pour éviter la lecture automatique si ces paramètres sont perdus mais que l'URL reste\">a▶",
 	"mt_preload": "commencer à charger la prochaine chanson près de la fin pour une lecture sans interruption\">preload",
@@ -306,6 +306,6 @@
 	"mt_compact": "contrôles compacts\">⟎",
 	"mt_uncache": "effacer le cache &nbsp;(essayez ceci si votre navigateur a mis en cache$Nun copie défectueuse d'une chanson, ce qui empêche sa lecture)\">uncache",
-	"mt_mloop": "lire en boucle le dossier ouvert\">🔁 loop",
-	"mt_mnext": "charger le dossier suivant et continuer\">📂 next",
+	"mt_mloop": "lire en boucle le dossier ouvert",
+	"mt_mnext": "charger le dossier suivant et continuer",
 	"mt_mstop": "arrêter la lecture\">⏸ stop",
 	"mt_cflac": "convertir flac / wav en {0}\">flac",
diff -NarU2 a/copyparty/web/tl/grc.js b/copyparty/web/tl/grc.js
--- a/copyparty/web/tl/grc.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/grc.js	2026-07-28 23:09:10.625370918 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "κουμπιά ενεργειών",
+		"!": "κουμπιά ενεργειών",
 		"dur": "διάρκεια",
 		"q": "ποιότητα / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "checksum βίντεο",
 		"Res": "ανάλυση",
-		"T": "τύπος αρχείου",
+		"Ext": "τύπος αρχείου",
 		"aq": "ποιότητα ήχου / bitrate",
 		"vq": "ποιότητα βίντεο / bitrate",
@@ -34,5 +34,5 @@
 			["T", "εναλλαγή μικρογραφιών / εικονιδίων"],
 			["⇧ A/D", "μέγεθος μικρογραφιών"],
-			["ctrl-K", "διαγραφή επιλεγμένων"],
+			["ctrl-K/Del", "διαγραφή επιλεγμένων"],
 			["ctrl-X", "αποκοπή επιλογής στο πρόχειρο"],
 			["ctrl-C", "αντιγραφή επιλογής στο πρόχειρο"],
@@ -108,7 +108,7 @@
 
 	"goh": "πίνακας ελέγχου",
-	"gop": 'προηγούμενος φάκελος στο ίδιο επίπεδο">προηγούμενο',
-	"gou": 'γονικός φάκελος">πάνω',
-	"gon": 'επόμενος φάκελος">επόμενο',
+	"gop": 'προηγούμενος φάκελος στο ίδιο επίπεδο',
+	"gou": 'γονικός φάκελος',
+	"gon": 'επόμενος φάκελος',
 	"logout": "Αποσύνδεση ",
 	"login": "Σύνδεση", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "αντικατάσταση σε ήδη υπάρχοντα αρχεία του server?$N🛡️: ποτέ (θα δημιουργηθεί νέο όνομα)$N🕒: αν το αρχείο του server είναι παλαιότερο$N♻️: πάντα να αντικαθίστανται αν διαφέρουν$N⏭️: παράλειψη όλων των υπαρχόντων αρχείων χωρίς όρους", //m
 	"ut_mt": "συνέχιση υπολογισμού hash για άλλα αρχεία κατά τη μεταφόρτωση$N$Nαπενεργοποίησέ το αν η CPU ή ο δίσκος σου ζορίζονται",
-	"ut_ask": 'επιβεβαίωση πριν ξεκινήσει η μεταφόρτωση">💭',
+	"ut_ask": 'επιβεβαίωση πριν ξεκινήσει η μεταφόρτωση',
 	"ut_pot": "βελτίωση ταχύτητας μεταφόρτωσης σε αργές συσκευές$Nμε απλοποίηση του UI",
 	"ut_srch": "μην ανεβάζεις, έλεγξε αν τα αρχεία$Nυπάρχουν ήδη στον server (ψάχνει σε όλους τους φακέλους που έχεις πρόσβαση)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 το πλέγμα',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ συμβουλές εργαλείων',
-	"ct_thumb": 'σε προβολή πλέγματος, εναλλαγή εικονιδίων ή μικρογραφιών$NΠλήκτρο συντόμευσης: T">🖼️ μικρογραφίες',
+	"ct_thumb": 'εναλλαγή εικονιδίων ή μικρογραφιών$NΠλήκτρο συντόμευσης: T">🖼️ μικρογραφίες',
 	"ct_csel": 'χρησιμοποίησε CTRL και SHIFT για επιλογή αρχείων σε προβολή πλέγματος">επιλογή',
 	"ct_dsel": 'χρησιμοποίησε επιλογή με σύρσιμο σε προβολή πλέγματος">σύρσιμο', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "παράβλεψη σιωπής", //m
 
-	"mt_loop": "επανάληψη ενός τραγουδιού\">🔁",
+	"mt_loop": "επανάληψη ενός τραγουδιού",
 	"mt_one": "σταμάτα μετά από ένα τραγούδι\">1️⃣",
-	"mt_shuf": "τυχαία σειρά τραγουδιών σε κάθε φάκελο\">🔀",
+	"mt_shuf": "τυχαία σειρά τραγουδιών σε κάθε φάκελο",
 	"mt_aplay": "αυτόματη αναπαραγωγή αν υπάρχει song-ID στη διεύθυνση που μπήκες στο διακομιστή$N$Nη απενεργοποίηση αυτού, σταματά το URL από το να ενημερώνεται με τα song-ID ενώ παίζει η μουσική για να αποτραπεί η αυτόματη αναπαραγωγή αν χαθούν αυτές οι ρυθμίσεις αλλά το URL παραμείνει το ίδιο\">a▶",
 	"mt_preload": "ξεκίνα  τη φόρτωση του επόμενου τραγουδιού κοντά στο τέλος για συνεχόμενη ακρόαση\">προφόρτωση",
@@ -306,6 +306,6 @@
 	"mt_compact": "συμπαγή κουμπιά ελέγχου\">⟎",
 	"mt_uncache": "καθάρισε την προσωρινή μνήμη &nbsp;(δοκίμασε αυτό αν ο browser έχει αποθηκεύσει$Nχαλασμένο αντίγραφο τραγουδιού και αρνείται να παίξει)\">εκκαθάριση",
-	"mt_mloop": "τυχαία αναπαραγωγή στον ανοικτό φάκελο\">🔁 τυχαία αναπαραγωγή",
-	"mt_mnext": "φόρτωση επόμενου φακέλου και συνέχιση\">📂 επόμενο",
+	"mt_mloop": "τυχαία αναπαραγωγή στον ανοικτό φάκελο",
+	"mt_mnext": "φόρτωση επόμενου φακέλου και συνέχιση",
 	"mt_mstop": "σταμάτησε την αναπαραγωγή\">⏸ σταμάτημα",
 	"mt_cflac": "μετατροπή flac / wav σε {0}\">flac",
diff -NarU2 a/copyparty/web/tl/hun.js b/copyparty/web/tl/hun.js
--- a/copyparty/web/tl/hun.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/hun.js	2026-07-28 23:09:10.625517585 +0000
@@ -3,5 +3,5 @@
 
 	"cols": {
-		"c": 'műveletek',
+		"!": 'műveletek',
 		"dur": 'hossz',
 		"q": 'minőség / bitrate',
@@ -12,5 +12,5 @@
 		"Vhash": 'videó hash',
 		"Res": 'felbontás',
-		"T": 'típus',
+		"Ext": 'típus',
 		"aq": 'audió minőség / bitrate',
 		"vq": 'videó minőség / bitrate',
@@ -31,5 +31,5 @@
 			['T', 'ikon / indexkép váltás'],
 			['⇧ A/D', 'méret módosítása'],
-			['ctrl-K', 'kijelöltek törlése'],
+			['ctrl-K/Del', 'kijelöltek törlése'],
 			['ctrl-X', 'kivágás vágólapra'],
 			['ctrl-C', 'másolás vágólapra'],
@@ -110,7 +110,7 @@
 
 	"goh": 'irányítópult',
-	"gop": 'előző mappába">előző',
-	"gou": 'szülőmappa">fel',
-	"gon": 'következő mappába">következő',
+	"gop": 'előző mappába',
+	"gou": 'szülőmappa',
+	"gon": 'következő mappába',
 	"logout": 'Kilépés ',
 	"login": 'Belépés',
@@ -160,5 +160,5 @@
 	"ut_ow": 'felülírás?$N🛡️: soha (új nevet ad)$N🕒: csak ha a tiéd újabb$N♻️: mindig, ha más a tartalom$N⏭️: létezőket kihagy',
 	"ut_mt": 'háttérben hashelés feltöltés alatt$N$Nkapcsold ki, ha fagy a géped',
-	"ut_ask": 'megerősítés feltöltés előtt">💭',
+	"ut_ask": 'megerősítés feltöltés előtt',
 	"ut_pot": 'feltöltés gyorsítása (egyszerűbb UI)',
 	"ut_srch": 'csak létezés ellenőrzése$N(nem tölt fel semmit)',
@@ -227,5 +227,5 @@
 	"ct_grid": '田 rács nézet',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ segítő szövegek',
-	"ct_thumb": 'rács nézetben ikonok/indexképek váltása$Ngyorsbillentyű: T">🖼️ képek',
+	"ct_thumb": 'ikonok/indexképek váltása$Ngyorsbillentyű: T">🖼️ képek',
 	"ct_csel": 'kijelölés CTRL és SHIFT gombokkal rács nézetben">kijelölés',
 	"ct_dsel": 'kijelölés egérhúzással rács nézetben">húzás',
@@ -290,7 +290,7 @@
 	"ml_ss": 'csönd átugrása',
 
-	"mt_loop": 'egy szám ismétlése">🔁',
+	"mt_loop": 'egy szám ismétlése',
 	"mt_one": 'leállás egy szám után">1️⃣',
-	"mt_shuf": 'véletlenszerű lejátszás mappánként">🔀',
+	"mt_shuf": 'véletlenszerű lejátszás mappánként',
 	"mt_aplay": 'automatikus kezdés a link alapján$N$Nha kikapcsolod, a linkek sem fognak szám-azonosítót tartalmazni">a▶',
 	"mt_preload": 'következő szám betöltése a végén (gapless)">preload',
@@ -307,6 +307,6 @@
 	"mt_compact": 'kompakt vezérlők">⟎',
 	"mt_uncache": 'gyorsítótár ürítése (próbáld ezt, ha a böngésző hibás fájlt mentett el és nem játssza le)">uncache',
-	"mt_mloop": 'mappa ismétlése">🔁 ismétlés',
-	"mt_mnext": 'következő mappa betöltése és folytatás">📂 tovább',
+	"mt_mloop": 'mappa ismétlése',
+	"mt_mnext": 'következő mappa betöltése és folytatás',
 	"mt_mstop": 'stop">⏸ stop',
 	"mt_cflac": 'flac / wav konvertálása ide: {0}">flac',
diff -NarU2 a/copyparty/web/tl/ita.js b/copyparty/web/tl/ita.js
--- a/copyparty/web/tl/ita.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/ita.js	2026-07-28 23:09:10.625688588 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "pulsanti azione",
+		"!": "pulsanti azione",
 		"dur": "durata",
 		"q": "qualità / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "checksum video",
 		"Res": "risoluzione",
-		"T": "tipo file",
+		"Ext": "tipo file",
 		"aq": "qualità audio / bitrate",
 		"vq": "qualità video / bitrate",
@@ -34,5 +34,5 @@
 			["T", "alterna miniature / icone"],
 			["⇧ A/D", "dimensione miniature"],
-			["ctrl-K", "elimina selezionati"],
+			["ctrl-K/Del", "elimina selezionati"],
 			["ctrl-X", "taglia selezione negli appunti"],
 			["ctrl-C", "copia selezione negli appunti"],
@@ -108,7 +108,7 @@
 
 	"goh": "control-panel",
-	"gop": 'cartella sorella precedente">prec',
-	"gou": 'cartella genitore">su',
-	"gon": 'prossima cartella">succ',
+	"gop": 'cartella sorella precedente',
+	"gou": 'cartella genitore',
+	"gon": 'prossima cartella',
 	"logout": "Logout ",
 	"login": "Accedi", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "sovrascrivere file esistenti sul server?$N🛡️: mai (genererà un nuovo nome file)$N🕒: sovrascrivi se il file del server è più vecchio del tuo$N♻️: sovrascrivi sempre se i file sono diversi$N⏭️: ignora sempre tutti i file esistenti", //m
 	"ut_mt": "continua l'hashing di altri file durante il caricamento$N$NProva a disabilitare se la tua CPU o HDD è un collo di bottiglia",
-	"ut_ask": 'chiedi conferma prima che inizi il caricamento">💭',
+	"ut_ask": 'chiedi conferma prima che inizi il caricamento',
 	"ut_pot": "migliora la velocità di caricamento su dispositivi lenti$Nrendendo l'interfaccia meno complessa",
 	"ut_srch": "non caricare realmente, invece controlla se i file esistono già $N sul server (scansionerà tutte le cartelle che puoi leggere)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 griglia',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ tooltip',
-	"ct_thumb": 'nella vista griglia, alterna icone o miniature$NTasto rapido: T">🖼️ miniature',
+	"ct_thumb": 'alterna icone o miniature$NTasto rapido: T">🖼️ miniature',
 	"ct_csel": 'usa CTRL e SHIFT per la selezione file nella vista griglia">sel',
 	"ct_dsel": 'usa la selezione tramite trascinamento nella vista griglia">trascina', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "salta i silenzi", //m
 
-	"mt_loop": "loop/ripeti una canzone\">🔁",
+	"mt_loop": "loop/ripeti una canzone",
 	"mt_one": "fermati dopo una canzone\">1️⃣",
-	"mt_shuf": "mescola le canzoni in ogni cartella\">🔀",
+	"mt_shuf": "mescola le canzoni in ogni cartella",
 	"mt_aplay": "autoplay se c'è un song-ID nel link che hai cliccato per accedere al server$N$NDisabilitando questo fermerà anche l'aggiornamento dell'URL della pagina con song-ID quando riproduci musica, per prevenire autoplay se queste impostazioni vengono perse ma l'URL rimane\">a▶",
 	"mt_preload": "inizia a caricare la prossima canzone verso la fine per riproduzione senza interruzioni\">preload",
@@ -306,6 +306,6 @@
 	"mt_compact": "controlli compatti\">⟎",
 	"mt_uncache": "pulisci cache &nbsp;(prova ad attivare se il tuo browser ha messo in cache$Nuna copia rotta di una canzone e si rifiuta di riprodurla)\">uncache",
-	"mt_mloop": "loop della cartella aperta\">🔁 loop",
-	"mt_mnext": "carica la prossima cartella e continua\">📂 succ",
+	"mt_mloop": "loop della cartella aperta",
+	"mt_mnext": "carica la prossima cartella e continua",
 	"mt_mstop": "ferma riproduzione\">⏸ stop",
 	"mt_cflac": "converti flac / wav in {0}\">flac",
diff -NarU2 a/copyparty/web/tl/jpn.js b/copyparty/web/tl/jpn.js
--- a/copyparty/web/tl/jpn.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/jpn.js	2026-07-28 23:09:10.625896121 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "アクションボタン",
+		"!": "アクションボタン",
 		"dur": "間隔",
 		"q": "品質 / ビットレート",
@@ -15,5 +15,5 @@
 		"Vhash": "ビデオチェックサム",
 		"Res": "解像度",
-		"T": "ファイル形式",
+		"Ext": "ファイル形式",
 		"aq": "オーディオ 品質 / ビットレート",
 		"vq": "ビデオ 品質 / ビットレート",
@@ -34,5 +34,5 @@
 			["T", "サムネイル / アイコンを切り替える"],
 			["⇧ A/D", "サムネイルサイズ"],
-			["ctrl-K", "選択した項目を削除"],
+			["ctrl-K/Del", "選択した項目を削除"],
 			["ctrl-X", "選択範囲をクリップボードに切り取る"],
 			["ctrl-C", "選択範囲をクリップボードにコピー"],
@@ -108,7 +108,7 @@
 
 	"goh": "コントロールパネル",
-	"gop": '前のフォルダ">prev',
-	"gou": '親フォルダ">up',
-	"gon": '次のフォルダ">next',
+	"gop": '前のフォルダ',
+	"gou": '親フォルダ',
+	"gon": '次のフォルダ',
 	"logout": "ログアウト ",
 	"login": "ログイン",
@@ -158,5 +158,5 @@
 	"ut_ow": "サーバー上の既存のファイルを上書きする？$N🛡️: しない（代わりに新しいファイル名を生成する）$N🕒: サーバーのファイルが古い場合は上書きする$N♻️: ファイルが異なる場合は常に上書きする$N⏭️: 既存のファイルをすべて無条件にスキップする",
 	"ut_mt": "アップロード中に他のファイルのハッシュを継続する$N$NCPUやHDDがボトルネックになっている場合は無効にしてください",
-	"ut_ask": 'aアップロードを開始する前に確認を求める">💭',
+	"ut_ask": 'aアップロードを開始する前に確認を求める',
 	"ut_pot": "UIをシンプルにすることで$N低速デバイスでのアップロード速度を向上させる",
 	"ut_srch": "実際にはアップロードせず、代わりにファイルが既にアップロードされているかどうかを確認 $N すでにサーバー上に存在（読み取り可能なすべてのフォルダをスキャン）",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 グリッド',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ ツールチップ',
-	"ct_thumb": 'グリッドビューではアイコンまたはサムネイルを切り替える$Nホットキー: T">🖼️ サムネイル',
+	"ct_thumb": 'アイコンまたはサムネイルを切り替える$Nホットキー: T">🖼️ サムネイル',
 	"ct_csel": 'グリッドビューでファイルを選択するにはCtrlとShiftを使用する。">選択',
 	"ct_dsel": 'グリッドビューでドラッグ選択を使用する。">ドラッグ', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "無音をスキップ", //m
 
-	"mt_loop": "1曲をループ/リピート再生\">🔁",
+	"mt_loop": "1曲をループ/リピート再生",
 	"mt_one": "1曲で止める\">1️⃣",
-	"mt_shuf": "各フォルダ内の曲をシャッフルする\">🔀",
+	"mt_shuf": "各フォルダ内の曲をシャッフルする",
 	"mt_aplay": "サーバーにアクセスするためにクリックしたリンクに曲IDがある場合は自動再生されます$N$Nこれを無効にすると、音楽を再生するときにページのURLが曲IDで更新されなくなります。これにより設定が失われてもURLが残っている場合の自動再生が防止されます。\">a▶",
 	"mt_preload": "ギャップレス再生のために曲の終わり近くに次の曲の読み込みを開始する\">preload",
@@ -306,6 +306,6 @@
 	"mt_compact": "コントローラーを小さく\">⟎",
 	"mt_uncache": "キャッシュクリア &nbsp;（ブラウザが破損した曲のコピーをキャッシュしているために$N再生できない場合はこれを試してください）\">uncache",
-	"mt_mloop": "開いているフォルダをループ\">🔁 ループ",
-	"mt_mnext": "次のフォルダを読み込んで続行\">📂 次",
+	"mt_mloop": "開いているフォルダをループ",
+	"mt_mnext": "次のフォルダを読み込んで続行",
 	"mt_mstop": "再生を停止\">⏸ 停止",
 	"mt_cflac": "flac / wavを{0}に変換\">flac",
diff -NarU2 a/copyparty/web/tl/kor.js b/copyparty/web/tl/kor.js
--- a/copyparty/web/tl/kor.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/kor.js	2026-07-28 23:09:10.626067775 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "작업 버튼",
+		"!": "작업 버튼",
 		"dur": "길이",
 		"q": "품질/비트레이트",
@@ -15,5 +15,5 @@
 		"Vhash": "비디오 체크섬",
 		"Res": "해상도",
-		"T": "파일 유형",
+		"Ext": "파일 유형",
 		"aq": "오디오 품질/비트레이트",
 		"vq": "비디오 품질/비트레이트",
@@ -34,5 +34,5 @@
 			["T", "썸네일/아이콘 전환"],
 			["⇧ A/D", "썸네일 이미지 크기"],
-			["ctrl-K", "선택 항목 삭제"],
+			["ctrl-K/Del", "선택 항목 삭제"],
 			["ctrl-X", "선택 항목 잘라내기"],
 			["ctrl-C", "선택 항목 복사"],
@@ -108,7 +108,7 @@
 
 	"goh": "제어판",
-	"gop": '이전 형제 폴더">이전',
-	"gou": '상위 폴더">위로',
-	"gon": '다음 폴더">다음',
+	"gop": '이전 형제 폴더',
+	"gou": '상위 폴더',
+	"gon": '다음 폴더',
 	"logout": "로그아웃 ",
 	"login": "로그인", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "서버에 있는 기존 파일을 덮어쓸까요?$N🛡️: 안 함 (대신 새 파일 이름 생성)$N🕒: 서버 파일이 더 오래된 경우 덮어쓰기$N♻️: 파일이 다르면 항상 덮어쓰기$N⏭️: 기존 파일을 모두 무조건 건너뜀", //m
 	"ut_mt": "업로드 중 다른 파일 해싱 계속하기$N$NCPU 또는 HDD가 병목 현상을 일으키는 경우 비활성화하세요",
-	"ut_ask": '업로드 시작 전 확인 요청">💭',
+	"ut_ask": '업로드 시작 전 확인 요청',
 	"ut_pot": "느린 기기에서 UI를 단순화하여$N업로드 속도 향상",
 	"ut_srch": "실제로 업로드하는 대신, 파일이 이미 서버에 있는지 확인합니다$N(읽을 수 있는 모든 폴더를 스캔합니다)",
@@ -226,5 +226,5 @@
 	"ct_grid": "田 그리드",
 	"ct_ttips": '◔ ◡ ◔">ℹ️ 도움말',
-	"ct_thumb": '그리드 보기에서 아이콘 또는 미리보기 이미지 전환$N단축키: T">🖼️ 미리보기',
+	"ct_thumb": '아이콘 또는 미리보기 이미지 전환$N단축키: T">🖼️ 미리보기',
 	"ct_csel": '그리드 보기에서 CTRL과 SHIFT를 사용하여 파일 선택">선택',
 	"ct_dsel": '그리드 보기에서 드래그 선택 사용">드래그', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "무음 건너뛰기", //m
 
-	"mt_loop": "한 곡 반복 재생\">🔁",
+	"mt_loop": "한 곡 반복 재생",
 	"mt_one": "한 곡 재생 후 중지\">1️⃣",
-	"mt_shuf": "각 폴더의 곡을 무작위 재생\">🔀",
+	"mt_shuf": "각 폴더의 곡을 무작위 재생",
 	"mt_aplay": "서버에 접속한 링크에 곡 ID가 있으면 자동 재생$N$N이것을 비활성화하면 음악 재생 시 페이지 URL이 곡 ID로 업데이트되지 않아, 이 설정이 손실되고 URL이 남아있을 경우 자동 재생되는 것을 방지합니다.\">a▶",
 	"mt_preload": "끊김 없는 재생을 위해 다음 곡을 미리 불러오기 시작\">미리로드",
@@ -306,6 +306,6 @@
 	"mt_compact": "컴팩트 컨트롤\">⟎",
 	"mt_uncache": "캐시 지우기 (브라우저가 곡의 깨진 사본을 캐시하여$N재생이 안되는 경우 시도해보세요)\">캐시삭제",
-	"mt_mloop": "열린 폴더 반복\">🔁 반복",
-	"mt_mnext": "다음 폴더 불러오고 계속\">📂 다음",
+	"mt_mloop": "열린 폴더 반복",
+	"mt_mnext": "다음 폴더 불러오고 계속",
 	"mt_mstop": "재생 중지\">⏸ 중지",
 	"mt_cflac": "flac/wav를 {0}로 변환\">flac",
diff -NarU2 a/copyparty/web/tl/nld.js b/copyparty/web/tl/nld.js
--- a/copyparty/web/tl/nld.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/nld.js	2026-07-28 23:09:10.626247004 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "Action knoppen",
+		"!": "Action knoppen",
 		"dur": "Duratie",
 		"q": "Kwaliteit / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "Video checksum",
 		"Res": "Resolution",
-		"T": "Bestandstype",
+		"Ext": "Bestandstype",
 		"aq": "Audio kwaliteit / bitrate",
 		"vq": "Video kwaliteit / bitrate",
@@ -34,5 +34,5 @@
 			["T", "Verwissel tussen miniaturen / iconen"],
 			["⇧ A/D", "Thumbnail formaat"],
-			["ctrl-K", "Verwijder geselecteerde"],
+			["ctrl-K/Del", "Verwijder geselecteerde"],
 			["ctrl-X", "Knip selectie naar klembord"],
 			["ctrl-C", "Kopieer selectie naar klembord"],
@@ -108,7 +108,7 @@
 
 	"goh": "Beheer-paneel",
-	"gop": 'Vorige map">Vorige',
-	"gou": 'Bovenligende map">Omhoog',
-	"gon": 'Volgende map">Volgende',
+	"gop": 'Vorige map',
+	"gou": 'Bovenligende map',
+	"gon": 'Volgende map',
 	"logout": "Uitloggen ",
 	"login": "Inloggen", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "Overschrijf bestaande bestanden op de server?$N🛡️: nooit (zal in plaats daarvan een nieuwe bestandsnaam genereren)$N🕒: overschrijven als de server-bestand ouder is dan het geüploade bestand$N♻️: altijd overschrijven als de bestanden verschillend zijn$N⏭️: alle bestaande bestanden onvoorwaardelijk overslaan", //m
 	"ut_mt": "Ga door met hashen van andere bestanden tijdens het uploaden$N$Moet je misschien uitschakelen als je CPU of HDD het niet aan kan",
-	"ut_ask": 'Vraag voor bevestiging voordat het uploaden start">💭',
+	"ut_ask": 'Vraag voor bevestiging voordat het uploaden start',
 	"ut_pot": "Verbeter de uploadsnelheid voor langzame apparaten$Ndoor de interface minder complex te maken",
 	"ut_srch": "Niet uploaden, maar check of de bestanden als op de server bestaan$N (checkt alle mappen die waar jij toegang op hebt)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 grid',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
-	"ct_thumb": 'In grid-overzicht, wissel tussen iconen of thumbnails$NHotkey: T">🖼️ thumbs',
+	"ct_thumb": 'wissel tussen iconen of thumbnails$NHotkey: T">🖼️ thumbs',
 	"ct_csel": 'Gebruik CTRL en SHIFT voor de bestand selectie in grid-overzicht>sel',
 	"ct_dsel": 'Gebruik slepen om te selecteren in grid-overzicht>slepen', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "Stiltes overslaan", //m
 
-	"mt_loop": "Loop/herhaal een nummer\">🔁",
+	"mt_loop": "Loop/herhaal een nummer",
 	"mt_one": "Stop na een nummer\">1️⃣",
-	"mt_shuf": "Shuffle alle muziek in alle mappen\">🔀",
+	"mt_shuf": "Shuffle alle muziek in alle mappen",
 	"mt_aplay": "Autoplay als er een song-ID staat in de link waarop je hebt geklikt om naar de server te gaan$N$NAls u dit uitschakelt, wordt de pagina-URL ook niet meer bijgewerkt met nummer-ID's tijdens het afspelen van muziek. Dit voorkomt automatisch afspelen als deze instellingen verloren gaan, maar de URL behouden blijft.\">a▶",
 	"mt_preload": "Begin het laden van de volgende nummer vlak voordat de huidige nummer het einde bereikt voor gapless playback\">preload",
@@ -306,6 +306,6 @@
 	"mt_compact": "Compacte bedieningselementen\">⟎",
 	"mt_uncache": "Cache wissen &nbsp;(Probeer dit als uw browser een kapotte kopie van een nummer heeft gecached, waardoor het niet afgespeeld kan worden)\">uncache",
-	"mt_mloop": "De open map herhalen\">🔁 loop",
-	"mt_mnext": "Laad de volgende map en ga verder\">📂 next",
+	"mt_mloop": "De open map herhalen",
+	"mt_mnext": "Laad de volgende map en ga verder",
 	"mt_mstop": "Stoppen met afspelen\">⏸ stop",
 	"mt_cflac": "flac / wav omzetten naar {0}\">flac",
diff -NarU2 a/copyparty/web/tl/nno.js b/copyparty/web/tl/nno.js
--- a/copyparty/web/tl/nno.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/nno.js	2026-07-28 23:09:10.626413869 +0000
@@ -3,5 +3,5 @@
 
 	"cols": {
-		"c": "handlingsknappar",
+		"!": "handlingsknappar",
 		"dur": "varigheit",
 		"q": "kvalitet / bitrate",
@@ -12,5 +12,5 @@
 		"Vhash": "videokontrollsum",
 		"Res": "oppløysing",
-		"T": "filtype",
+		"Ext": "filtype",
 		"aq": "lydkvalitet / bitrate",
 		"vq": "videokvalitet / bitrate",
@@ -31,5 +31,5 @@
 			["T", "miniatyrbilder på/av"],
 			["⇧ A/D", "ikonstorleik"],
-			["ctrl-K", "slett valde"],
+			["ctrl-K/Del", "slett valde"],
 			["ctrl-X", "klipp ut valde"],
 			["ctrl-C", "kopiér åt utklippstavle"],
@@ -105,7 +105,7 @@
 
 	"goh": "kontrollpanel",
-	"gop": 'navigér åt mappa før den her">forr.',
-	"gou": 'navigér eitt nivå opp">opp',
-	"gon": 'navigér åt mappa etter den her">neste',
+	"gop": 'navigér åt mappa før den her',
+	"gou": 'navigér eitt nivå opp',
+	"gon": 'navigér åt mappa etter den her',
 	"logout": "Logg ut ",
 	"login": "Logg inn",
@@ -155,5 +155,5 @@
 	"ut_ow": "overskrive eksisterande filer på serveren?$N🛡️: aldri (finn på eit nytt filnamn i staden for)$N🕒: overskriv viss fila åt serveren er eldre$N♻️: alltid, gitt at innhaldet er annleis$N⏭️: hopp over alle eksisterande filer",
 	"ut_mt": "fortsett å synfare køa mens opplasting føregår$N$Nskru denne av dersom du har ein$Ntreig prosessor eller harddisk",
-	"ut_ask": 'bekreft filutvalg før opplasting startar">💭',
+	"ut_ask": 'bekreft filutvalg før opplasting startar',
 	"ut_pot": "forbetre ytinga på treige einheiter ved å$Nforenkle brukergrensesnittet",
 	"ut_srch": "gjer eit søk i staden for å laste opp --$Nleitar gjennom alle mappane du har lov åt å sjå",
@@ -286,7 +286,7 @@
 	"ml_ss": "spol forbi stillheit",
 
-	"mt_loop": "spel den same songen om og om igjen\">🔁",
+	"mt_loop": "spel den same songen om og om igjen",
 	"mt_one": "spel kun éin song\">1️⃣",
-	"mt_shuf": "songane i kvar mappe$Nspelast i tilfeldig rekkefølge\">🔀",
+	"mt_shuf": "songane i kvar mappe$Nspelast i tilfeldig rekkefølge",
 	"mt_aplay": "prøv å starte avspeling viss linken du trykte på for å åpne nettsida inneheld ein song-ID$N$Nviss denne deaktiverast så vil heller ikkje nettside-URL'en bli oppdatert med song-ID'er når musikk spelast, i tilfelle innstillingane skulle gå tapt og nettsida lastast på ny\">a▶",
 	"mt_preload": "hent ned litt av neste song i forkant,$Nslik at pausa i overgangen blir mindre\">forsyn",
@@ -303,6 +303,6 @@
 	"mt_compact": "tettpakka spelarpanel\">⟎",
 	"mt_uncache": "prøv denne viss ein song ikkje spelar riktig\">oppfrisk",
-	"mt_mloop": "repetér heile mappa\">🔁 gjenta",
-	"mt_mnext": "hopp åt neste mappe og fortsett\">📂 neste",
+	"mt_mloop": "repetér heile mappa",
+	"mt_mnext": "hopp åt neste mappe og fortsett",
 	"mt_mstop": "stopp avspeling\">⏸ stopp",
 	"mt_cflac": "konvertér flac / wav-filer åt {0}\">flac",
diff -NarU2 a/copyparty/web/tl/nor.js b/copyparty/web/tl/nor.js
--- a/copyparty/web/tl/nor.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/nor.js	2026-07-28 23:09:10.626579903 +0000
@@ -3,5 +3,5 @@
 
 	"cols": {
-		"c": "handlingsknapper",
+		"!": "handlingsknapper",
 		"dur": "varighet",
 		"q": "kvalitet / bitrate",
@@ -12,5 +12,5 @@
 		"Vhash": "video-kontrollsum",
 		"Res": "oppløsning",
-		"T": "filtype",
+		"Ext": "filtype",
 		"aq": "lydkvalitet / bitrate",
 		"vq": "videokvalitet / bitrate",
@@ -31,5 +31,5 @@
 			["T", "miniatyrbilder på/av"],
 			["⇧ A/D", "ikonstørrelse"],
-			["ctrl-K", "slett valgte"],
+			["ctrl-K/Del", "slett valgte"],
 			["ctrl-X", "klipp ut valgte"],
 			["ctrl-C", "kopiér til utklippstavle"],
@@ -105,7 +105,7 @@
 
 	"goh": "kontrollpanel",
-	"gop": 'naviger til mappen før denne">forr.',
-	"gou": 'naviger ett nivå opp">opp',
-	"gon": 'naviger til mappen etter denne">neste',
+	"gop": 'naviger til mappen før denne',
+	"gou": 'naviger ett nivå opp',
+	"gon": 'naviger til mappen etter denne',
 	"logout": "Logg ut ",
 	"login": "Logg inn",
@@ -155,5 +155,5 @@
 	"ut_ow": "overskrive eksisterende filer på serveren?$N🛡️: aldri (finner på et nytt filnavn istedenfor)$N🕒: overskriv hvis serverens fil er eldre$N♻️: alltid, gitt at innholdet er forskjellig$N⏭️: hopp over alle eksisterende filer",
 	"ut_mt": "fortsett å befare køen mens opplastning foregår$N$Nskru denne av dersom du har en$Ntreg prosessor eller harddisk",
-	"ut_ask": 'bekreft filutvalg før opplastning starter">💭',
+	"ut_ask": 'bekreft filutvalg før opplastning starter',
 	"ut_pot": "forbedre ytelsen på trege enheter ved å$Nforenkle brukergrensesnittet",
 	"ut_srch": "utfør søk istedenfor å laste opp --$Nleter igjennom alle mappene du har lov til å se",
@@ -223,5 +223,5 @@
 	"ct_grid": '田 ikoner',
 	"ct_ttips": 'vis hjelpetekst ved å holde musen over ting">ℹ️ tips',
-	"ct_thumb": 'vis miniatyrbilder istedenfor ikoner$NSnarvei: T">🖼️ bilder',
+	"ct_thumb": 'vis miniatyrbilder isteden for ikoner$NSnarvei: T">🖼️ bilder',
 	"ct_csel": 'bruk tastene CTRL og SHIFT for markering av filer i ikonvisning">merk',
 	"ct_dsel": 'marker filer med klikk-og-dra i ikonvisning">dsel',
@@ -286,7 +286,7 @@
 	"ml_ss": "spol forbi stillhet",
 
-	"mt_loop": "spill den samme sangen om og om igjen\">🔁",
+	"mt_loop": "spill den samme sangen om og om igjen",
 	"mt_one": "spill kun én sang\">1️⃣",
-	"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge\">🔀",
+	"mt_shuf": "sangene i hver mappe$Nspilles i tilfeldig rekkefølge",
 	"mt_aplay": "forsøk å starte avspilling hvis linken du klikket på for å åpne nettsiden inneholder en sang-ID$N$Nhvis denne deaktiveres så vil heller ikke nettside-URL'en bli oppdatert med sang-ID'er når musikk spilles, i tilfelle innstillingene skulle gå tapt og nettsiden lastes på ny\">a▶",
 	"mt_preload": "hent ned litt av neste sang i forkant,$Nslik at pausen i overgangen blir mindre\">forles",
@@ -303,6 +303,6 @@
 	"mt_compact": "tettpakket avspillerpanel\">⟎",
 	"mt_uncache": "prøv denne hvis en sang ikke spiller riktig\">oppfrisk",
-	"mt_mloop": "repeter hele mappen\">🔁 gjenta",
-	"mt_mnext": "hopp til neste mappe og fortsett\">📂 neste",
+	"mt_mloop": "repeter hele mappen",
+	"mt_mnext": "hopp til neste mappe og fortsett",
 	"mt_mstop": "stopp avspilling\">⏸ stopp",
 	"mt_cflac": "konverter flac / wav-filer til {0}\">flac",
diff -NarU2 a/copyparty/web/tl/pol.js b/copyparty/web/tl/pol.js
--- a/copyparty/web/tl/pol.js	2026-07-08 15:13:32.823053216 +0000
+++ b/copyparty/web/tl/pol.js	2026-07-28 23:09:10.626784269 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "przyciski akcji",
+		"!": "przyciski akcji",
 		"dur": "czas trwania",
 		"q": "jakość / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "suma kontrolna wideo",
 		"Res": "rozdzielczość",
-		"T": "rodzaj pliku",
+		"Ext": "rodzaj pliku",
 		"aq": "jakość / bitrate audio",
 		"vq": "jakość / bitrate wideo",
@@ -34,5 +34,5 @@
 			["T", "przełącz miniaturki / ikony"],
 			["⇧ A/D", "wielkość miniaturki"],
-			["ctrl-K", "usuń zaznaczone"],
+			["ctrl-K/Del", "usuń zaznaczone"],
 			["ctrl-X", "wytnij zaznaczone do schowka"],
 			["ctrl-C", "skopiuj zaznaczone do schowka"],
@@ -111,7 +111,7 @@
 
 	"goh": "panel sterowania",
-	"gop": 'poprzedni plik/folder">poprzedni',
-	"gou": 'nadrzędny folder">w górę',
-	"gon": 'następny folder">następny',
+	"gop": 'poprzedni plik/folder',
+	"gou": 'nadrzędny folder',
+	"gon": 'następny folder',
 	"logout": "Wyloguj ",
 	"login": "Zaloguj się", //m
@@ -161,5 +161,5 @@
 	"ut_ow": "nadpisywać istniejące pliki na serwerzę?$N🛡️: nigdy (wygeneruje nową nazwę)$N🕒: nadpisz jeśli pliki na serwerze są starsze niż przesyłane$N♻️: zawsze nadpisuj jeśli zawartość plików się różni$N⏭️: bezwarunkowo pomiń wszystkie istniejące pliki", //m
 	"ut_mt": "hashuj inne pliki podczas przesyłania$N$Nmożna wyłączyć w przypadku wystąpienia wąskiego gardła na CPU lub HDD",
-	"ut_ask": 'pytaj o potwierdzenie rozpoczęcia przesyłania">💭',
+	"ut_ask": 'pytaj o potwierdzenie rozpoczęcia przesyłania',
 	"ut_pot": "przyspiesz przesyłanie na słabszych urządzeniach,$Nupraszczając interfejs",
 	"ut_srch": "nie przesyłaj plików, jedynie sprawdź czy istnieją$Njuż na serwerze (przeskanuje wszystkie foldery dostępne do odczytu)",
@@ -229,5 +229,5 @@
 	"ct_grid": '田 siatka',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ podpowiedzi',
-	"ct_thumb": 'w widoku siatki, przełącz ikony i miniaturki$NSkrót: T">🖼️ miniaturki',
+	"ct_thumb": 'przełącz ikony i miniaturki$NSkrót: T">🖼️ miniaturki',
 	"ct_csel": 'użyj CTRL i SHIFT do wybierania plików w widoku siatki">wybierz',
 	"ct_dsel": 'użyj zaznaczania przez przeciąganie w widoku siatki">przeciągnij', //m
@@ -292,7 +292,7 @@
 	"ml_ss": "pomijaj ciszę", //m
 
-	"mt_loop": "pętla/powtarzaj jeden utwór\">🔁",
+	"mt_loop": "pętla/powtarzaj jeden utwór",
 	"mt_one": "zatrzymaj po jednym utworze\">1️⃣",
-	"mt_shuf": "odtwarzaj losowo w każdym folderze\">🔀",
+	"mt_shuf": "odtwarzaj losowo w każdym folderze",
 	"mt_aplay": "autoodtwarzanie po kliknięciu linku do tego serwera, zawierającego identyfikator utworu$N$Nwyłączenie tej opcji zapobiegnie aktualizowaniu adresu strony podczas odtwarzania muzyki, aby zapobiec autoodtwarzaniu przy utracie ustawień\">a▶",
 	"mt_preload": "rozpocznij ładowanie kolejnego utworu blisko końca aktualnego w celu uzyskania odtwarzania bez przerw\">preload",
@@ -309,6 +309,6 @@
 	"mt_compact": "kompaktowe sterowanie\">⟎",
 	"mt_uncache": "wyczyść pamięć podręczną &nbsp;(spróbuj jeśli przeglądarka$Nzachowała zepsutą kopię utworu, przez co nie odtwarza się ona)\">uncache",
-	"mt_mloop": "odtwarzaj utwory w folderze w pętli\">🔁 loop",
-	"mt_mnext": "wczytaj następny folder i kontynuuj\">📂 next",
+	"mt_mloop": "odtwarzaj utwory w folderze w pętli",
+	"mt_mnext": "wczytaj następny folder i kontynuuj",
 	"mt_mstop": "zatrzymaj odtwarzanie\">⏸ stop",
 	"mt_cflac": "przekonwertuj format flac / wav na {0}\">flac",
diff -NarU2 a/copyparty/web/tl/por.js b/copyparty/web/tl/por.js
--- a/copyparty/web/tl/por.js	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/tl/por.js	2026-07-28 23:09:10.626961815 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "botões de ação",
+		"!": "botões de ação",
 		"dur": "duração",
 		"q": "qualidade / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "checksum de vídeo",
 		"Res": "resolução",
-		"T": "tipo de arquivo",
+		"Ext": "tipo de arquivo",
 		"aq": "qualidade / bitrate de áudio",
 		"vq": "qualidade / bitrate de vídeo",
@@ -34,5 +34,5 @@
 			["T", "alternar entre miniaturas / ícones"],
 			["⇧ A/D", "tamanho da miniatura"],
-			["ctrl-K", "excluir selecionados"],
+			["ctrl-K/Del", "excluir selecionados"],
 			["ctrl-X", "recortar seleção para a área de transferência"],
 			["ctrl-C", "copiar seleção para a área de transferência"],
@@ -108,7 +108,7 @@
 
 	"goh": "painel de controle",
-	"gop": 'pai anterior">anterior',
-	"gou": 'pasta pai">acima',
-	"gon": 'próxima pasta">próximo',
+	"gop": 'pai anterior',
+	"gou": 'pasta pai',
+	"gon": 'próxima pasta',
 	"logout": "Sair ",
 	"login": "Fazer login",
@@ -158,5 +158,5 @@
 	"ut_ow": "substituir arquivos existentes no servidor?$N🛡️: nunca (irá gerar um novo nome de arquivo em vez disso)$N🕒: substituir se o arquivo no servidor for mais antigo que o seu$N♻️: sempre substituir se os arquivos forem diferentes$N⏭️: ignorar incondicionalmente todos os arquivos existentes",
 	"ut_mt": "continuar a fazer o hash de outros arquivos enquanto faz upload$N$Ntalvez desativar se sua CPU ou HDD for um gargalo",
-	"ut_ask": 'pedir confirmação antes do upload começar">💭',
+	"ut_ask": 'pedir confirmação antes do upload começar',
 	"ut_pot": "melhorar a velocidade de upload em dispositivos lentos$Ntornando a UI menos complexa",
 	"ut_srch": "não fazer upload, em vez disso verificar se os arquivos já$N existem no servidor (irá escanear todas as pastas que você pode ler)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 a grade',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ dicas de ferramentas',
-	"ct_thumb": 'na visualização de grade, alternar entre ícones ou miniaturas$NHotkey: T">🖼️ miniaturas',
+	"ct_thumb": 'alternar entre ícones ou miniaturas$NHotkey: T">🖼️ miniaturas',
 	"ct_csel": 'usar CTRL e SHIFT para seleção de arquivo na visualização de grade">sel',
 	"ct_dsel": 'usar seleção por arrasto na visualização de grade">arrastar',
@@ -289,7 +289,7 @@
 	"ml_ss": "ignorar silêncio", //m
 
-	"mt_loop": "loop/repetir uma música\">🔁",
+	"mt_loop": "loop/repetir uma música",
 	"mt_one": "parar depois de uma música\">1️⃣",
-	"mt_shuf": "embaralhar as músicas em cada pasta\">🔀",
+	"mt_shuf": "embaralhar as músicas em cada pasta",
 	"mt_aplay": "reproduzir automaticamente se houver um ID de música no link que você clicou para acessar o servidor$N$Ndesativar isso também impedirá que a URL da página seja atualizada com IDs de música ao tocar música, para evitar a reprodução automática se essas configurações forem perdidas mas a URL permanecer\">a▶",
 	"mt_preload": "começar a carregar a próxima música perto do final para uma reprodução sem interrupções\">preload",
@@ -306,6 +306,6 @@
 	"mt_compact": "controles compactos\">⟎",
 	"mt_uncache": "limpar cache &nbsp;(tente isso se seu navegador armazenou em cache$Numa cópia quebrada de uma música e se recusa a tocar)\">uncache",
-	"mt_mloop": "loop na pasta aberta\">🔁 loop",
-	"mt_mnext": "carregar a próxima pasta e continuar\">📂 próximo",
+	"mt_mloop": "loop na pasta aberta",
+	"mt_mnext": "carregar a próxima pasta e continuar",
 	"mt_mstop": "parar reprodução\">⏸ parar",
 	"mt_cflac": "converter flac / wav para {0}\">flac",
diff -NarU2 a/copyparty/web/tl/rus.js b/copyparty/web/tl/rus.js
--- a/copyparty/web/tl/rus.js	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/tl/rus.js	2026-07-28 23:09:10.627182442 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "кнопки действий",
+		"!": "кнопки действий",
 		"dur": "длительность",
 		"q": "качество / битрейт",
@@ -15,5 +15,5 @@
 		"Vhash": "контрольная сумма видео",
 		"Res": "разрешение",
-		"T": "тип файла",
+		"Ext": "тип файла",
 		"aq": "качество аудио / битрейт",
 		"vq": "качество видео / битрейт",
@@ -34,5 +34,5 @@
 			["T", "переключиться между миниатюрами / иконками"],
 			["⇧ A/D", "размер миниатюры"],
-			["ctrl-K", "удалить выделенное"],
+			["ctrl-K/Del", "удалить выделенное"],
 			["ctrl-X", "вырезать выделенное в буфер"],
 			["ctrl-C", "копировать выделенное в буфер"],
@@ -108,7 +108,7 @@
 
 	"goh": "панель управления",
-	"gop": 'предыдущая папка">пред',
-	"gou": 'родительская папка">вверх',
-	"gon": 'следующая папка">след',
+	"gop": 'предыдущая папка',
+	"gou": 'родительская папка',
+	"gon": 'следующая папка',
 	"logout": "Выйти ",
 	"login": "Войти", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "перезаписывать существующие файлы на сервере?$N🛡️: нет (для повторяющихся файлов будут создаваться новые имена)$N🕒: перезаписать файлы с датой изменения старее, чем у загружаемых$N♻️: всегда перезаписывать (если файлы различаются по содержанию)$N⏭️: безусловно пропускать все существующие файлы", //m
 	"ut_mt": "продолжать хешировать другие файлы во время загрузки$N$Nесть смысл отключить при медленном диске или процессоре",
-	"ut_ask": 'требовать подтверждения перед началом загрузки">💭',
+	"ut_ask": 'требовать подтверждения перед началом загрузки',
 	"ut_pot": "улучшить скорость загрузки на слабых устройства$Nс помощью упрощения интерфейса",
 	"ut_srch": "не загружать, а проверять, существуют ли данные файлы $N на сервере (проверка всех доступных вам папок)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 сетка',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ подсказки',
-	"ct_thumb": 'переключение между иконками и миниатюрами в режиме сетки$NГорячая клавиша: T">🖼️ миниат.',
+	"ct_thumb": 'переключение между иконками и миниатюрами$NГорячая клавиша: T">🖼️ миниат.',
 	"ct_csel": 'держите CTRL или SHIFT для выделения файлов в режиме сетки">выбор',
 	"ct_dsel": 'использовать выделение перетаскиванием в режиме сетки">перетащить', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "пропускать тишину", //m
 
-	"mt_loop": "повторять один трек\">🔁",
+	"mt_loop": "повторять один трек",
 	"mt_one": "остановить после этого трека\">1️⃣",
-	"mt_shuf": "перемешать треки во всех папках\">🔀",
+	"mt_shuf": "перемешать треки во всех папках",
 	"mt_aplay": "автоматически играть треки по нажатии на ссылки с их ID$N$Nпри отключении адрес сайта также перестанет обновляться в соответствии с текущим треком\">a▶",
 	"mt_preload": "подгружать следующий трек перед концом текущего для бесшовного переключения\">предзагр.",
@@ -306,6 +306,6 @@
 	"mt_compact": "компактный плеер\">⟎",
 	"mt_uncache": "очистить кеш &nbsp;(если браузер кешировал повреждённый$Nтрек и отказывается его запускать)\">уд. кеш",
-	"mt_mloop": "повторять треки в папке\">🔁 цикл",
-	"mt_mnext": "загрузить следующую папку и продолжить в ней\">📂 след.",
+	"mt_mloop": "повторять треки в папке",
+	"mt_mnext": "загрузить следующую папку и продолжить в ней",
 	"mt_mstop": "приостановить воспроизведение\">⏸ стоп",
 	"mt_cflac": "конвертировать flac / wav в {0}\">flac",
diff -NarU2 a/copyparty/web/tl/spa.js b/copyparty/web/tl/spa.js
--- a/copyparty/web/tl/spa.js	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/tl/spa.js	2026-07-28 23:09:10.627336092 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "acciones",
+		"!": "acciones",
 		"dur": "duración",
 		"q": "calidad / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "checksum de vídeo",
 		"Res": "resolución",
-		"T": "tipo de archivo",
+		"Ext": "tipo de archivo",
 		"aq": "calidad de audio / bitrate",
 		"vq": "calidad de vídeo / bitrate",
@@ -34,5 +34,5 @@
 			["T", "alternar miniaturas / iconos"],
 			["⇧ A/D", "tamaño de miniatura"],
-			["ctrl-K", "eliminar seleccionados"],
+			["ctrl-K/Del", "eliminar seleccionados"],
 			["ctrl-X", "cortar selección al portapapeles"],
 			["ctrl-C", "copiar selección al portapapeles"],
@@ -108,7 +108,7 @@
 
 	"goh": "panel de control",
-	"gop": 'hermano anterior">anterior',
-	"gou": 'carpeta de nivel superior">subir',
-	"gon": 'siguiente carpeta">siguiente',
+	"gop": 'hermano anterior',
+	"gou": 'carpeta de nivel superior',
+	"gon": 'siguiente carpeta',
 	"logout": "Cerrar sesión ",
 	"login": "Iniciar sesión", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "sobrescribir archivos existentes en el servidor?$N🛡️: nunca (generará un nuevo nombre de archivo en su lugar)$N🕒: sobrescribir si el archivo del servidor es más antiguo que el tuyo$N♻️: siempre sobrescribir si los archivos son diferentes$N⏭️: omitir incondicionalmente todos los archivos existentes", //m
 	"ut_mt": "continuar generando hashes de otros archivos mientras se sube$N$Nquizás desactivar si tu CPU o HDD es un cuello de botella",
-	"ut_ask": 'pedir confirmación antes de iniciar la subida">💭',
+	"ut_ask": 'pedir confirmación antes de iniciar la subida',
 	"ut_pot": "mejorar la velocidad de subida en dispositivos lentos$Nsimplificando la interfaz de usuario",
 	"ut_srch": "no subir, en su lugar comprobar si los archivos ya $N existen en el servidor (escaneará todas las carpetas que puedas leer)",
@@ -225,5 +225,5 @@
 	"ct_grid": '田 cuadrícula',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
-	"ct_thumb": 'en vista de cuadrícula, alternar iconos o miniaturas$NAtajo: T">🖼️ miniaturas',
+	"ct_thumb": 'alternar iconos o miniaturas$NAtajo: T">🖼️ miniaturas',
 	"ct_csel": 'usa CTRL y SHIFT para seleccionar archivos en la vista de cuadrícula">sel',
 	"ct_dsel": 'usa la selección por arrastre en la vista de cuadrícula">arrastrar', //m
@@ -288,7 +288,7 @@
 	"ml_ss": "saltar silencios", //m
 
-	"mt_loop": 'poner en bucle/repetir una canción">🔁',
+	"mt_loop": 'poner en bucle/repetir una canción',
 	"mt_one": 'parar después de una canción">1️⃣',
-	"mt_shuf": 'reproducir aleatoriamente las canciones en cada carpeta">🔀',
+	"mt_shuf": 'reproducir aleatoriamente las canciones en cada carpeta',
 	"mt_aplay": 'reproducir automaticamente si hay un ID de canción en el enlace en el que hiciste clic para acceder al servidor$N$Ndesactivar esto también evitará que la URL de la página se actualice con IDs de canción al reproducir música, para prevenir la reproducción automática si se pierden estos ajustes pero la URL permanece">a▶',
 	"mt_preload": 'empezar a cargar la siguiente canción cerca del final para una reproducción sin pausas">precarga',
@@ -305,6 +305,6 @@
 	"mt_compact": 'controles compactos">⟎',
 	"mt_uncache": 'limpiar caché &nbsp;(prueba esto si tu navegador guardó en caché$Nuna copia rota de una canción que se niega a reproducir)">limpiar caché',
-	"mt_mloop": 'repetir la carpeta actual">🔁 bucle',
-	"mt_mnext": 'cargar la siguiente carpeta y continuar">📂 sig',
+	"mt_mloop": 'repetir la carpeta actual',
+	"mt_mnext": 'cargar la siguiente carpeta y continuar',
 	"mt_mstop": 'detener reproducción">⏸ parar',
 	"mt_cflac": 'convertir flac / wav a {0}">flac',
diff -NarU2 a/copyparty/web/tl/swe.js b/copyparty/web/tl/swe.js
--- a/copyparty/web/tl/swe.js	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/tl/swe.js	2026-07-28 23:09:10.627507776 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "aktion",
+		"!": "aktion",
 		"dur": "längd",
 		"q": "kvalitet / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "videochecksumma",
 		"Res": "upplösning",
-		"T": "filtyp",
+		"Ext": "filtyp",
 		"aq": "ljudkvalitet / bitrate",
 		"vq": "videokvalitet / bitrate",
@@ -34,5 +34,5 @@
 			["T", "växla mellan miniatyrer / ikoner"],
 			["⇧ A/D", "miniatyrstorlek"],
-			["ctrl-K", "radera urval"],
+			["ctrl-K/Del", "radera urval"],
 			["ctrl-X", "klipp urval till urklipp"],
 			["ctrl-C", "kopiera urval till urklipp"],
@@ -108,7 +108,7 @@
 
 	"goh": "kontrollpanel",
-	"gop": 'föregående mapp">föreg.',
-	"gou": 'överordnad mapp">upp',
-	"gon": 'nästa mapp">nästa',
+	"gop": 'föregående mapp',
+	"gou": 'överordnad mapp',
+	"gon": 'nästa mapp',
 	"logout": "Logga ut ",
 	"login": "Logga in", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "skriv över existerande filer på servern?$N🛡️: aldrig (skapar ett nytt filnamn istället)$N🕒: skriv över om serverns fil är äldre än din$N♻️: skriv alltid över om filerna skiljer sig$N⏭️: hoppa ovillkorligen över alla befintliga filer", //m
 	"ut_mt": "fortsätt hasha filer under uppladdningens gång$N$Nstäng av om din CPU eller disk är en flaskhals",
-	"ut_ask": 'bekräfta innan uppladdningar påbörjas">💭',
+	"ut_ask": 'bekräfta innan uppladdningar påbörjas',
 	"ut_pot": "förbättra uppladdningshastigheten på långsamma enheter$Ngenom att förenkla användargränssnittet",
 	"ut_srch": "ladda inte upp; kolla istället om filerna redan existerar på $N servern (detta kommer att skanna alla mappar med läsrättighet)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 rutnätet',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ tips',
-	"ct_thumb": 'växla mellan miniatyrer och ikoner i rutnätsvyn$NSnabbtangent: T">🖼️ miniatyrer',
+	"ct_thumb": 'växla mellan miniatyrer och ikoner$NSnabbtangent: T">🖼️ miniatyrer',
 	"ct_csel": 'använd CTRL och SKIFT för urval av filer i rutnätsvyn">val',
 	"ct_dsel": 'använd dra-urval i rutnätsvyn">dra', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "hoppa över tystnad", //m
 
-	"mt_loop": "upprepa en låt\">🔁",
+	"mt_loop": "upprepa en låt",
 	"mt_one": "stoppa uppspelningen efter en låt\">1️⃣",
-	"mt_shuf": "blanda låtarna i varje mapp\">🔀",
+	"mt_shuf": "blanda låtarna i varje mapp",
 	"mt_aplay": "spela automatiskt om det finns en låt-ID i länkar du har klickat på för att öppna sidan$N$Nom detta är avstängt kommer sidans adress inte att bli uppdaterad med en låt-ID om du spelar musik, för att förhindra automatisk uppspelning om dessa inställningar går förlorade men webbadressen återstår\">a▶",
 	"mt_preload": "påbörja nedladdning av nästa låt i förväg för gapfri uppspelning\">ladda",
@@ -306,6 +306,6 @@
 	"mt_compact": "kompakt kontrollpanel\">⟎",
 	"mt_uncache": "rensa cachen &nbsp;(prova detta om din webbläsare har cachat$Nen trasig kopia av en låt och den vägrar spela upp den)\">rensa",
-	"mt_mloop": "upprepa den öppna mappen\">🔁 upprepa",
-	"mt_mnext": "ladda nästa mapp och fortsätt\">📂 nästa",
+	"mt_mloop": "upprepa den öppna mappen",
+	"mt_mnext": "ladda nästa mapp och fortsätt",
 	"mt_mstop": "stoppa uppspelningen\">⏸ stopp",
 	"mt_cflac": "konvertera flac / wav till {0}\">flac",
diff -NarU2 a/copyparty/web/tl/tur.js b/copyparty/web/tl/tur.js
--- a/copyparty/web/tl/tur.js	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/tl/tur.js	2026-07-28 23:09:10.627683979 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "işlem butonları",
+		"!": "işlem butonları",
 		"dur": "süre",
 		"q": "kalite / bitrate",
@@ -15,5 +15,5 @@
 		"Vhash": "video denetim toplamı",
 		"Res": "çözünürlük",
-		"T": "dosya türü",
+		"Ext": "dosya türü",
 		"aq": "ses kalitesi / bitrate",
 		"vq": "video kalitesi / bitrate",
@@ -34,5 +34,5 @@
 			["T", "küçük resimler / simgeler arasında geçiş yap"],
 			["⇧ A/D", "küçük resim boyutu"],
-			["ctrl-K", "seçileni sil"],
+			["ctrl-K/Del", "seçileni sil"],
 			["ctrl-X", "seçimi panoya kes"],
 			["ctrl-C", "seçimi panoya kopyala"],
@@ -108,7 +108,7 @@
 
 	"goh": "kontrol paneli",
-	"gop": 'önceki kardeş">önceki',
-	"gou": 'üst klasör">üst',
-	"gon": 'sonraki klasör">sonraki',
+	"gop": 'önceki kardeş',
+	"gou": 'üst klasör',
+	"gon": 'sonraki klasör',
 	"logout": "Çıkış ",
 	"login": "Giriş",
@@ -158,5 +158,5 @@
 	"ut_ow": "sunucudaki mevcut dosyaları üzerine yazmak mı?$N🛡️: asla (yerine yeni bir dosya adı oluşturur)$N🕒: sunucu dosyası sizinkinden daha eskiyse üzerine yaz$N♻️: dosyalar farklıysa her zaman üzerine yaz$N⏭️: mevcut tüm dosyaları koşulsuz atla", //m
 	"ut_mt": "yükleme yaparken diğer dosyaların hash'lenmesini durdur$N$kötü bir CPU veya HDD'ye sahipseniz kullanabilirsiniz.",
-	"ut_ask": 'yüklemeye başlamadan önce doğrulama mesajı göster">💭',
+	"ut_ask": 'yüklemeye başlamadan önce doğrulama mesajı göster',
 	"ut_pot": "arayüzü daha az karmaşık hale getirerek$Nyükleme hızını yavaş cihazlarda artır",
 	"ut_srch": "gerçekten yükleme yapma, bunun yerine dosyaların $N sunucuda var olup olmadığını kontrol et (okuma izniniz olan tüm klasörleri tarar)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 ızgara',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ ipuçları',
-	"ct_thumb": 'ızgara görünümünde, simgeler ve küçük resimler arasında geçiş yapın$NKısayol: T">🖼️ küçük resimler',
+	"ct_thumb": 'simgeler ve küçük resimler arasında geçiş yapın$NKısayol: T">🖼️ küçük resimler',
 	"ct_csel": 'ızgara görünümünde dosya seçimi için CTRL ve SHIFT tuşlarını kullanın">seç',
 	"ct_dsel": 'ızgara görünümünde sürükleyerek seçimi kullanın">sürükle', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "sessizliği atla", //m
 
-	"mt_loop": "bir şarkıyı döngüye al / tekrar et\">🔁",
+	"mt_loop": "bir şarkıyı döngüye al / tekrar et",
 	"mt_one": "bir şarkıdan sonra dur\">1️⃣",
-	"mt_shuf": "klasörlerdeki şarkıları karıştır\">🔀",
+	"mt_shuf": "klasörlerdeki şarkıları karıştır",
 	"mt_aplay": "sunucuya erişmek için kullandığın bağlantıda geçerli bir şarkı varsa otomatik oynat$N$Nbunu etkisiz kılmak aynı zamanda müzik oynatıldığında sayfa URL'nin değişmesini de engeller\">a▶",
 	"mt_preload": "aralıksız oynatma için sıradaki şarkıyı önceden yüklemeye başla\">ön yükleme",
@@ -306,6 +306,6 @@
 	"mt_compact": "kompakt kontroller\">⟎",
 	"mt_uncache": "önbelleği temizle &nbsp;(bunu, tarayıcınızın bozuk bir şarkı kopyasını önbelleğe alması nedeniyle çalmayı reddettiğinde deneyin)\">önbelleği temizle",
-	"mt_mloop": "açık klasörü döngüye al\">🔁 döngü",
-	"mt_mnext": "bir sonraki klasörü yükle ve devam et\">📂 sonraki",
+	"mt_mloop": "açık klasörü döngüye al",
+	"mt_mnext": "bir sonraki klasörü yükle ve devam et",
 	"mt_mstop": "oynatmayı durdur\">⏸ durdur",
 	"mt_cflac": "flac / wav'ı {0}'a dönüştür\">flac",
diff -NarU2 a/copyparty/web/tl/ukr.js b/copyparty/web/tl/ukr.js
--- a/copyparty/web/tl/ukr.js	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/tl/ukr.js	2026-07-28 23:09:10.627915206 +0000
@@ -6,5 +6,5 @@
 
 	"cols": {
-		"c": "кнопки дій",
+		"!": "кнопки дій",
 		"dur": "тривалість",
 		"q": "якість / бітрейт",
@@ -15,5 +15,5 @@
 		"Vhash": "контрольна сума відео",
 		"Res": "роздільність",
-		"T": "тип файлу",
+		"Ext": "тип файлу",
 		"aq": "якість аудіо / бітрейт",
 		"vq": "якість відео / бітрейт",
@@ -34,5 +34,5 @@
 			["T", "перемкнути мініатюри / іконки"],
 			["⇧ A/D", "розмір мініатюр"],
-			["ctrl-K", "видалити вибране"],
+			["ctrl-K/Del", "видалити вибране"],
 			["ctrl-X", "вирізати до буфера"],
 			["ctrl-C", "копіювати до буфера"],
@@ -108,7 +108,7 @@
 
 	"goh": "панель керування",
-	"gop": 'попередній сусід">назад',
-	"gou": 'батьківська папка">вгору',
-	"gon": 'наступна папка">далі',
+	"gop": 'попередній сусід',
+	"gou": 'батьківська папка',
+	"gon": 'наступна папка',
 	"logout": "Вийти ",
 	"login": "увійти", //m
@@ -158,5 +158,5 @@
 	"ut_ow": "перезаписати існуючі файли на сервері?$N🛡️: ніколи (замість цього створить нове ім'я файлу)$N🕒: перезаписати, якщо файл на сервері старіший за ваш$N♻️: завжди перезаписувати, якщо файли відрізняються$N⏭️: безумовно пропускати всі наявні файли", //m
 	"ut_mt": "продовжувати хешування інших файлів під час завантаження$N$Nможливо, вимкніть, якщо ваш CPU або HDD є вузьким місцем",
-	"ut_ask": 'запитати підтвердження перед початком завантаження">💭',
+	"ut_ask": 'запитати підтвердження перед початком завантаження',
 	"ut_pot": "покращити швидкість завантаження на повільних пристроях$Nроблячи інтерфейс менш складним",
 	"ut_srch": "не завантажувати, а перевірити, чи файли вже $N існують на сервері (сканує всі папки, які ви можете читати)",
@@ -226,5 +226,5 @@
 	"ct_grid": '田 сітка',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ підказки',
-	"ct_thumb": 'у режимі сітки, перемкнути іконки або мініатюри$NГаряча клавіша: T">🖼️ мініатюри',
+	"ct_thumb": 'перемкнути іконки або мініатюри$NГаряча клавіша: T">🖼️ мініатюри',
 	"ct_csel": 'використовувати CTRL і SHIFT для вибору файлів у режимі сітки">вибір',
 	"ct_dsel": 'використовувати вибір перетягуванням у режимі сітки">перетягнути', //m
@@ -289,7 +289,7 @@
 	"ml_ss": "пропускати тишу", //m
 
-	"mt_loop": "зациклити/повторити одну пісню\">🔁",
+	"mt_loop": "зациклити/повторити одну пісню",
 	"mt_one": "зупинити після однієї пісні\">1️⃣",
-	"mt_shuf": "перемішати пісні в кожній папці\">🔀",
+	"mt_shuf": "перемішати пісні в кожній папці",
 	"mt_aplay": "автовідтворення, якщо є ID пісні в посиланні, по якому ви клацнули для доступу до сервера$N$Nвідключення цього також зупинить оновлення URL сторінки з ID пісень під час відтворення музики, щоб запобігти автовідтворенню, якщо ці налаштування втрачені, але URL залишається\">a▶",
 	"mt_preload": "почати завантаження наступної пісні ближче до кінця для безперервного відтворення\">preload",
@@ -306,6 +306,6 @@
 	"mt_compact": "компактні елементи керування\">⟎",
 	"mt_uncache": "очистити кеш &nbsp;(спробуйте це, якщо ваш браузер закешував$Nпошкоджену копію пісні, тому відмовляється її відтворювати)\">uncache",
-	"mt_mloop": "зациклити відкриту папку\">🔁 loop",
-	"mt_mnext": "завантажити наступну папку і продовжити\">📂 next",
+	"mt_mloop": "зациклити відкриту папку",
+	"mt_mnext": "завантажити наступну папку і продовжити",
 	"mt_mstop": "зупинити відтворення\">⏸ stop",
 	"mt_cflac": "конвертувати flac / wav в {0}\">flac",
diff -NarU2 a/copyparty/web/tl/vie.js b/copyparty/web/tl/vie.js
--- a/copyparty/web/tl/vie.js	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/tl/vie.js	2026-07-28 23:09:10.628077022 +0000
@@ -3,5 +3,5 @@
 
 	"cols": {
-		"c": "nút hành động",
+		"!": "nút hành động",
 		"dur": "thời lượng",
 		"q": "chất lượng / bitrate",
@@ -12,5 +12,5 @@
 		"Vhash": "checksum video",
 		"Res": "độ phân giải",
-		"T": "loại tệp",
+		"Ext": "loại tệp",
 		"aq": "chất lượng âm thanh / bitrate",
 		"vq": "chất lượng video / bitrate",
@@ -31,5 +31,5 @@
 			["T", "chuyển đổi ảnh thu nhỏ / biểu tượng"],
 			["⇧ A/D", "kích thước ảnh thu nhỏ"],
-			["ctrl-K", "xoá mục đã chọn"],
+			["ctrl-K/Del", "xoá mục đã chọn"],
 			["ctrl-X", "cắt mục đã chọn vào bảng nhớ tạm"],
 			["ctrl-C", "sao chép mục đã chọn vào bảng nhớ tạm"],
@@ -103,7 +103,7 @@
 
 	"goh": "bảng điều khiển",
-	"gop": 'thư mục trước">trước',
-	"gou": 'thư mục cha">lên',
-	"gon": 'thư mục sau">tiếp',
+	"gop": 'thư mục trước',
+	"gou": 'thư mục cha',
+	"gon": 'thư mục sau',
 	"logout": "Đăng xuất ",
 	"login": "Đăng nhập",
@@ -155,5 +155,5 @@
 	"ut_ow": "ghi đè các tệp đã có trên máy chủ?$N🛡️: không bao giờ (sẽ tạo tên tệp mới)$N🕒: ghi đè nếu tệp trên máy chủ cũ hơn$N♻️: luôn ghi đè nếu hai tệp khác nhau$N⏭️: bỏ qua vô điều kiện tất cả các tệp hiện có", //m
 	"ut_mt": "tiếp tục hash các tệp khác trong khi tải lên$N$NCó thể tắt nếu CPU hoặc HDD của bạn bị nghẽn",
-	"ut_ask": 'yêu cầu xác nhận trước khi bắt đầu tải lên">💭',
+	"ut_ask": 'yêu cầu xác nhận trước khi bắt đầu tải lên',
 	"ut_pot": "cải thiện tốc độ tải lên trên các thiết bị chậm$Nbằng cách đơn giản hoá giao diện người dùng",
 	"ut_srch": "không tải lên, chỉ kiểm tra xem tệp$Nđã tồn tại trên máy chủ hay chưa (sẽ quét toàn bộ thư mục bạn có quyền đọc)",
@@ -227,5 +227,5 @@
 	"ct_grid": '田 chế độ lưới',
 	"ct_ttips": '༼ ◕_◕ ༽">ℹ️ tooltips',
-	"ct_thumb": 'ở chế độ lưới, chuyển biểu tượng hoặc hình thu nhỏ$NPhím tắt: T">🖼️ ảnh thu nhỏ',
+	"ct_thumb": 'chuyển biểu tượng hoặc hình thu nhỏ$NPhím tắt: T">🖼️ ảnh thu nhỏ',
 	"ct_csel": 'dùng CTRL và SHIFT để chọn tệp trong chế độ lưới">sel',
 	"ct_dsel": 'dùng chọn bằng cách kéo trong chế độ lưới">kéo', //m
@@ -293,7 +293,7 @@
 	"ml_ss": "bỏ qua khoảng lặng", //m
 
-	"mt_loop": "lặp lại một bài\">🔁",
+	"mt_loop": "lặp lại một bài",
 	"mt_one": "dừng sau một bài\">1️⃣",
-	"mt_shuf": "trộn các bài trong thư mụcr\">🔀",
+	"mt_shuf": "trộn các bài trong thư mụcr",
 	"mt_aplay": "tự động phát nếu có ID bài trong link bạn nhấp để truy cập máy chủ$N$Ntắt tuỳ chọn sẽ ngăn URL của trang cập nhật theo ID bài khi phát nhạc, tránh tự động phát nếu cài đặt mất nhưng URL còn\">a▶",
 	"mt_preload": "bắt đầu tải bài hát tiếp theo khi gần hết bài để phát liền mạch\">preload",
@@ -310,6 +310,6 @@
 	"mt_compact": "giao diện điều khiển thu gọn\">⟎",
 	"mt_uncache": "xoá bộ nhớ đệm &nbsp;(thử nếu trình duyệt lưu trữ đệm $Nmột bản nhạc bị lỗi và không thể phát)\">uncache",
-	"mt_mloop": "lặp trong thư mục đang mở\">🔁 loop",
-	"mt_mnext": "tải thư mục tiếp theo và tiếp tục\">📂 next",
+	"mt_mloop": "lặp trong thư mục đang mở",
+	"mt_mnext": "tải thư mục tiếp theo và tiếp tục",
 	"mt_mstop": "dừng phát\">⏸ stop",
 	"mt_cflac": "chuyển flac / wav sang {0}\">flac",
diff -NarU2 a/copyparty/web/ui.css b/copyparty/web/ui.css
--- a/copyparty/web/ui.css	2026-07-08 15:13:32.823924491 +0000
+++ b/copyparty/web/ui.css	2026-07-13 09:49:10.693382031 +0000
@@ -1,20 +1,139 @@
 :root {
+	color-scheme: dark;
+
 	--font-main: sans-serif;
 	--font-serif: serif;
 	--font-mono: 'scp';
 
-	--fg: #ccc;
 	--fg-max: #fff;
-	--bg-u2: #2b2b2b;
-	--bg-u5: #444;
+	--bg-max: #080808;
+	--a: #fc5;
+	accent-color: var(--a);
+	--radius: 7px;
+	
+	--fg-max: #fefefe;
+	--bg-max: #010101;
+
+	--a-hil: rgb(255, 215, 121);
+	--a-dark: rgb(235, 165, 37); /* warning text etc */
+	--a-gray: #ccc;
+
+	--fg: #efefef;
+	--fg-weak: #c4c4c4;
+
+	--bg: #1f1f1f;
+	--bg-half: #01010182;
+	--bg-u1: #212121;
+	--bg-u2: #353535;
+	--bg-u3: #404040;
+	--bg-u5: #4e4e4e;
+	--bg-d1: #111111;
+	--bg-d2: #0e0e0e;
+	--bg-d3: #0b0b0b;
+
+	--bgg: var(--bg);
+
+	background: var(--bg);
+	color: #b9bdc1;
+    color: var(--fg);
+}
+html::before {
+	background-image: var(--bg-img);
+	background-size: cover;
+	background-position: center center;
+	content: "";
+}
+html::before,
+#bg_vid{
+	position: fixed;
+	top: 0;
+	left: 0;
+	height: 100%;
+	width: 100%;
+	object-fit: cover;
+	z-index: -1;
+	pointer-events: none;
+
+	opacity: var(--bg-opacity);
+	filter: var(--bg-blur);
+}
+#bg_vid{
+	display: none;
+}
+#bg_vid.vis{
+	display: block;
 }
 html.y {
-	--fg: #222;
+	color-scheme: light;
+
 	--fg-max: #000;
-	--bg-u2: #f7f7f7;
-	--bg-u5: #ccc;
+	--bg-max: #fff;
+	color-scheme: light;
+
+	--a: #07c;
+	
+	--a-hil: rgb(121, 193, 255);
+	--a-dark: rgb(7, 95, 183); /* warning text etc */
+	--a-gray: #ccc;
+
+	--fg: #101010;
+	--fg-weak: #323232;
+
+	--bg: #eeeeee;
+	--bg-half: #eeeeee82;
+	--bg-u1: #e3e3e3;
+	--bg-u2: #d6d6d6;
+	--bg-u3: #c7c7c7;
+	--bg-u5: #bcbcbc;
+	--bg-d1: #f4f4f4;
+	--bg-d2: #f5f5f5;
+	--bg-d3: #f6f6f6;
+
+	--chk-fg: var(--fg);
+	--txt-sh: #aaa;
+	--txt-bg: var(--bg-d1);
+	--mp-sh: #0002;
+
+	text-shadow: none;
 }
-html.bz {
-	--bg-u2: #202231;
+@supports (background: hsl(from #ccc, h s l)){
+	:root {
+		--a-hil: hsl(from var(--a) h calc(s * 1.2) l);
+		--a-dark: hsl(from var(--a) h calc(s * 1.3) l); /* warning text etc */
+		--a-gray: hsl(from var(--a) h calc(s * 0.1) l);
+	}
+}
+@supports (background: color-mix(in oklab, black, white)){
+	:root, html.y {
+		/* accent color in everything */
+		--fg-max: color-mix(in oklab, #fefefe 95%, var(--a) 5%);
+		--bg-max: color-mix(in oklab, #010101 95%, var(--a) 5%);
+
+		--a-hil: hsl(from color-mix(in oklab, var(--a) 80%, var(--fg-max) 20%) h calc(s * 1.2) l);
+		--a-dark: hsl(from color-mix(in oklab, var(--a) 60%, var(--bg-max) 40%) h calc(s * 1.3) l); /* warning text etc */
+		--a-gray: hsl(from var(--a) h calc(s * 0.1) l);
+
+		--fg: color-mix(in oklab, var(--fg-max) 90%, var(--bg-max) 10%);
+		--fg-weak: color-mix(in oklab, var(--fg-max) 70%, var(--bg-max) 30%);
+
+		--bg: color-mix(in oklab, var(--bg-max) 85%, var(--fg-max) 15%);
+		--bg-half: color-mix(in oklab, var(--bg) 50%, transparent 50%);
+		--bg-u1: color-mix(in oklab, var(--bg-max) 80%, var(--fg-max) 20%);
+		--bg-u2: color-mix(in oklab, var(--bg-max) 75%, var(--fg-max) 25%);
+		--bg-u3: color-mix(in oklab, var(--bg-max) 70%, var(--fg-max) 30%);
+		--bg-u5: color-mix(in oklab, var(--bg-max) 65%, var(--fg-max) 35%);
+		--bg-d1: color-mix(in oklab, var(--bg-max) 90%, var(--fg-max) 10%);
+		--bg-d2: color-mix(in oklab, var(--bg-max) 92%, var(--fg-max) 8%);
+		--bg-d3: color-mix(in oklab, var(--bg-max) 95%, var(--fg-max) 5%);
+	}
+	html.y {
+		--bg-max: color-mix(in oklab, #fefefe 95%, var(--a) 5%);
+		--fg-max: color-mix(in oklab, #010101 95%, var(--a) 5%);
+		
+		--bg-u1: color-mix(in oklab, var(--bg-max) 92%, var(--fg-max));
+		--bg-u2: color-mix(in oklab, var(--bg-max) 95%, var(--fg-max));
+		--bg-u5: color-mix(in oklab, var(--bg-max) 82%, var(--fg-max));
+		--bg: color-mix(in oklab, var(--bg-max) 98%, var(--fg-max));
+	}
 }
 @font-face {
@@ -23,4 +142,9 @@
 	src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'), url(deps/scp.woff2) format('woff2');
 }
+@font-face {
+	font-family: 'orbitron';
+	font-display: swap;
+	src: url(deps/orbitron.woff2) format('woff2');
+}
 html {
 	text-size-adjust: 100%;
@@ -55,7 +179,6 @@
 	right: -1em;
 	line-height: 1.5em;
-	padding: 1em 1.3em;
-	margin-left: 3em;
-	border-width: .4em 0;
+	padding: 1em 2em 1em 1em;
+	border-width: 1px;
 	overflow-wrap: break-word;
 	transform: translateX(100%);
@@ -93,9 +216,8 @@
 	position: absolute;
 	overflow: hidden;
-	left: 0;
-	width: 0;
+	right: 0;
+	top: 0;
 	opacity: 0;
 	padding: .3em 0;
-	margin: -.3em 0 0 0;
 	line-height: 1.3em;
 	color: #000;
@@ -103,5 +225,5 @@
 	outline: none;
 	text-shadow: none;
-	border-radius: .5em 0 0 .5em;
+	border-radius: .4em;
 	transition: left .3s, width .3s, padding .3s, opacity .3s;
 }
@@ -131,7 +253,5 @@
 }
 #toast.vis #toastc {
-	left: -2em;
-	width: .4em;
-	padding: .3em .8em;
+	padding: .3em .5em;
 	opacity: 1;
 }
@@ -201,5 +321,5 @@
 #tt.show {
 	padding: 1em 1.3em;
-	border-width: .4em 0;
+	border-width: 1px;
 	height: auto;
 	opacity: 1;
@@ -207,9 +327,10 @@
 #tt.show.b {
 	padding: 1.5em 2em;
-	border-width: .5em 0;
+	/* border-width: .5em 0; */
 }
 .logue code,
 #modalc code,
-#tt code {
+#tt code,
+.opview code{
 	color: #eee;
 	color: var(--fg-max);
@@ -221,10 +342,10 @@
 }
 #tt em {
-	color: #f6a;
+	color: var(--a);
 }
 html.y #tt {
 	border-color: #888 #000 #777 #000;
 }
-html.bz #tt {
+html.az #tt {
 	border-color: #3b3f58;
 }
@@ -234,11 +355,8 @@
 }
 #modalc code {
-	color: #060;
+	color: #03c;
 	background: transparent;
 	border: 1px solid #ccc;
 }
-html.y #tt em {
-	color: #d38;
-}
 html.y #tth {
 	color: #000;
@@ -343,6 +461,6 @@
 	border: none;
 }
-#modalb a:focus,
-#modalb a:hover {
+html:not(.fy) #modalb a:focus,
+html:not(.fy) #modalb a:hover {
 	background: #06d;
 	color: #fff;
@@ -362,5 +480,5 @@
 	border-radius: .4em;
 }
-#modali:focus {
+#modali:focus-visible {
 	border-color: #06d;
 }
@@ -368,19 +486,12 @@
 	max-width: 24em;
 }
-*:focus,
-*:focus+label,
-#pctl *:focus,
-.btn:focus {
-	box-shadow: 0 .1em .2em #fc0 inset;
-	outline: #fc0 solid .1em;
+*:focus-visible,
+*:focus-visible+label,
+.pctl *:focus-visible,
+.btn:focus-visible {
+	/* box-shadow: 0 .1em .2em #fc0 inset; */
+	outline: var(--a) solid .1em;
 	border-radius: .2em;
 }
-html.y *:focus,
-html.y *:focus+label,
-html.y #pctl *:focus,
-html.y .btn:focus {
-	box-shadow: 0 .1em .2em #037 inset;
-	outline: #037 solid .1em;
-}
 input, button {
 	font-family: var(--font-main), sans-serif;
@@ -389,14 +500,4 @@
 	cursor: pointer;
 }
-input[type="text"]:focus,
-input:not([type]):focus,
-textarea:focus {
-	box-shadow: 0 .1em .3em #fc0, 0 -.1em .3em #fc0;
-}
-html.y input[type="text"]:focus,
-html.y input:not([type]):focus,
-html.y textarea:focus {
-	box-shadow: 0 .1em .3em #037, 0 -.1em .3em #037;
-}
 
 
@@ -413,9 +514,8 @@
 .mdo code,
 .mdo a {
-	color: #480;
 	background: #f7f7f7;
 	border: .07em solid #ddd;
 	border-radius: .2em;
-	padding: .1em .3em;
+	padding: 0 .3em;
 	margin: 0 .1em;
 }
@@ -520,6 +620,5 @@
 }
 .mdo pre code:hover {
-	background: #fec;
-	color: #360;
+	background: #ddd;
 }
 .mdo table {
@@ -566,5 +665,5 @@
 		background: #444;
 		font-weight: normal;
-		border-top: .4em solid #fb0;
+		border-top: .4em solid rgba(203, 203, 203, 0.634);
 		border-bottom: .4em solid #777;
 		border-radius: 0 1em 0 1em;
@@ -593,5 +692,5 @@
 	html.z .mdo pre,
 	html.z .mdo code {
-		color: #8c0;
+		color: rgb(209, 209, 209);
 		background: #1a1a1a;
 		border: .07em solid #333;
@@ -612,8 +711,8 @@
 	html.z .mdo h1 {
 		background: #383838;
-		border-top: .4em solid #b80;
+		border-top: .4em solid rgba(203, 203, 203, 0.634);
 		border-bottom: .4em solid #4c4c4c;
 	}
-	html.bz .mdo h1 {
+	html.az .mdo h1 {
 		background: #202231;
 		border: 1px solid #2d2f45;
@@ -624,6 +723,6 @@
 		border-bottom: .22em solid #555;
 	}
-	html.bz .mdo h2,
-	html.bz .mdo h3 {
+	html.az .mdo h2,
+	html.az .mdo h3 {
 		background: transparent;
 		border-color: #3b3f58;
diff -NarU2 a/copyparty/web/up2k.js b/copyparty/web/up2k.js
--- a/copyparty/web/up2k.js	2026-07-28 23:16:17.540485393 +0000
+++ b/copyparty/web/up2k.js	2026-07-28 23:09:10.628391066 +0000
@@ -51,6 +51,19 @@
     }
     catch (ex) {
+        ebi('h_up2kcfg').style.display = 
+        ebi('u2btn_ct').style.display = 
+        ebi('u2c3t').style.display = 
+        ebi('up_info').style.display = 
+        ebi('opa_up').style.display = 
+            'none'
+
+        ebi('uq_up').onclick = function(){
+            modaltoggle('bup', true);
+            ebi('bup_btn').click();
+        }
+
         console.log('up2k init failed:', ex);
         toast.err(10, 'could not initialize up2k\n\n' + basenames(ex));
+
     }
 }
@@ -839,4 +852,5 @@
     var loading_deps = false;
     function init_deps() {
+        console.log('up2k init deps');
         if (!loading_deps && !got_deps()) {
             var fn = 'sha512.' + sha_js + '.js',
@@ -886,5 +900,5 @@
     }
 
-    setmsg(suggest_up2k, 'msg');
+    //setmsg(suggest_up2k, 'msg');
 
     var u2szs = u2sz.split(','),
@@ -919,5 +933,7 @@
     uc.owt = ['🛡️', '🕒', '♻️', '⏭️'];
     function set_ow() {
-        QS('label[for="u2ow"]').innerHTML = uc.owt[uc.ow];
+        var lbls = QSA('label[for="u2ow"]');
+        for(var i=0; i<lbls.length; i++)
+            lbls[i].innerHTML = uc.owt[uc.ow];
         ebi('u2ow').checked  = true; //cosmetic
     }
@@ -1030,5 +1046,5 @@
         modal.confirm(L.u_nav_m, uf, ud, null, L.u_nav_b);
     }
-    ebi('u2btn').onclick = nav;
+    ebi('uq_up').onclick = ebi('u2btn').onclick = nav;
 
     var nenters = 0;
@@ -1069,4 +1085,18 @@
         return onovercmn(this, e, true);
     }
+    function checkDropMode(){
+        if(ebi('fsearch').checked){
+            ebi('srch_zd').style.display = '';
+            ebi('up_zd').style.display = 'none';
+            ebi('srch_dz').style.display = '';
+            ebi('up_dz').style.display = 'none';
+        }
+        else{
+            ebi('srch_zd').style.display = 'none';
+            ebi('up_zd').style.display = '';
+            ebi('srch_dz').style.display = 'none';
+            ebi('up_dz').style.display = '';
+        }
+    }
     function onovercmn(self, e, btn) {
         var ok = false;
@@ -1074,4 +1104,10 @@
             var dt = e.dataTransfer.types;
 
+            for (var a = 0; a < dt.length; a++)
+                if(dt[a] == 'application/copyparty'){
+                    ev(e)
+                    return;
+                }
+
             for (var a = 0; a < dt.length && !ok; a++)
                 if (dt[a] == 'Files')
@@ -1082,5 +1118,5 @@
                     if (btn)
                         ok = true;
-                    else
+                    else if (!window.drag || !drag.no_warn)
                         return toast.inf(10, L.u_uri) || true;
                 }
@@ -1105,4 +1141,5 @@
             return;
 
+        checkDropMode();
         clmod(ebi('drops'), 'vis', 1);
         var v = self.getAttribute('v');
@@ -1118,4 +1155,5 @@
 
         if (--nenters <= 0) {
+            checkDropMode();
             clmod(ebi('drops'), 'vis');
             clmod(ebi('up_dz'), 'hl');
@@ -1132,4 +1170,6 @@
     ebi('u2btn').ondrop = gotfile;
     ebi('u2btn').ondragover = onoverbtn;
+    ebi('wrap').ondrop = gotfile;
+    ebi('wrap').ondragover = onover;
 
     var drops = [ebi('up_dz'), ebi('srch_dz')];
@@ -1174,10 +1214,22 @@
     function gotfile(e) {
         ev(e);
+
+        if (e.dataTransfer && e.dataTransfer.getData("text") && e.dataTransfer.getData("text").startsWith(window.location.origin)){
+			var currLink = e.dataTransfer.getData("text");
+			console.log("wrap.ondrop: " + currLink);
+			fileman.clip = currLink.split("\n");
+			
+			fileman.cut();
+			msel.evsel();
+            fileman.paste(true);
+            return
+		}
+
         nenters = 0;
         offdrag.call(this);
-        var dz = this && this.getAttribute('id');
-        if (!dz && e && e.clientY)
-            // cuo2duo fallback
-            dz = e.clientY < window.innerHeight / 2 ? 'up_dz' : 'srch_dz';
+        // var dz = this && this.getAttribute('id');
+        // if (!dz && e && e.clientY)
+        //     // cuo2duo fallback
+        //     dz = e.clientY < window.innerHeight / 2 ? 'up_dz' : 'srch_dz';
 
         var err = this.getAttribute('err');
@@ -1187,9 +1239,9 @@
         toast.inf(0, L.u_scan);
 
-        if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
-            tgl_fsearch();
+        // if ((dz == 'up_dz' && uc.fsearch) || (dz == 'srch_dz' && !uc.fsearch))
+        //     tgl_fsearch();
 
-        if (!QS('#op_up2k.act'))
-            goto('up2k');
+        if(!uc.fsearch)
+            up2k.init_deps();
 
         var files,
@@ -2000,5 +2052,5 @@
             spd = Math.floor(st.bytes.finished / st.time.busy),
             suf = '\n\n{0} @ {1}/s'.format(shumantime(st.time.busy), humansize(spd)),
-            t = uc.ask_up ? 0 : 10;
+            t = 10; // uc.ask_up ? 0 : 10;
 
         console.log('toast', ok, ng);
@@ -3043,39 +3095,4 @@
     //
 
-    function onresize(e) {
-        // 10x faster than matchMedia('(min-width
-        var bar = ebi('ops'),
-            wpx = window.innerWidth,
-            fpx = parseInt(getComputedStyle(bar)['font-size']),
-            wem = wpx * 1.0 / fpx,
-            wide = wem > 57 ? 'w' : '',
-            parent = ebi(wide ? 'u2btn_cw' : 'u2btn_ct'),
-            btn = ebi('u2btn');
-
-        if (btn.parentNode !== parent) {
-            parent.appendChild(btn);
-            ebi('u2conf').className = ebi('u2cards').className = ebi('u2etaw').className = wide;
-        }
-
-        wide = wem > 86 ? 'ww' : wide;
-        parent = ebi(wide == 'ww' ? 'u2c3w' : 'u2c3t');
-        var its = [ebi('u2etaw'), ebi('u2cards')];
-        if (its[0].parentNode !== parent) {
-            ebi('u2conf').className = wide;
-            for (var a = 0; a < 2; a++) {
-                parent.appendChild(its[a]);
-                its[a].className = wide;
-            }
-        }
-    }
-    onresize100.add(onresize, true);
-
-    if (MOBILE) {
-        // android-chrome wobbles for a bit; firefox / iOS-safari are OK
-        setTimeout(onresize, 20);
-        setTimeout(onresize, 100);
-        setTimeout(onresize, 500);
-    }
-
     var o = QSA('#u2conf .c *[tt]');
     for (var a = o.length - 1; a >= 0; a--) {
@@ -3308,5 +3325,8 @@
         try {
             clmod(ebi('u2c3w'), 's', !can_write);
-            QS('label[for="fsearch"]').style.display = QS('#fsearch').style.display = fixed ? 'none' : '';
+            QS('#fsearch').style.display = fixed ? 'none' : '';
+            var lbls = QSA('label[for="fsearch"]');
+            for(var i=0; i<lbls.length; i++)
+                lbls[i].style.display = fixed ? 'none' : '';
         }
         catch (ex) { }
@@ -3317,4 +3337,5 @@
 
             clmod(ebi('op_up2k'), 'srch', uc.fsearch);
+            clmod(ebi('u2conf'), 'srch', uc.fsearch);
             ebi('u2bm').innerHTML = ico + '&nbsp; <sup>' + desc + '</sup>';
         }
@@ -3332,5 +3353,4 @@
         draw_turbo();
         draw_life();
-        onresize();
     }
 
@@ -3450,4 +3470,50 @@
     }, 1);
 
+
+    r.save_here = function () {
+        try{
+            window.shr_target = false
+            fileman.render();
+            // get data from sw.js
+            caches.open('/media').then(function(cache){
+				cache.match('/shared-file-count').then(function(r){
+                    return r && r.text()
+                }).then(function(count){
+                    if(!(count > 0)) return;
+                    cache.delete('/shared-file-count');
+
+                    var files = []
+                    function load_from_cache(i){
+                        if(i >= count){
+                            up_them(files);
+                            return;
+                        }
+                        cache.match('/shared-file-' + i).then(function(file){
+                            return file.blob()
+                        }).then(function(blob){
+                            cache.match('/shared-filename-' + i).then(function(nameR){
+                                return nameR && nameR.text()
+                            }).then(function(name){
+                                return name || 'shared_file_' + i
+                            }).then(function(name){
+                                var realFile = new File([blob], name, { type: blob.type });
+                                files.push([realFile, name]);
+                                load_from_cache(i + 1);
+                            }).then(function(){
+                                cache.delete('/shared-file-' + i);
+                                cache.delete('/shared-filename-' + i);
+                            });
+                        });
+                    }
+                    load_from_cache(0);
+                });
+			});
+        }
+        catch(e){
+            alert(e)
+        }
+    };
+    if(ebi('fsav')) ebi('fsav').onclick = r.save_here;
+
     return r;
 }
@@ -3464,6 +3530,6 @@
 favico.init();
 ebi('ico1').onclick = function () {
-    var a = favico.txt == this.textContent;
-    swrite('icot', a ? 'c' : this.textContent);
+    var a = favico.txt == '🎉';
+    swrite('icot', a ? 'c' : '🎉');
     swrite('icof', a ? 'fc5' : '000');
     swrite('icob', a ? '222' : '');
diff -NarU2 a/copyparty/web/util.js b/copyparty/web/util.js
--- a/copyparty/web/util.js	2026-07-08 15:13:32.824924219 +0000
+++ b/copyparty/web/util.js	2026-07-28 23:09:10.628617173 +0000
@@ -20,4 +20,10 @@
     Object.assign(window, window.CGV);
 
+function supportsEmoji() {
+    var ctx = document.createElement('canvas').getContext('2d');
+    ctx.canvas.width = ctx.canvas.height = 1;
+    ctx.fillText('🎉', -4, 4);
+    return ctx.getImageData(0, 0, 1, 1).data[3] > 0; // Checks if pixels were drawn
+}
 
 var wah = '',
@@ -36,4 +42,6 @@
     UA = '' + navigator.userAgent,
     IE = !!document.documentMode,
+    EMOJI = supportsEmoji(),
+    N3DS = /Nintendo 3DS/i.test(UA),
     FIREFOX = ('netscape' in window) && / rv:/.test(UA),
     IPHONE = TOUCH && /iPhone|iPad|iPod/i.test(UA),
@@ -42,5 +50,10 @@
     WINDOWS = /Windows/.test(UA),
     APPLE = IPHONE || MACOS,
-    APPLEM = TOUCH && APPLE;
+    APPLEM = TOUCH && APPLE,
+    PWA = window.matchMedia && window.matchMedia('(display-mode: minimal-ui)').matches;
+
+window.onpointerdown = function (e) {
+    TOUCH = e.pointerType === "touch";
+}
 
 if (!window.WebAssembly || !WebAssembly.Memory)
@@ -455,4 +468,9 @@
     head.appendChild(script);
 }
+function import_mjs(name, id) {
+    import_js(SR + "/.cpr/w/" + name + ".js", function () {
+        if (id) jsldp(id, name);
+    });
+}
 
 
@@ -778,5 +796,5 @@
 
 
-function linksplit(rp, base, id) {
+function linksplit(rp, base, id, flink) {
     var ret = [],
         apath = base || '/',
@@ -815,5 +833,5 @@
         }
 
-        ret.push('<a href="' + apath + link + '">' + vlink + '</a>');
+        ret.push('<a href="' + apath + link + '" class="' + (!rp ? 'flink' : '') + '">' + vlink + '</a>');
         apath += link;
     }
@@ -1336,6 +1354,10 @@
         return val;
 
-    if (o.getAttribute('type') == 'checkbox')
+    if (o.getAttribute('type') == 'checkbox'){
         o.checked = val;
+        var lbls = QSA('label[for="' + o.id +'"]');
+        for(var i=0; i<lbls.length; i++)
+            clmod(lbls[i], 'on', val);
+    }
     else if (o) {
         clmod(o, 'on', val);
@@ -1409,5 +1431,5 @@
 function sethash(hv) {
     if (window.history && history.replaceState) {
-        hist_replace(location.pathname + location.search + '#' + hv);
+        hist_replace(location.pathname + location.search + (hv ? '#' : '') + hv);
     }
     else {
@@ -1602,4 +1624,16 @@
     };
 
+    r.parse = function(msg){
+        if(msg == null || msg.length == 0)
+            return msg;
+        if (msg.startsWith('`')) {
+            var x = false;
+            msg = msg.slice(1);
+            while (msg.indexOf('`') + 1)
+                msg = msg.replace('`', (x = !x) ? '<code>' : '</code>');
+        }
+        return msg.replace(/\$N/g, "<br />");
+    }
+
     r.show = function () {
         clearTimeout(tev);
@@ -1612,11 +1646,4 @@
             return;
 
-        if (msg.startsWith('`')) {
-            var x = false;
-            msg = msg.slice(1);
-            while (msg.indexOf('`') + 1)
-                msg = msg.replace('`', (x = !x) ? '<code>' : '</code>')
-        }
-
         r.el = this;
         var pos = this.getBoundingClientRect(),
@@ -1634,5 +1661,5 @@
         r.tt.style.top = '0';
 
-        r.tt.innerHTML = msg.replace(/\$N/g, "<br />");
+        r.tt.innerHTML = r.parse(msg);
         r.el.addEventListener('mouseleave', r.hide);
         window.addEventListener('scroll', r.hide);
@@ -1655,4 +1682,8 @@
     r.hide = function (e) {
         //ev(e);  // eats checkbox-label clicks
+
+        r.show.bind(null);
+        r.el = null;
+
         clearTimeout(tev);
         window.removeEventListener('scroll', r.hide);
@@ -1748,4 +1779,6 @@
         obj = mknod('div', 'toast');
 
+    clmod(obj, 'hide', true);
+
     document.body.appendChild(obj);
     r.visible = false;
@@ -1828,5 +1861,5 @@
             html += '<div id="toastt"></div>';
         }
-        obj.innerHTML = html + '<a href="#" id="toastc">x</a><div id="toastb">' + lf2br(txt) + '</div>';
+        obj.innerHTML = html + '<a href="#" id="toastc"><span class="x">×</span></a><div id="toastb">' + lf2br(txt) + '</div>';
         obj.className = cl;
         sec += obj.offsetWidth;
@@ -2276,5 +2309,5 @@
     var gx = function (txt) {
         return (svg_decl +
-            '<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">\n' +
+            '<svg version="1.1" viewBox="0 0 70 70" xmlns="http://www.w3.org/2000/svg">\n' +
             (r.bg ? '<rect width="100%" height="100%" rx="16" fill="#' + r.bg + '" />\n' : '') +
             '<text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle"' +
@@ -2313,4 +2346,9 @@
         }
         r.tag.href = 'data:image/svg+xml;base64,' + b64;
+
+        var fiop = ebi('favico_onpage');
+        if(fiop != null){
+            fiop.innerHTML = svg ? atob(b64) : gx(r.txt);
+        }
     };
 
@@ -2336,4 +2374,68 @@
 }
 
+// read accent / theme color
+function parseColor (strColor) {
+    if(strColor && strColor.length > 0 && strColor.length <= 8 && !strColor.startsWith('#') && !strColor.match(",")) 
+        strColor = '#' + strColor;
+	var s = new Option().style;
+	s.color = strColor;
+	return s.color !== '' ? s.color : '';
+}
+function setColor (color, temp) {
+    if(color == '#333333' || color == 'rgb(51, 51, 51)') color = '';
+	accent = color;
+    if(!temp)
+	    swrite('paccent', accent);
+	var a = accent || '';
+	console.log('accent color set to: ' + a);
+	setcvar('--a', a);
+	pbar.drawbuf();
+	pbar.drawpos();
+	vbar.draw();
+}
+
+var accent = sread('paccent');
+var tcolor = QS('meta[name=theme-color]');
+if(tcolor)
+    tcolor = tcolor.content;
+if((!accent || accent.length <= 3) && tcolor != "#333333")
+    accent = tcolor;
+if(accent && accent.length > 3){
+    console.log('read accent color from settings: ' + accent);
+    setcvar('--a', parseColor(accent));
+}
+
+var HAS_BG = false
+var setBg = function(bg){
+    var bgv = ebi('bg_vid'),
+        re_v = /^[^?]+\.(webm|mkv|mp4|m4v|mov)(\?|$)/i,
+        isVid = false,
+        src;
+    HAS_BG = bg != '' && bg !== true;
+    if(sread('enbg') == 0) bg = '';
+    console.log('setting bg to: ' + (bg || 'none'))
+    if(bg && bg !== true){
+        var parts = bg.split(',');
+        if(parts){
+            isVid = re_v.test(parts[0])
+            src = parts[0]
+        }
+        for(var i = 0; i < parts.length; i++){
+            if(parts[i].match('=')){
+                var o = parts[i].split('=')
+                if(o[0].trim() == 'blur')
+                    o[1] = 'blur(' + o[1] + 'px)'
+                setcvar('--bg-' + o[0].trim(), o[1])
+            }
+        }
+    }
+    setcvar('--bg-img', !isVid && src ? ('url("' + src + '")') : '');
+    if(bgv){
+        clmod(bgv, 'vis', isVid);
+        if(isVid)
+            bgv.innerHTML = '<source src="' + src + '" type="video/mp4"/>';
+    }
+}
+setBg(window.bg_img);
 
 function bchrome() {
@@ -2343,5 +2445,5 @@
 
     try {
-        v = cprop('--bg-u3');
+        v = cprop('--a');
     }
     catch (ex) { }
@@ -2411,3 +2513,15 @@
 }
 
+function jsldp(a, b) {
+	2 != window[a] && alert("FATAL ERROR: cannot load " + b + ".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R");
+}
+
+// polyfill for 3DS (IE11 can do this natively)
+var MouseEvent = MouseEvent ? MouseEvent : function (eventType, e) {
+	e = e || { bubbles: false, cancelable: false };
+	var mouseEvent = document.createEvent('MouseEvent');
+	mouseEvent.initMouseEvent(eventType, e.bubbles, e.cancelable, window, 0, 0, 0, 0, false, false, false, false, 0, null);
+	return mouseEvent;
+};
+
 J_UTL = 2;
diff -NarU2 a/docs/changelog.md b/docs/changelog.md
--- a/docs/changelog.md	2026-07-28 23:16:17.541485410 +0000
+++ b/docs/changelog.md	2026-07-28 23:15:23.919556082 +0000
@@ -1,3 +1,83 @@
 ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀  
+# 2026-0428-0907 `v1.21.0` UI V1.5
+
+## 😴 tl;dr
+
+* the web UI changed a LOT
+* nice mobile layout
+* progressive web app (more of a real copyparty app)
+* config GUI: server.com?config
+* try setting an accent color in the theme settings
+* --bg-img can set a background picture or video
+* drag n drop within copyparty and between browser windows of same server
+* the used storage metric is now at the bottom of the folder tree
+* the account details and control panel link are now in the account popup (top right)
+* media player settings and normal settings have merged
+* new file / new folder have moved from the top bar to the [+] button / right click menu (also accessible via long press on mobile)
+
+## 🧪 new features
+
+* Progressive web app (PWA). you can install this from the browser when visiting your server if it has real https. this PWA allows you to share to copyparty on mobile for example, basically the successor of the Party UP! app.
+* [Add moving files into other folders by dragging](https://github.com/tilse/copyparty-uiV1.5/pull/1) within the browser window to move files or folders
+* extend drag n drop logic to work between browser windows
+* custom accent color setting added. works best for light and dark themes (the first 4 in the list)
+* custom corner rounding in settings
+* added 2 new themes: poison // wing
+* long press on mobile selects the element in grid view by default
+* long pressing selected items brings up the context menu
+* selection checkboxes on items in grid view
+* the tree and path can be visible at the same time
+* resize drag handle at the edge of the folder tree
+* new design for placeholder thumbnails
+* #386 gallery view toggle for smaller margins / bigger image previews
+* settings and upload are now a popup/modal by default (can be moved)
+* settings can be navigated via sections and former tooltip info is now always visible
+* new floating [+] button in bottom right for quickly creating files / folders / uploading
+* searching folders / subfolders is now possible, and is the new default when clicking the search button. old-school extended search options are accessible via down arrow in the search box
+* storage capacity progress bar at bottom of folder tree
+* account button with popup in the top right
+* search in "quotes with spaces" is now possible to find file names including spaces
+* new simpler audio progress bar slider added as option
+* quick setting for changing loop mode via bottom media player bar
+* a volume icon that works as a mute toggle
+* name of playing audio is now displayed
+* audio equalizer can be operated via sliders
+* image viewer has nearest neighbor scale mode toggle
+* toggle to scale up small grid thumbnails to fit
+* --bg-img allows setting pictures or videos as the background like --bg-img=/folder/picture.jpg,opacity=0.5,blur=10. (volflag=bg_img)
+* new config GUI can be accessed via server.com?config or via the "edit config" button in the admin control panel
+
+## 🩹 bugfixes
+
+* #1455 tooltips disappear more consistently when element is no longer hovered
+* scrollable tree header so all options are accessible independent of tree width
+* #1378 improved mobile layout
+* media player has "fade" option now to disable fade in / fade out, which fixes play / pause on iOS lockscreens
+* #1002 proper overflow handling for text files or readmes (svg editor still overflows, but it doesn't break the page layout anymore)
+
+## 🔧 other changes
+
+* media player settings have moved to the main settings modal
+* creating files / folders is now done via right click menu, or the plus button in the bottom right
+* grid view toggle is now in grid header
+* default file sizes in MB
+* control panel button now lives in the account popup in the top bar
+* searching for files via drag n drop is now only possible when the search mode is toggled in the up2k UI
+* bup is an optional dropdown in the up2k modal
+* many mobile layout usability improvements
+* more consistent areas for clicking to navigate left / right in the image viewer (they don't depend on image fill mode anymore)
+* "folder up" / "next sibling" / "previous sibling" are now icon buttons at the top
+* new look for the login screen
+* pm-monokai theme has become "flat dark"
+
+## 🌠 fun facts
+
+* full support for the new 3DS, except some emoji rendering issues
+* it was a lot of work to keep all themes intact
+* some UI elements have gained IDs, so it should be easier to style them
+
+## 😔 unfun facts
+
+* #1209 (partially), #711 added a setting that gets rid of (most) emojis
 # 2026-0709-2254  `v1.20.18`  bwrap off
 
@@ -182,5 +262,4 @@
 
 
-
 ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀  
 # 2026-0323-0328  `v1.20.13`  dothidden
diff -NarU2 a/README.md b/README.md
--- a/README.md	2026-07-28 23:16:17.532485254 +0000
+++ b/README.md	2026-07-28 23:14:14.879615725 +0000
@@ -1,5 +1,5 @@
-<img src="https://github.com/9001/copyparty/raw/hovudstraum/docs/logo.svg" width="250" align="right"/>
+<img src="https://github.com/9001/copyparty/raw/hovudstraum/docs/logo.svg" width="220" align="right"/>
 
-### 💾🎉 copyparty
+### <nobr>💾🎉 copyparty</nobr>
 
 turn almost any device into a file server with resumable uploads/downloads using [*any*](#browser-support) web browser
@@ -7,5 +7,6 @@
 * server only needs Python (2 or 3), all dependencies optional
 * 🔌 protocols: [http(s)](#the-browser) // [webdav](#webdav-server) // [sftp](#sftp-server) // [ftp(s)](#ftp-server) // [tftp](#tftp-server) // [smb/cifs](#smb-server)
-* 📱 [android app](#android-app) // [iPhone shortcuts](#ios-shortcuts)
+* 📱 [progressive web app](#apps--integrations) // [android app](#party-up-android) // [iPhone shortcuts](#ios-shortcuts)
+* ⚙️ [config GUI](#config-gui)
 
 👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running on a nuc in my basement
@@ -132,6 +133,7 @@
     * [folder sync](#folder-sync) - sync folders to/from copyparty
     * [mount as drive](#mount-as-drive) - a remote copyparty server as a local filesystem
-* [android app](#android-app) - upload to copyparty with one tap
-* [iOS shortcuts](#iOS-shortcuts) - there is no iPhone app, but
+* [web app](#apps--integrations) - (almost) full OS integration (if you have https)
+* [android app](#party-up-android) - basic uploader (web app recommended)
+* [iOS shortcuts](#ios-shortcuts) - if you don't wanna use the web app
 * [performance](#performance) - defaults are usually fine - expect `8 GiB/s` download, `1 GiB/s` upload
     * [client-side](#client-side) - when uploading files
@@ -165,20 +167,26 @@
 just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
 
-> ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance
+> ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance, or choose your preferred method below
+
+| alternative run methods: | |
+|-|-|
+| [**python** pypi](https://pypi.org/project/copyparty/) | `python3 -m pip install --user -U copyparty`
+| [**docker 🐋**](./scripts/docker/) | dependencies built-in
+| [**copyparty.exe**](#copypartyexe) | (if you cannot install python)
+| [**arch**](#arch-package) / [homebrew](#homebrew-formulae) / [NixOS](#nixos-module) / [nix](#nix-package) | btw
+| **[android](#install-on-android)**| install copyparty in termux
+| [**iOS** iPhone / iPad](#install-on-iOS) | install in a-Shell on iOS
+| [**synology** nas / dsm](./docs/synology-dsm.md)
+| [**uv**](https://docs.astral.sh/uv/) | `uv tool run copyparty`
+| [**zipapp** (pyz)](#zipapp) | if your computer is messed up and nothing else works
+| [**bootable flashdrive** / cd-rom](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) | if your OS is dead entirely
 
-* or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
-* or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
-* or install [on arch](#arch-package) / [homebrew](#homebrew-formulae) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
-* or if you are on android, [install copyparty in termux](#install-on-android)
-* or maybe an iPhone or iPad? [install in a-Shell on iOS](#install-on-iOS)
-* or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
-* or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
-* or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
-* or if your OS is dead, give the [bootable flashdrive / cd-rom](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) a spin
 * or if you don't trust copyparty yet and want to isolate it a little, then...
-  * ...maybe [prisonparty](./bin/prisonparty.sh) to create a tiny [chroot](https://wiki.archlinux.org/title/Chroot) (very portable),
-  * ...or [bubbleparty](./bin/bubbleparty.sh) to wrap it in [bubblewrap](https://github.com/containers/bubblewrap) (much better)
-* or if you prefer to [use docker](./scripts/docker/) 🐋 you can do that too
-  * docker has all deps built-in, so skip this step:
+  * ...maybe [prisonparty](./bin/prisonparty.sh) to create a tiny [**chroot**](https://wiki.archlinux.org/title/Chroot) (very portable),
+  * ...or [bubbleparty](./bin/bubbleparty.sh) to wrap it in [**bubblewrap**](https://github.com/containers/bubblewrap) (much better)
+
+### dependencies
+
+if you're on **docker**, you can skip this step:
 
 enable thumbnails (images/audio/video), media indexing, and audio transcoding by installing some recommended deps:
@@ -195,7 +203,11 @@
 * see [optional dependencies](#optional-dependencies) to enable even more features
 
+### configuration
+
 running copyparty without arguments (for example doubleclicking it on Windows) will give everyone read/write access to the current folder; you may want [accounts and volumes](#accounts-and-volumes)
 
-or see [some usage examples](#complete-examples) for inspiration, or the [complete windows example](./docs/examples/windows.md)
+you can use the [**config GUI**](#config-gui) (recommended for beginners),
+or see [some usage examples](#complete-examples) for inspiration, 
+or the [complete windows example](./docs/examples/windows.md)
 
 some recommended options:
@@ -520,7 +532,23 @@
 
 
+# config GUI
+
+to help you configure copyparty, you can use the config GUI ("guest list"), which is accessible via the `✏️ edit config` button (only visible on the control panel when you're logged in as an admin), or by visiting `http://[your-ip]:3923?config`
+
+the config helper page currently has no option to load your real config, but you can import your config from a start command at the bottom of the page. otherwise, all changes are local and use your browser cache
+
+you will probably want to replace your run prefix according to the [quickstart section](#quickstart)
+
+in the config GUI, you can search for [commands](https://copyparty.eu/cli/) and see their descriptions, easily configure [users, groups and volumes](#accounts-and-volumes), as well as getting suggestions for some of the recommended options
+
+once you're done with your configuration, just copy it to your clipboard and run it or save it as a bash file
+
+
 # accounts and volumes
 
-per-folder, per-user permissions  - if your setup is getting complex, consider making a [config file](./docs/example.conf) instead of using arguments
+per-folder, per-user permissions  - if your setup is getting complex, consider using the [config GUI](#config-gui), or making a [config file](./docs/example.conf) instead of using arguments
+
+advantages of a [config file](./docs/example.conf):
+
 * much easier to manage, and you can modify the config at runtime with `systemctl reload copyparty` or more conveniently using the `[reload cfg]` button in the control-panel (if the user has `a`/admin in any volume)
   * changes to the `[global]` config section requires a restart to take effect
@@ -680,9 +708,13 @@
 * `[🧯]` [unpost](#unpost): undo/delete accidental uploads
 * `[🚀]` and `[🎈]` are the [uploaders](#uploading)
+* `[📟]` send-msg: either to server-log or into textfiles if `--urlform save`
+* `[⚙️]` client config options
+
+
+## quick actions (+)
+
 * `[📂]` mkdir: create directories
 * `[📝]` new-file: create a new textfile
-* `[📟]` send-msg: either to server-log or into textfiles if `--urlform save`
-* `[🎺]` audio-player config options
-* `[⚙️]` general client config options
+* `[🚀]` upload: quick way to pick files for upload
 
 
@@ -698,5 +730,5 @@
 * `T` toggle thumbnails / icons
 * `ESC` close various things
-* `ctrl-K` delete selected files/folders
+* `ctrl-K/Del` delete selected files/folders
 * `ctrl-X` cut selected files/folders
 * `ctrl-C` copy selected files/folders to clipboard
@@ -753,7 +785,7 @@
 ## navpane
 
-switching between breadcrumbs or navpane
+opening the navpane
 
-click the `🌲` or pressing the `B` hotkey to toggle between breadcrumbs path (default), or a navpane (tree-browser sidebar thing)
+clicking the `≡` or pressing the `B` hotkey toggles the navpane (tree-browser sidebar thing)
 
 * `[+]` and `[-]` (or hotkeys `A`/`D`) adjust the size
@@ -768,5 +800,5 @@
 ## thumbnails
 
-press `g` or `田` to toggle grid-view instead of the file listing  and `t` toggles icons / thumbnails
+press `g` or use the view mode buttons in the top left to toggle grid-view instead of the file listing  and `t` toggles icons / thumbnails
 * can be made default globally with `--grid` or per-volume with volflag `grid`
 * enable by adding `?imgs` to a link, or disable with `?imgs=0`
@@ -933,7 +965,5 @@
 ![copyparty-fsearch-fs8](https://user-images.githubusercontent.com/241032/129635361-c79286f0-b8f1-440e-aaf4-6e929428fac9.png)
 
-when you drag/drop files into the browser, you will see two dropzones: `Upload` and `Search`
-
-> on a phone? toggle the `[🔎]` switch green before tapping the big yellow Search button to select your files
+toggle the `[🔎]` switch in the upload options to search for files instead of uploading
 
 the files will be hashed on the client-side, and each hash is sent to the server, which checks if that file exists somewhere
@@ -1005,4 +1035,6 @@
 you can copy/move files across browser tabs (cut/copy in one tab, paste in another)
 
+you can drag/drop selected files into other folders
+
 
 ## shares
@@ -1181,5 +1213,5 @@
 click the `play` link next to an audio file, or copy the link target to [share it](https://a.ocv.me/pub/demo/music/Ubiktune%20-%20SOUNDSHOCK%202%20-%20FM%20FUNK%20TERRROR!!/#af-1fbfba61&t=18) (optionally with a timestamp to start playing from, like that example does)
 
-open the `[🎺]` media-player-settings tab to configure it,
+open the `[⚙]` configuration options and go to the `[🎵]` media player section to configure it,
 * "switches":
   * `[🔁]` repeats one single song forever
@@ -1233,5 +1265,5 @@
 alternatively, you can create the playlist using copyparty itself:
 
-* open the `[🎺]` media-player-settings tab and enable the `[📻]` create-playlist feature -- this adds two new buttons in the bottom-right tray, `[📻add]` and `[📻copy]` which appear when you listen to music, or when you select a few audiofiles
+* open the `[⚙]` configuration options, go to the `[🎵]` media player section and enable the `[📻]` create-playlist feature -- this adds two new buttons in the bottom-right tray, `[📻add]` and `[📻copy]` which appear when you listen to music, or when you select a few audiofiles
 
 * click the `📻add` button while a song is playing (or when you've selected some songs) and they'll be added to "the list" (you can't see it yet)
@@ -1322,4 +1354,10 @@
 search by size, date, path/name, mp3-tags, ...
 
+by default, the `[🔍]` search button shows the folder search bar.
+
+search for files within a folder and optionally its subfolders.
+
+for extended search options, use the dropdown arrow to open the full search UI
+
 ![copyparty-search-fs8](https://user-images.githubusercontent.com/241032/129635365-c0ff2a9f-0ee5-4fc3-8bb6-006033cf67b8.png)
 
@@ -2973,18 +3011,30 @@
 
 
-# android app
+# apps & integrations
+### Progressive web app (all platforms)
+<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Progressive_Web_Apps_Logo.svg/3840px-Progressive_Web_Apps_Logo.svg.png" height="40">
+
+**HTTPS REQUIRED**
+
+install the app straight from the browser and access it in the share menu to upload files! it uses the normal web interface.
 
+you can install this from the browser when visiting your server if it has real https. this PWA allows you to share to copyparty on mobile for example, basically the successor of the Party UP! app.
+
+if you use this, you might want to consider setting [--cookie-lax](https://copyparty.eu/cli/#flags-help-page:~:text=lax), which lets you follow file/folder links without having to log in again.  
+this might increase the likelihood of success on an attack where an attacker uploads a malicious html file, which tries to run scripts with your privileges, since clicking a link from a chat service would be enough to succeed. if this concerns you, have a look at [--no-html](https://copyparty.eu/cli/#g-no-html)
+
+### Party UP! (android)
 upload to copyparty with one tap
 
 <a href="https://f-droid.org/packages/me.ocv.partyup/"><img src="https://ocv.me/fdroid.png" alt="Get it on F-Droid" height="50" /> '' <img src="https://img.shields.io/f-droid/v/me.ocv.partyup.svg" alt="f-droid version info" /></a> '' <a href="https://github.com/9001/party-up"><img src="https://img.shields.io/github/release/9001/party-up.svg?logo=github" alt="github version info" /></a>
 
-the app is **NOT** the full copyparty server! just a basic upload client, nothing fancy yet
+the app is **NOT** the full copyparty server! just a basic upload client. if you want the full experience, [use the PWA](#progressive-web-app-all-platforms). unlike the PWA, this app doesn't require https.
 
 if you want to run the copyparty server on your android device, see [install on android](#install-on-android)
 
 
-# iOS shortcuts
+### iOS shortcuts
 
-there is no iPhone app, but  the following shortcuts are almost as good:
+if you don't want to use the PWA, the following shortcuts are almost as good:
 
 * [upload to copyparty](https://www.icloud.com/shortcuts/41e98dd985cb4d3bb433222bc1e9e770) ([offline](https://github.com/9001/copyparty/raw/hovudstraum/contrib/ios/upload-to-copyparty.shortcut)) ([png](https://user-images.githubusercontent.com/241032/226118053-78623554-b0ed-482e-98e4-6d57ada58ea4.png)) based on the [original](https://www.icloud.com/shortcuts/ab415d5b4de3467b9ce6f151b439a5d7) by [Daedren](https://github.com/Daedren) (thx!)
@@ -3378,4 +3428,64 @@
 if you are suddenly unable to access storage (permission issues), try forcequitting termux, revoke all of its permissions in android settings, and run the command `termux-setup-storage`
 
+## external storage on android
+
+**exFAT recommended!**
+
+if you want to use external storage like an SD card or USB stick, you need to get the mount path from the folder info in a file explorer (for example [CX explorer](https://play.google.com/store/apps/details?id=com.cxinventor.file.explorer) (closed source though))
+
+your path will look something like `/mnt/[device-ID]/`
+
+⚠️ to prevent copyparty from shutting down when your external storage is disconnected, use `--dbpath=./` to prevent storing your database on the removable media when you use any of the `-e2d*` arguments. this might also prevent other related errors
+
+### permission errors
+first, try re-running the initial setup command from the [section above](#install-on-android)
+
+if you still get permission errors, it might be neccessary to create a symlink to the external storage and authorize it via termux-usb.
+
+(if you're rooted, you can skip this section and just set [selinux permissive](https://github.com/evdenis/selinux_permissive))
+
+>(careful, this depends on the order you plug in your devices. the first in the list is the last one you plugged in. the paths will also change when you re-connect a device)
+
+`termux-usb -l` -> `termux-usb -r /dev/bus/...`
+
+`ln -sf /dev/bus/... ~/storage/external-symlink`
+
+while this will work temporarily, this will **break** if you restart your phone or unplug the external storage, because the path returned by termux-usb can change. to work around that, you need the following:
+
+**requirements**: `pkg install jq termux-usb`
+
+**step 1:** this script automatically creates a symlink to the first USB device in the `termux-usb -l` list and authorizes it if necessary 
+
+`nano ~/.termux/tasker/on-usb.sh`
+```
+#!/bin/bash
+CURRENT_USB=$(termux-usb -l | jq -r '.[0]')
+if [ -z "$CURRENT_USB" ]; then
+    exit 1
+fi
+ln -sf "$CURRENT_USB" ~/storage/external-symlink
+termux-usb -r "$CURRENT_USB"
+```
+`chmod +x ~/.termux/tasker/on-usb.sh`
+
+**step 2:** run this script when you launch copyparty
+
+`~/.termux/tasker/on-usb.sh && copyparty`
+
+**step 2.5 (optional):** create a tasker task to run the script when a USB device gets plugged in
+
+requirements:
+- [Tasker](https://tasker.joaoapps.com/) (paid)
+- [Termux:Tasker](https://f-droid.org/packages/com.termux.tasker/)
+- termux permission: draw over other apps
+
+set up a task:
+- type: State -> Hardware -> USB connected
+- action: Plugin -> termux:tasker -> configuration
+  - select script: on-usb.sh
+  - check "execute in a terminal session" (termux: draw over other apps is needed)
+
+**step 3:** include the symlink to the volume in your copyparty config! `-v=~/storage/external-symlink::`
+
 
 # install on iOS
diff -NarU2 a/scripts/make-sfx.sh b/scripts/make-sfx.sh
--- a/scripts/make-sfx.sh	2026-07-28 23:16:17.541485410 +0000
+++ b/scripts/make-sfx.sh	2026-07-13 09:49:10.696380106 +0000
@@ -406,6 +406,15 @@
 		exit 1
 	}
-
 	dt="$(git log -1 --format=%cd --date=short | sed -E 's/-0?/, /g')"
+}
+
+[ "$PRTY_SFX_VER" ] && {
+	# PRTY_SFX_VER='1.20.14-5-g139ef185' PRTY_SFX_DT='2026, 5, 8'
+	# PRTY_SFX_VER='2026-0510-2303' PRTY_SFX_DT='2026, 5, 10'
+	ver="$PRTY_SFX_VER"
+	dt="$PRTY_SFX_DT"
+}
+
+[ "$ver" ] && {
 	printf 'git %3s: \033[36m%s\033[0m\n' ver "$ver" dt "$dt"
 	sed -ri '
diff -NarU2 a/scripts/sfx.ls b/scripts/sfx.ls
--- a/scripts/sfx.ls	2026-07-28 23:16:17.542485428 +0000
+++ b/scripts/sfx.ls	2026-07-28 23:14:14.887600789 +0000
@@ -75,5 +75,9 @@
 copyparty/web/browser2.html,
 copyparty/web/cf.html,
+copyparty/web/config.css,
+copyparty/web/config.html,
+copyparty/web/config.js,
 copyparty/web/copyparty.gif,
+copyparty/web/copyparty.svg,
 copyparty/web/deps,
 copyparty/web/deps/__init__.py,
@@ -94,4 +98,5 @@
 copyparty/web/idp.html,
 copyparty/web/iiam.gif,
+copyparty/web/manifest.json,
 copyparty/web/md.css,
 copyparty/web/md.html,
@@ -116,4 +121,5 @@
 copyparty/web/svcs.html,
 copyparty/web/svcs.js,
+copyparty/web/sw.js,
 copyparty/web/tl,
 copyparty/web/tl/chi.js,
diff -NarU2 a/scripts/tl.js b/scripts/tl.js
--- a/scripts/tl.js	2026-07-08 15:13:32.832902264 +0000
+++ b/scripts/tl.js	2026-07-28 23:09:10.629020886 +0000
@@ -38,5 +38,5 @@
 
 	"cols": {
-		"c": "action buttons",
+		"!": "action buttons",
 		"dur": "duration",
 		"q": "quality / bitrate",
@@ -47,5 +47,5 @@
 		"Vhash": "video checksum",
 		"Res": "resolution",
-		"T": "filetype",
+		"Ext": "filetype",
 		"aq": "audio quality / bitrate",
 		"vq": "video quality / bitrate",
@@ -66,5 +66,5 @@
 			["T", "toggle thumbnails / icons"],
 			["⇧ A/D", "thumbnail size"],
-			["ctrl-K", "delete selected"],
+			["ctrl-K/Del", "delete selected"],
 			["ctrl-X", "cut selection to clipboard"],
 			["ctrl-C", "copy selection to clipboard"],
@@ -140,7 +140,7 @@
 
 	"goh": "control-panel",
-	"gop": 'previous sibling">prev',
-	"gou": 'parent folder">up',
-	"gon": 'next folder">next',
+	"gop": 'previous sibling',
+	"gou": 'parent folder',
+	"gon": 'next folder',
 	"logout": "Logout ",
 	"login": "Login",
@@ -190,5 +190,5 @@
 	"ut_ow": "overwrite existing files on the server?$N🛡️: never (will generate a new filename instead)$N🕒: overwrite if server-file is older than yours$N♻️: always overwrite if the files are different$N⏭️: unconditionally skip all existing files",
 	"ut_mt": "continue hashing other files while uploading$N$Nmaybe disable if your CPU or HDD is a bottleneck",
-	"ut_ask": 'ask for confirmation before upload starts">💭',
+	"ut_ask": 'ask for confirmation before upload starts',
 	"ut_pot": "improve upload speed on slow devices$Nby making the UI less complex",
 	"ut_srch": "don't actually upload, instead check if the files already $N exist on the server (will scan all folders you can read)",
@@ -238,10 +238,10 @@
 	"u_nav_b": '<a href="#" id="modal-ok">Files</a><a href="#" id="modal-ng">One folder</a>',
 
-	"cl_opts": "switches",
+	"cl_opts": "general",
 	"cl_hfsz": "filesize",
 	"cl_themes": "theme",
 	"cl_langs": "language",
 	"cl_ziptype": "folder download",
-	"cl_uopts": "up2k switches",
+	"cl_uopts": "upload",
 	"cl_favico": "favicon",
 	"cl_bigdir": "big dirs",
@@ -257,5 +257,5 @@
 	"ct_grid": '田 the grid',
 	"ct_ttips": '◔ ◡ ◔">ℹ️ tooltips',
-	"ct_thumb": 'in grid-view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs',
+	"ct_thumb": 'toggle icons or thumbnails$NHotkey: T">🖼️ thumbs',
 	"ct_csel": 'use CTRL and SHIFT for file selection in grid-view">sel',
 	"ct_dsel": 'use drag-selection in grid-view">dsel',
@@ -318,5 +318,5 @@
 	"ml_ss": "skip silence",
 
-	"mt_loop": "loop/repeat one song\">🔁",
+	"mt_loop": "loop/repeat one song",
 	"mt_one": "stop after one song\">1️⃣",
 	"mt_shuf": "shuffle the songs in each folder\">🔀",
@@ -336,5 +336,5 @@
 	"mt_uncache": "clear cache &nbsp;(try this if your browser cached$Na broken copy of a song so it refuses to play)\">uncache",
 	"mt_mloop": "loop the open folder\">🔁 loop",
-	"mt_mnext": "load the next folder and continue\">📂 next",
+	"mt_mnext": "load the next folder and continue",
 	"mt_mstop": "stop playback\">⏸ stop",
 	"mt_cflac": "convert flac / wav to {0}\">flac",
@@ -548,4 +548,5 @@
 	"s_t1": "tags contains &nbsp; (^=start, end=$)",
 	"s_a1": "specific metadata properties",
+	"s_dir": "search this folder",
 
 	"md_eshow": "cannot render ",
diff -NarU2 a/tests/util.py b/tests/util.py
--- a/tests/util.py	2026-07-28 23:16:17.543485445 +0000
+++ b/tests/util.py	2026-07-28 23:14:14.888896999 +0000
@@ -167,5 +167,5 @@
         ka.update(**{k: 0 for k in ex.split()})
 
-        ex = "ah_alg bname chdir chmod_f chpw_db csp_dl csp_ui db_xattr doctitle df epilogues exit favico fika ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts preadmes prologues readmes shr shr1 shr_site site smsg tcolor textfiles th_pregen txt_eol ufavico ufavico_h unlist up_site vc_url vname xff_src zipmaxt R RS SR"
+        ex = "ah_alg bname chdir chmod_f chpw_db csp_dl csp_ui db_xattr doctitle df epilogues exit favico fika ipa ipar html_head html_head_d html_head_s idp_login idp_logout lg_sba lg_sbf log_date log_fk md_sba md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i opds_exts preadmes prologues readmes shr shr1 shr_site site smsg tcolor textfiles th_pregen txt_eol ufavico ufavico_h unlist up_site vc_url vname xff_src zipmaxt R RS SR bg_img"
         ka.update(**{k: "" for k in ex.split()})
 
