Commit 191984b31e

191984b31ef228c4077904a2831a2af593352670

parent: c39399403e

Unsigned

lost+skunk <skunky@ebloid.ru> · 2024-09-23 06:38 UTC

v1.3.2

Layout: unified · split

.gitignore +1
@@ -1,3 +1,4 @@
11**/cache
22**/config.json
33**/skunkyart
4**/skunkyart-*
INSTANCES.md +4 −2
@@ -1,7 +1,9 @@
1JSON variant should be used from master — https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json
2
13|Instance|Yggdrasil|I2P|Tor|NSFW|Proxifying|Modified Sources|Country|
24|:------:|:-------:|:-:|:-:|:--:|:--------:|:--------------:|:-----:|
3|[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | No | No | Russia |
5|[skunky.ebloid.ru](https://skunky.ebloid.ru/art)|[Yes](http://[201:eba5:d1fc:bf7b:cfcb:a811:4b8b:7ea3]/art)|No|No| No | Yes | No | Russia |
46|[clovius.club](https://skunky.clovius.club)|No|No|No| Yes | Yes | No | Sweden |
57|[bloat.cat](https://skunky.bloat.cat)|No|No|No| Yes | Yes | No | Germany |
68|[lumaeris.com](https://skunkyart.lumaeris.com)|No|No|No| Yes | Yes | No | Germany |
7|[art.bloat.cat](https://art.bloat.cat)|No|No|No| Yes | Yes | No | Germany |
9|[art.bloat.cat](https://art.bloat.cat)|No|No|No| Yes | Yes | No | Germany |
\ No newline at end of file
README.md +12
@@ -7,6 +7,12 @@ Instances: [`INSTANCES.md`](/skunky/SkunkyArt/src/branch/master/INSTANCES.md)
77# EN 🇺🇸
88## Description
99SkunkyArt 🦨 — alternative frontend for DevianArt, which works without JS.
10## Build (translated via DeepL)
11It is recommended to build with the 'embed' tag because it embeds the presets in the binary. If you plan to modify the templates, then do not use this tag. You can also add the `-ldflags "-w -s"` argument (GCCGO has a different name for it — `gccgoflags`) to reduce the size of the output file. Here is an example:
12
13`go build -tags embed -ldflags "-w -s"`
14
15Pre-compiled binaries can be found in the [Releases](https://git.macaw.me/skunky/skunkyart/releases) tab.
1016## Setup
1117The sample config is in the `config.example.json` file. For custom config, use `--config` option.
1218See the [`SETUP.md`](/skunky/SkunkyArt/src/branch/master/SETUP.md) file for more info about directives.
@@ -21,6 +27,12 @@ To do this, you must either make a PR by adding your instance to the `instances.
2127# RU 🇷🇺
2228## Описание
2329SkunkyArt 🦨 — альтернативный фронтенд к DeviantArt, который полностью работает без JS (JavaScript).
30## Сборка
31Рекомендуется производить сборку с тегом 'embed', поскольку он встраивает заготовки в бинарный файл. Если вы планируете изменять заготовки, то не используйте этот тег. Также вы можете добавить аргумент `-ldflags "-w -s"` (у GCCGO он называется по-другому — `gccgoflags`) для уменьшения размера выходного файла. Вот пример:
32
33`go build -tags embed -ldflags "-w -s"`
34
35Готовые бинари находятся во вкладке [Releases](https://git.macaw.me/skunky/skunkyart/releases).
2436## Настройка
2537Пример конфига находится в файле `config.example.json`. Чтобы указать свой конфиг, используйте cli-аргумент `--config`.
2638См. [`SETUP-RU.md`](/skunky/SkunkyArt/src/branch/master/SETUP-RU.md) для информации о настройки фронтенда.
REDIRECTS.md added +13
@@ -0,0 +1,13 @@
1# Search
2* `deviantart.com/search?q=$QUERY` => `/search?q=$QUERY&type=all`
3# Daily Deviations
4* `deviantart.com` => `/dd`
5# Deviations
6* (`$USER_GROUP.deviantart.com/art/$ID`|`deviantart.com/$USER_GROUP/art/$ID`) => `/post/$USER_GROUP/$ID`
7# Groups and users
8## Main user page
9* (`$USER_GROUP.deviantart.com`|`deviantart.com/$USER_GROUP`) => `/group_user?type=about&q=$USER_GROUP`
10## Gallery
11* (`$USER_GROUP.deviantart.com/gallery`|`deviantart.com/$USER_GROUP/gallery`) => `/group_user?type=gallery&q=$USER_GROUP`
12## Favourites
13* (`$USER_GROUP.deviantart.com/favourites`|`deviantart.com/$USER_GROUP/favourites`) => `/group_user?type=favourites&q=$USER_GROUP`
SETUP-RU.md +2 −2
@@ -12,13 +12,13 @@
1212# Конфигурация
1313* `listen` — IP и порт для слушанья; заполняется по такой форме: ip:port
1414* `uri` — URI инстанса. Пример: `"uri":"/art/"` -> https://skunky.ebloid.ru/art/
15* `cache` — Система кеширования; по умолчанию выключена.
15* `cache` — Система кеширования; по умолчанию выключена
1616 * `enabled` — Состояние системы кеширования; требуется булёвое значение
1717 * `path` — Полный путь до каталога, куда будет сохраняться кеш
1818 * `lifetime` — Время жизни файла в кеше, требует целочисленное значение, дополненное суффиксом времени (см. 'Единицы времени')
1919 * `max-size` — Максимальный размер файла
2020 * `update-interval` — Интервал для автоматической ротации кеша
21* `dirs-to-memory` — Массив, заполнив который скопируются все файлы из указанных каталогов
21* `static-path` — Строка, являющаяся путём до статики. SkunkyArt при запуске скопирует содержимое этого каталога в ОЗУ. Однако, если вы собрали фронтенд с тегом 'embed', то этого не произайдёт
2222* `download-proxy` — Адрес прокси для загрузки файлов
2323* `user-agent` — Строка, которая используется в качестве User-Agent'а
2424
SETUP.md +1 −1
@@ -18,7 +18,7 @@ Time units:
1818 * `lifetime` — Cached file life time, requires numeric value, followed by multiplicative suffix (see Time Units for details)
1919 * `max-size` — Maximum file size in megabytes
2020 * `update-interval` — Automatic rotation interval
21* `dirs-to-memory` — This setting determines which directories will be copied to RAM when SkunkyArt is started. Mandatory
21* `static-path` — This setting determines path to static, which will be copied to RAM when SkunkyArt is started. Useless if you're use binary compiled with 'embed' tag.
2222* `download-proxy` — Proxy address for downloading files.
2323* `user-agent` — String, which SkunkyArt uses as UA
2424
app/api.go +45 −46
@@ -9,69 +9,68 @@ import (
99)
1010
1111type API struct {
12 main *skunkyart
12 main *skunkyart
1313}
1414
1515type info struct {
16 Version string `json:"version"`
17 Settings settingsParams `json:"settings"`
16 Version string `json:"version"`
17 Settings settingsParams `json:"settings"`
1818}
1919
2020func (a API) Info() {
21 json, err := json.Marshal(info{
22 Version: a.main.Version,
23 Settings: settingsParams{
24 Nsfw: CFG.Nsfw,
25 Proxy: CFG.Proxy,
26 },
27 })
28 try(err)
29 a.main.Writer.Write(json)
30 return
21 json, err := json.Marshal(info{
22 Version: a.main.Version,
23 Settings: settingsParams{
24 Nsfw: CFG.Nsfw,
25 Proxy: CFG.Proxy,
26 },
27 })
28 try(err)
29 a.main.Writer.Write(json)
3130}
3231
3332func (a API) Error(description string, status int) {
34 a.main.Writer.WriteHeader(status)
35 var response strings.Builder
36 response.WriteString(`{"error":"`)
37 response.WriteString(description)
38 response.WriteString(`"}`)
39 wr(a.main.Writer, response.String())
33 a.main.Writer.WriteHeader(status)
34 var response strings.Builder
35 response.WriteString(`{"error":"`)
36 response.WriteString(description)
37 response.WriteString(`"}`)
38 wr(a.main.Writer, response.String())
4039}
4140
4241func (a API) sendMedia(d *devianter.Deviation) {
43 mediaUrl, name := devianter.UrlFromMedia(d.Media)
42 mediaUrl, name := devianter.UrlFromMedia(d.Media)
4443 a.main.SetFilename(name)
45
46 if len(mediaUrl) != 0 {
47 mediaUrl = mediaUrl[21:]
44
45 if len(mediaUrl) != 0 {
46 mediaUrl = mediaUrl[21:]
4847 dot := strings.Index(mediaUrl, ".")
4948 a.main.Writer.Header().Del("Content-Type")
50 a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:])
51 }
49 a.main.DownloadAndSendMedia(mediaUrl[:dot], mediaUrl[dot+11:])
50 }
5251}
5352
5453// TODO: сделать фильтры
5554func (a API) Random() {
56 for attempt := 1;; {
57 if attempt > 3 {
58 a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500)
59 }
60
61 s, err, daErr := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a')
62 try(err)
63 if daErr.RAW != nil {
64 continue
65 }
66
67 deviation := &s.Results[rand.Intn(len(s.Results))]
68
69 if deviation.NSFW && !CFG.Nsfw {
70 attempt++
71 continue
72 }
73
74 a.sendMedia(deviation)
75 return
76 }
55 for attempt := 1; ; {
56 if attempt > 3 {
57 a.Error("Sorry, butt NSFW on this are disabled, and the instance failed to find a random art without NSFW", 500)
58 }
59
60 s, err, daErr := devianter.PerformSearch(string(rand.Intn(999)), rand.Intn(30), 'a')
61 try(err)
62 if daErr.RAW != nil {
63 continue
64 }
65
66 deviation := &s.Results[rand.Intn(len(s.Results))]
67
68 if deviation.NSFW && !CFG.Nsfw {
69 attempt++
70 continue
71 }
72
73 a.sendMedia(deviation)
74 return
75 }
7776}
app/cache.go +4 −4
@@ -27,10 +27,10 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) {
2727 url.WriteString(".wixmp.com/")
2828 url.WriteString(path)
2929 if t := s.Args.Get("token"); t != "" {
30 url.WriteString("?token=")
31 url.WriteString(t)
30 url.WriteString("?token=")
31 url.WriteString(t)
3232 }
33
33
3434 var response []byte
3535
3636 switch {
@@ -127,7 +127,7 @@ func InitCacheSystem() {
127127 try(os.RemoveAll(fileName))
128128 }
129129 }
130
130
131131 if c.MaxSize != 0 && fileInfo.Size() > c.MaxSize {
132132 try(os.RemoveAll(fileName))
133133 }
app/config.go +7 −2
@@ -56,13 +56,11 @@ func ExecuteConfig() {
5656 if CFG.cfg != "" {
5757 f, err := os.ReadFile(CFG.cfg)
5858 tryWithExitStatus(err, 1)
59
6059 tryWithExitStatus(json.Unmarshal(f, &CFG), 1)
6160 if CFG.Cache.Enabled && !CFG.Proxy {
6261 exit("Incompatible settings detected: cannot use caching media content without proxy", 1)
6362 }
6463
65 static.StaticPath = CFG.StaticPath
6664 if CFG.Cache.Enabled {
6765 if CFG.Cache.Lifetime != "" {
6866 var duration int64
@@ -92,6 +90,13 @@ func ExecuteConfig() {
9290 CFG.Cache.MaxSize *= 1024 ^ 2
9391 go InitCacheSystem()
9492 }
93
94 About = instanceAbout{
95 Proxy: CFG.Proxy,
96 Nsfw: CFG.Nsfw,
97 }
98
99 static.StaticPath = CFG.StaticPath
95100 devianter.UserAgent = CFG.UserAgent
96101 }
97102}
app/router.go +20 −11
@@ -3,7 +3,7 @@ package app
33import (
44 "io"
55 "net/http"
6 u "net/url"
6 url "net/url"
77 "skunkyart/static"
88 "strconv"
99 "strings"
@@ -57,7 +57,7 @@ func Router() {
5757 Path = r.URL.Path
5858 path := parsepath(Path)
5959 Host = "http://" + r.Host
60
60
6161 if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" {
6262 Host = "https://" + r.Host
6363 }
@@ -67,13 +67,13 @@ func Router() {
6767 skunky.Args = r.URL.Query()
6868 arg := skunky.Args.Get
6969 p, _ := strconv.Atoi(arg("p"))
70
70
7171 skunky.Endpoint = path[1]
7272 skunky.API.main = &skunky
7373 skunky.Writer = w
7474 skunky.BasePath = CFG.URI
7575 skunky.QueryRaw = arg("q")
76 skunky.Query = u.QueryEscape(skunky.QueryRaw)
76 skunky.Query = url.QueryEscape(skunky.QueryRaw)
7777 skunky.Page = p
7878
7979 if t := arg("type"); len(t) > 0 {
@@ -84,12 +84,21 @@ func Router() {
8484 skunky.Atom = true
8585 }
8686
87 if CFG.Proxy {
88 w.Header().Add("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'")
89 } else {
90 w.Header().Add("Content-Security-Policy", "default-src 'self'; img-src 'self' *.wixmp.com; script-src 'none'; style-src 'self' 'unsafe-inline'")
91 }
92
93 w.Header().Add("X-Frame-Options", "DENY")
94
8795 switch skunky.Endpoint {
8896 // main
8997 case "":
9098 skunky.ExecuteTemplate("index.htm", "html", &CFG.URI)
9199 case "about":
92 skunky.About()
100 skunky.Templates.About = About
101 skunky.ExecuteTemplate("about.htm", "html", &skunky)
93102 case "post":
94103 skunky.Deviation(path[2], path[3])
95104 case "search":
@@ -120,12 +129,12 @@ func Router() {
120129 case "api":
121130 w.Header().Add("Content-Type", "application/json")
122131 switch path[2] {
123 case "instance":
124 skunky.API.Info()
125 case "random":
126 skunky.API.Random()
127 default:
128 skunky.API.Error("Not Found", 404)
132 case "instance":
133 skunky.API.Info()
134 case "random":
135 skunky.API.Random()
136 default:
137 skunky.API.Error("Not Found", 404)
129138 }
130139
131140 // 404
app/util.go +15 −8
@@ -1,6 +1,7 @@
11package app
22
33import (
4 "encoding/json"
45 "io"
56 "net/http"
67 "net/url"
@@ -40,18 +41,26 @@ func restore() {
4041}
4142
4243var instances []byte
44var About instanceAbout
4345
4446func RefreshInstances() {
4547 for {
4648 func() {
4749 defer restore()
4850 instances = Download("https://git.macaw.me/skunky/SkunkyArt/raw/branch/master/instances.json").Body
51 try(json.Unmarshal(instances, &About))
4952 }()
5053 time.Sleep(1 * time.Hour)
5154 }
5255}
5356
5457// some crap for frontend
58type instanceAbout struct {
59 Proxy bool
60 Nsfw bool
61 Instances []settings
62}
63
5564type skunkyart struct {
5665 Writer http.ResponseWriter
5766
@@ -63,15 +72,11 @@ type skunkyart struct {
6372 BasePath, Endpoint string
6473 Query, QueryRaw string
6574
66 API API
67 Version string
75 API API
76 Version string
6877
6978 Templates struct {
70 About struct {
71 Proxy bool
72 Nsfw bool
73 Instances []settings
74 }
79 About instanceAbout
7580
7681 SomeList string
7782 DDStrips string
@@ -132,7 +137,7 @@ func UrlBuilder(strs ...string) string {
132137 str.WriteString(CFG.URI)
133138 for n, x := range strs {
134139 str.WriteString(x)
135 if n := n+1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') {
140 if n := n + 1; n < l && len(strs[n]) != 0 && !(strs[n][0] == '?' || strs[n][0] == '&') && !(x[0] == '?' || x[0] == '&') {
136141 str.WriteString("/")
137142 }
138143 }
@@ -215,6 +220,8 @@ func ParseMedia(media devianter.Media, thumb ...int) string {
215220 filename = "image.gif"
216221 }
217222 return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", filename)
223 } else if !CFG.Proxy {
224 return mediaUrl
218225 }
219226 return ""
220227}
app/wrapper.go +10 −17
@@ -1,7 +1,6 @@
11package app
22
33import (
4 "encoding/json"
54 "regexp"
65 "strconv"
76 "strings"
@@ -24,8 +23,8 @@ func (s skunkyart) GRUser() {
2423 s.Templates.GroupUser.GR, err, daError = g.Get()
2524 try(err)
2625 if daError.RAW != nil {
27 s.Error(daError)
28 return
26 s.Error(daError)
27 return
2928 }
3029
3130 group := &s.Templates.GroupUser
@@ -68,7 +67,7 @@ func (s skunkyart) GRUser() {
6867 group.About.Interests += interest.String()
6968 }
7069 }
71 group.About.Comments = s.ParseComments(devianter.GetComments(strconv.Itoa(group.GR.Gruser.ID),"",s.Page,4))
70 group.About.Comments = s.ParseComments(devianter.GetComments(strconv.Itoa(group.GR.Gruser.ID), "", s.Page, 4))
7271
7372 case "cover_deviation":
7473 group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation
@@ -225,8 +224,8 @@ func (s skunkyart) Deviation(author, postname string) {
225224func (s skunkyart) DD() {
226225 dd, err := devianter.GetDailyDeviations(s.Page)
227226 if err.RAW != nil {
228 s.Error(err)
229 return
227 s.Error(err)
228 return
230229 }
231230 var strips strings.Builder
232231 for _, x := range dd.Strips {
@@ -312,12 +311,13 @@ func (s skunkyart) Search() {
312311 return
313312 }
314313 try(err)
315 if daError.RAW != nil {
316 s.Error(daError)
317 return
318 }
319314
320315 if s.Type != 'r' {
316 if daError.RAW != nil {
317 s.Error(daError)
318 return
319 }
320
321321 ss.List = s.DeviationList(ss.Content.Results, false, DeviationList{
322322 Pages: ss.Content.Pages,
323323 More: ss.Content.HasMore,
@@ -339,10 +339,3 @@ func (s skunkyart) Emojitar(name string) {
339339 }
340340 wr(s.Writer, ae)
341341}
342
343func (s skunkyart) About() {
344 s.Templates.About.Nsfw = CFG.Nsfw
345 s.Templates.About.Proxy = CFG.Proxy
346 try(json.Unmarshal(instances, &s.Templates.About))
347 s.ExecuteTemplate("about.htm", "html", &s)
348}
config.example.json +2 −2
@@ -1,6 +1,6 @@
11{
22 "listen": "0.0.0.0:3003",
3 "uri": "/huy/",
3 "uri": "/",
44 "cache": {
55 "enabled": true,
66 "path": "cache",
@@ -12,5 +12,5 @@
1212 "download-proxy": "http://127.0.0.1:8080",
1313 "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
1414 "proxy": true,
15 "nsfw": false
15 "nsfw": true
1616}
instances.json +11 −11
@@ -8,8 +8,8 @@
88 "clearnet": "https://skunky.ebloid.ru/art"
99 },
1010 "settings": {
11 "nsfw": false,
12 "proxy": false
11 "proxy": true,
12 "nsfw": false
1313 }
1414 },
1515 {
@@ -19,8 +19,8 @@
1919 "clearnet": "https://skunky.clovius.club"
2020 },
2121 "settings": {
22 "nsfw": true,
23 "proxy": true
22 "proxy": true,
23 "nsfw": true
2424 }
2525 },
2626 {
@@ -30,8 +30,8 @@
3030 "clearnet": "https://skunky.bloat.cat"
3131 },
3232 "settings": {
33 "nsfw": true,
34 "proxy": true
33 "proxy": true,
34 "nsfw": true
3535 }
3636 },
3737 {
@@ -41,8 +41,8 @@
4141 "clearnet": "https://skunkyart.lumaeris.com"
4242 },
4343 "settings": {
44 "nsfw": true,
45 "proxy": true
44 "proxy": true,
45 "nsfw": true
4646 }
4747 },
4848 {
@@ -52,9 +52,9 @@
5252 "clearnet": "https://art.bloat.cat"
5353 },
5454 "settings": {
55 "nsfw": true,
56 "proxy": true
55 "proxy": true,
56 "nsfw": true
5757 }
5858 }
5959 ]
60}
60}
\ No newline at end of file
main.go +1 −1
@@ -9,7 +9,7 @@ import (
99)
1010
1111func main() {
12 app.Release.Version = "1.3.2-alpha"
12 app.Release.Version = "1.3.2"
1313 app.Release.Description = "Two API endpoints and template embedding into binary"
1414 go app.RefreshInstances()
1515
static/css/skunky.css +9 −13
@@ -19,11 +19,10 @@ header h1 {
1919header form {
2020 align-self: center;
2121}
22header {
22header, form {
2323 display: flex;
2424}
2525form {
26 font-size: 0;
2726 border: solid #164e3e 1px;
2827 max-width: fit-content;
2928}
@@ -147,21 +146,18 @@ input:focus {
147146 font-size: 80%
148147 }
149148
150 center form {
151 font-size: 60%
152 }
153
154 header form {
155 font-size: 60%;
156 max-width: unset;
157 border: 0;
158 }
159 header, center {
149 header {
150 margin-left: 3%;
160151 text-align: center;
161 display: block;
152 display: inline-block;
162153 clear: both;
163154 font-size: 200%;
164155 }
156
157 form {
158 font-size: 60%;
159 border: solid #164e3e 5px;
160 }
165161
166162 .content {
167163 margin: auto;
static/html/about.htm +1 −1
@@ -6,7 +6,7 @@
66 <p>
77 SkunkyArt is an alternative frontend for deviantart.com, written in Go.
88 </p>
9 <h3><a href="https://go.kde.org/matrix/#/#skunkyart:ebloid.ru" target="_blank">Room in Matrix</a></h3>
9 <h3><a href="https://go.kde.org/matrix/#/#skunkyart:ebloid.ru" target="_blank">Room in [matrix]</a></h3>
1010 <b>Instance settings:</b>
1111 <ul>
1212 <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li>
static/html/head.htm +1
@@ -21,5 +21,6 @@
2121 <meta name="referrer" content="no-referrer" />
2222 <link rel="stylesheet" href="{{.BasePath}}stylesheet">
2323 <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico">
24 <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4, user-scalable=no; user-scalable=0"/>
2425</head>
2526{{end}}
\ No newline at end of file
static/html/index.htm +71 −3
@@ -4,9 +4,75 @@
44 <title>SkunkyArt</title>
55 <link rel="stylesheet" href="{{.}}stylesheet"/>
66 <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico">
7 <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4, user-scalable=no; user-scalable=0"/>
8
9 <style>
10 main {
11 display: flex;
12 max-width: fit-content;
13 position: absolute;
14 top: 50%;
15 right: 50%;
16 transform: translate(50%, -50%);
17 }
18 div {
19 transform: translate(0, 50%);
20 margin-left: 4%;
21 flex-basis: 100%;
22 height: 30%;
23 display: block;
24 max-width: fit-content;
25 }
26 div h1, form {
27 margin: 0;
28 }
29 div form {
30 font-size: 100%;
31 max-width: 100%
32 }
33 div form input {
34 width: 100%
35 }
36 img {
37 width: 30%;
38 height: 30%;
39 }
40
41 @media (orientation: portrait) {
42 main {
43 display: block;
44 width: 200%;
45 }
46 img {
47 width: 100%;
48 height: 100%;
49 }
50 form {
51 width: 200%;
52 }
53 div {
54 margin: -25%;
55 margin-top: auto;
56 width: 200%;
57 }
58 div h1 {
59 text-align: center;
60 }
61 }
62 @media (max-width: 1155px) and (orientation: landscape) {
63 img {
64 width: 50%;
65 }
66 div {
67 transform: none;
68 }
69 }
70 </style>
771 </head>
872 <main>
9 <center>
73 <img src="{{.}}favicon.ico" title="SkunkyArt logo" draggable="false">
74 <div>
75 <h1><a href="{{.}}dd">Daily Deviations</a> | <a href="{{.}}about">About</a></h1>
1076 <form method="get" action="{{.}}search">
1177 <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
1278 <select name="type">
@@ -16,7 +82,9 @@
1682 </select>
1783 <button type="submit">Search!</button>
1884 </form>
19 <h1><a href="{{.}}dd">Daily Deviations</a> | <a href="{{.}}about">About</a> | <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank">Source Code</a></h1>
20 </center>
85 <h1 style="margin-top: 5%; font-size: 200%; text-align: center;">
86 <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank" title="Source Code">SkunkyArt</a>
87 </h1>
88 </div>
2189 </main>
2290</html>
\ No newline at end of file