Logs
Patchsets
Range Diff ↕ rd-4
1: 3b99dc0 < -: ------- feat: static assets
4: d8792d5 ! 1: 66cafc6 feat: static assets folder
2: 8919af5 ! 2: 5e76ed3 fix(cli): access control for removing patchsets
3: 7346122 < -: ------- chore: create patch for smol
Range Diff ↕ rd-5
1: 66cafc6 = 1: cc56ea1 feat: static assets folder
2: 5e76ed3 < -: ------- fix(cli): access control for removing patchsets
-: ------- > 2: ef749a4 review: typo and future enhancement comment
Range Diff ↕ rd-9
1: cc56ea1 = 1: 0467f9e feat: static assets folder
2: ef749a4 = 2: da1730f review: typo and future enhancement comment
-: ------- > 3: c038404 refactor: per-file override for static folder
Range-diff rd-4
- title
- feat: static assets
- description
-
Patch removed
- old #1
3b99dc0
- new #0
(none)
- title
- feat: static assets folder
- description
-
Patch changed
- old #4
d8792d5
- new #1
66cafc6
- title
- fix(cli): access control for removing patchsets
- description
-
Patch changed
- old #2
8919af5
- new #2
5e76ed3
- title
- chore: create patch for smol
- description
-
Patch removed
- old #3
7346122
- new #0
(none)
1: 3b99dc0 < -: ------- feat: static assets
4: d8792d5 ! 1: 66cafc6 feat: static assets folder
web.go
web.go
"io" "io/fs" "log/slog" + "mime" "net/http" "os" "path/filepath" logger := web.Logger file := r.PathValue("file") + logger.Info("serving file", "file", file, "fs", staticfs) reader, err := staticfs.Open(file) if err != nil { logger.Error(err.Error()) http.Error(w, "file not found", 404) return } - contentType := http.DetectContentType(contents) + contentType := mime.TypeByExtension(filepath.Ext(file)) + if contentType == "" { + contentType = http.DetectContentType(contents) + } w.Header().Add("Content-Type", contentType) _, err = w.Write(contents) } } -type StaticFs struct { - Dir string -} +func getFileSystem(logger *slog.Logger, ffs embed.FS, datadir string, dirName string) (fs.FS, error) { + dir := filepath.Join(datadir, dirName) + _, err := os.Stat(dir) + if err == nil { + logger.Info("found folder in data_dir", "dir", dir) + return os.DirFS(dir), nil + } -func (fs *StaticFs) Open(name string) (os.File, error) { - fp, err := os.Open(filepath.Join(fs.Dir, name)) - return *fp, err + logger.Info("using embeded folder", "dir", dir) + fsys, err := fs.Sub(ffs, dirName) + if err != nil { + return nil, err + } + + return fsys, nil } func StartWebServer(cfg *GitCfg) { dbpath := filepath.Join(cfg.DataDir, "pr.db") dbh, err := Open(dbpath, cfg.Logger) if err != nil { - panic(fmt.Sprintf("cannot find database file, check folder and perms: %s", dbpath)) + panic(fmt.Sprintf("cannot find database file, check folder and perms: %s: %s", dbpath, err)) } be := &Backend{ http.HandleFunc("GET /", ctxMdw(ctx, repoListHandler)) http.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler)) http.HandleFunc("GET /rss", ctxMdw(ctx, rssHandler)) - dir := filepath.Join(cfg.DataDir, "static") - var filesys fs.FS = staticFS - _, err = os.Stat(dir) - if err == nil { - cfg.Logger.Info("detected static folder, using instead of the default one") - filesys = os.DirFS(dir) + filesys, err := getFileSystem(cfg.Logger, staticFS, cfg.DataDir, "static") + if err != nil { + panic(err) } - http.HandleFunc("GET /static/{file}", serveFile(filesys)) + http.HandleFunc("GET /static/{file}", ctxMdw(ctx, serveFile(filesys))) cfg.Logger.Info("starting web server", "addr", addr) err = http.ListenAndServe(addr, nil)
web.go
web.go
"embed" "fmt" "html/template" + "io" + "io/fs" "log/slog" + "mime" "net/http" + "os" "path/filepath" "slices" "strconv" //go:embed tmpl/* var tmplFS embed.FS +//go:embed static/* +var staticFS embed.FS + type WebCtx struct { Pr *PrCmd Backend *Backend } } +func serveFile(staticfs fs.FS) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + web, err := getWebCtx(r) + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + return + } + logger := web.Logger + + file := r.PathValue("file") + logger.Info("serving file", "file", file, "fs", staticfs) + reader, err := staticfs.Open(file) + if err != nil { + logger.Error(err.Error()) + http.Error(w, "file not found", 404) + return + } + contents, err := io.ReadAll(reader) + if err != nil { + logger.Error(err.Error()) + http.Error(w, "file not found", 404) + return + } + contentType := mime.TypeByExtension(filepath.Ext(file)) + if contentType == "" { + contentType = http.DetectContentType(contents) + } + w.Header().Add("Content-Type", contentType) + + _, err = w.Write(contents) + if err != nil { + logger.Error(err.Error()) + http.Error(w, "server error", 500) + return + } + } +} + +func getFileSystem(logger *slog.Logger, ffs embed.FS, datadir string, dirName string) (fs.FS, error) { + dir := filepath.Join(datadir, dirName) + _, err := os.Stat(dir) + if err == nil { + logger.Info("found folder in data_dir", "dir", dir) + return os.DirFS(dir), nil + } + + logger.Info("using embeded folder", "dir", dir) + fsys, err := fs.Sub(ffs, dirName) + if err != nil { + return nil, err + } + + return fsys, nil +} + func StartWebServer(cfg *GitCfg) { addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.WebPort) dbpath := filepath.Join(cfg.DataDir, "pr.db") dbh, err := Open(dbpath, cfg.Logger) if err != nil { - panic(fmt.Sprintf("cannot find database file, check folder and perms: %s", dbpath)) + panic(fmt.Sprintf("cannot find database file, check folder and perms: %s: %s", dbpath, err)) } be := &Backend{ http.HandleFunc("GET /", ctxMdw(ctx, repoListHandler)) http.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler)) http.HandleFunc("GET /rss", ctxMdw(ctx, rssHandler)) + filesys, err := getFileSystem(cfg.Logger, staticFS, cfg.DataDir, "static") + if err != nil { + panic(err) + } + http.HandleFunc("GET /static/{file}", ctxMdw(ctx, serveFile(filesys))) cfg.Logger.Info("starting web server", "addr", addr) err = http.ListenAndServe(addr, nil)
Makefile
Makefile
$(DOCKER_BUILDX_BUILD) -t "ghcr.io/picosh/pico/git-ssh:$(DOCKER_TAG)" --target release-ssh . $(DOCKER_BUILDX_BUILD) -t "ghcr.io/picosh/pico/git-web:$(DOCKER_TAG)" --target release-web . .PHONY: bp + +smol: + curl https://pico.sh/smol.css -o ./static/smol.css + cat patches/smol.diff | git apply +.PHONY: smol
patches/smol.diff
+diff --git a/static/smol.css b/static/smol.css +index e9b59ec..9e9d925 100644 +--- a/static/smol.css ++++ b/static/smol.css +@@ -15,48 +15,6 @@ + box-shadow: none; + } + +-@media (prefers-color-scheme: light) { +- :root { +- --main-hue: 250; +- --white: #2e3f53; +- --white-light: #cfe0f4; +- --white-dark: #6c6a6a; +- --code: #52576f; +- --pre: #e1e7ee; +- --bg-color: #f4f4f4; +- --text-color: #24292f; +- --link-color: #005cc5; +- --visited: #6f42c1; +- --blockquote: #005cc5; +- --blockquote-bg: #cfe0f4; +- --hover: #c11e7a; +- --grey: #ccc; +- --grey-light: #6a708e; +- --shadow: #e8e8e8; +- } +-} +- +-@media (prefers-color-scheme: dark) { +- :root { +- --main-hue: 250; +- --white: #f2f2f2; +- --white-light: #f2f2f2; +- --white-dark: #e8e8e8; +- --code: #414558; +- --pre: #252525; +- --bg-color: #282a36; +- --text-color: #f2f2f2; +- --link-color: #8be9fd; +- --visited: #bd93f9; +- --blockquote: #bd93f9; +- --blockquote-bg: #353548; +- --hover: #ff80bf; +- --grey: #414558; +- --grey-light: #6a708e; +- --shadow: #252525; +- } +-} +- + html { + background-color: var(--bg-color); + color: var(--text-color);
static/smol.css
+*, +::before, +::after { + box-sizing: border-box; +} + +::-moz-focus-inner { + border-style: none; + padding: 0; +} +:-moz-focusring { + outline: 1px dotted ButtonText; +} +:-moz-ui-invalid { + box-shadow: none; +} + +html { + background-color: var(--bg-color); + color: var(--text-color); + font-size: 18px; + line-height: 1.5; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, + sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + -webkit-text-size-adjust: 100%; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; +} + +body { + margin: 0 auto; +} + +img { + max-width: 100%; + height: auto; +} + +b, +strong { + font-weight: bold; +} + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, + monospace; +} + +code, +kbd, +samp { + border: 2px solid var(--code); +} + +pre > code { + background-color: inherit; + padding: 0; + border: none; +} + +code { + font-size: 90%; + border-radius: 0.3rem; + padding: 0.1rem 0.3rem; +} + +pre { + font-size: 14px; + border-radius: 5px; + padding: 1rem; + margin: 1rem 0; + overflow-x: auto; + background-color: var(--pre) !important; +} + +small { + font-size: 0.8rem; +} + +summary { + display: list-item; + cursor: pointer; +} + +h1, +h2, +h3, +h4 { + margin: 0; + padding: 0.5rem 0 0 0; + border: 0; + font-style: normal; + font-weight: inherit; + font-size: inherit; +} + +path { + fill: var(--text-color); + stroke: var(--text-color); +} + +hr { + color: inherit; + border: 0; + margin: 0; + height: 2px; + background: var(--grey); + margin: 1rem auto; + text-align: center; +} + +a { + text-decoration: none; + color: var(--link-color); +} + +a:hover, +a:visited:hover { + text-decoration: underline; + color: var(--hover); +} + +a:visited { + color: var(--visited); +} + +a.link-grey { + text-decoration: underline; + color: var(--white); +} + +a.link-grey:visited { + color: var(--white); +} + +section { + margin-bottom: 1.4rem; +} + +section:last-child { + margin-bottom: 0; +} + +header { + margin: 1rem auto; +} + +p { + margin: 0.5rem 0; +} + +article { + overflow-wrap: break-word; +} + +blockquote { + border-left: 5px solid var(--blockquote); + background-color: var(--blockquote-bg); + padding: 0.5rem 0.75rem; + margin: 0.5rem 0; +} + +blockquote > p { + margin: 0; +} + +blockquote code { + border: 1px solid var(--blockquote); +} + +ul, +ol { + padding: 0 0 0 1rem; + list-style-position: outside; +} + +ul[style*="list-style-type: none;"] { + padding: 0; +} + +li { + margin: 0.5rem 0; +} + +li > pre { + padding: 0; +} + +footer { + text-align: center; + margin-bottom: 4rem; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +dd:not(:last-child) { + margin-bottom: 0.5rem; +} + +figure { + margin: 0; +} + +.container { + max-width: 50em; + width: 100%; +} + +.container-sm { + max-width: 40em; + width: 100%; +} + +.container-center { + width: 100%; + height: 100%; + display: flex; + justify-content: center; +} + +.mono { + font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, + monospace; +} + +.link-alt-adj, +.link-alt-adj:visited, +.link-alt-adj:visited:hover, +.link-alt-adj:hover { + color: var(--link-color); + text-decoration: none; +} + +.link-alt-adj:visited:hover, +.link-alt-adj:hover { + text-decoration: underline; +} + +.link-alt-hover, +.link-alt-hover:visited, +.link-alt-hover:visited:hover, +.link-alt-hover:hover { + color: var(--hover); + text-decoration: none; +} + +.link-alt-hover:visited:hover, +.link-alt-hover:hover { + text-decoration: underline; +} + +.link-alt, +.link-alt:visited, +.link-alt:visited:hover, +.link-alt:hover { + color: var(--white); + text-decoration: none; +} + +.link-alt:visited:hover, +.link-alt:hover { + text-decoration: underline; +} + +.text-3xl { + font-size: 2.5rem; +} + +.text-2xl { + font-size: 1.9rem; + line-height: 1.15; +} + +.text-xl { + font-size: 1.55rem; + line-height: 1.15; +} + +.text-lg { + font-size: 1.35rem; + line-height: 1.15; +} + +.text-md { + font-size: 1.15rem; + line-height: 1.15; +} + +.text-sm { + font-size: 0.875rem; +} + +.text-xs { + font-size: 0.775rem; +} + +.cursor-pointer { + cursor: pointer; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.border { + border: 2px solid var(--grey-light); +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-underline { + border-bottom: 3px solid var(--text-color); + padding-bottom: 3px; +} + +.text-hdr { + color: var(--hover); +} + +.text-underline-hdr { + border-bottom: 3px solid var(--hover); + padding-bottom: 3px; +} + +.font-bold { + font-weight: bold; +} + +.font-italic { + font-style: italic; +} + +.inline { + display: inline; +} + +.inline-block { + display: inline-block; +} + +.max-w-half { + max-width: 50%; +} + +.h-screen { + height: 100vh; +} + +.w-screen { + width: 100vw; +} + +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.m-0 { + margin: 0; +} + +.mt { + margin-top: 0.5rem; +} + +.mt-2 { + margin-top: 1rem; +} + +.mt-4 { + margin-top: 2rem; +} + +.mt-8 { + margin-top: 4rem; +} + +.mb { + margin-bottom: 0.5rem; +} + +.mb-2 { + margin-bottom: 1rem; +} + +.mb-4 { + margin-bottom: 2rem; +} + +.mb-8 { + margin-bottom: 4rem; +} + +.mb-16 { + margin-bottom: 8rem; +} + +.mr { + margin-right: 0.5rem; +} + +.ml-sm { + margin-left: 0.25rem; +} + +.ml { + margin-left: 0.5rem; +} + +.pt-0 { + padding-top: 0; +} + +.my { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-2 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-4 { + margin-top: 2rem; + margin-bottom: 2rem; +} + +.my-8 { + margin-top: 4rem; + margin-bottom: 4rem; +} + +.mx { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.mx-2 { + margin-left: 1rem; + margin-right: 1rem; +} + +.m-1 { + margin: 0.5rem; +} + +.p-1 { + padding: 0.5rem; +} + +.p-0 { + padding: 0; +} + +.px-2 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-4 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-2 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-4 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.py-8 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.justify-between { + justify-content: space-between; +} + +.justify-center { + justify-content: center; +} + +.gap { + gap: 0.5rem; +} + +.gap-2 { + gap: 1rem; +} + +.group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.group-2 { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.group-h { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.flex-1 { + flex: 1; +} + +.items-end { + align-items: end; +} + +.items-start { + align-items: start; +} + +.justify-end { + justify-content: end; +} + +.font-grey-light { + color: var(--grey-light); +} + +.hidden { + display: none; +} + +.align-right { + text-align: right; +} + +/* ==== MARKDOWN ==== */ + +.md h1, +.md h2, +.md h3, +.md h4 { + padding: 0; + margin: 1.5rem 0 0.9rem 0; + font-weight: bold; +} + +.md h1 a, +.md h2 a, +.md h3 a, +.md h4 a { + color: var(--grey-light); + text-decoration: none; +} + +.md h1 { + font-size: 1.6rem; + line-height: 1.15; + border-bottom: 2px solid var(--grey); + padding-bottom: 0.7rem; +} + +.md h2 { + font-size: 1.3rem; + line-height: 1.15; + color: var(--white-dark); +} + +.md h3 { + font-size: 1.2rem; + color: var(--white-dark); +} + +.md h4 { + font-size: 1rem; + color: var(--white-dark); +} + +/* ==== HELPERS ==== */ + +.logo-header { + line-height: 1; + display: inline-block; + background-color: #FF79C6; + background-image: linear-gradient(to right, #FF5555, #FF79C6, #F8F859); + color: transparent; + background-clip: text; + border: 3px solid #FF79C6; + padding: 8px 10px 10px 10px; + border-radius: 10px; + box-shadow: 0px 5px 0px 0px var(--shadow); + background-size: 100%; + -webkit-background-clip: text; + -moz-background-clip: text; + -webkit-text-fill-color: transparent; + -moz-text-fill-color: transparent; +} + +.btn { + border: 2px solid var(--link-color); + color: var(--link-color); + padding: 0.4rem 1rem; + font-weight: bold; + display: inline-block; +} + +.btn-link, +.btn-link:visited { + border: 2px solid var(--link-color); + color: var(--link-color); + padding: 0.4rem 1rem; + text-decoration: none; + font-weight: bold; + display: inline-block; +} + +.btn-link:visited:hover, +.btn-link:hover { + border: 2px solid var(--hover); +} + +.btn-link-alt, +.btn-link-alt:visited { + border: 2px solid var(--white); + color: var(--white); +} + +.box { + border: 2px solid var(--grey-light); + padding: 0.5rem 0.75rem; +} + +.box-sm { + border: 2px solid var(--grey-light); + padding: 0.15rem 0.35rem; +} + +.box-alert { + border: 2px solid var(--hover); + padding: 0.5rem 0.75rem; +} + +.box-sm-alert { + border: 2px solid var(--hover); + padding: 0.15rem 0.35rem; +} + +.list-none { + list-style-type: none; +} + +.list-disc { + list-style-type: disc; +} + +.list-decimal { + list-style-type: decimal; +} + +.pill { + border: 1px solid var(--link-color); + color: var(--link-color); +} + +.pill-alert { + border: 1px solid var(--hover); + color: var(--hover); +} + +.pill-info { + border: 1px solid var(--visited); + color: var(--visited); +} + +@media only screen and (max-width: 40em) { + body { + padding: 0 1rem; + } + + header { + margin: 0; + } + + .flex-collapse { + flex-direction: column; + } +}
2: 8919af5 ! 2: 5e76ed3 fix(cli): access control for removing patchsets
static/smol.css
+*, +::before, +::after { + box-sizing: border-box; +} + +::-moz-focus-inner { + border-style: none; + padding: 0; +} +:-moz-focusring { + outline: 1px dotted ButtonText; +} +:-moz-ui-invalid { + box-shadow: none; +} + +@media (prefers-color-scheme: light) { + :root { + --main-hue: 250; + --white: #2e3f53; + --white-light: #cfe0f4; + --white-dark: #6c6a6a; + --code: #52576f; + --pre: #e1e7ee; + --bg-color: #f4f4f4; + --text-color: #24292f; + --link-color: #005cc5; + --visited: #6f42c1; + --blockquote: #005cc5; + --blockquote-bg: #cfe0f4; + --hover: #c11e7a; + --grey: #ccc; + --grey-light: #6a708e; + --shadow: #e8e8e8; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --main-hue: 250; + --white: #f2f2f2; + --white-light: #f2f2f2; + --white-dark: #e8e8e8; + --code: #414558; + --pre: #252525; + --bg-color: #282a36; + --text-color: #f2f2f2; + --link-color: #8be9fd; + --visited: #bd93f9; + --blockquote: #bd93f9; + --blockquote-bg: #353548; + --hover: #ff80bf; + --grey: #414558; + --grey-light: #6a708e; + --shadow: #252525; + } +} + +html { + background-color: var(--bg-color); + color: var(--text-color); + font-size: 18px; + line-height: 1.5; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, + sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + -webkit-text-size-adjust: 100%; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; +} + +body { + margin: 0 auto; +} + +img { + max-width: 100%; + height: auto; +} + +b, +strong { + font-weight: bold; +} + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, + monospace; +} + +code, +kbd, +samp { + border: 2px solid var(--code); +} + +pre > code { + background-color: inherit; + padding: 0; + border: none; +} + +code { + font-size: 90%; + border-radius: 0.3rem; + padding: 0.1rem 0.3rem; +} + +pre { + font-size: 14px; + border-radius: 5px; + padding: 1rem; + margin: 1rem 0; + overflow-x: auto; + background-color: var(--pre) !important; +} + +small { + font-size: 0.8rem; +} + +summary { + display: list-item; + cursor: pointer; +} + +h1, +h2, +h3, +h4 { + margin: 0; + padding: 0.5rem 0 0 0; + border: 0; + font-style: normal; + font-weight: inherit; + font-size: inherit; +} + +path { + fill: var(--text-color); + stroke: var(--text-color); +} + +hr { + color: inherit; + border: 0; + margin: 0; + height: 2px; + background: var(--grey); + margin: 1rem auto; + text-align: center; +} + +a { + text-decoration: none; + color: var(--link-color); +} + +a:hover, +a:visited:hover { + text-decoration: underline; + color: var(--hover); +} + +a:visited { + color: var(--visited); +} + +a.link-grey { + text-decoration: underline; + color: var(--white); +} + +a.link-grey:visited { + color: var(--white); +} + +section { + margin-bottom: 1.4rem; +} + +section:last-child { + margin-bottom: 0; +} + +header { + margin: 1rem auto; +} + +p { + margin: 0.5rem 0; +} + +article { + overflow-wrap: break-word; +} + +blockquote { + border-left: 5px solid var(--blockquote); + background-color: var(--blockquote-bg); + padding: 0.5rem 0.75rem; + margin: 0.5rem 0; +} + +blockquote > p { + margin: 0; +} + +blockquote code { + border: 1px solid var(--blockquote); +} + +ul, +ol { + padding: 0 0 0 1rem; + list-style-position: outside; +} + +ul[style*="list-style-type: none;"] { + padding: 0; +} + +li { + margin: 0.5rem 0; +} + +li > pre { + padding: 0; +} + +footer { + text-align: center; + margin-bottom: 4rem; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +dd:not(:last-child) { + margin-bottom: 0.5rem; +} + +figure { + margin: 0; +} + +.container { + max-width: 50em; + width: 100%; +} + +.container-sm { + max-width: 40em; + width: 100%; +} + +.container-center { + width: 100%; + height: 100%; + display: flex; + justify-content: center; +} + +.mono { + font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, + monospace; +} + +.link-alt-adj, +.link-alt-adj:visited, +.link-alt-adj:visited:hover, +.link-alt-adj:hover { + color: var(--link-color); + text-decoration: none; +} + +.link-alt-adj:visited:hover, +.link-alt-adj:hover { + text-decoration: underline; +} + +.link-alt-hover, +.link-alt-hover:visited, +.link-alt-hover:visited:hover, +.link-alt-hover:hover { + color: var(--hover); + text-decoration: none; +} + +.link-alt-hover:visited:hover, +.link-alt-hover:hover { + text-decoration: underline; +} + +.link-alt, +.link-alt:visited, +.link-alt:visited:hover, +.link-alt:hover { + color: var(--white); + text-decoration: none; +} + +.link-alt:visited:hover, +.link-alt:hover { + text-decoration: underline; +} + +.text-3xl { + font-size: 2.5rem; +} + +.text-2xl { + font-size: 1.9rem; + line-height: 1.15; +} + +.text-xl { + font-size: 1.55rem; + line-height: 1.15; +} + +.text-lg { + font-size: 1.35rem; + line-height: 1.15; +} + +.text-md { + font-size: 1.15rem; + line-height: 1.15; +} + +.text-sm { + font-size: 0.875rem; +} + +.text-xs { + font-size: 0.775rem; +} + +.cursor-pointer { + cursor: pointer; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.border { + border: 2px solid var(--grey-light); +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-underline { + border-bottom: 3px solid var(--text-color); + padding-bottom: 3px; +} + +.text-hdr { + color: var(--hover); +} + +.text-underline-hdr { + border-bottom: 3px solid var(--hover); + padding-bottom: 3px; +} + +.font-bold { + font-weight: bold; +} + +.font-italic { + font-style: italic; +} + +.inline { + display: inline; +} + +.inline-block { + display: inline-block; +} + +.max-w-half { + max-width: 50%; +} + +.h-screen { + height: 100vh; +} + +.w-screen { + width: 100vw; +} + +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.m-0 { + margin: 0; +} + +.mt { + margin-top: 0.5rem; +} + +.mt-2 { + margin-top: 1rem; +} + +.mt-4 { + margin-top: 2rem; +} + +.mt-8 { + margin-top: 4rem; +} + +.mb { + margin-bottom: 0.5rem; +} + +.mb-2 { + margin-bottom: 1rem; +} + +.mb-4 { + margin-bottom: 2rem; +} + +.mb-8 { + margin-bottom: 4rem; +} + +.mb-16 { + margin-bottom: 8rem; +} + +.mr { + margin-right: 0.5rem; +} + +.ml-sm { + margin-left: 0.25rem; +} + +.ml { + margin-left: 0.5rem; +} + +.pt-0 { + padding-top: 0; +} + +.my { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-2 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-4 { + margin-top: 2rem; + margin-bottom: 2rem; +} + +.my-8 { + margin-top: 4rem; + margin-bottom: 4rem; +} + +.mx { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.mx-2 { + margin-left: 1rem; + margin-right: 1rem; +} + +.m-1 { + margin: 0.5rem; +} + +.p-1 { + padding: 0.5rem; +} + +.p-0 { + padding: 0; +} + +.px-2 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-4 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-2 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-4 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.py-8 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.justify-between { + justify-content: space-between; +} + +.justify-center { + justify-content: center; +} + +.gap { + gap: 0.5rem; +} + +.gap-2 { + gap: 1rem; +} + +.group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.group-2 { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.group-h { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.flex-1 { + flex: 1; +} + +.items-end { + align-items: end; +} + +.items-start { + align-items: start; +} + +.justify-end { + justify-content: end; +} + +.font-grey-light { + color: var(--grey-light); +} + +.hidden { + display: none; +} + +.align-right { + text-align: right; +} + +/* ==== MARKDOWN ==== */ + +.md h1, +.md h2, +.md h3, +.md h4 { + padding: 0; + margin: 1.5rem 0 0.9rem 0; + font-weight: bold; +} + +.md h1 a, +.md h2 a, +.md h3 a, +.md h4 a { + color: var(--grey-light); + text-decoration: none; +} + +.md h1 { + font-size: 1.6rem; + line-height: 1.15; + border-bottom: 2px solid var(--grey); + padding-bottom: 0.7rem; +} + +.md h2 { + font-size: 1.3rem; + line-height: 1.15; + color: var(--white-dark); +} + +.md h3 { + font-size: 1.2rem; + color: var(--white-dark); +} + +.md h4 { + font-size: 1rem; + color: var(--white-dark); +} + +/* ==== HELPERS ==== */ + +.logo-header { + line-height: 1; + display: inline-block; + background-color: #FF79C6; + background-image: linear-gradient(to right, #FF5555, #FF79C6, #F8F859); + color: transparent; + background-clip: text; + border: 3px solid #FF79C6; + padding: 8px 10px 10px 10px; + border-radius: 10px; + box-shadow: 0px 5px 0px 0px var(--shadow); + background-size: 100%; + -webkit-background-clip: text; + -moz-background-clip: text; + -webkit-text-fill-color: transparent; + -moz-text-fill-color: transparent; +} + +.btn { + border: 2px solid var(--link-color); + color: var(--link-color); + padding: 0.4rem 1rem; + font-weight: bold; + display: inline-block; +} + +.btn-link, +.btn-link:visited { + border: 2px solid var(--link-color); + color: var(--link-color); + padding: 0.4rem 1rem; + text-decoration: none; + font-weight: bold; + display: inline-block; +} + +.btn-link:visited:hover, +.btn-link:hover { + border: 2px solid var(--hover); +} + +.btn-link-alt, +.btn-link-alt:visited { + border: 2px solid var(--white); + color: var(--white); +} + +.box { + border: 2px solid var(--grey-light); + padding: 0.5rem 0.75rem; +} + +.box-sm { + border: 2px solid var(--grey-light); + padding: 0.15rem 0.35rem; +} + +.box-alert { + border: 2px solid var(--hover); + padding: 0.5rem 0.75rem; +} + +.box-sm-alert { + border: 2px solid var(--hover); + padding: 0.15rem 0.35rem; +} + +.list-none { + list-style-type: none; +} + +.list-disc { + list-style-type: disc; +} + +.list-decimal { + list-style-type: decimal; +} + +.pill { + border: 1px solid var(--link-color); + color: var(--link-color); +} + +.pill-alert { + border: 1px solid var(--hover); + color: var(--hover); +} + +.pill-info { + border: 1px solid var(--visited); + color: var(--visited); +} + +@media only screen and (max-width: 40em) { + body { + padding: 0 1rem; + } + + header { + margin: 0; + } + + .flex-collapse { + flex-direction: column; + } +}