History

Initial wormhole import

Rename the file server from the old gossa tree. Binary, Go sources, headers, and docs are wormhole throughout.

Kris Yotam committed 2 months agomaster

Commit 20c1d5aNo parent (first commit)58 files · +3014 −-5Browse files

Unified · Split

58 files
  1. .gitattributes +1 −0
  2. .github/workflows/always.yml +20 −0
  3. .github/workflows/deploy.yml +57 −0
  4. .gitignore +48 −0
  5. Dockerfile +10 −0
  6. LICENSE +21 −0
  7. Makefile +75 −0
  8. README.md +35 −0
  9. go.mod +3 −0
  10. support/build.Dockerfile +16 −0
  11. support/docker-compose.yml +39 −0
  12. support/readme.md +195 −0
  13. support/service +13 −0
  14. test-fixture/.some-hidden-file +1 −0
  15. test-fixture/.testhidden +1 −0
  16. test-fixture/404.html +1 −0
  17. test-fixture/404.html.gz +0 −0
  18. test-fixture/b.txt +1 −0
  19. test-fixture/compress/foo.js +1 −0
  20. test-fixture/compress/foo.js.gz +0 −0
  21. test-fixture/compress/foo_2.js +1 −0
  22. test-fixture/compress/hols-sym +1 −0
  23. test-fixture/curimit@gmail.com (40%)/test.html +1 −0
  24. test-fixture/custom_mime_type.types +3 −0
  25. test-fixture/ext/somefile.blank +0 −0
  26. test-fixture/ext/somefile.code +0 −0
  27. test-fixture/ext/somefile.db +0 −0
  28. test-fixture/ext/somefile.doc +0 −0
  29. test-fixture/ext/somefile.epub +0 −0
  30. test-fixture/ext/somefile.gif +0 −0
  31. test-fixture/ext/somefile.mp3 +0 −0
  32. test-fixture/ext/somefile.mp4 +0 −0
  33. test-fixture/ext/somefile.pdf +0 −0
  34. test-fixture/ext/somefile.ppt +0 −0
  35. test-fixture/ext/somefile.srt +0 −0
  36. test-fixture/ext/somefile.xls +0 −0
  37. test-fixture/ext/somefile.zip +0 −0
  38. test-fixture/fancy-path/a +1 −0
  39. test-fixture/gzip/fake_ecstatic +1 −0
  40. test-fixture/gzip/fake_ecstatic.gz +0 −0
  41. test-fixture/gzip/real_ecstatic +1 −0
  42. test-fixture/hols/.hidden-folder/some-file +1 −0
  43. test-fixture/hols/c.js +1 −0
  44. test-fixture/hols/elephant-1822636_1920.jpg +0 −0
  45. test-fixture/hols/glasgow.jpg +0 −0
  46. test-fixture/hols/scotland-1761292_1920.jpg +0 −0
  47. test-fixture/show-dir$$href_encoding$$/aname+aplus.txt +4 −0
  48. test-fixture/subdir/e.html +1 −0
  49. test-fixture/subdir_with space/file_with space.html +1 −0
  50. test-fixture/中文/檔案.html +1 −0
  51. ui/favicon.svg +1 −0
  52. ui/readme.md +4 −0
  53. ui/script.js +925 −0
  54. ui/style.css +645 −0
  55. ui/ui.tmpl +146 −0
  56. wormhole.go +318 −0
  57. wormhole_embed.go +34 −0
  58. wormhole_test.go +390 −0
.gitattributes+1−0
@@ -0,0 +1 @@
1+ui/** linguist-vendored
.github/workflows/always.yml+20−0
@@ -0,0 +1,20 @@
1+name: always
2+on: [push]
3+jobs:
4+ build:
5+ name: Build
6+ runs-on: ubuntu-latest
7+ steps:
8+
9+ - name: Checkout code
10+ uses: actions/checkout@v4
11+
12+ - name: Set up Go
13+ uses: actions/setup-go@v5
14+ with:
15+ go-version: 1.23.0
16+ id: go
17+
18+ - name: Run
19+ run: make ci
20+
.github/workflows/deploy.yml+57−0
@@ -0,0 +1,57 @@
1+name: deploy
2+
3+on:
4+ push:
5+ tags:
6+ - '*'
7+
8+jobs:
9+ build:
10+ runs-on: ubuntu-latest
11+ steps:
12+ - name: Checkout
13+ uses: actions/checkout@v4
14+
15+ - name: Set env
16+ run: echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
17+
18+ - name: Set up QEMU
19+ uses: docker/setup-qemu-action@v3
20+
21+ - name: Login to DockerHub
22+ uses: docker/login-action@v3
23+ with:
24+ username: ${{ secrets.DOCKER_USER }}
25+ password: ${{ secrets.DOCKER_PASSWORD }}
26+
27+ - name: Set up Docker Buildx
28+ uses: docker/setup-buildx-action@v3
29+
30+ - name: Build and release on dockerhub
31+ uses: docker/build-push-action@v6
32+ with:
33+ file: support/build.Dockerfile
34+ push: true
35+ platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
36+ tags: krisyotam/wormhole:latest,krisyotam/wormhole:${{github.ref_name}}
37+
38+ - name: Build all artifacts
39+ run: make build-all
40+
41+ - name: "Release gh release versioned"
42+ uses: ncipollo/release-action@v1
43+ with:
44+ allowUpdates: true
45+ artifacts: "builds/*"
46+ bodyFile: "builds/buildout"
47+ token: ${{ secrets.GITHUB_TOKEN }}
48+
49+ - name: "Release gh release latest"
50+ uses: ncipollo/release-action@v1
51+ with:
52+ tag: latest
53+ name: Latest
54+ allowUpdates: true
55+ artifacts: "builds/*"
56+ bodyFile: "builds/buildout"
57+ token: ${{ secrets.GITHUB_TOKEN }}
.gitignore+48−0
@@ -0,0 +1,48 @@
1+wormhole
2+wormhole-linux64
3+wormhole-linux-arm
4+wormhole-linux-arm64
5+wormhole-mac
6+wormhole-windows.exe
7+builds
8+wormhole.test
9+
10+*.out
11+.vscode
12+test-fixture/*
13+test-fixture/*/*
14+!test-fixture/.testhidden
15+!test-fixture/compress
16+!test-fixture/compress/foo*
17+!test-fixture/gzip
18+!test-fixture/gzip/fake_ecstatic*
19+!test-fixture/gzip/real_ecstatic
20+!test-fixture/curimit@gmail.com (40%)
21+!test-fixture/curimit@gmail.com (40%)/test.html
22+!test-fixture/show-dir$$href_encoding$$
23+!test-fixture/show-dir$$href_encoding$$/aname+aplus.txt
24+!test-fixture/subdir
25+!test-fixture/subdir/e.html
26+!test-fixture/subdir_with space
27+!test-fixture/subdir_with space/file_with space.html
28+!test-fixture/中文
29+!test-fixture/中文/檔案.html
30+!test-fixture/404*
31+!test-fixture/b.txt
32+!test-fixture/custom_mime_type*
33+!test-fixture/f_f
34+!test-fixture/hols
35+!test-fixture/hols/c.js
36+!test-fixture/hols/adler-2872995_1920.jpg
37+!test-fixture/hols/elephant-1822636_1920.jpg
38+!test-fixture/hols/glasgow.jpg
39+!test-fixture/hols/landscape-540116_1920.jpg
40+!test-fixture/hols/scotland-1761292_1920.jpg
41+!test-fixture/fancy-path
42+!test-fixture/fancy-path/*
43+!test-fixture/ext/*
44+!test-fixture/ext
45+!test-fixture/.some-hidden-file
46+!test-fixture/hols/.hidden-folder
47+!test-fixture/hols/.hidden-folder/some-file
48+!test-fixture/compress/hols-sym
Dockerfile+10−0
@@ -0,0 +1,10 @@
1+FROM golang:1.23-alpine AS builder
2+WORKDIR /src
3+COPY go.mod ./
4+RUN go mod download
5+COPY . .
6+RUN CGO_ENABLED=0 go build -trimpath -o /wormhole
7+
8+FROM alpine:3.20
9+COPY --from=builder /wormhole /usr/local/bin/wormhole
10+ENTRYPOINT ["wormhole"]
LICENSE+21−0
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2018 Pierre Dubouilh
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
Makefile+75−0
@@ -0,0 +1,75 @@
1+FLAGS := -trimpath
2+NOCGO := CGO_ENABLED=0
3+
4+build::
5+ go vet && go fmt
6+ ${NOCGO} go build ${FLAGS} -o wormhole
7+
8+install::
9+ sudo cp wormhole /usr/local/bin
10+
11+run::
12+ ./wormhole -verb=true test-fixture
13+
14+run-ro::
15+ ./wormhole -verb=true -ro=true test-fixture
16+
17+run-extra::
18+ ./wormhole -verb=true -prefix="/fancy-path/" -k=false -symlinks=true test-fixture
19+
20+ci:: build-all test
21+ echo "done"
22+
23+test::
24+ -@cd test-fixture && ln -s ../support .; true
25+ go test -cover -c -tags testrunmain
26+
27+ timeout -s SIGINT 3 ./wormhole.test -test.coverprofile=normal.out -test.run '^TestRunMain' -verb=true test-fixture &
28+ sleep 2
29+ go test -run TestNormal
30+ sleep 1
31+
32+ timeout -s SIGINT 3 ./wormhole.test -test.coverprofile=extra.out -test.run '^TestRunMain' -prefix='/fancy-path/' -k=false -symlinks=true test-fixture &
33+ sleep 2
34+ go test -run TestExtra
35+ sleep 1
36+
37+ timeout -s SIGINT 3 ./wormhole.test -test.coverprofile=ro.out -test.run '^TestRunMain' -ro=true test-fixture &
38+ sleep 2
39+ go test -run TestRo
40+ sleep 1
41+
42+ # gocovmerge ro.out extra.out normal.out > all.out
43+ # go tool cover -html all.out
44+ # go tool cover -func=all.out | grep main | grep '9.\..\%'
45+
46+watch::
47+ ls wormhole.go wormhole_test.go ui/* | entr -rc make build run
48+
49+watch-extra::
50+ ls wormhole.go wormhole_test.go ui/* | entr -rc make build run-extra
51+
52+watch-ro::
53+ ls wormhole.go wormhole_test.go ui/* | entr -rc make build run-ro
54+
55+watch-test::
56+ ls wormhole.go wormhole_test.go ui/* | entr -rc make test
57+
58+build-all:: build
59+ go version
60+ ${NOCGO} GOOS=linux GOARCH=amd64 go build ${FLAGS} -o builds/wormhole-linux-x64
61+ ${NOCGO} GOOS=linux GOARCH=arm go build ${FLAGS} -o builds/wormhole-linux-arm
62+ ${NOCGO} GOOS=linux GOARCH=arm64 go build ${FLAGS} -o builds/wormhole-linux-arm64
63+ ${NOCGO} GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o builds/wormhole-mac-x64
64+ ${NOCGO} GOOS=darwin GOARCH=arm64 go build ${FLAGS} -o builds/wormhole-mac-arm64
65+ ${NOCGO} GOOS=windows GOARCH=amd64 go build ${FLAGS} -o builds/wormhole-windows.exe
66+ sha256sum builds/* | tee builds/buildout
67+
68+clean::
69+ rm -f wormhole
70+ rm -f wormhole-linux64
71+ rm -f wormhole-linux-arm
72+ rm -f wormhole-linux-arm64
73+ rm -f wormhole-mac
74+ rm -f wormhole-windows.exe
75+
README.md+35−0
@@ -0,0 +1,35 @@
1+# wormhole
2+
3+A small Go file server with a built-in browser. One binary. No extra runtime.
4+
5+You point it at a directory and it serves it. Browse files, stream video, look at pictures and PDFs, take notes, upload if you want. Keyboard navigation is there. Read-only mode is there if you only want people to look.
6+
7+HTTPS and logins stay with a reverse proxy. wormhole does the files.
8+
9+## Build
10+
11+```sh
12+make
13+./wormhole --help
14+./wormhole -h 127.0.0.1 ~/storage
15+```
16+
17+`make install` copies the binary to `/usr/local/bin`.
18+
19+## Docker
20+
21+```sh
22+mkdir ~/LocalDirToShare
23+docker build -t wormhole .
24+docker run -v ~/LocalDirToShare:/shared -p 8001:8001 wormhole
25+```
26+
27+See `support/` for compose, a systemd unit, and Caddy examples for more than one user.
28+
29+## Shortcuts
30+
31+`Ctrl/Cmd + h` lists the UI keys.
32+
33+## License
34+
35+MIT.
go.mod+3−0
@@ -0,0 +1,3 @@
1+module github.com/krisyotam/wormhole
2+
3+go 1.23.0
support/build.Dockerfile+16−0
@@ -0,0 +1,16 @@
1+FROM docker.io/library/golang:1.23.0-alpine AS builder
2+RUN apk add --no-cache make
3+WORKDIR /wormholeSrc
4+COPY . /wormholeSrc
5+RUN make
6+
7+FROM docker.io/library/alpine:3.20
8+ENV UID="1000" GID="1000" HOST="0.0.0.0" PORT="8001" PREFIX="/" FOLLOW_SYMLINKS="false" SKIP_HIDDEN_FILES="true" DATADIR="/shared" READONLY="false" VERB="false"
9+COPY --from=builder /wormholeSrc/wormhole /wormhole
10+RUN addgroup -g ${GID} user \
11+ && adduser -D -u ${UID} -G user user
12+WORKDIR ${DATADIR}
13+RUN chown ${UID}:${GID} ${DATADIR}
14+USER ${UID}:${GID}
15+ENTRYPOINT /wormhole -h ${HOST} -p ${PORT} -k=${SKIP_HIDDEN_FILES} -ro=${READONLY} --symlinks=${FOLLOW_SYMLINKS} --prefix=${PREFIX} --verb=${VERB} ${DATADIR}
16+HEALTHCHECK --timeout=5s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider 127.0.0.1:8001 || exit 1
support/docker-compose.yml+39−0
@@ -0,0 +1,39 @@
1+version: '2'
2+
3+services:
4+ wormhole-server:
5+ image: docker.io/krisyotam/wormhole:latest
6+ container_name: wormhole
7+ restart: always
8+ read_only: true
9+ # uncomment to set the user
10+ # user: "1000:1000"
11+ # userns_mode: "keep-id" # uncomment if using rootless podman as well as the x-podman directive at the bottom
12+ # environment:
13+ #- READONLY=true # uncomment to set wormhole as read only
14+ # - UID=1000 # this should match the user set above
15+ # - GID=1000 # this should match the user's group
16+ cap_drop:
17+ - ALL
18+ cap_add:
19+ - SETGID
20+ - SETUID
21+ # uncomment to set resource usage limits
22+ # deploy:
23+ # resources:
24+ # limits:
25+ # cpus: "2"
26+ # memory: 250m
27+ # pids: 1024
28+ ports:
29+ - 8001:8001
30+ volumes:
31+ - ~/to-share:/shared
32+# labels:
33+# - "traefik.enable=true"
34+# - "traefik.port=8001"
35+# - "traefik.backend=wormhole"
36+# - "traefik.frontend.rule=Host:${WORMHOLE}.${DOMAIN}"
37+
38+# x-podman: # uncomment if using rootless podman as well as the userns_mode directive at the top
39+# in_pod: false
support/readme.md+195−0
@@ -0,0 +1,195 @@
1+## run as a service
2+
3+use the `service` file provided here to use as a systemd service.
4+
5+```sh
6+# edit service file
7+
8+# copy over our service, and start. Notice no sudo with systemctl command !
9+% mkdir -p ~/.config/systemd/user/
10+% cp service ~/.config/systemd/user/wormhole.service
11+% systemctl --user start wormhole
12+
13+# enable at boot time
14+% systemctl --user enable wormhole
15+```
16+
17+## run with docker
18+
19+the master branch is automatically built and pushed to [dockerhub](https://hub.docker.com/r/krisyotam/wormhole) under `krisyotam/wormhole`.
20+
21+```sh
22+# pull from dockerhub and run
23+% mkdir ~/LocalDirToShare
24+% sudo docker run -v ~/LocalDirToShare:/shared -p 8001:8001 krisyotam/wormhole
25+
26+# options are settable through env. variabes. all the options are the build.Dockerfile
27+% sudo docker run -e PREFIX="/wormhole/" -v ~/LocalDirToShare:/shared -p 8001:8001 krisyotam/wormhole
28+```
29+
30+if you prefer building the image yourself :
31+
32+```sh
33+# build wormhole within a build container, needs to be ran within the sources, ../ from here, and run
34+% mkdir ~/LocalDirToShare
35+% docker build -t wormhole -f support/build.Dockerfile .
36+% sudo docker run -v ~/LocalDirToShare:/shared -p 8001:8001 wormhole
37+```
38+
39+a docker-compose example image is also provided. running docker compose should be straightforward : `docker-compose up .` have a look in `docker-compose.yml` for further configuration.
40+
41+## multi-account setup with Caddy
42+
43+authentication / user routing has been left out of the design of wormhole, as simple tools are already available for this purpose. [caddy](https://caddyserver.com) is used here as an example, but other proxy can be used in a similar fashion.
44+
45+### example 1 root, multiple read-only users
46+
47+This sample Caddyfile will
48+ + enable https on the domain myserver.com (http will be automatically redirected to https)
49+ + password protect the access
50+ + route the root user requests to 1 wormhole instance
51+ + route user1 and user2 requests to a readonly wormhole instance
52+
53+<details>
54+ <summary>Legacy Caddy v1 Caddyfile</summary>
55+
56+ ```sh
57+ myserver.com
58+
59+ # proxy regular and read only instance
60+ proxy / 127.0.0.1:8001
61+ proxy /ro 127.0.0.1:8002 { without /ro }
62+
63+ # reroute non-root user to read-only
64+ # cm9... is the output of `printf "root:password" | base64`
65+ rewrite {
66+ if {>Authorization} not "Basic cm9vdDpwYXNzd29yZA=="
67+ to /ro/{path}
68+ }
69+
70+ # gate access
71+ basicauth / root password
72+ basicauth / ro_user1 passworduser1
73+ basicauth / ro_user2 passworduser2
74+ ```
75+</details>
76+
77+Caddy v2 Caddyfile
78+
79+```sh
80+myserver.com
81+
82+# gate access
83+basic_auth {
84+ root $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
85+ ro_user1 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
86+ ro_user2 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
87+}
88+
89+# named matcher for root user
90+@isroot {
91+ vars {http.auth.user.id} root
92+}
93+
94+# proxy regular and read only instance
95+handle @isroot {
96+ reverse_proxy 127.0.0.1:8001
97+}
98+# route non-root user to read only instance
99+handle {
100+ reverse_proxy 127.0.0.1:8002
101+}
102+```
103+
104+then simply start the 2 wormhole instances, and caddy
105+
106+```sh
107+# start an instance in readonly
108+% ./wormhole -ro=true -p 8002 ~/folder &
109+
110+# start an instance with access to hidden files
111+% ./wormhole -k=false -p 8001 ~/folder &
112+
113+# start caddy
114+% ./caddy
115+```
116+
117+### example 2 users on 2 different folders
118+
119+This sample Caddyfile will
120+ + enable https on the domain myserver.com (http will be automatically redirected to https)
121+ + password protect the access
122+ + route user1 to own folder
123+ + route user2 to own folder
124+ + share a folder between 2 users with a symlink
125+
126+<details>
127+ <summary>Legacy Caddy v1 Caddyfile</summary>
128+
129+ ```sh
130+ myserver.com
131+
132+ proxy /user1 127.0.0.1:8001 { without /user1 }
133+ proxy /user2 127.0.0.1:8002 { without /user2 }
134+
135+ basicauth / user1 passworduser1
136+ basicauth / user2 passworduser2
137+
138+ rewrite {
139+ if {>Authorization} is "Basic dXNlcjE6cGFzc3dvcmR1c2VyMQ=="
140+ to /user1/{path}
141+ }
142+
143+ rewrite {
144+ if {>Authorization} is "Basic dXNlcjI6cGFzc3dvcmR1c2VyMg=="
145+ to /user2/{path}
146+ }
147+ ```
148+</details>
149+
150+Caddy v2 Caddyfile
151+
152+```sh
153+myserver.com
154+
155+basic_auth {
156+ user1 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
157+ user2 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
158+}
159+
160+@user1auth {
161+ vars {http.auth.user.id} user1
162+}
163+handle @user1auth {
164+ reverse_proxy 127.0.0.1:8001
165+}
166+
167+@user2auth {
168+ vars {http.auth.user.id} user2
169+}
170+handle @user2auth {
171+ reverse_proxy 127.0.0.1:8002
172+}
173+```
174+
175+start 2 wormhole instances, and caddy
176+
177+```sh
178+# create symlink to share folder between 2 users
179+% ln -s /path/shared test/user1
180+% ln -s /path/shared test/user2
181+
182+# start wormhole & caddy
183+% ./wormhole -p 8001 -symlinks=true test/user1 &
184+% ./wormhole -p 8002 -symlinks=true test/user2 &
185+% ./caddy
186+```
187+
188+## nginx setup
189+
190+In order to allow for larger uploads, it's recommended to increase the maximum body size on your nginx config :
191+
192+```
193+# increase maximum request size
194+client_max_body_size 100M;
195+```
support/service+13−0
@@ -0,0 +1,13 @@
1+[Unit]
2+Description=Wormhole service for mytestfolder
3+Wants=network-online.target
4+After=network-online.target
5+
6+# note: absolute paths are necessary here
7+[Service]
8+ExecStart=/usr/bin/wormhole /home/joe/mytestfolder
9+Restart=always
10+RestartSec=30
11+
12+[Install]
13+WantedBy=multi-user.target
test-fixture/.some-hidden-file+1−0
@@ -0,0 +1 @@
1+hidden !
test-fixture/.testhidden+1−0
@@ -0,0 +1 @@
1+test
test-fixture/404.html+1−0
@@ -0,0 +1 @@
1+<h1>404</h1>
test-fixture/404.html.gz+0−0

Binary file not shown.

test-fixture/b.txt+1−0
@@ -0,0 +1 @@
1+B!!!
test-fixture/compress/foo.js+1−0
@@ -0,0 +1 @@
1+exports.foo = 'baz'
test-fixture/compress/foo.js.gz+0−0

Binary file not shown.

test-fixture/compress/foo_2.js+1−0
@@ -0,0 +1 @@
1+exports.foo = 'baz'
test-fixture/compress/hols-sym+1−0
@@ -0,0 +1 @@
1+../hols
test-fixture/curimit@gmail.com (40%)/test.html+1−0
@@ -0,0 +1 @@
1+index!!!
test-fixture/custom_mime_type.types+3−0
@@ -0,0 +1,3 @@
1+# This file is an example of the Apache .types file format for describing mime-types.
2+# Other example: http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
3+application/foo opml
test-fixture/ext/somefile.blank+0−0

Empty file added.

test-fixture/ext/somefile.code+0−0

Empty file added.

test-fixture/ext/somefile.db+0−0

Empty file added.

test-fixture/ext/somefile.doc+0−0

Empty file added.

test-fixture/ext/somefile.epub+0−0

Empty file added.

test-fixture/ext/somefile.gif+0−0

Empty file added.

test-fixture/ext/somefile.mp3+0−0

Empty file added.

test-fixture/ext/somefile.mp4+0−0

Empty file added.

test-fixture/ext/somefile.pdf+0−0

Empty file added.

test-fixture/ext/somefile.ppt+0−0

Empty file added.

test-fixture/ext/somefile.srt+0−0

Empty file added.

test-fixture/ext/somefile.xls+0−0

Empty file added.

test-fixture/ext/somefile.zip+0−0

Empty file added.

test-fixture/fancy-path/a+1−0
@@ -0,0 +1 @@
1+fancy!
test-fixture/gzip/fake_ecstatic+1−0
@@ -0,0 +1 @@
1+ecstatic
test-fixture/gzip/fake_ecstatic.gz+0−0

Empty file added.

test-fixture/gzip/real_ecstatic+1−0
@@ -0,0 +1 @@
1+ecstatic
test-fixture/hols/.hidden-folder/some-file+1−0
@@ -0,0 +1 @@
1+I'm hidden by my directory!
test-fixture/hols/c.js+1−0
@@ -0,0 +1 @@
1+console.log('C!!!')
test-fixture/hols/elephant-1822636_1920.jpg+0−0

Binary file not shown.

test-fixture/hols/glasgow.jpg+0−0

Binary file not shown.

test-fixture/hols/scotland-1761292_1920.jpg+0−0

Binary file not shown.

test-fixture/show-dir$$href_encoding$$/aname+aplus.txt+4−0
@@ -0,0 +1,4 @@
1+It should generate an href of <a href="/show-dir-href-encoding/aname+aplus.txt">..
2+
3+http://stackoverflow.com/a/1006074/147079
4+http://tools.ietf.org/html/rfc1738
test-fixture/subdir/e.html+1−0
@@ -0,0 +1 @@
1+<b>e!!</b>
test-fixture/subdir_with space/file_with space.html+1−0
@@ -0,0 +1 @@
1+<b>spacious!!</b>
test-fixture/中文/檔案.html+1−0
@@ -0,0 +1 @@
1+<b>file!!</b>
ui/favicon.svg+1−0
@@ -0,0 +1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 180 180"><image width="180" height="180" href="data:image/png;base64,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"/></svg>
ui/readme.md+4−0
@@ -0,0 +1,4 @@
1+# wormhole UI
2+
3+Plain JS, CSS, and HTML for the file browser. No npm. No bundler.
4+The Go server embeds these files at build time.
ui/script.js+925−0
@@ -0,0 +1,925 @@
1+/* eslint-env browser */
2+/* eslint-disable no-multi-str */
3+
4+function cancelDefault (e) {
5+ e.preventDefault()
6+ e.stopPropagation()
7+}
8+
9+const warningMsg = () => 'Leaving will interrupt transfer?\n'
10+const rmMsg = () => !confirm('Remove file?\n')
11+const ensureMove = () => !confirm('move items?')
12+const isRo = () => window.ro
13+
14+// DOM elements
15+const upBarName = document.getElementById('upBarName')
16+const upBarPc = document.getElementById('upBarPc')
17+const upGrid = document.getElementById('drop-grid')
18+const pics = document.getElementById('pics')
19+const picsHolder = document.getElementById('picsHolder')
20+const video = document.getElementById('video')
21+const videoHolder = document.getElementById('videoHolder')
22+const manualUpload = document.getElementById('clickupload')
23+const pdf = document.getElementById('pdf')
24+const help = document.getElementById('help')
25+const okBadge = document.getElementById('ok')
26+const sadBadge = document.getElementById('sad')
27+const pageTitle = document.head.querySelector('title')
28+const pageH1 = document.body.querySelector('h1')
29+const editor = document.getElementById('text-editor')
30+const crossIcon = document.getElementById('quitAll')
31+const toast = document.getElementById('toast')
32+const table = document.getElementById('linkTable')
33+const helpMsg = document.getElementById('help_message')
34+
35+const transparentPixel = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
36+
37+// helpers
38+let allA
39+let imgsIndex
40+let allImgs
41+const decode = a => decodeURIComponent(a).replace(location.origin, '')
42+const getArrowSelected = () => document.querySelector('.arrow-selected')
43+const getASelected = () => !getArrowSelected() ? false : getArrowSelected().parentElement.parentElement.querySelectorAll('a')[0]
44+const prependPath = a => a.startsWith('/') ? a : decodeURI(location.pathname) + a
45+const prevent = e => e.preventDefault()
46+const flicker = w => w.classList.remove('runFade') || void w.offsetWidth || w.classList.add('runFade') // eslint-disable-line
47+
48+const encodeURIHash = e => encodeURI(e).replaceAll('#', '%23')
49+
50+// Manual upload
51+manualUpload.addEventListener('change', () => Array.from(manualUpload.files).forEach(f => isDupe(f.name) || postFile(f, '/' + f.name)), false)
52+
53+// Soft nav
54+async function browseTo (href, flickerDone, skipHistory) {
55+ try {
56+ const r = await fetch(href, { credentials: 'include' })
57+ const t = await r.text()
58+ const parsed = new DOMParser().parseFromString(t, 'text/html')
59+
60+ table.innerHTML = parsed.getElementById('linkTable').innerHTML
61+ const title = parsed.head.querySelector('title').innerText
62+ // check if is current path - if so skip following
63+ if (pageTitle.innerText !== title) {
64+ if (!skipHistory) {
65+ const escaped = encodeURIHash(window.extraPath + title)
66+ history.pushState({}, '', escaped)
67+ }
68+ pageTitle.innerText = title
69+ pageH1.innerText = '.' + title
70+ setTitle()
71+ }
72+
73+ init()
74+ if (flickerDone) flicker(okBadge)
75+ } catch (error) {
76+ flicker(sadBadge)
77+ }
78+}
79+
80+window.onClickLink = e => {
81+ const a = e ? e.target : getASelected()
82+
83+ if (e) {
84+ setCursorTo(e.target.innerText)
85+ }
86+
87+ // always force download if ctrl pressed (also covers zipping folders)
88+ if (e && e.ctrlKey) {
89+ dl(a)
90+ return false
91+ }
92+
93+ // follow dirs
94+ if (isFolder(a)) {
95+ browseTo(a.href)
96+ return false
97+ // enable notepad if relevant
98+ } else if (!window.ro && isTextFile(a.innerText) && !isEditorMode()) {
99+ padOn(a)
100+ return false
101+ // toggle picture carousel
102+ } else if (isPic(a.href) && !isPicMode()) {
103+ picsOn(a.href)
104+ return false
105+ // toggle videos mode
106+ } else if (isVideo(a.href) && !isVideoMode()) {
107+ videoOn(a.href)
108+ return false
109+ // let html be displayed naturally
110+ } else if (a.innerText.endsWith('.html')) {
111+ return true
112+ } else if (isPdf(a.href)) {
113+ openPDF(a.href)
114+ return false
115+ }
116+
117+ // else just force download
118+ dl(a)
119+ return false
120+}
121+
122+let softStatePushed
123+function pushSoftState (d) {
124+ if (softStatePushed) { return }
125+ softStatePushed = true
126+ history.pushState({}, '', encodeURIHash(d))
127+}
128+
129+const refresh = () => browseTo(location.href, true)
130+
131+const softPrev = () => history.replaceState({}, '', location.href.split('/').slice(0, -1).join('/') + '/')
132+
133+const isAtExtraPath = url => location.origin + window.extraPath + '/../' === url
134+const prevPage = (url, skipHistory) => window.quitAll() || isAtExtraPath(url) || browseTo(url, false, skipHistory)
135+
136+window.onpopstate = () => prevPage(location.href, true)
137+
138+// RPC
139+function upload (id, what, path, cbDone, cbErr, cbUpdate) {
140+ const xhr = new XMLHttpRequest()
141+ xhr.open('POST', location.origin + window.extraPath + '/post')
142+ xhr.setRequestHeader('wormhole-path', path)
143+ xhr.upload.addEventListener('load', cbDone)
144+ xhr.upload.addEventListener('progress', cbUpdate)
145+ xhr.upload.addEventListener('error', cbErr)
146+ xhr.upload.id = id
147+ xhr.send(what)
148+}
149+
150+function rpc (call, args, cb) {
151+ console.log('RPC', call, args)
152+ const xhr = new XMLHttpRequest()
153+ xhr.open('POST', location.origin + window.extraPath + '/rpc')
154+ xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
155+ xhr.send(JSON.stringify({ call, args }))
156+ xhr.onload = cb
157+ xhr.onerror = () => flicker(sadBadge)
158+}
159+
160+const mkdirCall = (path, cb) => rpc('mkdirp', [prependPath(path)], cb)
161+const rmCall = (path1, cb) => rpc('rm', [prependPath(path1)], cb)
162+const mvCall = (path1, path2, cb) => rpc('mv', [path1, path2], cb)
163+const sumCall = (path, type, cb) => rpc('sum', [prependPath(path), type], cb)
164+
165+// File upload
166+let totalDone = 0
167+let totalUploads = 0
168+let totalUploadsSize = 0
169+let totalUploadedSize = []
170+
171+const dupe = test => allA.find(a => a.innerHTML.replace('/', '') === test)
172+const isDupe = t => dupe(t) ? alert(t + ' already already exists') || true : false
173+
174+function shouldRefresh () {
175+ totalDone += 1
176+ if (totalUploads === totalDone) {
177+ window.onbeforeunload = null
178+ console.log('done uploading ' + totalDone + ' files')
179+ totalDone = 0
180+ totalUploads = 0
181+ totalUploadsSize = 0
182+ totalUploadedSize = []
183+ upBarPc.style.display = upBarName.style.display = 'none'
184+ table.classList.remove('uploading-table')
185+ setTimeout(refresh, 200)
186+ }
187+}
188+
189+function updatePercent (ev) {
190+ totalUploadedSize[ev.target.id] = ev.loaded
191+ const ttlDone = totalUploadedSize.reduce((s, x) => s + x)
192+ const pc = Math.floor(100 * ttlDone / totalUploadsSize) + '%'
193+ upBarPc.innerText = pc
194+ upBarPc.style.width = pc
195+}
196+
197+function postFile (file, path) {
198+ if (window.ro) return
199+ path = decodeURI(location.pathname).slice(0, -1) + path
200+ window.onbeforeunload = warningMsg
201+
202+ table.classList.add('uploading-table')
203+ upBarPc.style.display = upBarName.style.display = 'block'
204+ totalUploads += 1
205+ totalUploadsSize += file.size
206+ upBarName.innerText = totalUploads > 1 ? totalUploads + ' files' : file.name
207+
208+ const formData = new FormData()
209+ formData.append(file.name, file)
210+ upload(totalUploads, formData, encodeURIComponent(path), shouldRefresh, null, updatePercent)
211+}
212+
213+const parseDomFolder = f => f.createReader().readEntries(e => e.forEach(i => parseDomItem(i)))
214+
215+function parseDomItem (domFile, shoudCheckDupes) {
216+ if (shoudCheckDupes && isDupe(domFile.name)) {
217+ return
218+ }
219+ if (domFile.isFile) {
220+ domFile.file(f => postFile(f, domFile.fullPath))
221+ } else {
222+ // remove absolute path
223+ const f = domFile.fullPath.startsWith('/') ? domFile.fullPath.slice(1) : domFile.fullPath
224+ mkdirCall(f, () => parseDomFolder(domFile))
225+ }
226+}
227+
228+function pushEntry (entry) {
229+ if (!entry.webkitGetAsEntry && !entry.getAsEntry) {
230+ return alert('Unsupported browser ! Please update to chrome/firefox.')
231+ } else {
232+ entry = entry.webkitGetAsEntry() || entry.getAsEntry()
233+ }
234+
235+ parseDomItem(entry, true)
236+}
237+
238+window.titleClick = function (e) {
239+ const p = Array.from(document.querySelector('h1').childNodes).map(k => k.innerText)
240+ const i = p.findIndex(s => s === e.target.innerText)
241+ const dst = p.slice(0, i + 1).join('').slice(1)
242+ const target = location.origin + window.extraPath + encodeURIHash(dst)
243+ browseTo(target, false)
244+}
245+
246+// Move files and folders
247+const isFolder = e => e && e.href && e.innerText.endsWith('/')
248+
249+const setBackgroundLinks = t => { t.classList.add('highlight') }
250+
251+const getLink = () => document.querySelector('.highlight') || {}
252+
253+const resetBackgroundLinks = () => { try { getLink().classList.remove('highlight') } catch(e) { /* */ } } // eslint-disable-line
254+
255+// Not the nicest - sometimes, upon hover, firefox reports nodeName === '#text', and chrome reports nodeName === 'A'...
256+const getClosestRow = t => t.nodeName === '#text' ? t.parentElement.parentElement : t.nodeName === 'A' ? t.parentElement : t
257+
258+let draggingSrc
259+
260+upGrid.ondragend = upGrid.ondragexit = upGrid.ondragleave = e => {
261+ cancelDefault(e)
262+ upGrid.style.display = 'none'
263+}
264+
265+// Handle hover
266+document.ondragenter = e => {
267+ if (isEditorMode() || isPicMode() || window.ro) { return }
268+ cancelDefault(e)
269+ resetBackgroundLinks()
270+
271+ // Display upload grid when uploading new elements
272+ if (!draggingSrc) {
273+ upGrid.style.display = 'flex'
274+ e.dataTransfer.dropEffect = 'copy'
275+ // Or highlight entry if drag and drop
276+ } else if (draggingSrc) {
277+ const t = getClosestRow(e.target)
278+ isFolder(t.firstChild) && setBackgroundLinks(t)
279+ }
280+}
281+
282+document.ondragstart = e => { draggingSrc = e.target.innerHTML }
283+
284+document.ondragend = e => resetBackgroundLinks()
285+
286+document.ondragover = e => {
287+ cancelDefault(e)
288+ return false
289+}
290+
291+// Handle drop
292+document.ondrop = e => {
293+ if (window.ro) return
294+
295+ cancelDefault(e)
296+ upGrid.style.display = 'none'
297+ const t = getLink().firstChild
298+
299+ // move to a folder
300+ if (draggingSrc && t) {
301+ const dest = t.innerHTML + draggingSrc
302+ ensureMove() || mvCall(prependPath(draggingSrc), prependPath(dest), refresh)
303+ // ... or upload
304+ } else if (e.dataTransfer.items.length) {
305+ Array.from(e.dataTransfer.items).forEach(pushEntry)
306+ }
307+
308+ resetBackgroundLinks()
309+ draggingSrc = null
310+ return false
311+}
312+
313+// Notepad
314+const isEditorMode = () => editor.style.display === 'block'
315+const textTypes = ['.txt', '.rtf', '.md', '.markdown', '.log', '.yaml', '.yml']
316+const isTextFile = src => src && textTypes.find(type => src.toLocaleLowerCase().includes(type))
317+let fileEdited
318+
319+function saveText (quitting) {
320+ const formData = new FormData()
321+ formData.append(fileEdited, editor.value)
322+ const path = encodeURIComponent(decodeURI(location.pathname) + fileEdited)
323+ upload(0, formData, path, () => {
324+ toast.style.display = 'none'
325+ if (!quitting) return
326+ clearInterval(window.padTimer)
327+ window.onbeforeunload = null
328+ resetView()
329+ softPrev()
330+ refresh()
331+ }, () => {
332+ toast.style.display = 'block'
333+ if (!quitting) return
334+ alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
335+ })
336+}
337+
338+function padOff () {
339+ if (!isEditorMode()) { return }
340+ saveText(true)
341+ return true
342+}
343+
344+async function padOn (a) {
345+ if (a) {
346+ try {
347+ fileEdited = a.innerHTML
348+ const f = await fetch(a.href, {
349+ credentials: 'include',
350+ headers: new Headers({ pragma: 'no-cache', 'cache-control': 'no-cache' })
351+ })
352+ editor.value = await f.text()
353+ } catch (error) {
354+ return alert('cant read file')
355+ }
356+ } else {
357+ fileEdited = prompt('new filename', '')
358+ if (!fileEdited) { return }
359+ fileEdited = isTextFile(fileEdited) ? fileEdited : fileEdited + '.txt'
360+ if (isDupe(fileEdited)) { return }
361+ editor.value = ''
362+ }
363+
364+ console.log('editing file', fileEdited)
365+ setCursorTo(fileEdited)
366+ editor.style.display = crossIcon.style.display = 'block'
367+ table.style.display = 'none'
368+ editor.focus()
369+ window.onbeforeunload = warningMsg
370+ window.padTimer = setInterval(saveText, 5000)
371+ pushSoftState('?editor=' + fileEdited)
372+}
373+
374+window.displayPad = padOn
375+
376+// quit pictures or editor
377+function resetView () {
378+ softStatePushed = false
379+ table.style.display = 'table'
380+ picsHolder.src = transparentPixel
381+ videoHolder.src = ''
382+ pdf.innerHTML = ''
383+ editor.style.display = pics.style.display = video.style.display = pdf.style.display = crossIcon.style.display = 'none'
384+ scrollToArrow()
385+}
386+
387+window.quitAll = () => helpOff() || sumsOff() || picsOff() || videosOff() || padOff() || pdfOff()
388+
389+// Mkdir icon
390+window.mkdirBtn = function () {
391+ const folder = prompt('new folder name', '')
392+ if (folder && !isDupe(folder)) {
393+ mkdirCall(folder, refresh)
394+ }
395+}
396+
397+// Icon click handler
398+const getBtnA = e => e.target.closest('tr').querySelector('a')
399+
400+window.rm = e => {
401+ if (window.ro) return true
402+ clearTimeout(window.clickToken)
403+ const target = e.key ? getASelected() : getBtnA(e)
404+ if (target.innerText === '../') return
405+ if (rmMsg()) return
406+
407+ moveArrow()
408+ rmCall(decode(target.href), refresh)
409+}
410+
411+window.rename = (e, commit) => {
412+ if (window.ro) return true
413+ clearTimeout(window.clickToken)
414+
415+ if (!commit) {
416+ window.clickToken = setTimeout(window.rename, 300, e, true)
417+ return
418+ }
419+
420+ const target = e.key ? getASelected() : getBtnA(e)
421+ if (target.innerText === '../') return
422+ const chg = prompt('rename to', target.innerText)
423+ if (chg && !isDupe(chg)) {
424+ mvCall(prependPath(target.innerText), prependPath(chg), refresh)
425+ }
426+}
427+
428+function aboveBelowRightin (el) {
429+ const itemPos = el.getBoundingClientRect()
430+ return itemPos.top < 0 ? -1 : itemPos.bottom > window.innerHeight ? 1 : 0
431+}
432+
433+function scrollToArrow () {
434+ const el = getASelected()
435+ while (1) {
436+ const pos = aboveBelowRightin(el)
437+ if (pos === -1) {
438+ scrollBy(0, -300)
439+ } else if (pos === 1) {
440+ scrollBy(0, 300)
441+ } else {
442+ break
443+ }
444+ }
445+}
446+
447+function clearArrowSelected () {
448+ const arr = getArrowSelected()
449+ if (!arr) { return }
450+ arr.classList.remove('arrow-selected')
451+}
452+
453+window.setCursorTo = setCursorTo
454+function setCursorTo (where) {
455+ if (!where) return false
456+ clearArrowSelected()
457+ let a = allA.find(el => el.innerText === where || el.innerText === where + '/')
458+
459+ if (!a) {
460+ if (allA[0].innerText === '../') {
461+ a = allA[1] || allA[0]
462+ } else {
463+ a = allA[0]
464+ }
465+ }
466+
467+ const icon = a.parentElement.parentElement.querySelectorAll('.arrow-icon')[0]
468+ icon.classList.add('arrow-selected')
469+ scrollToArrow()
470+ storeArrow(where)
471+ return true
472+}
473+
474+function moveArrow (down) {
475+ const all = Array.from(document.querySelectorAll('.arrow-icon'))
476+ let i = all.findIndex(el => el.classList.contains('arrow-selected'))
477+
478+ clearArrowSelected()
479+
480+ if (down) {
481+ i = all[i + 1] ? i + 1 : 0
482+ } else {
483+ i = all[i - 1] ? i - 1 : all.length - 1
484+ }
485+
486+ all[i].classList.add('arrow-selected')
487+ storeArrow(getASelected().innerText)
488+ scrollToArrow()
489+}
490+
491+const storeArrow = src => localStorage.setItem('last-selected' + window.extraPath + location.pathname, src)
492+
493+const isTop = () => window.scrollY === 0
494+const isBottom = () => (window.innerHeight + window.scrollY) >= document.body.offsetHeight
495+const hasScroll = () => table.clientHeight > window.innerHeight
496+
497+function movePage (up) {
498+ const current = getASelected().href
499+
500+ if (!hasScroll()) return
501+
502+ if (!up) {
503+ const i = allA.findIndex(e => aboveBelowRightin(e) === 1)
504+ if (isTop() && current !== allA[i - 1].href) {
505+ return setCursorTo(allA[i - 1].innerText)
506+ } else if (isBottom() && current !== allA[allA.length - 1].href) {
507+ return setCursorTo(allA[allA.length - 1].innerText)
508+ }
509+
510+ if (!allA[i - 1]) return
511+ setCursorTo(allA[i - 1].innerText)
512+ scrollBy(0, window.innerHeight - 100)
513+ } else {
514+ const i = allA.findIndex(e => aboveBelowRightin(e) === 0)
515+ if (isTop() && current !== allA[0].href) {
516+ return setCursorTo(allA[0].innerText)
517+ } else if (isBottom() && current !== allA[i].href) {
518+ return setCursorTo(allA[i].innerText)
519+ }
520+
521+ scrollBy(0, -(window.innerHeight - 100))
522+ setCursorTo(allA[i].innerText)
523+ }
524+}
525+
526+// Pictures carousel
527+const picTypes = ['.jpg', '.jpeg', '.png', '.gif']
528+const isPic = src => src && picTypes.find(type => src.toLocaleLowerCase().includes(type))
529+const isPicMode = () => pics.style.display === 'flex'
530+window.picsNav = () => picsNav(true)
531+
532+function setImage () {
533+ const src = allImgs[imgsIndex]
534+ picsHolder.src = src
535+ const name = src.split('/').pop()
536+ setCursorTo(decodeURI(name))
537+ history.replaceState({}, '', encodeURIHash(name))
538+}
539+
540+function picsOn (href) {
541+ imgsIndex = allImgs.findIndex(el => el.includes(href))
542+ setImage()
543+ table.style.display = 'none'
544+ crossIcon.style.display = 'block'
545+ pics.style.display = 'flex'
546+ const name = href.split('/').pop()
547+ pushSoftState(name)
548+ return true
549+}
550+
551+function picsOff () {
552+ if (!isPicMode()) { return }
553+ resetView()
554+ softPrev()
555+ return true
556+}
557+
558+function picsNav (down) {
559+ if (!isPicMode()) { return false }
560+
561+ if (down) {
562+ imgsIndex = allImgs[imgsIndex + 1] ? imgsIndex + 1 : 0
563+ } else {
564+ imgsIndex = allImgs[imgsIndex - 1] ? imgsIndex - 1 : allImgs.length - 1
565+ }
566+
567+ setImage()
568+ return true
569+}
570+
571+let picsTouchStart = 0
572+
573+picsHolder.addEventListener('touchstart', e => {
574+ picsTouchStart = e.changedTouches[0].screenX
575+}, false)
576+
577+picsHolder.addEventListener('touchend', e => {
578+ if (e.changedTouches[0].screenX < picsTouchStart) {
579+ picsNav(true)
580+ } else if (e.changedTouches[0].screenX > picsTouchStart) {
581+ picsNav(false)
582+ }
583+}, false)
584+
585+// Video player
586+const videosTypes = ['.mp4', '.webm', '.ogv', '.ogg', '.mp3', '.flac', '.wav']
587+const isVideo = src => src && videosTypes.find(type => src.toLocaleLowerCase().includes(type))
588+const isVideoMode = () => video.style.display === 'flex'
589+const videoFs = () => video.requestFullscreen()
590+const videoFf = future => { videoHolder.currentTime += future ? 10 : -10 }
591+const videoSound = up => { videoHolder.volume += up ? 0.1 : -0.1 }
592+videoHolder.oncanplay = () => videoHolder.play()
593+
594+async function videoOn (src) {
595+ const name = src.split('/').pop()
596+ table.style.display = 'none'
597+ crossIcon.style.display = 'block'
598+ video.style.display = 'flex'
599+ videoHolder.pause()
600+
601+ const time = localStorage.getItem('video-time' + src)
602+ videoHolder.currentTime = parseInt(time) || 0
603+
604+ videoHolder.src = src
605+ pushSoftState(decodeURI(name))
606+ return true
607+}
608+
609+function videosOff () {
610+ if (!isVideoMode()) { return }
611+ localStorage.setItem('video-time' + videoHolder.src, videoHolder.currentTime)
612+ resetView()
613+ softPrev()
614+ return true
615+}
616+
617+window.videodl = function () {
618+ dl(getASelected())
619+}
620+
621+// PDF Viewer
622+const pdfTypes = ['.pdf']
623+const isPdf = src => src && pdfTypes.find(type => src.toLocaleLowerCase().includes(type))
624+const isPdfMode = () => pdf.style.display === 'flex'
625+function openPDF (src) {
626+ const name = src.split('/').pop()
627+ table.style.display = 'none'
628+ crossIcon.style.display = 'block'
629+ pdf.style.display = 'flex'
630+ const pdfEmbed = document.createElement('embed')
631+ pdfEmbed.setAttribute('src', src + '#toolbar=0')
632+ pdfEmbed.setAttribute('type', 'application/pdf')
633+ pdf.appendChild(pdfEmbed)
634+ pushSoftState(decodeURI(name))
635+ return false
636+}
637+
638+function pdfOff () {
639+ if (!isPdfMode()) { return }
640+ resetView()
641+ softPrev()
642+ return true
643+}
644+
645+// help
646+const isHelpMode = () => help.style.display === 'block'
647+
648+const helpToggle = () => isHelpMode() ? helpOff() : helpOn()
649+
650+function helpOn () {
651+ help.style.display = 'block'
652+ table.style.display = 'none'
653+}
654+
655+window.helpOff = helpOff
656+function helpOff () {
657+ if (!isHelpMode()) return
658+ help.style.display = 'none'
659+ table.style.display = 'table'
660+ return true
661+}
662+
663+// checksums
664+function getSum (type) {
665+ upBarPc.style.display = 'block'
666+ upBarPc.innerText = 'computing checksum...'
667+ upBarPc.style.width = '100%'
668+ sumsOff()
669+ sumCall(getASelected().innerText, type, loaded => {
670+ navigator.clipboard.writeText(loaded.target.responseText)
671+ upBarPc.style.display = 'none'
672+ flicker(okBadge)
673+ })
674+}
675+
676+const isSumsMode = () => sums.style.display === 'block'
677+
678+const sumsToggle = () => isSumsMode() ? sumsOff() : sumsOn()
679+
680+function sumsOn () {
681+ if (isFolder(getASelected())) {
682+ alert('cannot checksum a directory')
683+ return
684+ }
685+ sums.style.display = 'block'
686+ table.style.display = 'none'
687+}
688+
689+window.sumsOff = sumsOff
690+function sumsOff () {
691+ if (!isSumsMode()) return
692+ sums.style.display = 'none'
693+ table.style.display = 'table'
694+ return true
695+}
696+
697+// Paste handler
698+const cuts = []
699+function onPaste () {
700+ if (!cuts.length) { return refresh() }
701+ const a = getASelected()
702+ const root = cuts.pop()
703+ const filename = root.split('/').pop()
704+ const pwd = decodeURIComponent(location.pathname)
705+ const dest = isFolder(a) ? pwd + a.innerHTML : pwd
706+ mvCall(root, dest + filename, onPaste)
707+}
708+
709+function onCut () {
710+ const a = getASelected()
711+ a.classList.add('linkSelected')
712+ cuts.push(prependPath(decode(a.href)))
713+}
714+
715+function dl (a) {
716+ const orig = a.onclick
717+ a.onclick = ''
718+
719+ // download as zip if folder
720+ if (isFolder(a)) {
721+ const loc = a.href
722+ a.href = window.extraPath + '/zip?zipPath=' + encodeURIComponent(prependPath(a.innerText)) + '&zipName=' + encodeURIComponent(a.innerText.slice(0, -1))
723+ a.click()
724+ a.href = loc
725+ } else {
726+ a.download = a.innerText
727+ a.click()
728+ a.download = ''
729+ }
730+
731+ a.onclick = orig
732+}
733+
734+// Kb handler
735+let typedPath = ''
736+let typedToken = null
737+
738+function cpPath () {
739+ const t = document.createElement('textarea')
740+ t.value = getASelected().href
741+ document.body.appendChild(t)
742+ t.select()
743+ document.execCommand('copy')
744+ document.body.removeChild(t)
745+}
746+
747+document.body.addEventListener('keydown', e => {
748+ if (e.code === 'Escape') {
749+ return resetBackgroundLinks() || window.quitAll()
750+ }
751+
752+ if (isHelpMode()) { return prevent(e) || window.quitAll() }
753+
754+ if (isEditorMode()) { return }
755+
756+ if (isPicMode()) {
757+ switch (e.code) {
758+ case 'ArrowLeft':
759+ case 'ArrowUp':
760+ return prevent(e) || picsNav(false)
761+
762+ case 'Enter':
763+ case 'Tab':
764+ case 'ArrowRight':
765+ case 'ArrowDown':
766+ return prevent(e) || picsNav(true)
767+ }
768+ return
769+ }
770+
771+ if (isVideoMode()) {
772+ switch (e.code) {
773+ case 'ArrowDown':
774+ case 'ArrowUp':
775+ return prevent(e) || videoSound(e.code === 'ArrowUp')
776+
777+ case 'ArrowLeft':
778+ case 'ArrowRight':
779+ return prevent(e) || videoFf(e.code === 'ArrowRight')
780+
781+ case 'KeyF':
782+ return prevent(e) || videoFs()
783+ }
784+ return
785+ }
786+
787+ // Modifier keys
788+ if (!e.shiftKey) {
789+ if (e.ctrlKey || e.metaKey) {
790+ switch (e.code) {
791+ case 'KeyC':
792+ return prevent(e) || isRo() || cpPath()
793+
794+ case 'KeyH':
795+ return prevent(e) || isRo() || helpToggle()
796+
797+ case 'KeyZ':
798+ return prevent(e) || isRo() || sumsToggle()
799+
800+ case 'KeyX':
801+ return prevent(e) || isRo() || onCut()
802+
803+ case 'KeyR':
804+ return prevent(e) || refresh()
805+
806+ case 'KeyV':
807+ return prevent(e) || isRo() || ensureMove() || onPaste()
808+
809+ case 'Backspace':
810+ return prevent(e) || isRo() || window.rm(e)
811+
812+ case 'KeyE':
813+ return prevent(e) || isRo() || window.rename(e)
814+
815+ case 'KeyM':
816+ return prevent(e) || isRo() || window.mkdirBtn()
817+
818+ case 'KeyU':
819+ return prevent(e) || isRo() || manualUpload.click()
820+
821+ case 'Enter':
822+ case 'ArrowRight':
823+ return prevent(e) || dl(getASelected())
824+ }
825+ } else if (isSumsMode()) {
826+ switch (e.code) {
827+ case 'Digit1':
828+ return prevent(e) || isRo() || getSum('sha1')
829+
830+ case 'Digit2':
831+ return prevent(e) || isRo() || getSum('sha256')
832+
833+ case 'Digit3':
834+ return prevent(e) || isRo() || getSum('sha512')
835+
836+ case 'Digit5':
837+ return prevent(e) || isRo() || getSum('md5')
838+ }
839+ }
840+ } else {
841+ // Workaround Firefox requirement for transient activation
842+ // https://developer.mozilla.org/en-US/docs/Web/Security/User_activation
843+ // Firefox requires user interaction (that is not reserved by the user agent)
844+ // before a file picker can be displayed. This means that ctrl/meta are not
845+ // usable as modifiers until the user clicks the page or presses another
846+ // non-modifier key. To work around this, the shift key can be used, instead.
847+ if (e.code == 'KeyU') {
848+ return prevent(e) || isRo() || manualUpload.click()
849+ }
850+ }
851+
852+ switch (e.code) {
853+ case 'Tab':
854+ case 'ArrowDown':
855+ return prevent(e) || moveArrow(true)
856+
857+ case 'ArrowUp':
858+ return prevent(e) || moveArrow(false)
859+
860+ case 'Enter':
861+ case 'ArrowRight':
862+ return prevent(e) || window.onClickLink()
863+
864+ case 'ArrowLeft':
865+ return prevent(e) || prevPage(location.href + '../')
866+
867+ case 'PageDown':
868+ case 'PageUp':
869+ return prevent(e) || movePage(e.key === 'PageUp')
870+
871+ case 'Space':
872+ return prevent(e) || movePage(e.shiftKey)
873+ }
874+
875+ // text search
876+ if (e.code.includes('Key') && !e.ctrlKey && !e.metaKey) {
877+ typedPath += e.code.replace('Key', '').toLocaleLowerCase()
878+ clearTimeout(typedToken)
879+ typedToken = setTimeout(() => { typedPath = '' }, 1000)
880+
881+ const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(typedPath)) || allA.find(el => el.innerText.toLocaleLowerCase().includes(typedPath))
882+ if (!a) { return }
883+ setCursorTo(a.innerText)
884+ }
885+}, false)
886+
887+function setTitle () {
888+ pageH1.innerHTML = '<span>' + pageH1.innerText.split('/').join('/</span><span>') + '</span>'
889+}
890+
891+function init () {
892+ allA = Array.from(document.querySelectorAll('a.list-links'))
893+ allImgs = allA.map(el => el.href).filter(isPic)
894+ imgsIndex = softStatePushed = 0
895+
896+ const successRestore = setCursorTo(localStorage.getItem('last-selected' + window.extraPath + location.pathname))
897+ if (!successRestore) {
898+ const entries = table.querySelectorAll('.arrow-icon')
899+ entries.length === 1 ? entries[0].classList.add('arrow-selected') : entries[1].classList.add('arrow-selected')
900+ }
901+
902+ setTitle()
903+ scrollToArrow()
904+ console.log('browsed to ' + location.href)
905+
906+ if (cuts.length) {
907+ const match = allA.filter(a => cuts.find(c => c === decode(a.href)))
908+ match.forEach(m => m.classList.add('linkSelected'))
909+ }
910+
911+ // restore editor if was queried
912+ if (location.search.includes('?editor=')) {
913+ const cleanURL = location.href.replace('?editor=', '')
914+ const matchingA = allA.find(a => a.href === cleanURL)
915+ padOn(matchingA)
916+ }
917+
918+ // check if we're at root path
919+ if (location.pathname === window.extraPath + '/') {
920+ helpMsg.style.display = 'block'
921+ } else {
922+ helpMsg.style.display = 'none'
923+ }
924+}
925+init()
ui/style.css+645−0
@@ -0,0 +1,645 @@
1+/* ============================================================================
2+ * FILE: style.css
3+ * ROLE: STYLESHEET
4+ * PURPOSE: Wormhole file browser — krisyotam.com cell-strip design language
5+ * ========================================================================== */
6+
7+/* -- Variables ------------------------------------------------------------- */
8+:root {
9+ --bg: hsl(0 0% 98%);
10+ --bg-muted: hsl(0 0% 96%);
11+ --bg-muted-30: hsla(0,0%,92%,0.3);
12+ --fg: hsl(0 0% 20%);
13+ --fg-muted: hsl(0 0% 50%);
14+ --border: hsl(0 0% 88%);
15+ --hover: hsla(0,0%,92%,0.3);
16+ --highlight: hsl(0 0% 90%);
17+ --accent: hsl(0 0% 10%);
18+ --icon-invert: 0%;
19+ color-scheme: light;
20+}
21+
22+[data-theme="dark"] {
23+ --bg: hsl(0 0% 7%);
24+ --bg-muted: hsl(0 0% 12%);
25+ --bg-muted-30: hsla(0,0%,15%,0.3);
26+ --fg: hsl(0 0% 90%);
27+ --fg-muted: hsl(0 0% 55%);
28+ --border: hsl(0 0% 18%);
29+ --hover: hsla(0,0%,20%,0.3);
30+ --highlight: hsl(0 0% 20%);
31+ --accent: hsl(0 0% 90%);
32+ --icon-invert: 85%;
33+ color-scheme: dark;
34+}
35+
36+@media (prefers-color-scheme: dark) {
37+ :root:not([data-theme="light"]) {
38+ --bg: hsl(0 0% 7%);
39+ --bg-muted: hsl(0 0% 12%);
40+ --bg-muted-30: hsla(0,0%,15%,0.3);
41+ --fg: hsl(0 0% 90%);
42+ --fg-muted: hsl(0 0% 55%);
43+ --border: hsl(0 0% 18%);
44+ --hover: hsla(0,0%,20%,0.3);
45+ --highlight: hsl(0 0% 20%);
46+ --accent: hsl(0 0% 90%);
47+ --icon-invert: 85%;
48+ color-scheme: dark;
49+ }
50+}
51+
52+/* -- Base ------------------------------------------------------------------ */
53+@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap');
54+
55+* { box-sizing: border-box; }
56+
57+::-webkit-scrollbar { background: var(--bg); width: 6px; }
58+::-webkit-scrollbar-thumb { background: var(--border); }
59+
60+html {
61+ color: var(--fg);
62+ background-color: var(--bg);
63+ font-size: 15px;
64+ font-family: 'IBM Plex Sans', -apple-system, Helvetica, Arial, sans-serif;
65+ line-height: 1.65;
66+ overflow-x: hidden;
67+ scrollbar-color: var(--border) var(--bg);
68+ -webkit-tap-highlight-color: transparent;
69+ -webkit-font-smoothing: antialiased;
70+}
71+
72+body {
73+ margin: 0;
74+ padding: 0;
75+ min-height: 100vh;
76+ display: flex;
77+ align-items: center;
78+ justify-content: center;
79+}
80+
81+a {
82+ color: var(--fg);
83+ text-decoration: none;
84+ background-color: transparent !important;
85+ outline: 0;
86+}
87+
88+a:hover { color: var(--accent); }
89+
90+a.linkSelected { border-bottom: 1px solid var(--fg); }
91+
92+div, i { background-size: cover !important; }
93+
94+/* -- Page Container (cell-strip article) ----------------------------------- */
95+#page {
96+ border: 1px solid var(--border);
97+ width: 92vw;
98+ max-width: 960px;
99+ min-height: 60vh;
100+ display: flex;
101+ flex-direction: column;
102+}
103+
104+/* -- Rows ------------------------------------------------------------------ */
105+.row {
106+ display: flex;
107+ align-items: stretch;
108+ border-bottom: 1px solid var(--border);
109+}
110+
111+.row:last-child { border-bottom: none; }
112+
113+/* -- Cells ----------------------------------------------------------------- */
114+.cell {
115+ padding: 8px 12px;
116+}
117+
118+/* -- Header Row ------------------------------------------------------------ */
119+.row-header {
120+ min-height: 52px;
121+}
122+
123+.cell-label {
124+ width: 56px;
125+ display: flex;
126+ align-items: center;
127+ justify-content: center;
128+ border-right: 1px solid var(--border);
129+ background: var(--bg-muted-30);
130+ flex-shrink: 0;
131+ font-family: 'IBM Plex Mono', monospace;
132+ font-size: 10px;
133+ font-weight: 500;
134+ text-transform: uppercase;
135+ letter-spacing: 0.08em;
136+ color: var(--fg-muted);
137+}
138+
139+.cell-path {
140+ flex: 1;
141+ display: flex;
142+ align-items: center;
143+ min-width: 0;
144+ cursor: pointer;
145+}
146+
147+.cell-path h1 {
148+ margin: 0;
149+ font-family: 'IBM Plex Mono', monospace;
150+ font-size: 13px;
151+ font-weight: 500;
152+ color: var(--fg);
153+ letter-spacing: 0.01em;
154+ white-space: nowrap;
155+ overflow: hidden;
156+ text-overflow: ellipsis;
157+ display: block;
158+ width: 100%;
159+}
160+
161+.cell-path h1 > span:hover {
162+ color: var(--fg-muted);
163+}
164+
165+.cell-actions {
166+ display: flex;
167+ align-items: center;
168+ justify-content: center;
169+ border-left: 1px solid var(--border);
170+ background: var(--bg-muted-30);
171+ flex-shrink: 0;
172+ width: 52px;
173+ padding: 0;
174+}
175+
176+/* -- Column Headers -------------------------------------------------------- */
177+.row-colheader {
178+ background: var(--bg-muted-30);
179+ font-family: 'IBM Plex Mono', monospace;
180+ font-size: 10px;
181+ font-weight: 500;
182+ text-transform: uppercase;
183+ letter-spacing: 0.08em;
184+ color: var(--fg-muted);
185+ min-height: 28px;
186+}
187+
188+.cell-col-icon {
189+ width: 56px;
190+ border-right: 1px solid var(--border);
191+ flex-shrink: 0;
192+}
193+
194+.cell-col-size {
195+ width: 72px;
196+ text-align: right;
197+ padding: 6px 12px;
198+ border-right: 1px solid var(--border);
199+ flex-shrink: 0;
200+}
201+
202+.cell-col-name {
203+ flex: 1;
204+ padding: 6px 12px;
205+}
206+
207+/* -- Listing --------------------------------------------------------------- */
208+#listing {
209+ flex: 1;
210+ min-height: 120px;
211+ border-bottom: 1px solid var(--border);
212+}
213+
214+/* -- Table ----------------------------------------------------------------- */
215+table {
216+ width: 100%;
217+ border-collapse: collapse;
218+}
219+
220+tr {
221+ line-height: 40px;
222+ border-bottom: 1px solid var(--border);
223+ transition: background-color 0.1s;
224+}
225+
226+
227+tr:hover { background-color: var(--hover); }
228+
229+td.iconRow {
230+ text-align: center;
231+ width: 56px;
232+ padding: 0;
233+ border-right: 1px solid var(--border);
234+}
235+
236+.icon {
237+ height: 18px;
238+ width: 18px;
239+ display: block;
240+ cursor: pointer;
241+ filter: grayscale(1) invert(var(--icon-invert));
242+ opacity: 0.5;
243+ margin: 0 auto;
244+}
245+
246+td.file-size {
247+ text-align: right;
248+ width: 72px;
249+ padding: 0 12px;
250+ border-right: 1px solid var(--border);
251+ font-family: 'IBM Plex Mono', monospace;
252+ font-size: 12px;
253+ color: var(--fg-muted);
254+ white-space: nowrap;
255+}
256+
257+td.file-size code { font-family: inherit; }
258+
259+.arrow {
260+ height: 14px;
261+ width: 14px;
262+ text-align: center;
263+ padding-left: 8px;
264+ filter: invert(var(--icon-invert));
265+ opacity: 0.25;
266+ padding-bottom: 2px;
267+}
268+
269+.arrow-selected {
270+ height: inherit;
271+ opacity: 0.6;
272+}
273+
274+td.display-name {
275+ padding-left: 12px;
276+ text-overflow: ellipsis;
277+ overflow-x: hidden;
278+ white-space: nowrap;
279+ max-width: 100px;
280+ font-size: 15px;
281+}
282+
283+.highlight { background-color: var(--highlight); }
284+
285+/* -- Footer Row ------------------------------------------------------------ */
286+.row-footer {
287+ min-height: 32px;
288+ font-family: 'IBM Plex Mono', monospace;
289+ font-size: 11px;
290+ color: var(--fg-muted);
291+ background: var(--bg-muted-30);
292+}
293+
294+.cell-footer-help {
295+ flex: 1;
296+ display: flex;
297+ align-items: center;
298+ padding: 6px 12px;
299+ opacity: 0.6;
300+}
301+
302+.cell-footer-brand {
303+ display: flex;
304+ align-items: center;
305+ padding: 6px 12px;
306+ border-left: 1px solid var(--border);
307+ letter-spacing: 0.02em;
308+}
309+
310+/* -- Theme Toggle Button --------------------------------------------------- */
311+#themeToggle {
312+ cursor: pointer;
313+ background: none;
314+ border: none;
315+ outline: none;
316+ color: var(--fg-muted);
317+ padding: 4px;
318+ line-height: 1;
319+ display: flex;
320+ align-items: center;
321+ justify-content: center;
322+ transition: color 0.15s;
323+ -webkit-appearance: none;
324+ appearance: none;
325+}
326+
327+#themeToggle:hover {
328+ color: var(--fg);
329+}
330+
331+#themeToggle:focus {
332+ outline: none;
333+}
334+
335+/* -- Action Icons ---------------------------------------------------------- */
336+#icHolder { display: none; }
337+
338+.ic {
339+ width: 24px;
340+ height: 24px;
341+ cursor: pointer;
342+ display: inline-block;
343+ overflow: hidden;
344+ color: transparent;
345+ opacity: 0.45;
346+ filter: grayscale(1) invert(var(--icon-invert));
347+ transition: opacity 0.15s;
348+}
349+
350+.ic:hover { opacity: 1; }
351+
352+/* -- Upload Bar ------------------------------------------------------------ */
353+.uploading-table { padding-bottom: 80px; }
354+
355+.bar {
356+ width: 100%;
357+ left: 0;
358+ right: 0;
359+ position: fixed;
360+ bottom: 0;
361+ max-height: 50%;
362+ overflow-x: hidden;
363+ background-color: var(--bg);
364+ border-top: 1px solid var(--border);
365+}
366+
367+.barName {
368+ width: 100%;
369+ font-size: 12px;
370+ color: var(--fg-muted);
371+ font-family: 'IBM Plex Mono', monospace;
372+}
373+
374+.barPc {
375+ width: 1%;
376+ height: 28px;
377+ text-align: center;
378+ line-height: 28px;
379+ min-width: 30px;
380+ margin-top: 4px;
381+ color: var(--bg);
382+ background-color: var(--accent) !important;
383+ margin-bottom: 4px;
384+ font-family: 'IBM Plex Mono', monospace;
385+ font-size: 11px;
386+}
387+
388+/* -- Drop Zone ------------------------------------------------------------- */
389+#drop-grid {
390+ display: none !important;
391+}
392+
393+/* -- Text Editor ----------------------------------------------------------- */
394+#text-editor {
395+ font-family: 'IBM Plex Mono', monospace;
396+ font-size: 13px;
397+ line-height: 1.6;
398+ background-color: var(--bg);
399+ color: var(--fg);
400+ position: fixed;
401+ inset: 0;
402+ z-index: 100;
403+ overflow-y: auto;
404+ word-wrap: anywhere;
405+ padding: 40px;
406+ outline: none;
407+ width: -moz-available;
408+ width: -webkit-fill-available;
409+ height: 100%;
410+ border: none;
411+ resize: none;
412+ box-sizing: border-box;
413+}
414+
415+/* -- Media Viewers --------------------------------------------------------- */
416+#pics, #pdf, #video {
417+ user-select: none;
418+ -webkit-tap-highlight-color: transparent;
419+ justify-content: center;
420+ align-items: center;
421+ position: fixed;
422+ background-color: hsl(0 0% 0%);
423+ inset: 0;
424+ overflow: hidden;
425+ z-index: 99;
426+}
427+
428+#picsHolder {
429+ user-select: none;
430+ height: 100%;
431+ width: 100%;
432+ cursor: pointer;
433+ object-fit: contain;
434+ image-orientation: from-image;
435+}
436+
437+#pdf embed { width: 100%; height: 100%; }
438+
439+#videoHolder {
440+ height: auto;
441+ max-height: 100%;
442+ width: 100%;
443+ cursor: pointer;
444+ object-fit: contain;
445+}
446+
447+#video-dl {
448+ position: fixed;
449+ right: 8px;
450+ top: 80px;
451+ width: 50px;
452+ height: 50px;
453+ cursor: pointer;
454+ z-index: 101;
455+ filter: invert(var(--icon-invert));
456+}
457+
458+/* -- Close Button ---------------------------------------------------------- */
459+#quitAll {
460+ position: fixed;
461+ right: 16px;
462+ top: 16px;
463+ width: 28px;
464+ height: 28px;
465+ cursor: pointer;
466+ z-index: 101;
467+ filter: invert(var(--icon-invert));
468+ opacity: 0.7;
469+}
470+
471+#quitAll:hover { opacity: 1; }
472+
473+/* -- Toast ----------------------------------------------------------------- */
474+#toast {
475+ top: 7px;
476+ position: absolute;
477+ right: 70px;
478+ font-style: normal;
479+ font-family: 'IBM Plex Mono', monospace;
480+ font-size: 12px;
481+ color: var(--fg-muted);
482+ width: max-content;
483+}
484+
485+/* -- Notifications --------------------------------------------------------- */
486+@keyframes fade {
487+ 0% { opacity: 0; }
488+ 20% { opacity: 1; }
489+ 80% { opacity: 1; }
490+ 100% { opacity: 0; }
491+}
492+
493+.runFade { animation: fade 3s 1; }
494+
495+.notif {
496+ opacity: 0;
497+ width: 60px;
498+ height: 60px;
499+ position: fixed;
500+ bottom: 24px;
501+ right: 24px;
502+ filter: invert(var(--icon-invert));
503+}
504+
505+/* -- Help / Sums Overlays -------------------------------------------------- */
506+#helpTable,
507+#sumsTable {
508+ border-collapse: collapse;
509+ width: 70%;
510+ max-width: 600px;
511+ margin: 80px auto;
512+}
513+
514+#helpTable td,
515+#sumsTable td {
516+ width: 200px;
517+ padding: 8px 12px;
518+ border: 1px solid var(--border);
519+ color: var(--fg);
520+ font-size: 13px;
521+ font-family: 'IBM Plex Sans', sans-serif;
522+}
523+
524+#help,
525+#sums {
526+ background-color: var(--bg);
527+ position: absolute;
528+ inset: 0;
529+ overflow-y: scroll;
530+ z-index: 99;
531+ font-size: 1em !important;
532+}
533+
534+/* -- Legacy (hidden, replaced by footer) ----------------------------------- */
535+#help_message { display: none; }
536+
537+/* -- Mobile ---------------------------------------------------------------- */
538+@media (max-width: 800px) {
539+ html {
540+ font-size: 13px !important;
541+ user-select: none;
542+ }
543+ body {
544+ padding: 8px 0 !important;
545+ align-items: flex-start !important;
546+ }
547+ #page {
548+ border-left: none;
549+ border-right: none;
550+ width: 100vw !important;
551+ min-height: 90vh;
552+ }
553+ .cell-label { width: 40px; font-size: 9px; }
554+ td.iconRow { width: 40px; }
555+ .cell-col-icon { width: 40px; }
556+ .cell-col-size, td.file-size { display: none; }
557+ .arrow { display: none !important; }
558+ td.display-name { padding-left: 10px !important; }
559+ tr { line-height: 2.6em !important; }
560+ .cell-path h1 { font-size: 12px; }
561+ .row-footer { font-size: 10px; }
562+ .cell-footer-help { display: none; }
563+}
564+
565+/* -- Icon Classes ---------------------------------------------------------- */
566+.manualUp {
567+ display: none;
568+ margin: unset;
569+}
570+
571+
572+.icon-large-sad-server {
573+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAHlBMVEUAAACAgIDAwMAAAAD///8A/wAAgAD/AACAgAD//wAxEuVaAAAAAXRSTlMAQObYZgAAAAFiS0dEBI9o2VEAAAAHdElNRQfiBhoALCLL7Xg4AAAAy0lEQVQ4y+WSPQ7CMAyFHbUSa38G5iLBHCkXKBUnQOICDMwgBnMDuEJvi00TKM3L0IWFN/rLs/zsEP2BSiCtm1WsxgqoAChtwqAAGgSYXaztC3So0xywPuwx2JyOM1uNwWRN1RtMUnQ/alVTAtgEqPWw6OJ6WHJA358lGx7GMs0swHwWsGCeAikJEAwst8d9bCh8kCVzz3zxQQpDbe53x6qwyDYnm/lQ0oevIaHNPj2dAEdIHM8aBgbDDgZo0dDQokWwEV8DlqE0sjwBakRwyV+dKtoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTgtMDYtMjZUMDA6NDQ6MzQtMDQ6MDBDgg+9AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTA2LTI2VDAwOjQ0OjM0LTA0OjAwMt+3AQAAAABJRU5ErkJggg==");
574+}
575+
576+.icon-large-ok {
577+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAElBMVEUAAAAAAAAAgACAgID////AwMAbJqpVAAAAAXRSTlMAQObYZgAAAAFiS0dEBI9o2VEAAAAHdElNRQfiBhoANRyRjMyLAAAArElEQVQoz22R0Q3EIAiGNbfAeU4gad+9sMGFLtC4/yyngBaa+tCELz+fFkJ4PimltyljAoCSrvoDfBbRGkC74qyh3AIauQIacaCajo1+ANmARqcHRP2T61LsdIgkWoUFrICCC7Bigo1OUUzQK1GslkaiGIDfsZMoICvokcODXRTjpXpvU0W9/T5WP7GhcDNF9EPN3GH2ooEQvrI51EAIL+y7zbgCg/Cx68eZ/wNuLjeFQzbY1AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNlQwMDo1MzoyOC0wNDowMGvaoe4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjZUMDA6NTM6MjgtMDQ6MDAahxlSAAAAAElFTkSuQmCC");
578+}
579+
580+.icon-large-pad {
581+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAGFBMVEUAAAAAAACAgID////AwMAAAL8AAP///wCsLlssAAAAAXRSTlMAQObYZgAAAAFiS0dEAxEMTPIAAAAHdElNRQfiBhoAOCQMIApYAAAAvUlEQVQoz22Ryw7CIBREqYl7xlb3Pj7APpQ1iaZbXcjaxBTXhCi/bx+2XBomd8PJDDcMjCUc43DWab8rpunIIi/TcbYyCkovKdltBpbPGVAtuHjJqz6GDvUOI1d9DiPqpcMt6tsEkXyjwy04NWEkhWooEMD6wUnEGuBOHidEBXACrKsNuuf/A7Wr3AekD2trB1JQe4MzUvqIK4BMeofIV0OFIzBpOYG+qmxoLNJ6DNCSow528I6s//4EXu3xB1FNX4O6vHKSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA2LTI2VDAwOjU2OjM2LTA0OjAwEWYRaQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wNi0yNlQwMDo1NjozNi0wNDowMGA7qdUAAAAASUVORK5CYII=");
582+}
583+
584+.icon-large-upload {
585+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAXVBMVEUAAAAzzDPM/8z///8z/2YzmQAzZgAzZv8Amf8AAAAzzP+ZzP8AgADM//8z//8AMwAzzGaZmQD4+Pj//5kAZgD/zGYzmTP/zJmAgAAzMwDMZjPMmTP//wBmMwDwyqYv7S31AAAAAXRSTlMAQObYZgAAAAFiS0dEAxEMTPIAAAAHdElNRQfiBhoAOCALTc5BAAAB3klEQVRIx5WUCZKDIBBFAQXEbTTRQbPM/Y85TQMKClm+piplvcdv0YSQINSFfBQAWcHKsqSUc/EBbmnAgedCyje8Xxxxw782LI+zCPORVSWlesPj4qKqETdC3nA8R76uasBr+cJoCmHGdzwadSsxaaPpCiHcPJ6naLQqw3d9J2jEix/AW540msIYZnMMX/et5xnPCNjQC+SrvoUvlh8GzlWW73tcH+YRdk+tIFRqoq6jwPf+Diph5mcjJiEAThkNeQb4MF6uJmej6ShjrJymnR+GjU8YDfLwJk2Wr9DY+LNBi4DHSMHGOcjo418k5Es+TY6vxO94SUU5oUQDXg1QpDlEOBFEu3MOjJKCsfioUNBe0fNqBcfTZaMWdYkKXMUmIF8uAbSo0zx7A+GH9SPDwNqegWD4Oc6yx1dsAuHw61ku2ShsCRoImY4T6fCYlZ0oEMjEl8NG7rd6nVd9bAAjbrCOb1i1PgkkELR7VnpveCV4OLiH1W9dpiGaR5sGH5IQjvPgJKn/Disc9ke755UXzjQ23NTdJRb0NZ5ne16Pp8+HDSEYCPbFvMb0cfuTu3QwcoKOnpf+umFPRpjzSQpkzYe8z/OuPqC+E+5hbhAVXUmsGebx93eLLjzf1L1i/gHK3l4JVuA9nAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNlQwMDo1NjozMi0wNDowMOUpNXoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjZUMDA6NTY6MzItMDQ6MDCUdI3GAAAAAElFTkSuQmCC");
586+ margin-bottom: 3px;
587+}
588+
589+.icon-folder {
590+ height: 27px;
591+ width: 27px;
592+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAIVBMVEUAAACZmQAICAj4+Pj//5n/+/DMzGYAAAD/zJkzMwD///9AsZvJAAAAAXRSTlMAQObYZgAAAAFiS0dECmjQ9FYAAAAHdElNRQfiBhgXARMJeV+TAAAAuElEQVQoz62QMQ7CQBADLyVlQKLmD/ABpC38AF5AgZSOR7i4kpbf4t3b4xLR4i6+sb1KKb+apf3qe7qY2fU0jIO5BjLdwhjI8dE0Nylx3uiZFS4YjfduAJADLCvCCNrKoAL2JajPyG0IZ9IQGzP1S2iSMdsMZgbshOIVsRsGs7WidzAm1IGaEfgNIugRxIJblXUQ/my1dXgaUaDLZDDm5HgiIvGqQyw7GJVOIO7oPzm1lN17o1f5gz69jIC7vf9PIQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNFQyMzowMToxOS0wNDowMO2EXMYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjRUMjM6MDE6MTktMDQ6MDCc2eR6AAAAAElFTkSuQmCC") !important;
593+}
594+
595+.icon-blank {
596+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlMGUwZTAiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlZWUiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjI2MykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMjQ5LjE4OSIgeDI9IjI0OS4xODkiIHhsaW5rOmhyZWY9IiNhIiB5MT0iMjM2LjUyNCIgeTI9IjI1NC42NjciLz48bGluZWFyR3JhZGllbnQgaWQ9ImoiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzllOWU5ZSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2JkYmRiZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJrIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwLjU4MzM0Mzc1IiB4Mj0iMTAuNTgzMzQzNzUiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMTQuMDE2ODc1IiB5Mj0iMS40NDkxNTYyNSIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNTQiIHgyPSIyNTQiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMjMzLjUiIHkyPSItMTY4LjY2NyIvPjxwYXRoIGQ9Im0zLjk2OSAxNC4wMTZoNy42MXYuNjY0aC03LjYxem0wIDAiIGZpbGw9InVybCgjYikiLz48cGF0aCBkPSJtMTEuNTc4IDE0LjAxNmgxLjMydi0uNjZoLTEuMzJ6bTAgMCIgZmlsbD0idXJsKCNkKSIvPjxwYXRoIGQ9Im0zLjk2OSAxNC4wMTZoLTEuMzI0di42NjRoMS4zMjR6bTAgMCIgZmlsbD0idXJsKCNlKSIvPjxwYXRoIGQ9Im0zLjk2OSAxNC4wMTZoLTEuMzI0di0uNjZoMS4zMjR6bTAgMCIgZmlsbD0idXJsKCNmKSIvPjxwYXRoIGQ9Im0xMS41NzggMTQuMDE2aDEuMzJ2LjY2NGgtMS4zMnptMCAwIiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0ibTkuMjcgMS40NWgtNS45NjFhLjY2LjY2IDAgMCAwIC0uNjY0LjY2djExLjI0NWMwIC4zNjguMjk2LjY2LjY2NC42Nmg4LjkzYy4zNjYgMCAuNjYtLjI5Mi42Ni0uNjZ2LTguMjc3em0wIDAiIGZpbGw9InVybCgjaCkiLz48cGF0aCBkPSJtOS4yNjIgNC41MDhoMy4xNGwuNDk2LjQ5NnYuNjZoLTMuNjM2em0wIDAiIGZpbGw9InVybCgjaSkiLz48cGF0aCBkPSJtMi42NDUgMTMuMDk4di4yNTdjMCAuMzY4LjI5Ni42Ni42NjQuNjZoOC45M2MuMzY2IDAgLjY2LS4yOTIuNjYtLjY2di0uMjU3YS42Ni42NiAwIDAgMSAtLjY2LjY2NGgtOC45M2EuNjY0LjY2NCAwIDAgMSAtLjY2NC0uNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ1YS42Ni42NiAwIDAgMCAtLjY2NC42NnYuMjVjMC0uMzY4LjI5Ni0uNjY1LjY2NC0uNjY1aDUuOTZsMy42MyAzLjYzdi0uMjQ3bC0zLjYzMS0zLjYyOHptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii43ODQiLz48cGF0aCBkPSJtMTIuODk4IDUuMDc4LTMuNjI4LTMuNjI4djIuOTY1YzAgLjM2Ny4yOTYuNjY0LjY2NC42NjR6bTAgMCIgZmlsbD0idXJsKCNrKSIvPjxwYXRoIGQ9Im05LjI3IDQuMjU4di4xNTZjMCAuMzY3LjI5Ni42NjQuNjY0LjY2NGgyLjk2NGwtLjE1Ni0uMTU2aC0yLjgwOGEuNjY0LjY2NCAwIDAgMSAtLjY2NC0uNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNSIvPjxwYXRoIGQ9Im05LjI3IDEuNDV2LjI0NWwzLjM4MiAzLjM4M2guMjQ2em0wIDAiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjM5MiIvPjxwYXRoIGQ9Im05LjI3IDEuNDAyYS4wNDcuMDQ3IDAgMCAwIC0uMDQ3LjA0N3YyLjk2NWMwIC4zOTUuMzE2LjcxMS43MS43MTFoMi45NjVjLjAyIDAgLjA0LS4wMTIuMDQzLS4wMzEuMDA4LS4wMTYuMDA0LS4wMzUtLjAwNy0uMDUxbC0zLjYzLTMuNjI5Yy0uMDA3LS4wMDgtLjAyMy0uMDEyLS4wMzQtLjAxMnptLjA0Ni4xNiAzLjQ3IDMuNDdoLTIuODUzYS42MTQuNjE0IDAgMCAxIC0uNjE4LS42MTh6bTAgMCIgZmlsbD0iIzIxMjEyMSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQwMmEuNzA4LjcwOCAwIDAgMCAtLjcxMS43MDd2MTEuMjQ2YzAgLjM5MS4zMTYuNzA3LjcxLjcwN2g4LjkzYy4zOSAwIC43MDctLjMxNi43MDctLjcwN3YtOC4yNzdjMC0uMDE2LS4wMDQtLjAyNy0uMDExLS4wMzVsLTMuNjMtMy42MjljLS4wMDctLjAwOC0uMDE5LS4wMTItLjAzNC0uMDEyem0wIC4wOTRoNS45NDFsMy42MDIgMy42MDJ2OC4yNTdjMCAuMzQtLjI3NC42MTQtLjYxNC42MTRoLTguOTNhLjYxMy42MTMgMCAwIDEgLS42MTctLjYxNHYtMTEuMjQ1YzAtLjM0LjI3NC0uNjEzLjYxOC0uNjEzem0wIDAiIGZpbGw9IiMyMTIxMjEiLz48ZyBmaWxsPSJ1cmwoI2wpIiBzdHJva2U9IiMyMTIxMjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIj48cGF0aCBkPSJtMTYzLjk4NC0zMS4xNDNoMTY5LjM2YzIuOTk5IDAgNS4zNzQgMi4zNzUgNS4zNzQgNS4zNzV2MTAuNDk5YzAgMy0yLjM3NSA1LjM3NC01LjM3NSA1LjM3NGgtMTY5LjM1OWMtMi44NzQgMC01LjI1LTIuMzc0LTUuMjUtNS4zNzR2LTEwLjVjMC0yLjk5OSAyLjM3Ni01LjM3NCA1LjI1LTUuMzc0em0wIDAiIHRyYW5zZm9ybT0ibWF0cml4KC4wMzEyNSAwIDAgLjAzMTI1IDAgNi43MikiLz48cGF0aCBkPSJtMTYzLjk4NCAxMS4yMjhoMTY5LjM2YzIuOTk5IDAgNS4zNzQgMi4zNzUgNS4zNzQgNS4zNzV2MTAuNDk5YzAgMy0yLjM3NSA1LjM3NC01LjM3NSA1LjM3NGgtMTY5LjM1OWMtMi44NzQgMC01LjI1LTIuMzc0LTUuMjUtNS4zNzR2LTEwLjVjMC0yLjk5OSAyLjM3Ni01LjM3NCA1LjI1LTUuMzc0em0wIDAiIHRyYW5zZm9ybT0ibWF0cml4KC4wMzEyNSAwIDAgLjAzMTI1IDAgNi43MikiLz48cGF0aCBkPSJtMTYzLjk4NCA1My42aDE2OS4zNmMyLjk5OSAwIDUuMzc0IDIuMzc0IDUuMzc0IDUuMjQ5djEwLjYyNGMwIDIuODc1LTIuMzc1IDUuMjUtNS4zNzUgNS4yNWgtMTY5LjM1OWMtMi44NzQgMC01LjI1LTIuMzc1LTUuMjUtNS4yNXYtMTAuNjI0YzAtMi44NzUgMi4zNzYtNS4yNSA1LjI1LTUuMjV6bTAgMCIgdHJhbnNmb3JtPSJtYXRyaXgoLjAzMTI1IDAgMCAuMDMxMjUgMCA2LjcyKSIvPjxwYXRoIGQ9Im0xNjMuOTg0IDk1Ljk3aDE2OS4zNmMyLjk5OSAwIDUuMzc0IDIuMzc1IDUuMzc0IDUuMjV2MTAuNjI0YzAgMi44NzUtMi4zNzUgNS4yNS01LjM3NSA1LjI1aC0xNjkuMzU5Yy0yLjg3NCAwLTUuMjUtMi4zNzUtNS4yNS01LjI1di0xMC42MjRjMC0yLjg3NSAyLjM3Ni01LjI1IDUuMjUtNS4yNXptMCAwIiB0cmFuc2Zvcm09Im1hdHJpeCguMDMxMjUgMCAwIC4wMzEyNSAwIDYuNzIpIi8+PC9nPjwvc3ZnPg==");
597+}
598+
599+.icon-code, .icon-js, .icon-c, .icon-cpp, .icon-go, .icon-css, .icon-html, .icon-rs, .icon-py, .icon-rb {
600+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlMGUwZTAiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlZWUiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjI2MykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMjQ5LjE4OSIgeDI9IjI0OS4xODkiIHhsaW5rOmhyZWY9IiNhIiB5MT0iMjM2LjUyNCIgeTI9IjI1NC42NjciLz48bGluZWFyR3JhZGllbnQgaWQ9ImoiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzllOWU5ZSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2JkYmRiZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJrIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwLjU4MzM0Mzc1IiB4Mj0iMTAuNTgzMzQzNzUiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMTQuMDE2ODc1IiB5Mj0iMS40NDkxNTYyNSIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNDguNjg3OTIiIHgyPSIyNDguNjg3OTIiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMjMzLjQ2OTA4IiB5Mj0iLTE2OC42NzI2NiIvPjxwYXRoIGQ9Im0zLjk2OSAxNC4wMTZoNy42MXYuNjY0aC03LjYxem0wIDAiIGZpbGw9InVybCgjYikiLz48cGF0aCBkPSJtMTEuNTc4IDE0LjAxNmgxLjMydi0uNjZoLTEuMzJ6bTAgMCIgZmlsbD0idXJsKCNkKSIvPjxwYXRoIGQ9Im0zLjk2OSAxNC4wMTZoLTEuMzI0di42NjRoMS4zMjR6bTAgMCIgZmlsbD0idXJsKCNlKSIvPjxwYXRoIGQ9Im0zLjk2OSAxNC4wMTZoLTEuMzI0di0uNjZoMS4zMjR6bTAgMCIgZmlsbD0idXJsKCNmKSIvPjxwYXRoIGQ9Im0xMS41NzggMTQuMDE2aDEuMzJ2LjY2NGgtMS4zMnptMCAwIiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0ibTkuMjcgMS40NWgtNS45NjFhLjY2LjY2IDAgMCAwIC0uNjY0LjY2djExLjI0NWMwIC4zNjguMjk2LjY2LjY2NC42Nmg4LjkzYy4zNjYgMCAuNjYtLjI5Mi42Ni0uNjZ2LTguMjc3em0wIDAiIGZpbGw9InVybCgjaCkiLz48cGF0aCBkPSJtOS4yNjIgNC41MDhoMy4xNGwuNDk2LjQ5NnYuNjZoLTMuNjM2em0wIDAiIGZpbGw9InVybCgjaSkiLz48cGF0aCBkPSJtMi42NDUgMTMuMDk4di4yNTdjMCAuMzY4LjI5Ni42Ni42NjQuNjZoOC45M2MuMzY2IDAgLjY2LS4yOTIuNjYtLjY2di0uMjU3YS42Ni42NiAwIDAgMSAtLjY2LjY2NGgtOC45M2EuNjY0LjY2NCAwIDAgMSAtLjY2NC0uNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ1YS42Ni42NiAwIDAgMCAtLjY2NC42NnYuMjVjMC0uMzY4LjI5Ni0uNjY1LjY2NC0uNjY1aDUuOTZsMy42MyAzLjYzdi0uMjQ3bC0zLjYzMS0zLjYyOHptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii43ODQiLz48cGF0aCBkPSJtMTIuODk4IDUuMDc4LTMuNjI4LTMuNjI4djIuOTY1YzAgLjM2Ny4yOTYuNjY0LjY2NC42NjR6bTAgMCIgZmlsbD0idXJsKCNrKSIvPjxwYXRoIGQ9Im05LjI3IDQuMjU4di4xNTZjMCAuMzY3LjI5Ni42NjQuNjY0LjY2NGgyLjk2NGwtLjE1Ni0uMTU2aC0yLjgwOGEuNjY0LjY2NCAwIDAgMSAtLjY2NC0uNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNSIvPjxwYXRoIGQ9Im05LjI3IDEuNDV2LjI0NWwzLjM4MiAzLjM4M2guMjQ2em0wIDAiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjM5MiIvPjxwYXRoIGQ9Im05LjI3IDEuNDAyYS4wNDcuMDQ3IDAgMCAwIC0uMDQ3LjA0N3YyLjk2NWMwIC4zOTUuMzE2LjcxMS43MS43MTFoMi45NjVjLjAyIDAgLjA0LS4wMTIuMDQzLS4wMzEuMDA4LS4wMTYuMDA0LS4wMzUtLjAwNy0uMDUxbC0zLjYzLTMuNjI5Yy0uMDA3LS4wMDgtLjAyMy0uMDEyLS4wMzQtLjAxMnptLjA0Ni4xNiAzLjQ3IDMuNDdoLTIuODUzYS42MTQuNjE0IDAgMCAxIC0uNjE4LS42MTh6bTAgMCIgZmlsbD0iIzIxMjEyMSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQwMmEuNzA4LjcwOCAwIDAgMCAtLjcxMS43MDd2MTEuMjQ2YzAgLjM5MS4zMTYuNzA3LjcxLjcwN2g4LjkzYy4zOSAwIC43MDctLjMxNi43MDctLjcwN3YtOC4yNzdjMC0uMDE2LS4wMDQtLjAyNy0uMDExLS4wMzVsLTMuNjMtMy42MjljLS4wMDctLjAwOC0uMDE5LS4wMTItLjAzNC0uMDEyem0wIC4wOTRoNS45NDFsMy42MDIgMy42MDJ2OC4yNTdjMCAuMzQtLjI3NC42MTQtLjYxNC42MTRoLTguOTNhLjYxMy42MTMgMCAwIDEgLS42MTctLjYxNHYtMTEuMjQ1YzAtLjM0LjI3NC0uNjEzLjYxOC0uNjEzem0wIDAiIGZpbGw9IiMyMTIxMjEiLz48cGF0aCBkPSJtMjA2LjM1NS0zMS4xNDNjLTIuODc0IDAtNS4yNSAyLjM3NS01LjI1IDUuMzc1djI2LjM3MmgtMjEuMjQ3Yy0yLjg3NSAwLTUuMjUgMi4zNzUtNS4yNSA1LjM3NXYxMC42MjRjMCAyLjg3NSAyLjM3NSA1LjI1IDUuMjUgNS4yNWgyMS4yNDh2NDIuMzdoLTIxLjI0OGMtMi44NzUgMC01LjI1IDIuMjUtNS4yNSA1LjI1djEwLjYyNGMwIDIuODc1IDIuMzc1IDUuMjUgNS4yNSA1LjI1aDIxLjI0OHYyNi40OTdjMCAyLjg3NSAyLjM3NSA1LjI1IDUuMjUgNS4yNWgxMC42MjNjMi44NzUgMCA1LjI1LTIuMzc1IDUuMjUtNS4yNXYtMjYuNDk4aDUyLjk5NXYyNi40OThjMCAyLjg3NSAyLjI1IDUuMjUgNS4yNSA1LjI1aDEwLjYyM2MyLjg3NSAwIDUuMjUtMi4zNzUgNS4yNS01LjI1di0yNi40OThoMjEuMTIzYzMgMCA1LjM3NC0yLjM3NCA1LjM3NC01LjI1di0xMC42MjJjMC0zLTIuMzc1LTUuMjUtNS4zNzQtNS4yNWgtMjEuMTIzdi00Mi4zN2gyMS4xMjNjMyAwIDUuMzc0LTIuMzc1IDUuMzc0LTUuMjV2LTEwLjYyNWMwLTMtMi4zNzUtNS4zNzUtNS4zNzQtNS4zNzVoLTIxLjEyM3YtMjYuMzcyYzAtMy0yLjM3NS01LjM3NS01LjI1LTUuMzc1aC0xMC42MjRjLTMgMC01LjI1IDIuMzc1LTUuMjUgNS4zNzV2MjYuMzcyaC01Mi45OTN2LTI2LjM3MmMwLTMtMi4zNzUtNS4zNzUtNS4yNS01LjM3NXptMTUuODc0IDUyLjk5NWg1Mi45OTV2NDIuMzcxaC01Mi45OTV6bTAgMCIgZmlsbD0idXJsKCNsKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMyIgdHJhbnNmb3JtPSJtYXRyaXgoLjAzMTI1IDAgMCAuMDMxMjUgMCA2LjcyKSIvPjwvc3ZnPg==");
601+}
602+
603+.icon-doc, .icon-docx, .icon-odt {
604+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5ZmE4ZGEiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNjNWNhZTkiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjE4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNDkuMTg5IiB4Mj0iMjQ5LjE4OSIgeGxpbms6aHJlZj0iI2EiIHkxPSIyMzMuNSIgeTI9IjI1NC42NjciLz48ZmlsdGVyIGlkPSJqIiBoZWlnaHQ9IjEwMCUiIHdpZHRoPSIxMDAlIiB4PSIwJSIgeT0iMCUiPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VHcmFwaGljIiB2YWx1ZXM9IjAgMCAwIDAgMSAwIDAgMCAwIDEgMCAwIDAgMCAxIDAgMCAwIDEgMCIvPjwvZmlsdGVyPjxtYXNrIGlkPSJrIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiIGZpbGwtb3BhY2l0eT0iLjk4OCIgZmlsdGVyPSJ1cmwoI2opIi8+PC9tYXNrPjxsaW5lYXJHcmFkaWVudCBpZD0ibCI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNWM2YmMwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNzk4NmNiIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9Im0iIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTYwIiB4Mj0iOTYwIiB4bGluazpocmVmPSIjbCIgeTE9IjE2OTUuMTE4IiB5Mj0iMTc1LjExOCIvPjxjbGlwUGF0aCBpZD0ibiI+PHBhdGggZD0ibTAgMGgxNnYxNmgtMTZ6Ii8+PC9jbGlwUGF0aD48bGluZWFyR3JhZGllbnQgaWQ9Im8iIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTAuNTgzMzQzNzUiIHgyPSIxMC41ODMzNDM3NSIgeGxpbms6aHJlZj0iI2wiIHkxPSIxNC4wMTY4NzUiIHkyPSIxLjQ0OTE1NjI1Ii8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmg3LjYxdi42NjRoLTcuNjF6bTAgMCIgZmlsbD0idXJsKCNiKSIvPjxwYXRoIGQ9Im0xMS41NzggMTQuMDE2aDEuMzJ2LS42NmgtMS4zMnptMCAwIiBmaWxsPSJ1cmwoI2QpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LjY2NGgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2UpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LS42NmgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2YpIi8+PHBhdGggZD0ibTExLjU3OCAxNC4wMTZoMS4zMnYuNjY0aC0xLjMyem0wIDAiIGZpbGw9InVybCgjZykiLz48cGF0aCBkPSJtOS4yNyAxLjQ1aC01Ljk2MWEuNjYuNjYgMCAwIDAgLS42NjQuNjZ2MTEuMjQ1YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtOC4yNzd6bTAgMCIgZmlsbD0idXJsKCNoKSIvPjxwYXRoIGQ9Im05LjI2MiA0LjU5aDMuMTRsLjQ5Ni40OTZ2LjY2aC0zLjYzNnptMCAwIiBmaWxsPSJ1cmwoI2kpIi8+PHBhdGggZD0ibTIuNjQ1IDEzLjA5OHYuMjU3YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtLjI1N2EuNjYuNjYgMCAwIDEgLS42Ni42NjRoLTguOTNhLjY2NC42NjQgMCAwIDEgLS42NjQtLjY2NHptMCAwIiBmaWxsLW9wYWNpdHk9Ii4yMzUiLz48ZyBjbGlwLXBhdGg9InVybCgjbikiIG1hc2s9InVybCgjaykiPjxwYXRoIGQ9Im02MTkuNzgzIDY5NC44OTRjLTEwLjg2NSAwLTE5Ljg0IDguOTc2LTE5Ljg0IDIwLjMxM3YzOS42ODJjMCAxMS4zMzcgOC45NzUgMjAuMzEzIDE5Ljg0IDIwLjMxM2g2NDAuMDk3YzExLjMzNyAwIDIwLjMxMy04Ljk3NiAyMC4zMTMtMjAuMzEzdi0zOS42ODJjMC0xMS4zMzctOC45NzYtMjAuMzEzLTIwLjMxMy0yMC4zMTN6bTAgMTYwLjE0M2MtMTAuODY1IDAtMTkuODQgOC45NzUtMTkuODQgMjAuMzEzdjM5LjY4YzAgMTEuMzM4IDguOTc1IDIwLjMxNCAxOS44NCAyMC4zMTRoNjQwLjA5N2MxMS4zMzcgMCAyMC4zMTMtOC45NzYgMjAuMzEzLTIwLjMxM3YtMzkuNjgxYzAtMTEuMzM4LTguOTc2LTIwLjMxMy0yMC4zMTMtMjAuMzEzem0wIDE2MC4xNDJjLTEwLjg2NSAwLTE5Ljg0IDguOTc1LTE5Ljg0IDE5Ljg0djQwLjE1NGMwIDEwLjg2NSA4Ljk3NSAxOS44NCAxOS44NCAxOS44NGg2NDAuMDk3YzExLjMzNyAwIDIwLjMxMy04Ljk3NSAyMC4zMTMtMTkuODR2LTQwLjE1NGMwLTEwLjg2NS04Ljk3Ni0xOS44NC0yMC4zMTMtMTkuODR6bTAgMTYwLjE0MmMtMTAuODY1IDAtMTkuODQgOC41MDMtMTkuODQgMTkuODR2NDAuMTU0YzAgMTAuODY1IDguOTc1IDE5Ljg0IDE5Ljg0IDE5Ljg0aDY0MC4wOTdjMTEuMzM3IDAgMjAuMzEzLTguOTc1IDIwLjMxMy0xOS44NHYtNDAuMTUzYzAtMTEuMzM4LTguOTc2LTE5Ljg0MS0yMC4zMTMtMTkuODQxem0wIDAiIGZpbGw9InVybCgjbSkiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjExLjMzOSIgdHJhbnNmb3JtPSJtYXRyaXgoLjAwODI2OTAyIC4wMDAxMjczIC0uMDAwMTI3MyAuMDA4MjY5MDIgMCAwKSIvPjwvZz48cGF0aCBkPSJtMy4zMDkgMS40NWEuNjYuNjYgMCAwIDAgLS42NjQuNjZ2LjI1YzAtLjM2OC4yOTYtLjY2NS42NjQtLjY2NWg1Ljk2bDMuNjMgMy42M3YtLjI0N2wtMy42MzEtMy42Mjh6bTAgMCIgZmlsbD0iI2Y2ZjZmNiIgZmlsbC1vcGFjaXR5PSIuNTg4Ii8+PHBhdGggZD0ibTEyLjg5OCA1LjA3OC0zLjYyOC0zLjYyOHYyLjk2NWMwIC4zNjcuMjk2LjY2NC42NjQuNjY0em0wIDAiIGZpbGw9InVybCgjbykiLz48cGF0aCBkPSJtOS4yNyA0LjI1OHYuMTU2YzAgLjM2Ny4yOTYuNjY0LjY2NC42NjRoMi45NjRsLS4xNTYtLjE1NmgtMi44MDhhLjY2NC42NjQgMCAwIDEgLS42NjQtLjY2NHptMCAwIiBmaWxsLW9wYWNpdHk9Ii4yMzUiLz48cGF0aCBkPSJtOS4yNyAxLjQ1di4yNDVsMy4zODIgMy4zODNoLjI0NnptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii4zOTIiLz48ZyBmaWxsPSIjMjEyMTIxIj48cGF0aCBkPSJtOS4yNyAxLjQwMmEuMDQ3LjA0NyAwIDAgMCAtLjA0Ny4wNDd2Mi45NjVjMCAuMzk1LjMxNi43MTEuNzEuNzExaDIuOTY1Yy4wMiAwIC4wNC0uMDEyLjA0My0uMDMxLjAwOC0uMDE2LjAwNC0uMDM1LS4wMDctLjA1MWwtMy42My0zLjYyOWMtLjAwNy0uMDA4LS4wMjMtLjAxMi0uMDM0LS4wMTJ6bS4wNDYuMTYgMy40NyAzLjQ3aC0yLjg1M2EuNjE0LjYxNCAwIDAgMSAtLjYxOC0uNjE4em0wIDAiLz48cGF0aCBkPSJtMy4zMDkgMS40MDJhLjcwOC43MDggMCAwIDAgLS43MTEuNzA3djExLjI0NmMwIC4zOTEuMzE2LjcwNy43MS43MDdoOC45M2MuMzkgMCAuNzA3LS4zMTYuNzA3LS43MDd2LTguMjc3YzAtLjAxNi0uMDA0LS4wMjctLjAxMS0uMDM1bC0zLjYzLTMuNjI5Yy0uMDA3LS4wMDgtLjAxOS0uMDEyLS4wMzQtLjAxMnptMCAuMDk0aDUuOTQxbDMuNjAyIDMuNjAydjguMjU3YzAgLjM0LS4yNzQuNjE0LS42MTQuNjE0aC04LjkzYS42MTMuNjEzIDAgMCAxIC0uNjE3LS42MTR2LTExLjI0NWMwLS4zNC4yNzQtLjYxMy42MTgtLjYxM3ptMCAwIi8+PC9nPjwvc3ZnPg==");
605+}
606+
607+.icon-epub, .icon-mobi {
608+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSI3LjkzNzUiIHgyPSI3LjkzNzUiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3Atb3BhY2l0eT0iLjMxNCIvPjxzdG9wIG9mZnNldD0iLjIyMiIgc3RvcC1vcGFjaXR5PSIuMjc1Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9IjAiLz48L2xpbmVhckdyYWRpZW50PjxyYWRpYWxHcmFkaWVudCBpZD0iYyIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAtLjA3MDMyIDAgMjUuMDczIDMxLjc5OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNiIi8+PHJhZGlhbEdyYWRpZW50IGlkPSJkIiBjeD0iNDUwLjkwOSIgY3k9IjE4OS41NzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMCAuMDM5MDcgLjA3MDMyIDAgLTkuMTk3IC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNiIi8+PHJhZGlhbEdyYWRpZW50IGlkPSJlIiBjeD0iNDUwLjkwOSIgY3k9IjE4OS41NzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMCAtLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYiIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC0uMDcwMzIgMCAyNS4wNzMgLTMuNzY0KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2IiLz48bGluZWFyR3JhZGllbnQgaWQ9ImciIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjAzNzY3IDAgMCAuMTA4NyAtMi45MjcgMTYuODIpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjMwNi45MTciIHgyPSIzMDUuOTU1IiB5MT0iLTI1Ljc5MiIgeTI9Ii0xNDEuNDE1Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5MGNhZjkiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNiYmRlZmIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaCI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNDJhNWY1Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNjRiNWY2Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoNC41NTYxNCAwIDAgMTMuMTQ2MTggLTM1My45NzMgMjAzNC4xOCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTY1LjQ4NSIgeDI9IjE2NS40ODUiIHhsaW5rOmhyZWY9IiNoIiB5MT0iLTI1Ljc5MiIgeTI9Ii0xNDEuNDE1Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJqIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9Ijc2MCIgeDI9Ijc2MCIgeGxpbms6aHJlZj0iI2giIHkxPSIxNjk1LjExNyIgeTI9IjE3NS4xMTciLz48cGF0aCBkPSJtMy45NjkgMTQuMDE2aDcuOTM3di42NjRoLTcuOTM2em0wIDAiIGZpbGw9InVybCgjYSkiLz48cGF0aCBkPSJtMTEuOTA2IDE0LjAxNmgxLjMyNHYtLjY2aC0xLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2MpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LjY2NGgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2QpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LS42NmgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2UpIi8+PHBhdGggZD0ibTExLjkwNiAxNC4wMTZoMS4zMjR2LjY2NGgtMS4zMjR6bTAgMCIgZmlsbD0idXJsKCNmKSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ1aDkuMjYxYy4zNjQgMCAuNjYuMjk2LjY2LjY2djExLjI0NWEuNjYuNjYgMCAwIDEgLS42Ni42NmgtOS4yNmEuNjYuNjYgMCAwIDEgLS42NjQtLjY2di0xMS4yNDVjMC0uMzYzLjI5Ni0uNjYuNjY0LS42NnptMCAwIiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0ibTQwMC4xMiAxNzUuMjU5Yy00NC40MDYgMC04MC4zMDggMzUuNDMtODAuMzA4IDc5LjgzNXYxMzYwLjAyNmMwIDQ0LjQwNiAzNS45MDIgNzkuODM1IDgwLjMwNyA3OS44MzVoMTE5Ljk4OXYtMTUxOS42OTV6bTAgMCIgZmlsbD0idXJsKCNpKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMTEuMzM5IiB0cmFuc2Zvcm09Im1hdHJpeCguMDA4MjY5MDIgLjAwMDEyNzMgLS4wMDAxMjczIC4wMDgyNjkwMiAwIDApIi8+PHBhdGggZD0ibTMuMzA5IDEzLjk4YS42NjQuNjY0IDAgMCAxIC0uNjY0LS42NjR2LS4wODJjMCAuMzY4LjI5Ni42NjQuNjY0LjY2NGg5LjI2MWMuMzY0IDAgLjY2LS4yOTYuNjYtLjY2NHYuMDgyYS42NjMuNjYzIDAgMCAxIC0uNjYuNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjE5NiIvPjxwYXRoIGQ9Im02NzkuNzc4IDE3NS4yNTl2OTU5LjkwOGM1OS45OTQtMTYwLjE0MiAxMDAuMTQ4LTE2MC4xNDIgMTYwLjE0MiAwdi05NTkuOTA3em0wIDAiIGZpbGw9InVybCgjaikiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjExLjMzOSIgdHJhbnNmb3JtPSJtYXRyaXgoLjAwODI2OTAyIC4wMDAxMjczIC0uMDAwMTI3MyAuMDA4MjY5MDIgMCAwKSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ4NGEuNjY0LjY2NCAwIDAgMCAtLjY2NC42NjR2LjA4MmMwLS4zNjcuMjk2LS42NjQuNjY0LS42NjRoOS4yNjFjLjM2NCAwIC42Ni4yOTcuNjYuNjY0di0uMDgyYS42NjMuNjYzIDAgMCAwIC0uNjYtLjY2NHptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii4zOTIiLz48cGF0aCBkPSJtMy4zMDkgMS40MDJhLjcwOC43MDggMCAwIDAgLS43MTEuNzA3djExLjI0NmMwIC4zOTEuMzE2LjcwNy43MS43MDdoOS4yNjJjLjM5IDAgLjcwNy0uMzE2LjcwNy0uNzA3di0xMS4yNDVhLjcwNy43MDcgMCAwIDAgLS43MDctLjcwN3ptMCAuMDk0aDkuMjYxYy4zNCAwIC42MTQuMjc0LjYxNC42MTN2MTEuMjQ2YzAgLjM0LS4yNzQuNjE0LS42MTQuNjE0aC05LjI2YS42MTMuNjEzIDAgMCAxIC0uNjE4LS42MTR2LTExLjI0NWMwLS4zNC4yNzQtLjYxMy42MTgtLjYxM3ptMCAwIiBmaWxsPSIjMjEyMTIxIi8+PC9zdmc+")
609+}
610+
611+.icon-gif, .icon-png, .icon-jpg, .icon-jpeg, .icon-gif {
612+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNhNWQ2YTciLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNjOGU2YzkiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjE4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNDkuMTg5IiB4Mj0iMjQ5LjE4OSIgeGxpbms6aHJlZj0iI2EiIHkxPSIyMzMuNSIgeTI9IjI1NC42NjciLz48bGluZWFyR3JhZGllbnQgaWQ9ImoiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzY2YmI2YSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzgxYzc4NCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJrIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwLjU4MzM0Mzc1IiB4Mj0iMTAuNTgzMzQzNzUiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMTQuMDE2ODc1IiB5Mj0iMS40NDkxNTYyNSIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSI5NDAiIHgyPSI5NDAiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ii8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmg3LjYxdi42NjRoLTcuNjF6bTAgMCIgZmlsbD0idXJsKCNiKSIvPjxwYXRoIGQ9Im0xMS41NzggMTQuMDE2aDEuMzJ2LS42NmgtMS4zMnptMCAwIiBmaWxsPSJ1cmwoI2QpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LjY2NGgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2UpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LS42NmgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2YpIi8+PHBhdGggZD0ibTExLjU3OCAxNC4wMTZoMS4zMnYuNjY0aC0xLjMyem0wIDAiIGZpbGw9InVybCgjZykiLz48cGF0aCBkPSJtOS4yNyAxLjQ1aC01Ljk2MWEuNjYuNjYgMCAwIDAgLS42NjQuNjZ2MTEuMjQ1YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtOC4yNzd6bTAgMCIgZmlsbD0idXJsKCNoKSIvPjxwYXRoIGQ9Im05LjI2MiA0LjU5aDMuMTRsLjQ5Ni40OTZ2LjY2aC0zLjYzNnptMCAwIiBmaWxsPSJ1cmwoI2kpIi8+PHBhdGggZD0ibTIuNjQ1IDEzLjA5OHYuMjU3YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtLjI1N2EuNjYuNjYgMCAwIDEgLS42Ni42NjRoLTguOTNhLjY2NC42NjQgMCAwIDEgLS42NjQtLjY2NHptMCAwIiBmaWxsLW9wYWNpdHk9Ii4yMzUiLz48cGF0aCBkPSJtMy4zMDkgMS40NWEuNjYuNjYgMCAwIDAgLS42NjQuNjZ2LjI1YzAtLjM2OC4yOTYtLjY2NS42NjQtLjY2NWg1Ljk2bDMuNjMgMy42M3YtLjI0N2wtMy42MzEtMy42Mjh6bTAgMCIgZmlsbD0iI2Y2ZjZmNiIgZmlsbC1vcGFjaXR5PSIuNTg4Ii8+PHBhdGggZD0ibTEyLjg5OCA1LjA3OC0zLjYyOC0zLjYyOHYyLjk2NWMwIC4zNjcuMjk2LjY2NC42NjQuNjY0em0wIDAiIGZpbGw9InVybCgjaykiLz48cGF0aCBkPSJtOS4yNyA0LjI1OHYuMTU2YzAgLjM2Ny4yOTYuNjY0LjY2NC42NjRoMi45NjRsLS4xNTYtLjE1NmgtMi44MDhhLjY2NC42NjQgMCAwIDEgLS42NjQtLjY2NHptMCAwIiBmaWxsLW9wYWNpdHk9Ii4yMzUiLz48cGF0aCBkPSJtOS4yNyAxLjQ1di4yNDVsMy4zODIgMy4zODNoLjI0NnptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii4zOTIiLz48cGF0aCBkPSJtOS4yNyAxLjQwMmEuMDQ3LjA0NyAwIDAgMCAtLjA0Ny4wNDd2Mi45NjVjMCAuMzk1LjMxNi43MTEuNzEuNzExaDIuOTY1Yy4wMiAwIC4wNC0uMDEyLjA0My0uMDMxLjAwOC0uMDE2LjAwNC0uMDM1LS4wMDctLjA1MWwtMy42My0zLjYyOWMtLjAwNy0uMDA4LS4wMjMtLjAxMi0uMDM0LS4wMTJ6bS4wNDYuMTYgMy40NyAzLjQ3aC0yLjg1M2EuNjE0LjYxNCAwIDAgMSAtLjYxOC0uNjE4em0wIDAiIGZpbGw9IiMyMTIxMjEiLz48cGF0aCBkPSJtMy4zMDkgMS40MDJhLjcwOC43MDggMCAwIDAgLS43MTEuNzA3djExLjI0NmMwIC4zOTEuMzE2LjcwNy43MS43MDdoOC45M2MuMzkgMCAuNzA3LS4zMTYuNzA3LS43MDd2LTguMjc3YzAtLjAxNi0uMDA0LS4wMjctLjAxMS0uMDM1bC0zLjYzLTMuNjI5Yy0uMDA3LS4wMDgtLjAxOS0uMDEyLS4wMzQtLjAxMnptMCAuMDk0aDUuOTQxbDMuNjAyIDMuNjAydjguMjU3YzAgLjM0LS4yNzQuNjE0LS42MTQuNjE0aC04LjkzYS42MTMuNjEzIDAgMCAxIC0uNjE3LS42MTR2LTExLjI0NWMwLS4zNC4yNzQtLjYxMy42MTgtLjYxM3ptMCAwIiBmaWxsPSIjMjEyMTIxIi8+PHBhdGggZD0ibTYxOS43ODMgNjk0Ljg5NGMtMTAuODY1IDAtMTkuODQgOC45NzYtMTkuODQgMjAuMzEzdjUyMC4xMDhjMCAxMC44NjUgOC45NzUgMTkuODQgMTkuODQgMTkuODRoNjQwLjA5N2MxMS4zMzcgMCAyMC4zMTMtOC45NzUgMjAuMzEzLTE5Ljg0di01MjAuMTA4YzAtMTEuMzM3LTguOTc2LTIwLjMxMy0yMC4zMTMtMjAuMzEzem04MC4zMDggODAuMzA4aDQ3OS45NTRjMTAuODY1IDAgMTkuODQgOC45NzUgMTkuODQgMTkuODR2MzU5Ljk2NmMwIDExLjMzNy0xMC44NjUgMjYuNDU0LTE5Ljg0IDIwLjMxMy0zNy4zMi0yNS45ODItNTkuOTk0LTU5Ljk5NC0xMDAuMTQ4LTU5Ljk5NC00Ni4yOTUgMC05MC4yMjggMzkuNjgtMTM5LjgzIDM5LjY4LTQwLjE1MyAwLTU5Ljk5NC0xMTkuOTg4LTExOS45ODgtMTE5Ljk4OHMtNjUuMTkgMzQuMDEzLTExOS45ODggMTQwLjMwMmMtNS4xOTcgOS40NDgtMjAuMzEzLTguOTc2LTIwLjMxMy0yMC4zMTN2LTM1OS45NjZjMC0xMC44NjUgOC45NzUtMTkuODQgMjAuMzEzLTE5Ljg0em0zNTAuMDQ1IDU5Ljk5NGMtMzguNzM2IDAtNjkuOTE1IDMxLjE3OC02OS45MTUgNjkuOTE0IDAgMzguNzM3IDMxLjE3OSA2OS45MTUgNjkuOTE1IDY5LjkxNXM2OS45MTUtMzEuMTc4IDY5LjkxNS02OS45MTVjMC0zOC43MzYtMzEuMTc5LTY5LjkxNC02OS45MTUtNjkuOTE0em0wIDAiIGZpbGw9InVybCgjbCkiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjExLjMzOSIgdHJhbnNmb3JtPSJtYXRyaXgoLjAwODI2OTAyIC4wMDAxMjczIC0uMDAwMTI3MyAuMDA4MjY5MDIgMCAwKSIvPjwvc3ZnPg==");
613+}
614+
615+.icon-mp3, .icon-wav, .icon-flac, .icon-ogg {
616+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmNjODAiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmUwYjIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjI2MykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMjQ5LjE4OSIgeDI9IjI0OS4xODkiIHhsaW5rOmhyZWY9IiNhIiB5MT0iMjMzLjUiIHkyPSIyNTQuNjY3Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJqIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmE3MjYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmI3NGQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iayIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxMC41ODMzNDM3NSIgeDI9IjEwLjU4MzM0Mzc1IiB4bGluazpocmVmPSIjaiIgeTE9IjE0LjAxNjg3NSIgeTI9IjEuNDQ5MTU2MjUiLz48bGluZWFyR3JhZGllbnQgaWQ9ImwiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iNTIuOTE1MzAzNDIiIHgyPSI1Mi45MTUzMDM0MiIgeGxpbms6aHJlZj0iI2oiIHkxPSIyMzMuNDk0MzIwNDQiIHkyPSItMTY4LjY2NzI3OTU2Ii8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmg3LjYxdi42NjRoLTcuNjF6bTAgMCIgZmlsbD0idXJsKCNiKSIvPjxwYXRoIGQ9Im0xMS41NzggMTQuMDE2aDEuMzJ2LS42NmgtMS4zMnptMCAwIiBmaWxsPSJ1cmwoI2QpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LjY2NGgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2UpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LS42NmgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2YpIi8+PHBhdGggZD0ibTExLjU3OCAxNC4wMTZoMS4zMnYuNjY0aC0xLjMyem0wIDAiIGZpbGw9InVybCgjZykiLz48cGF0aCBkPSJtOS4yNyAxLjQ1aC01Ljk2MWEuNjYuNjYgMCAwIDAgLS42NjQuNjZ2MTEuMjQ1YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtOC4yNzd6bTAgMCIgZmlsbD0idXJsKCNoKSIvPjxwYXRoIGQ9Im0yLjY0NSAxMy4wOTh2LjI1N2MwIC4zNjguMjk2LjY2LjY2NC42Nmg4LjkzYy4zNjYgMCAuNjYtLjI5Mi42Ni0uNjZ2LS4yNTdhLjY2LjY2IDAgMCAxIC0uNjYuNjY0aC04LjkzYS42NjQuNjY0IDAgMCAxIC0uNjY0LS42NjR6bTAgMCIgZmlsbC1vcGFjaXR5PSIuMjM1Ii8+PHBhdGggZD0ibTkuMjYyIDQuNTA4aDMuMTRsLjQ5Ni40OTZ2LjY2aC0zLjYzNnptMCAwIiBmaWxsPSJ1cmwoI2kpIi8+PHBhdGggZD0ibTMuMzA5IDEuNDVhLjY2LjY2IDAgMCAwIC0uNjY0LjY2di4yNWMwLS4zNjguMjk2LS42NjUuNjY0LS42NjVoNS45NmwzLjYzIDMuNjN2LS4yNDdsLTMuNjMxLTMuNjI4em0wIDAiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjM5MiIvPjxwYXRoIGQ9Im0xMi44OTggNS4wNzgtMy42MjgtMy42Mjh2Mi45NjVjMCAuMzY3LjI5Ni42NjQuNjY0LjY2NHptMCAwIiBmaWxsPSJ1cmwoI2spIi8+PHBhdGggZD0ibTkuMjcgNC4yNTh2LjE1NmMwIC4zNjcuMjk2LjY2NC42NjQuNjY0aDIuOTY0bC0uMTU2LS4xNTZoLTIuODA4YS42NjQuNjY0IDAgMCAxIC0uNjY0LS42NjR6bTAgMCIgZmlsbC1vcGFjaXR5PSIuMjM1Ii8+PHBhdGggZD0ibTkuMjcgMS40NXYuMjQ1bDMuMzgyIDMuMzgzaC4yNDZ6bTAgMCIgZmlsbD0iI2ZmZiIgZmlsbC1vcGFjaXR5PSIuMzkyIi8+PHBhdGggZD0ibTkuMjcgMS40MDJhLjA0Ny4wNDcgMCAwIDAgLS4wNDcuMDQ3djIuOTY1YzAgLjM5NS4zMTYuNzExLjcxLjcxMWgyLjk2NWMuMDIgMCAuMDQtLjAxMi4wNDMtLjAzMS4wMDgtLjAxNi4wMDQtLjAzNS0uMDA3LS4wNTFsLTMuNjMtMy42MjljLS4wMDctLjAwOC0uMDIzLS4wMTItLjAzNC0uMDEyem0uMDQ2LjE2IDMuNDcgMy40N2gtMi44NTNhLjYxNC42MTQgMCAwIDEgLS42MTgtLjYxOHptMCAwIiBmaWxsPSIjMjEyMTIxIi8+PHBhdGggZD0ibTMuMzA5IDEuNDAyYS43MDguNzA4IDAgMCAwIC0uNzExLjcwN3YxMS4yNDZjMCAuMzkxLjMxNi43MDcuNzEuNzA3aDguOTNjLjM5IDAgLjcwNy0uMzE2LjcwNy0uNzA3di04LjI3N2MwLS4wMTYtLjAwNC0uMDI3LS4wMTEtLjAzNWwtMy42My0zLjYyOWMtLjAwNy0uMDA4LS4wMTktLjAxMi0uMDM0LS4wMTJ6bTAgLjA5NGg1Ljk0MWwzLjYwMiAzLjYwMnY4LjI1N2MwIC4zNC0uMjc0LjYxNC0uNjE0LjYxNGgtOC45M2EuNjEzLjYxMyAwIDAgMSAtLjYxNy0uNjE0di0xMS4yNDVjMC0uMzQuMjc0LS42MTMuNjE4LS42MTN6bTAgMCIgZmlsbD0iIzIxMjEyMSIvPjxwYXRoIGQ9Im0yMDYuMzU1LTMxLjE0M2MtMi44NzQgMC01LjI1IDIuMzc1LTUuMjUgNS4zNzV2MTAxLjc0Yy0zLjM3NC0uNzUtNi44NzQtMS4yNS0xMC42MjMtMS4yNS0xNy42MjQgMC0zMS43NDcgOS41LTMxLjc0NyAyMS4yNDggMCAxMS42MjQgMTQuMTIzIDIxLjEyMyAzMS43NDcgMjEuMTIzIDE3LjYyMyAwIDMxLjc0Ny05LjQ5OSAzMS43NDctMjEuMTIzdi0xMDUuODY1aDc0LjExOHY2NC42MmMtMy4zNzUtLjc1LTYuODc1LTEuMTI2LTEwLjYyNC0xLjEyNi0xNy42MjQgMC0zMS43NDcgOS4zNzQtMzEuNzQ3IDIxLjEyMyAwIDExLjc1IDE0LjEyMyAyMS4yNDggMzEuNzQ3IDIxLjI0OCAxNy42MjMgMCAzMS43NDctOS40OTkgMzEuNzQ3LTIxLjI0OHYtMTAwLjQ5YzAtMy0yLjM3NS01LjM3NS01LjI1LTUuMzc1em0wIDAiIGZpbGw9InVybCgjbCkiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMiIHRyYW5zZm9ybT0ibWF0cml4KC4wMzEyNSAwIDAgLjAzMTI1IDAgNi43MikiLz48L3N2Zz4=");
617+}
618+
619+.icon-mp4, .icon-mkv, .icon-m4v, .icon-avi, .icon-ogv, .icon-mov {
620+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNiMzlkZGIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNkMWM0ZTkiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjE4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNDkuMTg5IiB4Mj0iMjQ5LjE4OSIgeGxpbms6aHJlZj0iI2EiIHkxPSIyMzMuNSIgeTI9IjI1NC42NjciLz48bGluZWFyR3JhZGllbnQgaWQ9ImoiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzdlNTdjMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzk1NzVjZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJrIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwLjU4MzM0Mzc1IiB4Mj0iMTAuNTgzMzQzNzUiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMTQuMDE2ODc1IiB5Mj0iMS40NDkxNTYyNSIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNDguNzA4IiB4Mj0iMjQ4LjcwOCIgeGxpbms6aHJlZj0iI2oiIHkxPSIyMzMuNSIgeTI9Ii0xNjguNjY3Ii8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmg3LjYxdi42NjRoLTcuNjF6bTAgMCIgZmlsbD0idXJsKCNiKSIvPjxwYXRoIGQ9Im0xMS41NzggMTQuMDE2aDEuMzJ2LS42NmgtMS4zMnptMCAwIiBmaWxsPSJ1cmwoI2QpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LjY2NGgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2UpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LS42NmgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2YpIi8+PHBhdGggZD0ibTExLjU3OCAxNC4wMTZoMS4zMnYuNjY0aC0xLjMyem0wIDAiIGZpbGw9InVybCgjZykiLz48cGF0aCBkPSJtOS4yNyAxLjQ1aC01Ljk2MWEuNjYuNjYgMCAwIDAgLS42NjQuNjZ2MTEuMjQ1YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtOC4yNzd6bTAgMCIgZmlsbD0idXJsKCNoKSIvPjxwYXRoIGQ9Im05LjI2MiA0LjU5aDMuMTRsLjQ5Ni40OTZ2LjY2aC0zLjYzNnptMCAwIiBmaWxsPSJ1cmwoI2kpIi8+PHBhdGggZD0ibTIuNjQ1IDEzLjA5OHYuMjU3YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtLjI1N2EuNjYuNjYgMCAwIDEgLS42Ni42NjRoLTguOTNhLjY2NC42NjQgMCAwIDEgLS42NjQtLjY2NHptMCAwIiBmaWxsLW9wYWNpdHk9Ii4yMzUiLz48cGF0aCBkPSJtMy4zMDkgMS40NWEuNjYuNjYgMCAwIDAgLS42NjQuNjZ2LjI1YzAtLjM2OC4yOTYtLjY2NS42NjQtLjY2NWg1Ljk2bDMuNjMgMy42M3YtLjI0N2wtMy42MzEtMy42Mjh6bTAgMCIgZmlsbD0iI2ZmZiIgZmlsbC1vcGFjaXR5PSIuMzkyIi8+PHBhdGggZD0ibTEyLjg5OCA1LjA3OC0zLjYyOC0zLjYyOHYyLjk2NWMwIC4zNjcuMjk2LjY2NC42NjQuNjY0em0wIDAiIGZpbGw9InVybCgjaykiLz48cGF0aCBkPSJtOS4yNyAxLjQ1di4yNDVsMy4zODIgMy4zODNoLjI0NnptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii4zOTIiLz48cGF0aCBkPSJtOS4yNyAxLjQwMmEuMDQ3LjA0NyAwIDAgMCAtLjA0Ny4wNDd2Mi45NjVjMCAuMzk1LjMxNi43MTEuNzEuNzExaDIuOTY1Yy4wMiAwIC4wNC0uMDEyLjA0My0uMDMxLjAwOC0uMDE2LjAwNC0uMDM1LS4wMDctLjA1MWwtMy42My0zLjYyOWMtLjAwNy0uMDA4LS4wMjMtLjAxMi0uMDM0LS4wMTJ6bS4wNDYuMTYgMy40NyAzLjQ3aC0yLjg1M2EuNjE0LjYxNCAwIDAgMSAtLjYxOC0uNjE4em0wIDAiIGZpbGw9IiMyMTIxMjEiLz48cGF0aCBkPSJtMy4zMDkgMS40MDJhLjcwOC43MDggMCAwIDAgLS43MTEuNzA3djExLjI0NmMwIC4zOTEuMzE2LjcwNy43MS43MDdoOC45M2MuMzkgMCAuNzA3LS4zMTYuNzA3LS43MDd2LTguMjc3YzAtLjAxNi0uMDA0LS4wMjctLjAxMS0uMDM1bC0zLjYzLTMuNjI5Yy0uMDA3LS4wMDgtLjAxOS0uMDEyLS4wMzQtLjAxMnptMCAuMDk0aDUuOTQxbDMuNjAyIDMuNjAydjguMjU3YzAgLjM0LS4yNzQuNjE0LS42MTQuNjE0aC04LjkzYS42MTMuNjEzIDAgMCAxIC0uNjE3LS42MTR2LTExLjI0NWMwLS4zNC4yNzQtLjYxMy42MTgtLjYxM3ptMCAwIiBmaWxsPSIjMjEyMTIxIi8+PHBhdGggZD0ibTIwMS4xMDYtMzEuMTQzYy01Ljg3NS4xMjUtMTAuNjI0IDQuNzUtMTAuNjI0IDEwLjYyNHYxMjYuOTg5YzAgOCA4LjYyNCAxMy4xMjQgMTUuNjIzIDkuMjVsMTE2LjQ5LTYzLjQ5NWM3LjM3My00IDcuMzczLTE0LjQ5OCAwLTE4LjQ5OGwtMTE2LjQ5LTYzLjQ5NGMtMS41LS44NzUtMy4yNS0xLjM3NS01LTEuMzc1em0wIDAiIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlOiMyMTIxMjE7ZmlsbDp1cmwoI2wpIiB0cmFuc2Zvcm09Im1hdHJpeCguMDMxMjUgMCAwIC4wMzEyNSAwIDYuNzIpIi8+PC9zdmc+");
621+}
622+
623+.icon-pdf {
624+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSI3LjkzNzUiIHgyPSI3LjkzNzUiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3Atb3BhY2l0eT0iLjMxNCIvPjxzdG9wIG9mZnNldD0iLjIyMiIgc3RvcC1vcGFjaXR5PSIuMjc1Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9IjAiLz48L2xpbmVhckdyYWRpZW50PjxyYWRpYWxHcmFkaWVudCBpZD0iYyIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAtLjA3MDMyIDAgMjUuMDczIDMxLjc5OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNiIi8+PHJhZGlhbEdyYWRpZW50IGlkPSJkIiBjeD0iNDUwLjkwOSIgY3k9IjE4OS41NzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMCAuMDM5MDcgLjA3MDMyIDAgLTkuMTk3IC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNiIi8+PHJhZGlhbEdyYWRpZW50IGlkPSJlIiBjeD0iNDUwLjkwOSIgY3k9IjE4OS41NzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMCAtLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYiIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC0uMDcwMzIgMCAyNS4wNzMgLTMuNzY0KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2IiLz48bGluZWFyR3JhZGllbnQgaWQ9ImciIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjAzNzY3IDAgMCAuMTA4NyAtMi45MjcgMTYuODIpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjMwNi45MTciIHgyPSIzMDUuOTU1IiB5MT0iLTI1Ljc5MiIgeTI9Ii0xNDEuNDE1Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlZjlhOWEiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmNkZDIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaCI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZWY1MzUwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZTU3MzczIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoNC41NTYxNCAwIDAgMTMuMTQ2MTggLTM1My45NzMgMjAzNC4xOCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTY1LjQ4NSIgeDI9IjE2NS40ODUiIHhsaW5rOmhyZWY9IiNoIiB5MT0iLTI1Ljc5MiIgeTI9Ii0xNDEuNDE1Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJqIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9Ijc2MCIgeDI9Ijc2MCIgeGxpbms6aHJlZj0iI2giIHkxPSIxNjk1LjExNyIgeTI9IjE3NS4xMTciLz48cGF0aCBkPSJtMy45NjkgMTQuMDE2aDcuOTM3di42NjRoLTcuOTM2em0wIDAiIGZpbGw9InVybCgjYSkiLz48cGF0aCBkPSJtMTEuOTA2IDE0LjAxNmgxLjMyNHYtLjY2aC0xLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2MpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LjY2NGgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2QpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LS42NmgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2UpIi8+PHBhdGggZD0ibTExLjkwNiAxNC4wMTZoMS4zMjR2LjY2NGgtMS4zMjR6bTAgMCIgZmlsbD0idXJsKCNmKSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ1aDkuMjYxYy4zNjQgMCAuNjYuMjk2LjY2LjY2djExLjI0NWEuNjYuNjYgMCAwIDEgLS42Ni42NmgtOS4yNmEuNjYuNjYgMCAwIDEgLS42NjQtLjY2di0xMS4yNDVjMC0uMzYzLjI5Ni0uNjYuNjY0LS42NnptMCAwIiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0ibTQwMC4xMiAxNzUuMjU5Yy00NC40MDYgMC04MC4zMDggMzUuNDMtODAuMzA4IDc5LjgzNXYxMzYwLjAyNmMwIDQ0LjQwNiAzNS45MDIgNzkuODM1IDgwLjMwNyA3OS44MzVoMTE5Ljk4OXYtMTUxOS42OTV6bTAgMCIgZmlsbD0idXJsKCNpKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMTEuMzM5IiB0cmFuc2Zvcm09Im1hdHJpeCguMDA4MjY5MDIgLjAwMDEyNzMgLS4wMDAxMjczIC4wMDgyNjkwMiAwIDApIi8+PHBhdGggZD0ibTMuMzA5IDEzLjk4YS42NjQuNjY0IDAgMCAxIC0uNjY0LS42NjR2LS4wODJjMCAuMzY4LjI5Ni42NjQuNjY0LjY2NGg5LjI2MWMuMzY0IDAgLjY2LS4yOTYuNjYtLjY2NHYuMDgyYS42NjMuNjYzIDAgMCAxIC0uNjYuNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjE5NiIvPjxwYXRoIGQ9Im02NzkuNzc4IDE3NS4yNTl2OTU5LjkwOGM1OS45OTQtMTYwLjE0MiAxMDAuMTQ4LTE2MC4xNDIgMTYwLjE0MiAwdi05NTkuOTA3em0wIDAiIGZpbGw9InVybCgjaikiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjExLjMzOSIgdHJhbnNmb3JtPSJtYXRyaXgoLjAwODI2OTAyIC4wMDAxMjczIC0uMDAwMTI3MyAuMDA4MjY5MDIgMCAwKSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ4NGEuNjY0LjY2NCAwIDAgMCAtLjY2NC42NjR2LjA4MmMwLS4zNjcuMjk2LS42NjQuNjY0LS42NjRoOS4yNjFjLjM2NCAwIC42Ni4yOTcuNjYuNjY0di0uMDgyYS42NjMuNjYzIDAgMCAwIC0uNjYtLjY2NHptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii4zOTIiLz48cGF0aCBkPSJtMy4zMDkgMS40MDJhLjcwOC43MDggMCAwIDAgLS43MTEuNzA3djExLjI0NmMwIC4zOTEuMzE2LjcwNy43MS43MDdoOS4yNjJjLjM5IDAgLjcwNy0uMzE2LjcwNy0uNzA3di0xMS4yNDVhLjcwNy43MDcgMCAwIDAgLS43MDctLjcwN3ptMCAuMDk0aDkuMjYxYy4zNCAwIC42MTQuMjc0LjYxNC42MTN2MTEuMjQ2YzAgLjM0LS4yNzQuNjE0LS42MTQuNjE0aC05LjI2YS42MTMuNjEzIDAgMCAxIC0uNjE4LS42MTR2LTExLjI0NWMwLS4zNC4yNzQtLjYxMy42MTgtLjYxM3ptMCAwIiBmaWxsPSIjMjEyMTIxIi8+PC9zdmc+");
625+}
626+
627+.icon-ppt, .icon-pptx {
628+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmFiOTEiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmNjYmMiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjE4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNDkuMTg5IiB4Mj0iMjQ5LjE4OSIgeGxpbms6aHJlZj0iI2EiIHkxPSIyMzMuNSIgeTI9IjI1NC42NjciLz48bGluZWFyR3JhZGllbnQgaWQ9ImoiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmNzA0MyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmOGE2NSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJrIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwLjU4MzM0Mzc1IiB4Mj0iMTAuNTgzMzQzNzUiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMTQuMDE2ODc1IiB5Mj0iMS40NDkxNTYyNSIvPjxmaWx0ZXIgaWQ9ImwiIGhlaWdodD0iMTAwJSIgd2lkdGg9IjEwMCUiIHg9IjAlIiB5PSIwJSI+PGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUdyYXBoaWMiIHZhbHVlcz0iMCAwIDAgMCAxIDAgMCAwIDAgMSAwIDAgMCAwIDEgMCAwIDAgMSAwIi8+PC9maWx0ZXI+PG1hc2sgaWQ9Im0iPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIgZmlsbC1vcGFjaXR5PSIuOTg4IiBmaWx0ZXI9InVybCgjbCkiLz48L21hc2s+PG1hc2sgaWQ9Im4iPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIgZmlsbC1vcGFjaXR5PSIuOTg4IiBmaWx0ZXI9InVybCgjbCkiLz48L21hc2s+PGxpbmVhckdyYWRpZW50IGlkPSJvIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9Ijk1OS45ODU2NDkyNiIgeDI9Ijk1OS45ODU2NDkyNiIgeGxpbms6aHJlZj0iI2oiIHkxPSIxNjk1LjA5NDAzNTUyIiB5Mj0iMTc1LjExMzE1NTk2Ii8+PGNsaXBQYXRoIGlkPSJwIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxtYXNrIGlkPSJxIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiIGZpbGwtb3BhY2l0eT0iLjk4OCIgZmlsdGVyPSJ1cmwoI2wpIi8+PC9tYXNrPjxsaW5lYXJHcmFkaWVudCBpZD0iciIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSI2My40OTY4IiB4Mj0iNjMuNDk2OCIgeGxpbms6aHJlZj0iI2oiIHkxPSIyMzMuNDk0ODkwMDgiIHkyPSItMTY0LjEzMTI3OTU2Ii8+PGNsaXBQYXRoIGlkPSJzIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0idCI+PHBhdGggZD0ibTAgMGgxNnYxNmgtMTZ6Ii8+PC9jbGlwUGF0aD48cGF0aCBkPSJtMy45NjkgMTQuMDE2aDcuNjF2LjY2NGgtNy42MXptMCAwIiBmaWxsPSJ1cmwoI2IpIi8+PHBhdGggZD0ibTExLjU3OCAxNC4wMTZoMS4zMnYtLjY2aC0xLjMyem0wIDAiIGZpbGw9InVybCgjZCkiLz48cGF0aCBkPSJtMy45NjkgMTQuMDE2aC0xLjMyNHYuNjY0aDEuMzI0em0wIDAiIGZpbGw9InVybCgjZSkiLz48cGF0aCBkPSJtMy45NjkgMTQuMDE2aC0xLjMyNHYtLjY2aDEuMzI0em0wIDAiIGZpbGw9InVybCgjZikiLz48cGF0aCBkPSJtMTEuNTc4IDE0LjAxNmgxLjMydi42NjRoLTEuMzJ6bTAgMCIgZmlsbD0idXJsKCNnKSIvPjxwYXRoIGQ9Im05LjI3IDEuNDVoLTUuOTYxYS42Ni42NiAwIDAgMCAtLjY2NC42NnYxMS4yNDVjMCAuMzY4LjI5Ni42Ni42NjQuNjZoOC45M2MuMzY2IDAgLjY2LS4yOTIuNjYtLjY2di04LjI3N3ptMCAwIiBmaWxsPSJ1cmwoI2gpIi8+PHBhdGggZD0ibTIuNjQ1IDEzLjA5OHYuMjU3YzAgLjM2OC4yOTYuNjYuNjY0LjY2aDguOTNjLjM2NiAwIC42Ni0uMjkyLjY2LS42NnYtLjI1N2EuNjYuNjYgMCAwIDEgLS42Ni42NjRoLTguOTNhLjY2NC42NjQgMCAwIDEgLS42NjQtLjY2NHptMCAwIiBmaWxsLW9wYWNpdHk9Ii4yMzUiLz48cGF0aCBkPSJtOS4yNjIgNC41OWgzLjE0bC40OTYuNDk2di42NmgtMy42MzZ6bTAgMCIgZmlsbD0idXJsKCNpKSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ1YS42Ni42NiAwIDAgMCAtLjY2NC42NnYuMjVjMC0uMzY4LjI5Ni0uNjY1LjY2NC0uNjY1aDUuOTZsMy42MyAzLjYzdi0uMjQ3bC0zLjYzMS0zLjYyOHptMCAwIiBmaWxsPSIjZjZmNmY2IiBmaWxsLW9wYWNpdHk9Ii41ODgiLz48cGF0aCBkPSJtMTIuODk4IDUuMDc4LTMuNjI4LTMuNjI4djIuOTY1YzAgLjM2Ny4yOTYuNjY0LjY2NC42NjR6bTAgMCIgZmlsbD0idXJsKCNrKSIvPjxwYXRoIGQ9Im05LjI3IDQuMjU4di4xNTZjMCAuMzY3LjI5Ni42NjQuNjY0LjY2NGgyLjk2NGwtLjE1Ni0uMTU2aC0yLjgwOGEuNjY0LjY2NCAwIDAgMSAtLjY2NC0uNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNSIvPjxwYXRoIGQ9Im05LjI3IDEuNDV2LjI0NWwzLjM4MiAzLjM4M2guMjQ2em0wIDAiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjM5MiIvPjxwYXRoIGQ9Im05LjI3IDEuNDAyYS4wNDcuMDQ3IDAgMCAwIC0uMDQ3LjA0N3YyLjk2NWMwIC4zOTUuMzE2LjcxMS43MS43MTFoMi45NjVjLjAyIDAgLjA0LS4wMTIuMDQzLS4wMzEuMDA4LS4wMTYuMDA0LS4wMzUtLjAwNy0uMDUxbC0zLjYzLTMuNjI5Yy0uMDA3LS4wMDgtLjAyMy0uMDEyLS4wMzQtLjAxMnptLjA0Ni4xNiAzLjQ3IDMuNDdoLTIuODUzYS42MTQuNjE0IDAgMCAxIC0uNjE4LS42MTh6bTAgMCIgZmlsbD0iIzIxMjEyMSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQwMmEuNzA4LjcwOCAwIDAgMCAtLjcxMS43MDd2MTEuMjQ2YzAgLjM5MS4zMTYuNzA3LjcxLjcwN2g4LjkzYy4zOSAwIC43MDctLjMxNi43MDctLjcwN3YtOC4yNzdjMC0uMDE2LS4wMDQtLjAyNy0uMDExLS4wMzVsLTMuNjMtMy42MjljLS4wMDctLjAwOC0uMDE5LS4wMTItLjAzNC0uMDEyem0wIC4wOTRoNS45NDFsMy42MDIgMy42MDJ2OC4yNTdjMCAuMzQtLjI3NC42MTQtLjYxNC42MTRoLTguOTNhLjYxMy42MTMgMCAwIDEgLS42MTctLjYxNHYtMTEuMjQ1YzAtLjM0LjI3NC0uNjEzLjYxOC0uNjEzem0wIDAiIGZpbGw9IiMyMTIxMjEiLz48ZyBjbGlwLXBhdGg9InVybCgjdCkiIG1hc2s9InVybCgjbSkiPjxnIGNsaXAtcGF0aD0idXJsKCNwKSIgbWFzaz0idXJsKCNuKSI+PHBhdGggZD0ibTkwOS44MzQgNzY4LjExNmMtMTcxLjQ4IDAtMzA5Ljg5MSAxMDguNjUtMzA5Ljg5MSAyNDMuMjg0IDAgMTM1LjEwNSAxMzguNDEyIDI0My43NTYgMzA5Ljg5MSAyNDMuNzU2IDE3MC41MzUgMCAzMDcuNTMtMTA2Ljc2MiAzMDkuODkyLTIzOS45NzdoLTI4OS41NzljLTEuNDE3IDAtMi44MzQgMC00LjI1MS0uNDczLTEuNDE3LS40NzItMi4zNjItLjQ3Mi0zLjc4LS45NDQtLjk0NC0uOTQ1LTIuMzYxLTEuNDE4LTMuMzA2LTEuODlsLTIuODM0LTIuODM0Yy0uOTQ1LS40NzMtMS44OS0xLjg5LTIuMzYyLTIuODM1LS45NDUtLjk0NS0xLjQxOC0xLjg5LTEuODktMy4zMDctLjQ3Mi0uOTQ0LS45NDUtMi4zNjItMS40MTctMy43NzkgMC0xLjQxNy0uNDczLTIuODM0LS40NzMtMy43Nzl6bTAgMCIgZmlsbD0idXJsKCNvKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMTEuMzM5IiB0cmFuc2Zvcm09Im1hdHJpeCguMDA4MjY5MDIgLjAwMDEyNzMgLS4wMDAxMjczIC4wMDgyNjkwMiAwIDApIi8+PC9nPjxnIGNsaXAtcGF0aD0idXJsKCNzKSIgbWFzaz0idXJsKCNxKSI+PHBhdGggZD0ibTI1Ni42LTI1Ljc2OHY2MC4xMmMwIC4zNzQuMTI1LjYyNC4xMjUgMSAuMTI1LjM3NC4yNS43NDkuMzc1Ljk5OS4xMjUuMzc1LjI1LjYyNS41Ljg3NS4xMjUuMzc1LjM3NS42MjUuNjI1Ljg3NXMuNS4zNzUuNzUuNjI1Yy4yNS4xMjUuNjI1LjM3NS44NzUuNS4zNzUuMTI1LjYyNS4yNSAxIC4yNS4zNzUuMTI1Ljc1LjEyNSAxLjEyNS4xMjVoNzYuNjE4YzAtLjM3NS4xMjUtLjYyNS4xMjUtMSAwLTM1LjYyMi0zNi42MjItNjQuMzctODIuMTE4LTY0LjM3em0wIDAiIGZpbGw9InVybCgjcikiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMiIHRyYW5zZm9ybT0ibWF0cml4KC4wMzEyNSAwIDAgLjAzMTI1IDAgNi43MikiLz48L2c+PC9nPjwvc3ZnPg==");
629+}
630+
631+.icon-srt {
632+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjcuOTM4MjYiIHgyPSI3LjkzODI2IiB5MT0iMTQuMDE2OTY4NSIgeTI9IjE0LjY3ODQ5MDUwMDAyIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3Atb3BhY2l0eT0iLjI3NDUxIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9IjAiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYiI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTM3MjYiLz48c3RvcCBvZmZzZXQ9Ii4yMjIyMjIiIHN0b3Atb3BhY2l0eT0iLjI3NDUxIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9IjAiLz48L2xpbmVhckdyYWRpZW50PjxyYWRpYWxHcmFkaWVudCBpZD0iYyIgY3g9IjQ1MC45MDg5MzYiIGN5PSIxODkuNTc5MzYxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDAwMDAwOSAtLjAzOTA2NjIgLS4wNzAzMTkyIC0uMDAwMDAwMDUgMjUuMDczMDQzIDMxLjc5NzY2NikiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjY2NTYiIHhsaW5rOmhyZWY9IiNiIi8+PHJhZGlhbEdyYWRpZW50IGlkPSJkIiBjeD0iNDUwLjkwODkzNiIgY3k9IjE4OS41NzkzNjEiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLS4wMDAwMDAwOSAuMDM5MDY2MiAuMDcwMzE5MiAuMDAwMDAwMDUgLTkuMTk2NTI0IC0zLjc2Mzc0KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NjY1NiIgeGxpbms6aHJlZj0iI2IiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImUiIGN4PSI0NTAuOTA4OTM2IiBjeT0iMTg5LjU3OTM2MSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtLjAwMDAwMDA5IC0uMDM5MDY2MiAuMDcwMzE5MiAtLjAwMDAwMDA1IC05LjE5NjUyNCAzMS43OTc2NjYpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY2NjU2IiB4bGluazpocmVmPSIjYiIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDg5MzYiIGN5PSIxODkuNTc5MzYxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDAwMDAwOSAuMDM5MDY2MiAtLjA3MDMxOTIgLjAwMDAwMDA1IDI1LjA3MzA0MiAtMy43NjM3NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjY2NTYiIHhsaW5rOmhyZWY9IiNiIi8+PGxpbmVhckdyYWRpZW50IGlkPSJnIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMzc2NzQ4IDAgMCAuMTA4NzA2IC0yLjkyNzAwNyAxNi44MjA2NzQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjMwNi45MTY2NTYiIHgyPSIzMDUuOTU0NTU5IiB5MT0iLTI1Ljc5MTY3NiIgeTI9Ii0xNDEuNDE0NTgxIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM4MGNiYzQiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNiMmRmZGIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaCI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMjZhNjlhIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNGRiNmFjIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoNC41NTYxNDMgMCAwIDEzLjE0NjE4NCAtMzUzLjk3MjY1NiAyMDM0LjE4MDE3NikiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTY1LjQ4NDg0OCIgeDI9IjE2NS40ODQ4NDgiIHhsaW5rOmhyZWY9IiNoIiB5MT0iLTI1Ljc5MTY3MiIgeTI9Ii0xNDEuNDE0NTgxIi8+PGxpbmVhckdyYWRpZW50IGlkPSJqIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY5MDIzMjUwMDMiIHgyPSI4LjI2OTAyMzI1MDAzIiB4bGluazpocmVmPSIjaCIgeTE9IjE0LjAxNjk2NzUiIHkyPSIxLjQ0ODA1MzAwMDAzIi8+PHBhdGggZD0ibTMuOTY4NzUgMTQuMDE1NjI1aDcuOTM3NXYuNjY0MDYzaC03LjkzNzV6bTAgMCIgZmlsbD0idXJsKCNhKSIvPjxwYXRoIGQ9Im0xMS45MDYyNSAxNC4wMTU2MjVoMS4zMjQyMTl2LS42NjAxNTZoLTEuMzI0MjE5em0wIDAiIGZpbGw9InVybCgjYykiLz48cGF0aCBkPSJtMy45Njg3NSAxNC4wMTU2MjVoLTEuMzI0MjE5di42NjQwNjNoMS4zMjQyMTl6bTAgMCIgZmlsbD0idXJsKCNkKSIvPjxwYXRoIGQ9Im0zLjk2ODc1IDE0LjAxNTYyNWgtMS4zMjQyMTl2LS42NjAxNTZoMS4zMjQyMTl6bTAgMCIgZmlsbD0idXJsKCNlKSIvPjxwYXRoIGQ9Im0xMS45MDYyNSAxNC4wMTU2MjVoMS4zMjQyMTl2LjY2NDA2M2gtMS4zMjQyMTl6bTAgMCIgZmlsbD0idXJsKCNmKSIvPjxwYXRoIGQ9Im0zLjMwODU5NCAxLjQ0OTIxOWg5LjI2MTcxOGMuMzYzMjgyIDAgLjY2MDE1Ny4yOTY4NzUuNjYwMTU3LjY2MDE1NnYxMS4yNDYwOTRjMCAuMzY3MTg3LS4yOTY4NzUuNjYwMTU2LS42NjAxNTcuNjYwMTU2aC05LjI2MTcxOGMtLjM2NzE4OCAwLS42NjQwNjMtLjI5Mjk2OS0uNjY0MDYzLS42NjAxNTZ2LTExLjI0NjA5NGMwLS4zNjMyODEuMjk2ODc1LS42NjAxNTYuNjY0MDYzLS42NjAxNTZ6bTAgMCIgZmlsbD0idXJsKCNnKSIvPjxwYXRoIGQ9Im00MDAuMTE5MjkyIDE3NS4yNTg4NjNjLTQ0LjQwNTIxMSAwLTgwLjMwNzI5NiAzNS40Mjk2OS04MC4zMDcyOTYgNzkuODM0OXYxMzYwLjAyNzY3NmMwIDQ0LjQwNTIxMSAzNS45MDIwODUgNzkuODM0OSA4MC4zMDcyOTYgNzkuODM0OWgxMTkuOTg4NTQ4di0xNTE5LjY5NzQ3NnptMCAwIiBmaWxsPSJ1cmwoI2kpIiBzdHJva2U9IiMyMTIxMjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxMS4zMzg1ODMiIHRyYW5zZm9ybT0ic2NhbGUoLjAwODI2OTAyKSIvPjxwYXRoIGQ9Im0zLjMwODU5NCAxMy45ODA0NjljLS4zNjcxODggMC0uNjY0MDYzLS4yOTY4NzUtLjY2NDA2My0uNjY0MDYzdi0uMDgyMDMxYzAgLjM2NzE4Ny4yOTY4NzUuNjY0MDYzLjY2NDA2My42NjQwNjNoOS4yNjE3MThjLjM2MzI4MiAwIC42NjAxNTctLjI5Njg3Ni42NjAxNTctLjY2NDA2M3YuMDgyMDMxYzAgLjM2NzE4OC0uMjk2ODc1LjY2NDA2My0uNjYwMTU3LjY2NDA2M3ptMCAwIiBmaWxsLW9wYWNpdHk9Ii4xOTYwNzgiLz48cGF0aCBkPSJtMy4zMDg1OTQgMS40ODQzNzVjLS4zNjcxODggMC0uNjY0MDYzLjI5Njg3NS0uNjY0MDYzLjY2NDA2M3YuMDgyMDMxYzAtLjM2NzE4OC4yOTY4NzUtLjY2NDA2My42NjQwNjMtLjY2NDA2M2g5LjI2MTcxOGMuMzYzMjgyIDAgLjY2MDE1Ny4yOTY4NzUuNjYwMTU3LjY2NDA2M3YtLjA4MjAzMWMwLS4zNjcxODgtLjI5Njg3NS0uNjY0MDYzLS42NjAxNTctLjY2NDA2M3ptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii4zOTIxNTciLz48cGF0aCBkPSJtMy4zMDg1OTQgMS40MDIzNDRjLS4zOTQ1MzIgMC0uNzEwOTM4LjMxNjQwNi0uNzEwOTM4LjcwNzAzMXYxMS4yNDYwOTRjMCAuMzkwNjI1LjMxNjQwNi43MDcwMzEuNzEwOTM4LjcwNzAzMWg5LjI2MTcxOGMuMzkwNjI2IDAgLjcwNzAzMi0uMzE2NDA2LjcwNzAzMi0uNzA3MDMxdi0xMS4yNDYwOTRjMC0uMzkwNjI1LS4zMTY0MDYtLjcwNzAzMS0uNzA3MDMyLS43MDcwMzF6bTAgLjA5Mzc1aDkuMjYxNzE4Yy4zMzk4NDQgMCAuNjEzMjgyLjI3MzQzNy42MTMyODIuNjEzMjgxdjExLjI0NjA5NGMwIC4zMzk4NDMtLjI3MzQzOC42MTMyODEtLjYxMzI4Mi42MTMyODFoLTkuMjYxNzE4Yy0uMzQzNzUgMC0uNjE3MTg4LS4yNzM0MzgtLjYxNzE4OC0uNjEzMjgxdi0xMS4yNDYwOTRjMC0uMzM5ODQ0LjI3MzQzOC0uNjEzMjgxLjYxNzE4OC0uNjEzMjgxem0wIDAiIGZpbGw9IiMyMTIxMjEiLz48cGF0aCBkPSJtNi4xMTcxODggNC43NTM5MDZjLS4yNzM0MzggMC0uNDk2MDk0LjIyMjY1Ni0uNDk2MDk0LjQ5NjA5NHYzLjY0MDYyNWMwIC4yNzM0MzcuMjIyNjU2LjQ5NjA5NC40OTYwOTQuNDk2MDk0aDMuNjcxODc0bDEuMjIyNjU3IDEuMjI2NTYyYy4yMDcwMzEuMjA3MDMxLjU2MjUuMDU4NTk0LjU2MjUtLjIzNDM3NXYtMS4wMTk1MzFjLjE5NTMxMi0uMDY2NDA2LjMzMjAzMS0uMjUuMzMyMDMxLS40Njg3NXYtMy42NDA2MjVjMC0uMjczNDM4LS4yMTg3NS0uNDk2MDk0LS40OTYwOTQtLjQ5NjA5NHptMCAwIiBmaWxsPSJ1cmwoI2opIi8+PHBhdGggZD0ibTYuMTE3MTg4IDQuNzUzOTA2Yy0uMjczNDM4IDAtLjQ5NjA5NC4yMjI2NTYtLjQ5NjA5NC40OTYwOTR2LjE2Nzk2OWMwLS4yNzczNDQuMjIyNjU2LS40OTYwOTQuNDk2MDk0LS40OTYwOTRoNS4yOTI5NjhjLjI3NzM0NCAwIC40OTYwOTQuMjE4NzUuNDk2MDk0LjQ5NjA5NHYtLjE2Nzk2OWMwLS4yNzM0MzgtLjIxODc1LS40OTYwOTQtLjQ5NjA5NC0uNDk2MDk0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNTI5NCIvPjxwYXRoIGQ9Im01LjYyMTA5NCA4LjkxNzk2OXYuMTM2NzE5YzAgLjI3NzM0My4yMjI2NTYuNDk2MDkzLjQ5NjA5NC40OTYwOTNoMy42NzE4NzRsMS4yMjI2NTcgMS4yMjY1NjNjLjIwNzAzMS4yMDcwMzEuNTYyNS4wNTg1OTQuNTYyNS0uMjM0Mzc1di0uMTM2NzE5YzAgLjI5Mjk2OS0uMzU1NDY5LjQ0MTQwNi0uNTYyNS4yMzQzNzVsLTEuMjIyNjU3LTEuMjI2NTYzaC0zLjY3MTg3NGMtLjI3MzQzOCAwLS40OTYwOTQtLjIyMjY1Ni0uNDk2MDk0LS40OTYwOTN6bTYuMjg1MTU2IDBjMCAuMjE4NzUtLjEzNjcxOS40MDIzNDMtLjMzMjAzMS40Njg3NXYuMTM2NzE5Yy4xOTUzMTItLjA2NjQwNy4zMzIwMzEtLjI1LjMzMjAzMS0uNDY4NzV6bTAgMCIgZmlsbD0iI2ZmZiIgZmlsbC1vcGFjaXR5PSIuMzkyMTU3Ii8+PHBhdGggZD0ibTE5NS43MzEzMDktNjIuODg5NTI2Yy04Ljc0OTE2NSAwLTE1Ljg3MzQ4NSA3LjEyNDMyLTE1Ljg3MzQ4NSAxNS44NzM0ODR2MTE2LjQ4ODg3NmMwIDguNzQ5MTY1IDcuMTI0MzIgMTUuODczNDg0IDE1Ljg3MzQ4NSAxNS44NzM0ODRoMTE3LjQ4ODc4bDM5LjEyMTI2NCAzOS4yNDYyNTNjNi42MjQzNjcgNi42MjQzNjcgMTcuOTk4MjgxIDEuODc0ODIgMTcuOTk4MjgxLTcuNDk5Mjg0di0zMi42MjE4ODVjNi4yNDk0MDQtMi4xMjQ3OTcgMTAuNjIzOTg2LTcuOTk5MjM2IDEwLjYyMzk4Ni0xNC45OTg1Njh2LTExNi40ODg4NzZjMC04Ljc0OTE2NC02Ljk5OTMzMi0xNS44NzM0ODQtMTUuODczNDg0LTE1Ljg3MzQ4NHptMCAwIiBmaWxsPSJub25lIiBzdHJva2U9IiMyMTIxMjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiB0cmFuc2Zvcm09Im1hdHJpeCguMDMxMjUzIDAgMCAuMDMxMjUzIDAgNi43MTkzOTIpIi8+PC9zdmc+");
633+}
634+
635+.icon-xls, .icon-xlsx, .icon-csv {
636+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjguMjY3NSIgeDI9IjguMjY3NSIgeGxpbms6aHJlZj0iI2EiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSIvPjxsaW5lYXJHcmFkaWVudCBpZD0iYyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4zMTQiLz48c3RvcCBvZmZzZXQ9Ii4yMjIiIHN0b3Atb3BhY2l0eT0iLjI3NSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImQiIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC0uMDM5MDcgLS4wNzAzMiAwIDI0Ljc0MiAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZSIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAtMy43NjQpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAuMDcwMzIgMCAtOS4xOTcgMzEuNzk4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2MiLz48cmFkaWFsR3JhZGllbnQgaWQ9ImciIGN4PSI0NTAuOTA5IiBjeT0iMTg5LjU3OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC4wMzkwNyAtLjA3MDMyIDAgMjQuNzQyIC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJoIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4wMDgyNjkwMiAuMDAwMTI3MyAtLjAwMDEyNzMgLjAwODI2OTAyIDAgMCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTQwIiB4Mj0iOTQwIiB5MT0iMTY5NS4xMTgiIHkyPSIxNzUuMTE4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM4MGNiYzQiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNiMmRmZGIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCguMDE0OTUgMCAwIC4wNTQ3IDcuNTIxIC04LjE4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIyNDkuMTg5IiB4Mj0iMjQ5LjE4OSIgeGxpbms6aHJlZj0iI2EiIHkxPSIyMzMuNSIgeTI9IjI1NC42NjciLz48bGluZWFyR3JhZGllbnQgaWQ9ImoiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzI2YTY5YSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzRkYjZhYyIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJrIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwLjU4MzM0Mzc1IiB4Mj0iMTAuNTgzMzQzNzUiIHhsaW5rOmhyZWY9IiNqIiB5MT0iMTQuMDE2ODc1IiB5Mj0iMS40NDkxNTYyNSIvPjxmaWx0ZXIgaWQ9ImwiIGhlaWdodD0iMTAwJSIgd2lkdGg9IjEwMCUiIHg9IjAlIiB5PSIwJSI+PGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUdyYXBoaWMiIHZhbHVlcz0iMCAwIDAgMCAxIDAgMCAwIDAgMSAwIDAgMCAwIDEgMCAwIDAgMSAwIi8+PC9maWx0ZXI+PG1hc2sgaWQ9Im0iPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIgZmlsbC1vcGFjaXR5PSIuOTg4IiBmaWx0ZXI9InVybCgjbCkiLz48L21hc2s+PGxpbmVhckdyYWRpZW50IGlkPSJuIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjI0OC43MDgiIHgyPSIyNDguNzA4IiB4bGluazpocmVmPSIjaiIgeTE9IjIzMy41IiB5Mj0iLTE2OC42NjciLz48Y2xpcFBhdGggaWQ9Im8iPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIvPjwvY2xpcFBhdGg+PG1hc2sgaWQ9InAiPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIgZmlsbC1vcGFjaXR5PSIuOTg4IiBmaWx0ZXI9InVybCgjbCkiLz48L21hc2s+PGNsaXBQYXRoIGlkPSJxIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxtYXNrIGlkPSJyIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiIGZpbGwtb3BhY2l0eT0iLjk4OCIgZmlsdGVyPSJ1cmwoI2wpIi8+PC9tYXNrPjxjbGlwUGF0aCBpZD0icyI+PHBhdGggZD0ibTAgMGgxNnYxNmgtMTZ6Ii8+PC9jbGlwUGF0aD48bWFzayBpZD0idCI+PHBhdGggZD0ibTAgMGgxNnYxNmgtMTZ6IiBmaWxsLW9wYWNpdHk9Ii45ODgiIGZpbHRlcj0idXJsKCNsKSIvPjwvbWFzaz48Y2xpcFBhdGggaWQ9InUiPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIvPjwvY2xpcFBhdGg+PG1hc2sgaWQ9InYiPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIgZmlsbC1vcGFjaXR5PSIuOTg4IiBmaWx0ZXI9InVybCgjbCkiLz48L21hc2s+PGNsaXBQYXRoIGlkPSJ3Ij48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxtYXNrIGlkPSJ4Ij48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiIGZpbGwtb3BhY2l0eT0iLjk4OCIgZmlsdGVyPSJ1cmwoI2wpIi8+PC9tYXNrPjxjbGlwUGF0aCBpZD0ieSI+PHBhdGggZD0ibTAgMGgxNnYxNmgtMTZ6Ii8+PC9jbGlwUGF0aD48bWFzayBpZD0ieiI+PHBhdGggZD0ibTAgMGgxNnYxNmgtMTZ6IiBmaWxsLW9wYWNpdHk9Ii45ODgiIGZpbHRlcj0idXJsKCNsKSIvPjwvbWFzaz48Y2xpcFBhdGggaWQ9IkEiPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIvPjwvY2xpcFBhdGg+PG1hc2sgaWQ9IkIiPjxwYXRoIGQ9Im0wIDBoMTZ2MTZoLTE2eiIgZmlsbC1vcGFjaXR5PSIuOTg4IiBmaWx0ZXI9InVybCgjbCkiLz48L21hc2s+PGNsaXBQYXRoIGlkPSJDIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxtYXNrIGlkPSJEIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiIGZpbGwtb3BhY2l0eT0iLjk4OCIgZmlsdGVyPSJ1cmwoI2wpIi8+PC9tYXNrPjxjbGlwUGF0aCBpZD0iRSI+PHBhdGggZD0ibTAgMGgxNnYxNmgtMTZ6Ii8+PC9jbGlwUGF0aD48cGF0aCBkPSJtMy45NjkgMTQuMDE2aDcuNjF2LjY2NGgtNy42MXptMCAwIiBmaWxsPSJ1cmwoI2IpIi8+PHBhdGggZD0ibTExLjU3OCAxNC4wMTZoMS4zMnYtLjY2aC0xLjMyem0wIDAiIGZpbGw9InVybCgjZCkiLz48cGF0aCBkPSJtMy45NjkgMTQuMDE2aC0xLjMyNHYuNjY0aDEuMzI0em0wIDAiIGZpbGw9InVybCgjZSkiLz48cGF0aCBkPSJtMy45NjkgMTQuMDE2aC0xLjMyNHYtLjY2aDEuMzI0em0wIDAiIGZpbGw9InVybCgjZikiLz48cGF0aCBkPSJtMTEuNTc4IDE0LjAxNmgxLjMydi42NjRoLTEuMzJ6bTAgMCIgZmlsbD0idXJsKCNnKSIvPjxwYXRoIGQ9Im05LjI3IDEuNDVoLTUuOTYxYS42Ni42NiAwIDAgMCAtLjY2NC42NnYxMS4yNDVjMCAuMzY4LjI5Ni42Ni42NjQuNjZoOC45M2MuMzY2IDAgLjY2LS4yOTIuNjYtLjY2di04LjI3N3ptMCAwIiBmaWxsPSJ1cmwoI2gpIi8+PHBhdGggZD0ibTkuMjYyIDQuNTloMy4xNGwuNDk2LjQ5NnYuNjZoLTMuNjM2em0wIDAiIGZpbGw9InVybCgjaSkiLz48cGF0aCBkPSJtMi42NDUgMTMuMDk4di4yNTdjMCAuMzY4LjI5Ni42Ni42NjQuNjZoOC45M2MuMzY2IDAgLjY2LS4yOTIuNjYtLjY2di0uMjU3YS42Ni42NiAwIDAgMSAtLjY2LjY2NGgtOC45M2EuNjY0LjY2NCAwIDAgMSAtLjY2NC0uNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ1YS42Ni42NiAwIDAgMCAtLjY2NC42NnYuMjVjMC0uMzY4LjI5Ni0uNjY1LjY2NC0uNjY1aDUuOTZsMy42MyAzLjYzdi0uMjQ3bC0zLjYzMS0zLjYyOHptMCAwIiBmaWxsPSIjZjZmNmY2IiBmaWxsLW9wYWNpdHk9Ii41ODgiLz48cGF0aCBkPSJtMTIuODk4IDUuMDc4LTMuNjI4LTMuNjI4djIuOTY1YzAgLjM2Ny4yOTYuNjY0LjY2NC42NjR6bTAgMCIgZmlsbD0idXJsKCNrKSIvPjxwYXRoIGQ9Im05LjI3IDQuMjU4di4xNTZjMCAuMzY3LjI5Ni42NjQuNjY0LjY2NGgyLjk2NGwtLjE1Ni0uMTU2aC0yLjgwOGEuNjY0LjY2NCAwIDAgMSAtLjY2NC0uNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjIzNSIvPjxwYXRoIGQ9Im05LjI3IDEuNDV2LjI0NWwzLjM4MiAzLjM4M2guMjQ2em0wIDAiIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iLjM5MiIvPjxwYXRoIGQ9Im05LjI3IDEuNDAyYS4wNDcuMDQ3IDAgMCAwIC0uMDQ3LjA0N3YyLjk2NWMwIC4zOTUuMzE2LjcxMS43MS43MTFoMi45NjVjLjAyIDAgLjA0LS4wMTIuMDQzLS4wMzEuMDA4LS4wMTYuMDA0LS4wMzUtLjAwNy0uMDUxbC0zLjYzLTMuNjI5Yy0uMDA3LS4wMDgtLjAyMy0uMDEyLS4wMzQtLjAxMnptLjA0Ni4xNiAzLjQ3IDMuNDdoLTIuODUzYS42MTQuNjE0IDAgMCAxIC0uNjE4LS42MTh6bTAgMCIgZmlsbD0iIzIxMjEyMSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQwMmEuNzA4LjcwOCAwIDAgMCAtLjcxMS43MDd2MTEuMjQ2YzAgLjM5MS4zMTYuNzA3LjcxLjcwN2g4LjkzYy4zOSAwIC43MDctLjMxNi43MDctLjcwN3YtOC4yNzdjMC0uMDE2LS4wMDQtLjAyNy0uMDExLS4wMzVsLTMuNjMtMy42MjljLS4wMDctLjAwOC0uMDE5LS4wMTItLjAzNC0uMDEyem0wIC4wOTRoNS45NDFsMy42MDIgMy42MDJ2OC4yNTdjMCAuMzQtLjI3NC42MTQtLjYxNC42MTRoLTguOTNhLjYxMy42MTMgMCAwIDEgLS42MTctLjYxNHYtMTEuMjQ1YzAtLjM0LjI3NC0uNjEzLjYxOC0uNjEzem0wIDAiIGZpbGw9IiMyMTIxMjEiLz48ZyBjbGlwLXBhdGg9InVybCgjbykiIG1hc2s9InVybCgjbSkiPjxwYXRoIGQ9Im0xNjMuOTg0LTMxLjE0M2gzNy4xMjJjMi44NzUgMCA1LjI1IDIuMzc1IDUuMjUgNS4zNzV2MjYuMzcyYzAgMy0yLjM3NSA1LjM3NS01LjI1IDUuMzc1aC0zNy4xMjJjLTIuODc0IDAtNS4yNS0yLjM3NS01LjI1LTUuMzc1di0yNi4zNzJjMC0zIDIuMzc2LTUuMzc1IDUuMjUtNS4zNzV6bTAgMCIgZmlsbD0idXJsKCNuKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMyIgdHJhbnNmb3JtPSJtYXRyaXgoLjAzMTI1IDAgMCAuMDMxMjUgMCA2LjcyKSIvPjwvZz48ZyBjbGlwLXBhdGg9InVybCgjcSkiIG1hc2s9InVybCgjcCkiPjxwYXRoIGQ9Im0yMzAuMjI4LTMxLjE0M2gzNi45OTZjMi44NzUgMCA1LjI1IDIuMzc1IDUuMjUgNS4zNzV2MjYuMzcyYzAgMy0yLjM3NSA1LjM3NS01LjI1IDUuMzc1aC0zNi45OTZjLTMgMC01LjM3NC0yLjM3NS01LjM3NC01LjM3NXYtMjYuMzcyYzAtMyAyLjM3NC01LjM3NSA1LjM3NC01LjM3NXptMCAwIiBmaWxsPSJ1cmwoI24pIiBzdHJva2U9IiMyMTIxMjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiB0cmFuc2Zvcm09Im1hdHJpeCguMDMxMjUgMCAwIC4wMzEyNSAwIDYuNzIpIi8+PC9nPjxnIGNsaXAtcGF0aD0idXJsKCNzKSIgbWFzaz0idXJsKCNyKSI+PHBhdGggZD0ibTI5Ni4zNDctMzEuMTQzaDM2Ljk5NmMzIDAgNS4zNzUgMi4zNzUgNS4zNzUgNS4zNzV2MjYuMzcyYzAgMy0yLjM3NSA1LjM3NS01LjM3NSA1LjM3NWgtMzYuOTk2Yy0yLjg3NSAwLTUuMjUtMi4zNzUtNS4yNS01LjM3NXYtMjYuMzcyYzAtMyAyLjM3NS01LjM3NSA1LjI1LTUuMzc1em0wIDAiIGZpbGw9InVybCgjbikiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMiIHRyYW5zZm9ybT0ibWF0cml4KC4wMzEyNSAwIDAgLjAzMTI1IDAgNi43MikiLz48L2c+PGcgY2xpcC1wYXRoPSJ1cmwoI3UpIiBtYXNrPSJ1cmwoI3QpIj48cGF0aCBkPSJtMTYzLjk4NCA4MC4wOTdoMzcuMTIyYzIuODc1IDAgNS4yNSAyLjM3NSA1LjI1IDUuMjV2MjYuNDk3YzAgMi44NzUtMi4zNzUgNS4yNS01LjI1IDUuMjVoLTM3LjEyMmMtMi44NzQgMC01LjI1LTIuMzc1LTUuMjUtNS4yNXYtMjYuNDk4YzAtMi44NzQgMi4zNzYtNS4yNSA1LjI1LTUuMjV6bTAgMCIgZmlsbD0idXJsKCNuKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMyIgdHJhbnNmb3JtPSJtYXRyaXgoLjAzMTI1IDAgMCAuMDMxMjUgMCA2LjcyKSIvPjwvZz48ZyBjbGlwLXBhdGg9InVybCgjdykiIG1hc2s9InVybCgjdikiPjxwYXRoIGQ9Im0yMzAuMjI4IDgwLjA5N2gzNi45OTZjMi44NzUgMCA1LjI1IDIuMzc1IDUuMjUgNS4yNXYyNi40OTdjMCAyLjg3NS0yLjM3NSA1LjI1LTUuMjUgNS4yNWgtMzYuOTk2Yy0zIDAtNS4zNzQtMi4zNzUtNS4zNzQtNS4yNXYtMjYuNDk4YzAtMi44NzQgMi4zNzQtNS4yNSA1LjM3NC01LjI1em0wIDAiIGZpbGw9InVybCgjbikiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMiIHRyYW5zZm9ybT0ibWF0cml4KC4wMzEyNSAwIDAgLjAzMTI1IDAgNi43MikiLz48L2c+PGcgY2xpcC1wYXRoPSJ1cmwoI3kpIiBtYXNrPSJ1cmwoI3gpIj48cGF0aCBkPSJtMjk2LjM0NyA4MC4wOTdoMzYuOTk2YzMgMCA1LjM3NSAyLjM3NSA1LjM3NSA1LjI1djI2LjQ5N2MwIDIuODc1LTIuMzc1IDUuMjUtNS4zNzUgNS4yNWgtMzYuOTk2Yy0yLjg3NSAwLTUuMjUtMi4zNzUtNS4yNS01LjI1di0yNi40OThjMC0yLjg3NCAyLjM3NS01LjI1IDUuMjUtNS4yNXptMCAwIiBmaWxsPSJ1cmwoI24pIiBzdHJva2U9IiMyMTIxMjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiB0cmFuc2Zvcm09Im1hdHJpeCguMDMxMjUgMCAwIC4wMzEyNSAwIDYuNzIpIi8+PC9nPjxnIGNsaXAtcGF0aD0idXJsKCNBKSIgbWFzaz0idXJsKCN6KSI+PHBhdGggZD0ibTE2My45ODQgMjQuNDc3aDM3LjEyMmMyLjg3NSAwIDUuMjUgMi4zNzUgNS4yNSA1LjI1djI2LjQ5N2MwIDIuODc1LTIuMzc1IDUuMjUtNS4yNSA1LjI1aC0zNy4xMjJjLTIuODc0IDAtNS4yNS0yLjM3NS01LjI1LTUuMjV2LTI2LjQ5N2MwLTIuODc1IDIuMzc2LTUuMjUgNS4yNS01LjI1em0wIDAiIGZpbGw9InVybCgjbikiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMiIHRyYW5zZm9ybT0ibWF0cml4KC4wMzEyNSAwIDAgLjAzMTI1IDAgNi43MikiLz48L2c+PGcgY2xpcC1wYXRoPSJ1cmwoI0MpIiBtYXNrPSJ1cmwoI0IpIj48cGF0aCBkPSJtMjMwLjIyOCAyNC40NzdoMzYuOTk2YzIuODc1IDAgNS4yNSAyLjM3NSA1LjI1IDUuMjV2MjYuNDk3YzAgMi44NzUtMi4zNzUgNS4yNS01LjI1IDUuMjVoLTM2Ljk5NmMtMyAwLTUuMzc0LTIuMzc1LTUuMzc0LTUuMjV2LTI2LjQ5N2MwLTIuODc1IDIuMzc0LTUuMjUgNS4zNzQtNS4yNXptMCAwIiBmaWxsPSJ1cmwoI24pIiBzdHJva2U9IiMyMTIxMjEiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzIiB0cmFuc2Zvcm09Im1hdHJpeCguMDMxMjUgMCAwIC4wMzEyNSAwIDYuNzIpIi8+PC9nPjxnIGNsaXAtcGF0aD0idXJsKCNFKSIgbWFzaz0idXJsKCNEKSI+PHBhdGggZD0ibTI5Ni4zNDcgMjQuNDc3aDM2Ljk5NmMzIDAgNS4zNzUgMi4zNzUgNS4zNzUgNS4yNXYyNi40OTdjMCAyLjg3NS0yLjM3NSA1LjI1LTUuMzc1IDUuMjVoLTM2Ljk5NmMtMi44NzUgMC01LjI1LTIuMzc1LTUuMjUtNS4yNXYtMjYuNDk3YzAtMi44NzUgMi4zNzUtNS4yNSA1LjI1LTUuMjV6bTAgMCIgZmlsbD0idXJsKCNuKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMyIgdHJhbnNmb3JtPSJtYXRyaXgoLjAzMTI1IDAgMCAuMDMxMjUgMCA2LjcyKSIvPjwvZz48L3N2Zz4=");
637+}
638+
639+.icon-zip, .icon-tar, .icon-gz, .icon-bz2, .icon-rar, .icon-7z {
640+ background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2cHQiIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2cHQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSI3LjkzNzUiIHgyPSI3LjkzNzUiIHkxPSIxNC4wMTY4NzUiIHkyPSIxNC42NzgzNDM3NSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLW9wYWNpdHk9Ii4yNzUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3Atb3BhY2l0eT0iLjMxNCIvPjxzdG9wIG9mZnNldD0iLjIyMiIgc3RvcC1vcGFjaXR5PSIuMjc1Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9IjAiLz48L2xpbmVhckdyYWRpZW50PjxyYWRpYWxHcmFkaWVudCBpZD0iYyIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLS4wMzkwNyAtLjA3MDMyIDAgMjUuMDczIDMxLjc5OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNiIi8+PHJhZGlhbEdyYWRpZW50IGlkPSJkIiBjeD0iNDUwLjkwOSIgY3k9IjE4OS41NzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMCAuMDM5MDcgLjA3MDMyIDAgLTkuMTk3IC0zLjc2NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiByPSIyMS4xNjciIHhsaW5rOmhyZWY9IiNiIi8+PHJhZGlhbEdyYWRpZW50IGlkPSJlIiBjeD0iNDUwLjkwOSIgY3k9IjE4OS41NzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMCAtLjAzOTA3IC4wNzAzMiAwIC05LjE5NyAzMS43OTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMjEuMTY3IiB4bGluazpocmVmPSIjYiIvPjxyYWRpYWxHcmFkaWVudCBpZD0iZiIgY3g9IjQ1MC45MDkiIGN5PSIxODkuNTc5IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLjAzOTA3IC0uMDcwMzIgMCAyNS4wNzMgLTMuNzY0KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjIxLjE2NyIgeGxpbms6aHJlZj0iI2IiLz48bGluZWFyR3JhZGllbnQgaWQ9ImciIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjAzNzY3IDAgMCAuMTA4NyAtMi45MjcgMTYuODIpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjMwNi45MTciIHgyPSIzMDUuOTU1IiB5MT0iLTI1Ljc5MiIgeTI9Ii0xNDEuNDE1Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmNjODAiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmUwYjIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaCI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZhNzI2Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZiNzRkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoNC41NTYxNCAwIDAgMTMuMTQ2MTggLTM1My45NzMgMjAzNC4xOCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTY1LjQ4NSIgeDI9IjE2NS40ODUiIHhsaW5rOmhyZWY9IiNoIiB5MT0iLTI1Ljc5MiIgeTI9Ii0xNDEuNDE1Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJqIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9Ijc2MCIgeDI9Ijc2MCIgeGxpbms6aHJlZj0iI2giIHkxPSIxNjk1LjExNyIgeTI9IjE3NS4xMTciLz48cGF0aCBkPSJtMy45NjkgMTQuMDE2aDcuOTM3di42NjRoLTcuOTM2em0wIDAiIGZpbGw9InVybCgjYSkiLz48cGF0aCBkPSJtMTEuOTA2IDE0LjAxNmgxLjMyNHYtLjY2aC0xLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2MpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LjY2NGgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2QpIi8+PHBhdGggZD0ibTMuOTY5IDE0LjAxNmgtMS4zMjR2LS42NmgxLjMyNHptMCAwIiBmaWxsPSJ1cmwoI2UpIi8+PHBhdGggZD0ibTExLjkwNiAxNC4wMTZoMS4zMjR2LjY2NGgtMS4zMjR6bTAgMCIgZmlsbD0idXJsKCNmKSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ1aDkuMjYxYy4zNjQgMCAuNjYuMjk2LjY2LjY2djExLjI0NWEuNjYuNjYgMCAwIDEgLS42Ni42NmgtOS4yNmEuNjYuNjYgMCAwIDEgLS42NjQtLjY2di0xMS4yNDVjMC0uMzYzLjI5Ni0uNjYuNjY0LS42NnptMCAwIiBmaWxsPSJ1cmwoI2cpIi8+PHBhdGggZD0ibTQwMC4xMiAxNzUuMjU5Yy00NC40MDYgMC04MC4zMDggMzUuNDMtODAuMzA4IDc5LjgzNXYxMzYwLjAyNmMwIDQ0LjQwNiAzNS45MDIgNzkuODM1IDgwLjMwNyA3OS44MzVoMTE5Ljk4OXYtMTUxOS42OTV6bTAgMCIgZmlsbD0idXJsKCNpKSIgc3Ryb2tlPSIjMjEyMTIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMTEuMzM5IiB0cmFuc2Zvcm09Im1hdHJpeCguMDA4MjY5MDIgLjAwMDEyNzMgLS4wMDAxMjczIC4wMDgyNjkwMiAwIDApIi8+PHBhdGggZD0ibTMuMzA5IDEzLjk4YS42NjQuNjY0IDAgMCAxIC0uNjY0LS42NjR2LS4wODJjMCAuMzY4LjI5Ni42NjQuNjY0LjY2NGg5LjI2MWMuMzY0IDAgLjY2LS4yOTYuNjYtLjY2NHYuMDgyYS42NjMuNjYzIDAgMCAxIC0uNjYuNjY0em0wIDAiIGZpbGwtb3BhY2l0eT0iLjE5NiIvPjxwYXRoIGQ9Im02NzkuNzc4IDE3NS4yNTl2OTU5LjkwOGM1OS45OTQtMTYwLjE0MiAxMDAuMTQ4LTE2MC4xNDIgMTYwLjE0MiAwdi05NTkuOTA3em0wIDAiIGZpbGw9InVybCgjaikiIHN0cm9rZT0iIzIxMjEyMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjExLjMzOSIgdHJhbnNmb3JtPSJtYXRyaXgoLjAwODI2OTAyIC4wMDAxMjczIC0uMDAwMTI3MyAuMDA4MjY5MDIgMCAwKSIvPjxwYXRoIGQ9Im0zLjMwOSAxLjQ4NGEuNjY0LjY2NCAwIDAgMCAtLjY2NC42NjR2LjA4MmMwLS4zNjcuMjk2LS42NjQuNjY0LS42NjRoOS4yNjFjLjM2NCAwIC42Ni4yOTcuNjYuNjY0di0uMDgyYS42NjMuNjYzIDAgMCAwIC0uNjYtLjY2NHptMCAwIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii4zOTIiLz48cGF0aCBkPSJtMy4zMDkgMS40MDJhLjcwOC43MDggMCAwIDAgLS43MTEuNzA3djExLjI0NmMwIC4zOTEuMzE2LjcwNy43MS43MDdoOS4yNjJjLjM5IDAgLjcwNy0uMzE2LjcwNy0uNzA3di0xMS4yNDVhLjcwNy43MDcgMCAwIDAgLS43MDctLjcwN3ptMCAuMDk0aDkuMjYxYy4zNCAwIC42MTQuMjc0LjYxNC42MTN2MTEuMjQ2YzAgLjM0LS4yNzQuNjE0LS42MTQuNjE0aC05LjI2YS42MTMuNjEzIDAgMCAxIC0uNjE4LS42MTR2LTExLjI0NWMwLS4zNC4yNzQtLjYxMy42MTgtLjYxM3ptMCAwIiBmaWxsPSIjMjEyMTIxIi8+PC9zdmc+");
641+}
642+
643+.icon-dl {
644+ background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0yMCAxNVYxOEMyMCAxOS4xMDQ2IDE5LjEwNDYgMjAgMTggMjBINkM0Ljg5NTQzIDIwIDQgMTkuMTA0NiA0IDE4TDQgMTVNOCAxMUwxMiAxNU0xMiAxNUwxNiAxMU0xMiAxNVYzIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4NCjwvc3ZnPg==");
645+}
ui/ui.tmpl+146−0
@@ -0,0 +1,146 @@
1+<!doctype html>
2+<html>
3+<head>
4+ <meta charset="utf-8">
5+ <meta name="theme-color" content="#fafafa">
6+ <meta name="msapplication-navbutton-color" content="#fafafa">
7+ <meta name="mobile-web-app-capable" content="yes">
8+ <meta name="viewport" content="width=device-width">
9+
10+ <link rel="manifest" href='data:application/manifest+json,{"name":"{{.Title}}","short_name":"{{.Title}}","description":" ","icons":[{"src":"data:image/svg+xml;base64,favicon_will_be_here","sizes":"150x150","type":"image/svg+xml"}],"background":"#fafafa","theme_color":"#fafafa","display":"standalone"}' />
11+
12+ <title>{{.Title}}</title>
13+ <link href="data:image/svg+xml;base64,favicon_will_be_here" rel="icon" type="image/svg+xml" />
14+ <style type="text/css">css_will_be_here</style>
15+ <script>
16+ window.ro = {{.Ro}}
17+ window.extraPath = {{.ExtraPath}}.slice(0, -1)
18+ window.onload = function () { js_will_be_here }
19+ </script>
20+</head>
21+
22+<body>
23+ <!-- Overlays (help, sums, media, etc.) -->
24+ <div onclick="window.helpOff()" style="display: none;" id="help"><table id="helpTable"><tbody>
25+ <tr><td>Arrows/Enter</td><td>browse files/folders and pictures</td></tr>
26+ <tr><td>Ctrl/Meta + Enter</td><td>download selected item as archive</td></tr>
27+ <tr><td>Ctrl/Meta + C</td><td>copy URL to clipboard</td></tr>
28+ <tr><td>Ctrl/Meta + E</td><td>rename item</td></tr>
29+ <tr><td>Ctrl/Meta + Backspace</td><td>delete item</td></tr>
30+ <tr><td>Ctrl/Meta/Shift + U</td><td>upload new file/folder</td></tr>
31+ <tr><td>Ctrl/Meta + M</td><td>create a new directory</td></tr>
32+ <tr><td>Ctrl/Meta + X</td><td>cut selected path</td></tr>
33+ <tr><td>Ctrl/Meta + V</td><td>paste previously selected paths to directory</td></tr>
34+ <tr><td>Ctrl/Meta + Z</td><td>copy checksums of selected file</td></tr>
35+ <tr><td>Ctrl + click</td><td>download selected item as archive</td></tr>
36+ <tr><td>click file icon </td><td>rename item</td></tr>
37+ <tr><td>double click file icon</td><td>delete item</td></tr>
38+ <tr><td>drag-and-drop item on UI</td><td>move item</td></tr>
39+ <tr><td>drag-and-drop external item</td><td>upload file/folders</td></tr>
40+ <tr><td>any other letter</td><td>fuzzy search</td></tr>
41+ </tbody></table></div>
42+
43+ <div onclick="window.sumsOff()" style="display: none;" id="sums"><table id="sumsTable"><tbody>
44+ <tr><td>Key</td><td>Hash Algorithm</td></tr>
45+ <tr><td>1</td><td>copy sha1 sum</td></tr>
46+ <tr><td>2</td><td>copy sha256 sum</td></tr>
47+ <tr><td>3</td><td>copy sha512 sum</td></tr>
48+ <tr><td>5</td><td>copy md5 sum</td></tr>
49+ </tbody></table></div>
50+
51+ <div style="display: none;" onclick="window.quitAll()" id="quitAll"><i style="display: none;" id="toast">cant reach server</i></div>
52+ <textarea style="display: none;" id="text-editor"></textarea>
53+ <div id="drop-grid" style="display:none !important"></div>
54+ <input type="file" id="clickupload" multiple style="display:none"/>
55+
56+ <div id="pics" style="display:none;"> <img draggable="false" onclick="window.picsNav()" id="picsHolder" /></div>
57+ <div id="video" style="display:none;">
58+ <div onclick="window.videodl()" class="icon-dl" id="video-dl"></div>
59+ <video controls id="videoHolder"> </video>
60+ </div>
61+ <div id="pdf" style="display:none;"> </div>
62+
63+ <!-- Theme toggle (init before DOM to prevent flash) -->
64+ <script>
65+ (function(){
66+ var t = localStorage.getItem('wormhole-theme');
67+ if (!t) t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
68+ document.documentElement.setAttribute('data-theme', t);
69+ window.toggleTheme = function() {
70+ var c = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
71+ document.documentElement.setAttribute('data-theme', c);
72+ localStorage.setItem('wormhole-theme', c);
73+ updateToggle();
74+ };
75+ window.updateToggle = function() {
76+ var sun = document.getElementById('icon-sun');
77+ var moon = document.getElementById('icon-moon');
78+ if (!sun || !moon) return;
79+ var dark = document.documentElement.getAttribute('data-theme') === 'dark';
80+ sun.style.display = dark ? 'block' : 'none';
81+ moon.style.display = dark ? 'none' : 'block';
82+ };
83+ document.addEventListener('DOMContentLoaded', updateToggle);
84+ })();
85+ </script>
86+
87+ <!-- Page container -->
88+ <article id="page">
89+
90+ <!-- Header row: site label + path + actions -->
91+ <div class="row row-header">
92+ <div class="cell cell-label">CDN</div>
93+ <div class="cell cell-path" onclick="return titleClick(event)"><h1>.{{.Title}}</h1></div>
94+ <div class="cell cell-actions">
95+ <button id="themeToggle" onclick="toggleTheme()" title="Toggle theme">
96+ <svg id="icon-moon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
97+ <svg id="icon-sun" style="display:none" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
98+ </button>
99+ </div>
100+ </div>
101+
102+ <!-- Column headers -->
103+ <div class="row row-colheader">
104+ <div class="cell cell-col-icon"></div>
105+ <div class="cell cell-col-size">SIZE</div>
106+ <div class="cell cell-col-name">NAME</div>
107+ </div>
108+
109+ <!-- File listing -->
110+ <div id="listing">
111+ <table id="linkTable">
112+ {{range .RowsFolders}}
113+ <tr>
114+ <td class="iconRow"><i ondblclick="return rm(event)" onclick="return rename(event)" class="btn icon icon-{{.Ext}} icon-blank"></i></td>
115+ <td class="file-size"><code>{{.Size}}</code></td>
116+ <td class="arrow"><div class="arrow-icon"></div></td>
117+ <td class="display-name"><a class="list-links" oncontextmenu="return setCursorTo(event.target.innerText)" onclick="return onClickLink(event)" href="{{.Href}}">{{.Name}}</a></td>
118+ </tr>
119+ {{end}}
120+ {{range .RowsFiles}}
121+ <tr>
122+ <td class="iconRow"><i ondblclick="return rm(event)" onclick="return rename(event)" class="btn icon icon-{{.Ext}} icon-blank"></i></td>
123+ <td class="file-size"><code>{{.Size}}</code></td>
124+ <td class="arrow"><div class="arrow-icon"></div></td>
125+ <td class="display-name"><a class="list-links" oncontextmenu="return setCursorTo(event.target.innerText)" onclick="return onClickLink(event)" href="{{.Href}}">{{.Name}}</a></td>
126+ </tr>
127+ {{end}}
128+ </table>
129+ </div>
130+
131+ <!-- Footer row -->
132+ <div class="row row-footer">
133+ <div class="cell cell-footer-help">Ctrl/Cmd + h</div>
134+ <div class="cell cell-footer-brand">cdn.krisyotam.com</div>
135+ </div>
136+
137+ </article>
138+
139+</body>
140+<div id="upBar" class="bar">
141+ <span style="display: none;" class="barName" id="upBarName"></span>
142+ <div style="display: none;" class="barPc" id="upBarPc">1%</div>
143+</div>
144+<div id="ok" class="notif icon-large-ok"></div>
145+<div id="sad" class="notif icon-large-sad-server"></div>
146+</html>
wormhole.go+318−0
@@ -0,0 +1,318 @@
1+package main
2+
3+import (
4+ "archive/zip"
5+ "compress/gzip"
6+ "crypto/md5"
7+ "crypto/sha1"
8+ "crypto/sha256"
9+ "crypto/sha512"
10+ _ "embed"
11+ "encoding/hex"
12+ "encoding/json"
13+ "errors"
14+ "flag"
15+ "fmt"
16+ "hash"
17+ "html"
18+ "html/template"
19+ "io"
20+ "io/fs"
21+ "log"
22+ "net/http"
23+ "net/url"
24+ "os"
25+ "path/filepath"
26+ "sort"
27+ "strconv"
28+ "strings"
29+)
30+
31+type rowTemplate struct {
32+ Name string
33+ Href template.URL
34+ Size string
35+ Ext string
36+}
37+
38+type pageTemplate struct {
39+ Title template.HTML
40+ ExtraPath template.HTML
41+ Ro bool
42+ RowsFiles []rowTemplate
43+ RowsFolders []rowTemplate
44+}
45+
46+var host = flag.String("h", "127.0.0.1", "host to listen to")
47+var port = flag.String("p", "8001", "port to listen to")
48+var extraPath = flag.String("prefix", "/", "url prefix at which wormhole can be reached, e.g. /wormhole/ (slashes of importance)")
49+var symlinks = flag.Bool("symlinks", false, "follow symlinks \033[4mWARNING\033[0m: symlinks will by nature allow to escape the defined path (default: false)")
50+var verb = flag.Bool("verb", false, "verbosity")
51+var skipHidden = flag.Bool("k", true, "\nskip hidden files")
52+var ro = flag.Bool("ro", false, "read only mode (no upload, rename, move, etc...)")
53+
54+type rpcCall struct {
55+ Call string `json:"call"`
56+ Args []string `json:"args"`
57+}
58+
59+var rootPath = ""
60+var handler http.Handler
61+
62+func check(e error) {
63+ if e != nil {
64+ panic(e)
65+ }
66+}
67+
68+func exitPath(w http.ResponseWriter, s ...interface{}) {
69+ if r := recover(); r != nil {
70+ log.Println("error", s, r)
71+ w.WriteHeader(500)
72+ w.Write([]byte("error"))
73+ } else if *verb {
74+ log.Println(s...)
75+ }
76+}
77+
78+func humanize(bytes int64) string {
79+ b := float64(bytes)
80+ u := 0
81+ for {
82+ if b < 1024 {
83+ return strconv.FormatFloat(b, 'f', 1, 64) + [9]string{"B", "k", "M", "G", "T", "P", "E", "Z", "Y"}[u]
84+ }
85+ b = b / 1024
86+ u++
87+ }
88+}
89+
90+func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path string) {
91+ files, err := os.ReadDir(fullPath)
92+ check(err)
93+ sort.Slice(files, func(i, j int) bool { return strings.ToLower(files[i].Name()) < strings.ToLower(files[j].Name()) })
94+
95+ if !strings.HasSuffix(path, "/") {
96+ path += "/"
97+ }
98+
99+ title := "/" + strings.TrimPrefix(path, *extraPath)
100+ p := pageTemplate{}
101+ if path != *extraPath {
102+ p.RowsFolders = append(p.RowsFolders, rowTemplate{"../", "../", "", "folder"})
103+ }
104+ p.ExtraPath = template.HTML(html.EscapeString(*extraPath))
105+ p.Ro = *ro
106+ p.Title = template.HTML(html.EscapeString(title))
107+
108+ for _, el := range files {
109+ info, errInfo := el.Info()
110+ el, err := os.Stat(fullPath + "/" + el.Name())
111+ if err != nil || errInfo != nil {
112+ log.Println("error - cant stat a file", err)
113+ continue
114+ }
115+
116+ if *skipHidden && strings.HasPrefix(el.Name(), ".") {
117+ continue // dont print hidden files if we're not allowed
118+ }
119+ if !*symlinks && info.Mode()&os.ModeSymlink != 0 {
120+ continue // dont follow symlinks if we're not allowed
121+ }
122+
123+ href := url.PathEscape(el.Name())
124+ name := el.Name()
125+
126+ if el.IsDir() && strings.HasPrefix(href, "/") {
127+ href = strings.Replace(href, "/", "", 1)
128+ }
129+
130+ if el.IsDir() {
131+ row := rowTemplate{name + "/", template.URL(href), "", "folder"}
132+ p.RowsFolders = append(p.RowsFolders, row)
133+ } else {
134+ sl := strings.Split(name, ".")
135+ ext := strings.ToLower(sl[len(sl)-1])
136+ row := rowTemplate{name, template.URL(href), humanize(el.Size()), ext}
137+ p.RowsFiles = append(p.RowsFiles, row)
138+ }
139+ }
140+
141+ if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
142+ w.Header().Set("Content-Type", "text/html")
143+ w.Header().Add("Content-Encoding", "gzip")
144+ gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test, and only ~30% larger (compression remains still very effective, ~6x)
145+ check(err)
146+ defer gz.Close()
147+ tmpl.Execute(gz, p)
148+ } else {
149+ tmpl.Execute(w, p)
150+ }
151+}
152+
153+func doContent(w http.ResponseWriter, r *http.Request) {
154+ if !strings.HasPrefix(r.URL.Path, *extraPath) { // redir when were not hitting the supplementary path if one is set
155+ http.Redirect(w, r, *extraPath, http.StatusFound)
156+ return
157+ }
158+
159+ path := html.UnescapeString(r.URL.Path)
160+ defer exitPath(w, "get content", path)
161+ fullPath := enforcePath(path)
162+ stat, errStat := os.Stat(fullPath)
163+ check(errStat)
164+
165+ if stat.IsDir() {
166+ replyList(w, r, fullPath, path)
167+ } else {
168+ handler.ServeHTTP(w, r)
169+ }
170+}
171+
172+func upload(w http.ResponseWriter, r *http.Request) {
173+ path := r.Header.Get("wormhole-path")
174+ defer exitPath(w, "upload", path)
175+
176+ path, err := url.PathUnescape(path)
177+ check(err)
178+ reader, err := r.MultipartReader()
179+ check(err)
180+ part, err := reader.NextPart()
181+ if err != nil && err != io.EOF { // errs EOF when no more parts to process
182+ check(err)
183+ }
184+ dst, err := os.Create(enforcePath(path))
185+ check(err)
186+ io.Copy(dst, part)
187+ w.Write([]byte("ok"))
188+}
189+
190+func zipRPC(w http.ResponseWriter, r *http.Request) {
191+ zipPath := r.URL.Query().Get("zipPath")
192+ zipName := r.URL.Query().Get("zipName")
193+ defer exitPath(w, "zip", zipPath)
194+ zipFullPath := enforcePath(zipPath)
195+ _, err := os.Lstat(zipFullPath)
196+ check(err)
197+ w.Header().Add("Content-Disposition", "attachment; filename=\""+zipName+".zip\"")
198+ zipWriter := zip.NewWriter(w)
199+ defer zipWriter.Close()
200+
201+ err = filepath.Walk(zipFullPath, func(path string, f fs.FileInfo, err error) error {
202+ check(err)
203+ if f.IsDir() {
204+ return nil
205+ }
206+
207+ rel, err := filepath.Rel(zipFullPath, path)
208+ check(err)
209+ if *skipHidden && (strings.HasPrefix(rel, ".") || strings.HasPrefix(f.Name(), ".")) {
210+ return nil // hidden files not allowed
211+ }
212+ if f.Mode()&os.ModeSymlink != 0 {
213+ panic(errors.New("symlink not allowed in zip downloads")) // filepath.Walk doesnt support symlinks
214+ }
215+
216+ header, err := zip.FileInfoHeader(f)
217+ check(err)
218+ header.Name = filepath.ToSlash(rel) // make the paths consistent between OSes
219+ header.Method = zip.Store
220+ headerWriter, err := zipWriter.CreateHeader(header)
221+ check(err)
222+ file, err := os.Open(path)
223+ check(err)
224+ defer file.Close()
225+ _, err = io.Copy(headerWriter, file)
226+ check(err)
227+ return nil
228+ })
229+
230+ check(err)
231+}
232+
233+func rpc(w http.ResponseWriter, r *http.Request) {
234+ var err error
235+ var rpc rpcCall
236+ defer exitPath(w, "rpc", &rpc)
237+ bodyBytes, err := io.ReadAll(r.Body)
238+ check(err)
239+ json.Unmarshal(bodyBytes, &rpc)
240+ ret := []byte("ok")
241+
242+ switch rpc.Call {
243+ case "mkdirp":
244+ err = os.MkdirAll(enforcePath(rpc.Args[0]), os.ModePerm)
245+ case "mv":
246+ err = os.Rename(enforcePath(rpc.Args[0]), enforcePath(rpc.Args[1]))
247+ case "rm":
248+ err = os.RemoveAll(enforcePath(rpc.Args[0]))
249+ case "sum":
250+ file, err := os.Open(enforcePath(rpc.Args[0]))
251+ check(err)
252+ var hash hash.Hash
253+ switch rpc.Args[1] {
254+ case "md5":
255+ hash = md5.New()
256+ case "sha1":
257+ hash = sha1.New()
258+ case "sha256":
259+ hash = sha256.New()
260+ case "sha512":
261+ hash = sha512.New()
262+ }
263+ _, err = io.Copy(hash, file)
264+ check(err)
265+ checksum := hash.Sum(nil)
266+ ret = make([]byte, hex.EncodedLen(len(checksum)))
267+ hex.Encode(ret, checksum)
268+ }
269+
270+ check(err)
271+ w.Write(ret)
272+}
273+
274+func enforcePath(p string) string {
275+ joined := filepath.Join(rootPath, strings.TrimPrefix(p, *extraPath))
276+ fp, err := filepath.Abs(joined)
277+ sl, _ := filepath.EvalSymlinks(fp) // err skipped as it would error for unexistent files (RPC check). The actual behaviour is tested below
278+
279+ // panic if we had a error getting absolute path,
280+ // ... or if path doesnt contain the prefix path we expect,
281+ // ... or if we're skipping hidden folders, and one is requested,
282+ // ... or if we're skipping symlinks, path exists, and a symlink out of bound requested
283+ if err != nil || !strings.HasPrefix(fp, rootPath) || *skipHidden && strings.Contains(p, "/.") || !*symlinks && len(sl) > 0 && !strings.HasPrefix(sl, rootPath) {
284+ panic(errors.New("invalid path"))
285+ }
286+
287+ return fp
288+}
289+
290+func main() {
291+ if flag.Parse(); len(flag.Args()) == 1 {
292+ rootPath = flag.Args()[0]
293+ } else {
294+ fmt.Printf("\nusage: ./wormhole [OPTIONS] ~/directory-to-share\n\n")
295+ flag.PrintDefaults()
296+ os.Exit(1)
297+ }
298+
299+ var err error
300+ rootPath, err = filepath.Abs(rootPath)
301+ check(err)
302+ server := &http.Server{Addr: *host + ":" + *port, Handler: handler}
303+
304+ if !*ro {
305+ http.HandleFunc(*extraPath+"rpc", rpc)
306+ http.HandleFunc(*extraPath+"post", upload)
307+ }
308+ http.HandleFunc(*extraPath+"zip", zipRPC)
309+ http.HandleFunc("/", doContent)
310+ handler = http.StripPrefix(*extraPath, http.FileServer(http.Dir(rootPath)))
311+
312+ fmt.Printf("Wormhole starting on directory %s\n", rootPath)
313+ fmt.Printf("Verbose: %t, Symlinks: %t, Read-Only: %t, Hidden-Files Skipped: %t\n", *verb, *symlinks, *ro, *skipHidden)
314+ fmt.Printf("Listening on http://%s:%s%s\n", *host, *port, *extraPath)
315+ if err = server.ListenAndServe(); err != http.ErrServerClosed {
316+ check(err)
317+ }
318+}
wormhole_embed.go+34−0
@@ -0,0 +1,34 @@
1+package main
2+
3+import (
4+ _ "embed"
5+ "encoding/base64"
6+ "html/template"
7+ "strings"
8+)
9+
10+//go:embed ui/script.js
11+var scriptJs string
12+
13+//go:embed ui/style.css
14+var styleCss string
15+
16+//go:embed ui/favicon.svg
17+var faviconSvg []byte
18+
19+//go:embed ui/ui.tmpl
20+var uiTmpl string
21+
22+var tmpl *template.Template
23+
24+// fill in template
25+func init() {
26+ var err error
27+ t := strings.Replace(uiTmpl, "css_will_be_here", styleCss, 1)
28+ t = strings.Replace(t, "js_will_be_here", scriptJs, 1)
29+ t = strings.Replace(t, "favicon_will_be_here", base64.StdEncoding.EncodeToString(faviconSvg), 2)
30+ tmpl, err = template.New("").Parse(t)
31+ if err != nil {
32+ panic(err)
33+ }
34+}
wormhole_test.go+390−0
@@ -0,0 +1,390 @@
1+package main
2+
3+import (
4+ "archive/zip"
5+ "bytes"
6+ "fmt"
7+ "io/ioutil"
8+ "net/http"
9+ "regexp"
10+ "strings"
11+ "testing"
12+)
13+
14+func dieMaybe(t *testing.T, err error) {
15+ if err != nil {
16+ t.Fatal(err)
17+ }
18+}
19+
20+func trimSpaces(str string) string {
21+ space := regexp.MustCompile(`\s+`)
22+ return space.ReplaceAllString(str, " ")
23+}
24+
25+func getRaw(t *testing.T, url string) []byte {
26+ resp, err := http.Get(url)
27+ dieMaybe(t, err)
28+ body, err := ioutil.ReadAll(resp.Body)
29+ dieMaybe(t, err)
30+ return body
31+}
32+
33+func getZip(t *testing.T, needle string, dest string) (int, bool) {
34+ b := getRaw(t, dest)
35+ unzipped, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
36+ dieMaybe(t, err)
37+ length := len(unzipped.File)
38+ for _, file := range unzipped.File {
39+ if file.Name == needle {
40+ return length, true
41+ }
42+ }
43+ return length, false
44+}
45+
46+func get(t *testing.T, url string) string {
47+ body := getRaw(t, url)
48+ return trimSpaces(string(body))
49+}
50+
51+func postDummyFile(t *testing.T, url string, path string, payload string) string {
52+ // Generated by curl-to-Go: https://mholt.github.io/curl-to-go
53+ body := strings.NewReader("------WebKitFormBoundarycCRIderiXxJWEUcU\r\nContent-Disposition: form-data; name=\"\u1112\u1161 \u1112\u1161\"; filename=\"\u1112\u1161 \u1112\u1161\"\r\nContent-Type: application/octet-stream\r\n\r\n" + payload)
54+ req, err := http.NewRequest("POST", url+"post", body)
55+ dieMaybe(t, err)
56+ req.Header.Set("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarycCRIderiXxJWEUcU")
57+ req.Header.Set("Wormhole-Path", path)
58+
59+ resp, err := http.DefaultClient.Do(req)
60+ dieMaybe(t, err)
61+ defer resp.Body.Close()
62+ bodyS, err := ioutil.ReadAll(resp.Body)
63+ dieMaybe(t, err)
64+ return trimSpaces(string(bodyS))
65+}
66+
67+func postJSON(t *testing.T, url string, what string) string {
68+ resp, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(what)))
69+ dieMaybe(t, err)
70+ body, err := ioutil.ReadAll(resp.Body)
71+ dieMaybe(t, err)
72+ return trimSpaces(string(body))
73+}
74+
75+func fetchAndTestDefault(t *testing.T, url string) string {
76+ body0 := get(t, url)
77+
78+ if !strings.Contains(body0, `<title>/</title>`) {
79+ t.Fatal("error title")
80+ }
81+
82+ if !strings.Contains(body0, `<h1 onclick="return titleClick(event)">./</h1>`) {
83+ t.Fatal("error header")
84+ }
85+
86+ if !strings.Contains(body0, `href="hols">hols/</a>`) {
87+ t.Fatal("error hols folder")
88+ }
89+
90+ if !strings.Contains(body0, `href="curimit@gmail.com%20%2840%25%29">curimit@gmail.com (40%)/</a>`) {
91+ t.Fatal("error curimit@gmail.com (40%) folder")
92+ }
93+
94+ if !strings.Contains(body0, `href="%E4%B8%AD%E6%96%87">中文/</a>`) {
95+ t.Fatal("error 中文 folder")
96+ }
97+
98+ if !strings.Contains(body0, `href="custom_mime_type.types">custom_mime_type.types</a>`) {
99+ t.Fatal("error row custom_mime_type")
100+ }
101+
102+ return body0
103+}
104+
105+func doTestRegular(t *testing.T, url string, testExtra bool) {
106+ var payload, path, body0, body1, body2 string
107+
108+ // ~~~~~~~~~~~~~~~~~
109+ fmt.Println("\r\n~~~~~~~~~~ test fetching default path")
110+ fetchAndTestDefault(t, url)
111+
112+ // ~~~~~~~~~~~~~~~~~
113+ fmt.Println("\r\n~~~~~~~~~~ test fetching another page")
114+ body0 = get(t, url+"/hols")
115+ if !strings.Contains(body0, "glasgow.jpg") {
116+ t.Fatal("fetching a subfolder failed")
117+ }
118+
119+ // ~~~~~~~~~~~~~~~~~
120+ fmt.Println("\r\n~~~~~~~~~~ test fetching an invalid path - redirected to root")
121+ fetchAndTestDefault(t, url+"../../")
122+ fetchAndTestDefault(t, url+"hols/../../")
123+
124+ // ~~~~~~~~~~~~~~~~~
125+ fmt.Println("\r\n~~~~~~~~~~ test fetching regular files")
126+ body0 = get(t, url+"subdir_with%20space/file_with%20space.html")
127+ body1 = get(t, url+"fancy-path/a")
128+ if body0 != `<b>spacious!!</b> ` || body1 != `fancy! ` {
129+ t.Fatal("fetching a regular file errored")
130+ }
131+
132+ // ~~~~~~~~~~~~~~~~~
133+ fmt.Println("\r\n~~~~~~~~~~ test fetching a invalid file")
134+ path = "../../../../../../../../../../etc/passwd"
135+ if !testExtra && get(t, url+path) != `error` {
136+ t.Fatal("fetching a invalid file didnt errored")
137+ } else if testExtra {
138+ fetchAndTestDefault(t, url+path) // extra path will just redirect to root dir
139+ }
140+
141+ // ~~~~~~~~~~~~~~~~~
142+ fmt.Println("\r\n~~~~~~~~~~ test zipping of folder 中文")
143+ len, foundFile := getZip(t, "檔案.html", url+"zip?zipPath=%2F%E4%B8%AD%E6%96%87%2F&zipName=%E4%B8%AD%E6%96%87")
144+ if len != 1 || !foundFile {
145+ t.Fatal("invalid zip generated")
146+ }
147+
148+ // ~~~~~~~~~~~~~~~~~
149+ fmt.Println("\r\n~~~~~~~~~~ test zipping of folder with hidden file")
150+ _, foundHidden := getZip(t, ".hidden-folder/some-file", url+"zip?zipPath=%2fhols%2f&zipName=hols")
151+ if foundHidden && !testExtra {
152+ t.Fatal("invalid zip generated - shouldnt contain hidden folder")
153+ } else if !foundHidden && testExtra {
154+ t.Fatal("invalid zip generated - should contain hidden folder")
155+ }
156+
157+ // ~~~~~~~~~~~~~~~~~
158+ fmt.Println("\r\n~~~~~~~~~~ test zip invalid path")
159+ body0 = get(t, url+"zip?zipPath=%2Ftmp&zipName=subdir")
160+ println(body0)
161+ if body0 != `error` {
162+ t.Fatal("zip passed for invalid path")
163+ }
164+
165+ // ~~~~~~~~~~~~~~~~~
166+ fmt.Println("\r\n~~~~~~~~~~ test mkdir rpc")
167+ body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/AAA"]}`)
168+ if body0 != `ok` {
169+ t.Fatal("mkdir rpc errored")
170+ }
171+
172+ body0 = fetchAndTestDefault(t, url)
173+ if !strings.Contains(body0, `href="AAA">AAA/</a>`) {
174+ t.Fatal("mkdir rpc folder not created")
175+ }
176+
177+ // ~~~~~~~~~~~~~~~~~
178+ fmt.Println("\r\n~~~~~~~~~~ test invalid mkdir rpc")
179+ body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["../BBB"]}`)
180+ if body0 != `error` {
181+ t.Fatal("invalid mkdir rpc didnt errored #0")
182+ }
183+
184+ body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/../BBB"]}`)
185+ if body0 != `error` {
186+ t.Fatal("invalid mkdir rpc didnt errored #1")
187+ }
188+
189+ // ~~~~~~~~~~~~~~~~~
190+ fmt.Println("\r\n~~~~~~~~~~ test post file")
191+ path = "%2F%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1" // "하 하" encoded
192+ payload = "123 하"
193+ body0 = postDummyFile(t, url, path, payload)
194+ body1 = get(t, url+path)
195+ body2 = fetchAndTestDefault(t, url)
196+ if body0 != `ok` || body1 != payload || !strings.Contains(body2, `href="%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1">하 하</a>`) {
197+ t.Fatal("post file errored")
198+ }
199+
200+ // ~~~~~~~~~~~~~~~~~
201+ fmt.Println("\r\n~~~~~~~~~~ test post file incorrect path")
202+ body0 = postDummyFile(t, url, "%2E%2E"+path, payload)
203+ if !strings.Contains(body0, `err`) {
204+ t.Fatal("post file incorrect path didnt errored")
205+ }
206+
207+ // ~~~~~~~~~~~~~~~~~
208+ fmt.Println("\r\n~~~~~~~~~~ test post file")
209+ path = "2024-01-02-10:36:58.png"
210+ payload = "123123123123123123123123"
211+ body0 = postDummyFile(t, url, path, payload)
212+ body1 = get(t, url+path)
213+ body2 = fetchAndTestDefault(t, url)
214+ if body0 != `ok` || body1 != payload || !strings.Contains(body2, `href="2024-01-02-10:36:58.png"`) {
215+ t.Fatal("post file errored")
216+ }
217+
218+ // ~~~~~~~~~~~~~~~~~
219+ fmt.Println("\r\n~~~~~~~~~~ test mv rpc")
220+ body0 = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`)
221+ body1 = fetchAndTestDefault(t, url)
222+ if body0 != `ok` || strings.Contains(body1, `href="AAA">AAA/</a></td> </tr>`) {
223+ t.Fatal("mv rpc errored")
224+ }
225+
226+ // ~~~~~~~~~~~~~~~~~
227+ fmt.Println("\r\n~~~~~~~~~~ test upload in new folder")
228+ payload = "test"
229+ body0 = postDummyFile(t, url, "%2Fhols%2FAAA%2Fabcdef", payload)
230+ body1 = get(t, url+"hols/AAA/abcdef")
231+ if body0 != `ok` || body1 != payload {
232+ t.Fatal("upload in new folder errored")
233+ }
234+
235+ // ~~~~~~~~~~~~~~~~~
236+ fmt.Println("\r\n~~~~~~~~~~ test symlink, should succeed: ", testExtra)
237+ body0 = get(t, url+"/support/")
238+ hasListing := strings.Contains(body0, `readme.md`)
239+ body1 = get(t, url+"/support/readme.md")
240+ hasReadme := strings.Contains(body1, `the master branch is automatically built and pushed`)
241+ body2 = get(t, url)
242+ hasMainListing := strings.Contains(body2, `href="support">support/</a>`)
243+
244+ if !testExtra && hasReadme {
245+ t.Fatal("error symlink file reached where illegal")
246+ } else if testExtra && !hasReadme {
247+ t.Fatal("error symlink file unreachable")
248+ }
249+ if !testExtra && hasListing {
250+ t.Fatal("error symlink folder reached where illegal")
251+ } else if testExtra && !hasListing {
252+ t.Fatal("error symlink folder unreachable")
253+ }
254+ if !testExtra && hasMainListing {
255+ t.Fatal("error symlink folder where illegal")
256+ } else if testExtra && !hasMainListing {
257+ t.Fatal("error symlink folder unreachable")
258+ }
259+
260+ if testExtra {
261+ fmt.Println("\r\n~~~~~~~~~~ test symlink mkdir & cleanup")
262+ body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/support/testfolder"]}`)
263+ if body0 != `ok` {
264+ t.Fatal("error symlink mkdir")
265+ }
266+
267+ body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/support/testfolder"]}`)
268+ if body0 != `ok` {
269+ t.Fatal("error symlink rm")
270+ }
271+ }
272+
273+ fmt.Println("\r\n~~~~~~~~~~ test hidden file, should succeed: ", testExtra)
274+ body0 = get(t, url+"/.testhidden")
275+ hasHidden := strings.Contains(body0, `test`)
276+ if !testExtra && hasHidden {
277+ t.Fatal("error hidden file reached where illegal")
278+ } else if testExtra && !hasHidden {
279+ t.Fatal("error hidden file unreachable")
280+ }
281+
282+ //
283+ fmt.Println("\r\n~~~~~~~~~~ test upload in new folder")
284+ payload = "test"
285+ body0 = postDummyFile(t, url, "%2Fhols%2FAAA%2Fabcdef", payload)
286+ body1 = get(t, url+"hols/AAA/abcdef")
287+ if body0 != `ok` || body1 != payload {
288+ t.Fatal("upload in new folder errored")
289+ }
290+
291+ // ~~~~~~~~~~~~~~~~~
292+ fmt.Println("\r\n~~~~~~~~~~ test rm rpc & cleanup")
293+ body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/hols/AAA"]}`)
294+ if body0 != `ok` {
295+ t.Fatal("cleanup errored #0")
296+ }
297+
298+ body0 = get(t, url+"hols/AAA")
299+ if !strings.Contains(body0, `error`) {
300+ t.Fatal("cleanup errored #1")
301+ }
302+
303+ body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/하 하"]}`)
304+ if body0 != `ok` {
305+ t.Fatal("cleanup errored #2")
306+ }
307+
308+ fmt.Printf("\r\n=========\r\n")
309+}
310+
311+func doTestReadonly(t *testing.T, url string) {
312+ var payload, path, body0, body1 string
313+
314+ // ~~~~~~~~~~~~~~~~~
315+ fmt.Println("\r\n~~~~~~~~~~ test fetching default path")
316+ fetchAndTestDefault(t, url)
317+
318+ // ~~~~~~~~~~~~~~~~~
319+ fmt.Println("\r\n~~~~~~~~~~ test fetching an invalid path - redirected to root")
320+ fetchAndTestDefault(t, url+"../../")
321+ fetchAndTestDefault(t, url+"hols/../../")
322+
323+ // ~~~~~~~~~~~~~~~~~
324+ fmt.Println("\r\n~~~~~~~~~~ test fetching regular files")
325+ body0 = get(t, url+"subdir_with%20space/file_with%20space.html")
326+ body1 = get(t, url+"fancy-path/a")
327+ if body0 != `<b>spacious!!</b> ` || body1 != `fancy! ` {
328+ t.Fatal("fetching a regular file errored")
329+ }
330+
331+ // ~~~~~~~~~~~~~~~~~
332+ fmt.Println("\r\n~~~~~~~~~~ test fetching a invalid file")
333+ path = "../../../../../../../../../../etc/passwd"
334+ if get(t, url+path) != `error` {
335+ t.Fatal("fetching a invalid file didnt errored")
336+ }
337+
338+ // ~~~~~~~~~~~~~~~~~
339+ fmt.Println("\r\n~~~~~~~~~~ test mkdir rpc")
340+ body0 = postJSON(t, url+"rpc", `{"call":"mkdirp","args":["/AAA"]}`)
341+ if body0 == `ok` {
342+ t.Fatal("mkdir rpc passed - should not be allowed")
343+ }
344+
345+ // ~~~~~~~~~~~~~~~~~
346+ fmt.Println("\r\n~~~~~~~~~~ test post file")
347+ path = "%2F%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1" // "하 하" encoded
348+ payload = "123 하"
349+ body0 = postDummyFile(t, url, path, payload)
350+ get(t, url+path)
351+ if body0 == `ok` {
352+ t.Fatal("post file passed - should not be allowed")
353+ }
354+
355+ // ~~~~~~~~~~~~~~~~~
356+ fmt.Println("\r\n~~~~~~~~~~ test mv rpc")
357+ body0 = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`)
358+ fetchAndTestDefault(t, url)
359+ if body0 == `ok` {
360+ t.Fatal("mv rpc passed - should not be allowed")
361+ }
362+
363+ // ~~~~~~~~~~~~~~~~~
364+ fmt.Println("\r\n~~~~~~~~~~ test rm rpc & cleanup")
365+ body0 = postJSON(t, url+"rpc", `{"call":"rm","args":["/hols/AAA"]}`)
366+ if body0 == `ok` {
367+ t.Fatal("cleanup passed - should not be allowed")
368+ }
369+
370+ fmt.Printf("\r\n=========\r\n")
371+}
372+
373+func TestNormal(t *testing.T) {
374+ fmt.Println("========== testing normal path ============")
375+ doTestRegular(t, "http://127.0.0.1:8001/", false)
376+}
377+
378+func TestExtra(t *testing.T) {
379+ fmt.Println("========== testing extras options ============")
380+ doTestRegular(t, "http://127.0.0.1:8001/fancy-path/", true)
381+}
382+
383+func TestRo(t *testing.T) {
384+ fmt.Println("========== testing read only ============")
385+ doTestReadonly(t, "http://127.0.0.1:8001/")
386+}
387+
388+func TestRunMain(t *testing.T) {
389+ main()
390+}