// Eko: A terminal-native social media platform // Copyright (C) 2025 Kyren223 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package webserver import ( "fmt" "io" "log/slog" "net/http" "strings" "time" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/kyren223/eko/embeds" "github.com/kyren223/eko/pkg/assert" ) func ServePrometheusMetrics() { slog.Info("starting metrics webserver...") metricsMux := http.NewServeMux() metricsMux.Handle("/metrics", promhttp.Handler()) srv := &http.Server{ Addr: ":2112", Handler: metricsMux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } err := srv.ListenAndServe() if err != nil { slog.Error("metrics webserver error", "error", err) } else { slog.Info("metrics webserver terminated") } } func ServeEkoWebsite() { slog.Info("starting public webserver...") publicMux := http.NewServeMux() publicMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://github.com/kyren223/eko", http.StatusFound) }) publicMux.HandleFunc("/install.sh", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/x-sh") _, err := io.WriteString(w, embeds.Installer) assert.NoError(err, "installer should be valid") }) publicMux.HandleFunc("/style.css", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/css") _, err := w.Write([]byte(css)) assert.NoError(err, "css *should* be valid") }) publicMux.HandleFunc("/terms-of-service", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") tos := embeds.TermsOfService.Load().(string) tos = strings.ReplaceAll(tos, "Privacy Policy", "[Privacy Policy](../privacy-policy)") html := mdToHTML(tos) writeLegalLayoutHtml(w, html) }) publicMux.HandleFunc("/privacy-policy", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") privacy := embeds.PrivacyPolicy.Load().(string) privacy = strings.ReplaceAll(privacy, "Terms of Service", "[Terms of Service](../terms-of-service)") html := mdToHTML(privacy) writeLegalLayoutHtml(w, html) }) srv := &http.Server{ Addr: ":7443", Handler: publicMux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } err := srv.ListenAndServe() if err != nil { slog.Error("public webserver error", "error", err) } else { slog.Info("public webserver terminated") } } func writeLegalLayoutHtml(w io.Writer, html string) { _, err := fmt.Fprintf(w, ` Terms of Service
%s
`, html) assert.NoError(err, "html *should* be valid") } func mdToHTML(md string) string { // create markdown parser with extensions extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock p := parser.NewWithExtensions(extensions) doc := p.Parse([]byte(md)) // create HTML renderer with extensions htmlFlags := html.CommonFlags | html.HrefTargetBlank opts := html.RendererOptions{Flags: htmlFlags} renderer := html.NewRenderer(opts) return string(markdown.Render(doc, renderer)) } // Yes this is horrible, but it works and I don't like web dev :) // Feel free to improve it and open a PR // Probably most of these are unncessary, I just copy pasted it from my website css var css = ` .sl-markdown-content :not(.expressive-code *) + :not(a, strong, em, del, span, input, code, br) + :not(a, strong, em, del, span, input, code, br, :where(.not-content *)) { margin-top: 1rem; } /* Headings after non-headings have more spacing. */ .sl-markdown-content :not(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3, h4, h5, h6):not(:where(.not-content *)) { margin-top: 1.5em; } .sl-markdown-content li + li:not(:where(.not-content *)), .sl-markdown-content dt + dt:not(:where(.not-content *)), .sl-markdown-content dt + dd:not(:where(.not-content *)), .sl-markdown-content dd + dd:not(:where(.not-content *)) { margin-top: 0.25rem; } .sl-markdown-content li:not(:where(.not-content *)) { overflow-wrap: anywhere; } .sl-markdown-content li > :last-child:not(li, ul, ol):not( a, strong, em, del, span, input, :where(.not-content *) ) { margin-bottom: 1.25rem; } .sl-markdown-content dt:not(:where(.not-content *)) { font-weight: 700; } .sl-markdown-content dd:not(:where(.not-content *)) { padding-inline-start: 1rem; } .sl-markdown-content :is(h1, h2, h3, h4, h5, h6):not(:where(.not-content *)) { color: var(--sl-color-white); line-height: var(--sl-line-height-headings); font-weight: 600; font-size: 10000%; } .sl-markdown-content :is(img, picture, video, canvas, svg, iframe):not(:where(.not-content *)) { display: block; max-width: 100%; height: auto; } .sl-markdown-content h1:not(:where(.not-content *)) { font-size: var(--sl-text-h1); } .sl-markdown-content h2:not(:where(.not-content *)) { font-size: var(--sl-text-h2); } .sl-markdown-content h3:not(:where(.not-content *)) { font-size: var(--sl-text-h3); } .sl-markdown-content h4:not(:where(.not-content *)) { font-size: var(--sl-text-h4); } .sl-markdown-content h5:not(:where(.not-content *)) { font-size: var(--sl-text-h5); } .sl-markdown-content h6:not(:where(.not-content *)) { font-size: var(--sl-text-h6); } .sl-markdown-content a:not(:where(.not-content *)) { color: var(--sl-color-text-accent); } .sl-markdown-content a:hover:not(:where(.not-content *)) { color: var(--sl-color-white); } .sl-markdown-content code:not(:where(.not-content *)) { background-color: var(--sl-color-bg-inline-code); margin-block: -0.125rem; padding: 0.125rem 0.375rem; font-size: var(--sl-text-code-sm); } .sl-markdown-content :is(h1, h2, h3, h4, h5, h6) code { font-size: inherit; } .sl-markdown-content pre:not(:where(.not-content *)) { border: 1px solid var(--sl-color-gray-5); padding: 0.75rem 1rem; font-size: var(--sl-text-code); tab-size: 2; } .sl-markdown-content pre code:not(:where(.not-content *)) { all: unset; font-family: var(--__sl-font-mono); } .sl-markdown-content blockquote:not(:where(.not-content *)) { border-inline-start: 1px solid var(--sl-color-gray-5); padding-inline-start: 1rem; } /* Table styling */ .sl-markdown-content table:not(:where(.not-content *)) { display: block; overflow: auto; border-spacing: 0; } .sl-markdown-content :is(th, td):not(:where(.not-content *)) { border-bottom: 1px solid var(--sl-color-gray-5); padding: 0.5rem 1rem; /* Align text to the top of the row in multiline tables. */ vertical-align: baseline; } .sl-markdown-content :is(th:first-child, td:first-child):not(:where(.not-content *)) { padding-inline-start: 0; } .sl-markdown-content :is(th:last-child, td:last-child):not(:where(.not-content *)) { padding-inline-end: 0; } .sl-markdown-content th:not(:where(.not-content *)) { color: var(--sl-color-white); font-weight: 600; } /* Align headings to the start of the line unless set by the align attribute. */ .sl-markdown-content th:not([align]):not(:where(.not-content *)) { text-align: start; } /* s,
s, and
s inside asides */ .sl-markdown-content .starlight-aside :is(th, td, hr, blockquote):not(:where(.not-content *)) { border-color: var(--sl-color-gray-4); } @supports ( border-color: color-mix(in srgb, var(--sl-color-asides-text-accent) 30%, transparent) ) { .sl-markdown-content .starlight-aside :is(th, td, hr, blockquote):not(:where(.not-content *)) { border-color: color-mix( in srgb, var(--sl-color-asides-text-accent) 30%, transparent ); } } /* inside asides */ @supports ( border-color: color-mix(in srgb, var(--sl-color-asides-text-accent) 12%, transparent) ) { .sl-markdown-content .starlight-aside code:not(:where(.not-content *)) { background-color: color-mix( in srgb, var(--sl-color-asides-text-accent) 12%, transparent ); } } .sl-markdown-content hr:not(:where(.not-content *)) { border: 0; border-bottom: 1px solid var(--sl-color-hairline); } /*
and styles */ .sl-markdown-content details:not(:where(.not-content *)) { --sl-details-border-color: var(--sl-color-gray-5); --sl-details-border-color--hover: var(--sl-color-text-accent); border-inline-start: 2px solid var(--sl-details-border-color); padding-inline-start: 1rem; } .sl-markdown-content details:not([open]):hover:not(:where(.not-content *)), .sl-markdown-content details:has(> summary:hover):not(:where(.not-content *)) { border-color: var(--sl-details-border-color--hover); } .sl-markdown-content summary:not(:where(.not-content *)) { color: var(--sl-color-white); cursor: pointer; display: block; /* Needed to hide the default marker in some browsers. */ font-weight: 600; /* Expand the outline so that the marker cannot distort it. */ margin-inline-start: -0.5rem; padding-inline-start: 0.5rem; } .sl-markdown-content details[open] > summary:not(:where(.not-content *)) { margin-bottom: 1rem; } /* marker styles */ .sl-markdown-content summary:not(:where(.not-content *))::marker, .sl-markdown-content summary:not(:where(.not-content *))::-webkit-details-marker { display: none; } .sl-markdown-content summary:not(:where(.not-content *))::before { --sl-details-marker-size: 1.25rem; background-color: currentColor; content: ""; display: inline-block; height: var(--sl-details-marker-size); width: var(--sl-details-marker-size); margin-inline: calc((var(--sl-details-marker-size) / 4) * -1) 0.25rem; vertical-align: middle; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14.8 11.3 10.6 7a1 1 0 1 0-1.4 1.5l3.5 3.5-3.5 3.5a1 1 0 0 0 0 1.4 1 1 0 0 0 .7.3 1 1 0 0 0 .7-.3l4.2-4.2a1 1 0 0 0 0-1.4Z'/%3E%3C/svg%3E%0A"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14.8 11.3 10.6 7a1 1 0 1 0-1.4 1.5l3.5 3.5-3.5 3.5a1 1 0 0 0 0 1.4 1 1 0 0 0 .7.3 1 1 0 0 0 .7-.3l4.2-4.2a1 1 0 0 0 0-1.4Z'/%3E%3C/svg%3E%0A"); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; } @media (prefers-reduced-motion: no-preference) { .sl-markdown-content summary:not(:where(.not-content *))::before { transition: transform 0.2s ease-in-out; } } .sl-markdown-content details[open] > summary:not(:where(.not-content *))::before { transform: rotateZ(90deg); } [dir="rtl"] .sl-markdown-content summary:not(:where(.not-content *))::before, .sl-markdown-content [dir="rtl"] summary:not(:where(.not-content *))::before { transform: rotateZ(180deg); } /* with only a paragraph automatically added when using MDX */ .sl-markdown-content summary:not(:where(.not-content *)) p:only-child { display: inline; } /*
styles inside asides */ .sl-markdown-content .starlight-aside details:not(:where(.not-content *)) { --sl-details-border-color: var(--sl-color-asides-border); --sl-details-border-color--hover: var(--sl-color-asides-text-accent); } .starlight-aside { padding: 1rem; border-inline-start: 0.25rem solid var(--sl-color-asides-border); color: var(--sl-color-white); } .starlight-aside--note { --sl-color-asides-text-accent: var(--sl-color-blue-high); --sl-color-asides-border: var(--sl-color-blue); background-color: var(--sl-color-blue-low); } .starlight-aside--tip { --sl-color-asides-text-accent: var(--sl-color-purple-high); --sl-color-asides-border: var(--sl-color-purple); background-color: var(--sl-color-purple-low); } .starlight-aside--caution { --sl-color-asides-text-accent: var(--sl-color-orange-high); --sl-color-asides-border: var(--sl-color-orange); background-color: var(--sl-color-orange-low); } .starlight-aside--danger { --sl-color-asides-text-accent: var(--sl-color-red-high); --sl-color-asides-border: var(--sl-color-red); background-color: var(--sl-color-red-low); } .starlight-aside__title { display: flex; gap: 0.5rem; align-items: center; font-size: var(--sl-text-h5); font-weight: 600; line-height: var(--sl-line-height-headings); color: var(--sl-color-asides-text-accent); } .starlight-aside__icon { font-size: 1.333em; width: 1em; height: 1em; } .starlight-aside__title + .starlight-aside__content { margin-top: 0.5rem; } .starlight-aside__content a { color: var(--sl-color-asides-text-accent); } :root, ::backdrop { /* Colors (dark mode) */ --sl-color-white: hsl(0, 0%, 100%); /* “white” */ --sl-color-gray-1: hsl(224, 20%, 94%); --sl-color-gray-2: hsl(224, 6%, 77%); --sl-color-gray-3: hsl(224, 6%, 56%); --sl-color-gray-4: hsl(224, 7%, 36%); --sl-color-gray-5: hsl(224, 10%, 23%); --sl-color-gray-6: hsl(224, 14%, 16%); --sl-color-black: hsl(224, 10%, 10%); --sl-hue-orange: 41; --sl-color-orange-low: hsl(var(--sl-hue-orange), 39%, 22%); --sl-color-orange: hsl(var(--sl-hue-orange), 82%, 63%); --sl-color-orange-high: hsl(var(--sl-hue-orange), 82%, 87%); --sl-hue-green: 101; --sl-color-green-low: hsl(var(--sl-hue-green), 39%, 22%); --sl-color-green: hsl(var(--sl-hue-green), 82%, 63%); --sl-color-green-high: hsl(var(--sl-hue-green), 82%, 80%); --sl-hue-blue: 234; --sl-color-blue-low: hsl(var(--sl-hue-blue), 54%, 20%); --sl-color-blue: hsl(var(--sl-hue-blue), 100%, 60%); --sl-color-blue-high: hsl(var(--sl-hue-blue), 100%, 87%); --sl-hue-purple: 281; --sl-color-purple-low: hsl(var(--sl-hue-purple), 39%, 22%); --sl-color-purple: hsl(var(--sl-hue-purple), 82%, 63%); --sl-color-purple-high: hsl(var(--sl-hue-purple), 82%, 89%); --sl-hue-red: 339; --sl-color-red-low: hsl(var(--sl-hue-red), 39%, 22%); --sl-color-red: hsl(var(--sl-hue-red), 82%, 63%); --sl-color-red-high: hsl(var(--sl-hue-red), 82%, 87%); --sl-color-accent-low: hsl(224, 54%, 20%); --sl-color-accent: hsl(224, 100%, 60%); --sl-color-accent-high: hsl(224, 100%, 85%); --sl-color-text: var(--sl-color-gray-2); --sl-color-text-accent: var(--sl-color-accent-high); --sl-color-text-invert: var(--sl-color-accent-low); --sl-color-bg: var(--sl-color-black); --sl-color-bg-nav: var(--sl-color-gray-6); --sl-color-bg-sidebar: var(--sl-color-gray-6); --sl-color-bg-inline-code: var(--sl-color-gray-5); --sl-color-bg-accent: var(--sl-color-accent-high); --sl-color-hairline-light: var(--sl-color-gray-5); --sl-color-hairline: var(--sl-color-gray-6); --sl-color-hairline-shade: var(--sl-color-black); --sl-color-backdrop-overlay: hsla(223, 13%, 10%, 0.66); /* Shadows (dark mode) */ --sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, 0.12), 0px 2px 1px hsla(0, 0%, 0%, 0.24); --sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, 0.08), 0px 5px 2px hsla(0, 0%, 0%, 0.08), 0px 3px 2px hsla(0, 0%, 0%, 0.12), 0px 1px 1px hsla(0, 0%, 0%, 0.15); --sl-shadow-lg: 0px 25px 7px hsla(0, 0%, 0%, 0.03), 0px 16px 6px hsla(0, 0%, 0%, 0.1), 0px 9px 5px hsla(223, 13%, 10%, 0.33), 0px 4px 4px hsla(0, 0%, 0%, 0.75), 0px 4px 2px hsla(0, 0%, 0%, 0.25); /* Text size and line height */ --sl-text-2xs: 0.75rem; /* 12px */ --sl-text-xs: 0.8125rem; /* 13px */ --sl-text-sm: 0.875rem; /* 14px */ --sl-text-base: 1rem; /* 16px */ --sl-text-lg: 1.125rem; /* 18px */ --sl-text-xl: 1.25rem; /* 20px */ --sl-text-2xl: 1.5rem; /* 24px */ --sl-text-3xl: 1.8125rem; /* 29px */ --sl-text-4xl: 2.1875rem; /* 35px */ --sl-text-5xl: 2.625rem; /* 42px */ --sl-text-6xl: 4rem; /* 64px */ --sl-text-body: var(--sl-text-base); --sl-text-body-sm: var(--sl-text-xs); --sl-text-code: var(--sl-text-sm); --sl-text-code-sm: var(--sl-text-xs); --sl-text-h1: var(--sl-text-4xl); --sl-text-h2: var(--sl-text-3xl); --sl-text-h3: var(--sl-text-2xl); --sl-text-h4: var(--sl-text-xl); --sl-text-h5: var(--sl-text-lg); --sl-line-height: 1.75; --sl-line-height-headings: 1.2; --sl-font-system: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --sl-font-system-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --__sl-font: var(--sl-font, var(--sl-font-system)), var(--sl-font-system); --__sl-font-mono: var(--sl-font-mono, var(--sl-font-system-mono)), var(--sl-font-system-mono); /** Key layout values */ --sl-nav-height: 3.5rem; --sl-nav-pad-x: 1rem; --sl-nav-pad-y: 0.75rem; --sl-mobile-toc-height: 3rem; --sl-sidebar-width: 18.75rem; --sl-sidebar-pad-x: 1rem; --sl-content-width: 45rem; --sl-content-pad-x: 1rem; --sl-menu-button-size: 2rem; --sl-nav-gap: var(--sl-content-pad-x); /* Offset required to show outline inside an element instead of round the outside */ --sl-outline-offset-inside: -0.1875rem; /* Global z-index values */ --sl-z-index-toc: 4; --sl-z-index-menu: 5; --sl-z-index-navbar: 10; --sl-z-index-skiplink: 20; } :root[data-theme="light"], [data-theme="light"] ::backdrop { /* Colours (light mode) */ --sl-color-white: hsl(224, 10%, 10%); --sl-color-gray-1: hsl(224, 14%, 16%); --sl-color-gray-2: hsl(224, 10%, 23%); --sl-color-gray-3: hsl(224, 7%, 36%); --sl-color-gray-4: hsl(224, 6%, 56%); --sl-color-gray-5: hsl(224, 6%, 77%); --sl-color-gray-6: hsl(224, 20%, 94%); --sl-color-gray-7: hsl(224, 19%, 97%); --sl-color-black: hsl(0, 0%, 100%); --sl-color-orange-high: hsl(var(--sl-hue-orange), 80%, 25%); --sl-color-orange: hsl(var(--sl-hue-orange), 90%, 60%); --sl-color-orange-low: hsl(var(--sl-hue-orange), 90%, 88%); --sl-color-green-high: hsl(var(--sl-hue-green), 80%, 22%); --sl-color-green: hsl(var(--sl-hue-green), 90%, 46%); --sl-color-green-low: hsl(var(--sl-hue-green), 85%, 90%); --sl-color-blue-high: hsl(var(--sl-hue-blue), 80%, 30%); --sl-color-blue: hsl(var(--sl-hue-blue), 90%, 60%); --sl-color-blue-low: hsl(var(--sl-hue-blue), 88%, 90%); --sl-color-purple-high: hsl(var(--sl-hue-purple), 90%, 30%); --sl-color-purple: hsl(var(--sl-hue-purple), 90%, 60%); --sl-color-purple-low: hsl(var(--sl-hue-purple), 80%, 90%); --sl-color-red-high: hsl(var(--sl-hue-red), 80%, 30%); --sl-color-red: hsl(var(--sl-hue-red), 90%, 60%); --sl-color-red-low: hsl(var(--sl-hue-red), 80%, 90%); --sl-color-accent-high: hsl(234, 80%, 30%); --sl-color-accent: hsl(234, 90%, 60%); --sl-color-accent-low: hsl(234, 88%, 90%); --sl-color-text-accent: var(--sl-color-accent); --sl-color-text-invert: var(--sl-color-black); --sl-color-bg-nav: var(--sl-color-gray-7); --sl-color-bg-sidebar: var(--sl-color-bg); --sl-color-bg-inline-code: var(--sl-color-gray-6); --sl-color-bg-accent: var(--sl-color-accent); --sl-color-hairline-light: var(--sl-color-gray-6); --sl-color-hairline-shade: var(--sl-color-gray-6); --sl-color-backdrop-overlay: hsla(225, 9%, 36%, 0.66); /* Shadows (light mode) */ --sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, 0.06), 0px 2px 1px hsla(0, 0%, 0%, 0.06); --sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, 0.03), 0px 5px 2px hsla(0, 0%, 0%, 0.03), 0px 3px 2px hsla(0, 0%, 0%, 0.06), 0px 1px 1px hsla(0, 0%, 0%, 0.06); --sl-shadow-lg: 0px 25px 7px rgba(0, 0, 0, 0.01), 0px 16px 6px hsla(0, 0%, 0%, 0.03), 0px 9px 5px hsla(223, 13%, 10%, 0.08), 0px 4px 4px hsla(0, 0%, 0%, 0.16), 0px 4px 2px hsla(0, 0%, 0%, 0.04); } @media (min-width: 50em) { :root { --sl-nav-height: 4rem; --sl-nav-pad-x: 1.5rem; --sl-text-h1: var(--sl-text-5xl); --sl-text-h2: var(--sl-text-4xl); --sl-text-h3: var(--sl-text-3xl); --sl-text-h4: var(--sl-text-2xl); } } @media (min-width: 72rem) { :root { --sl-content-pad-x: 1.5rem; --sl-mobile-toc-height: 0rem; } } *, *::before, *::after { box-sizing: border-box; } * { margin: 0; } html { color-scheme: dark; accent-color: var(--sl-color-accent); } html[data-theme="light"] { color-scheme: light; } body { font-family: var(--__sl-font); line-height: var(--sl-line-height); -webkit-font-smoothing: antialiased; color: var(--sl-color-text); background-color: var(--sl-color-bg); } input, button, textarea, select { font: inherit; } p, h1, h2, h3, h4, h5, h6, code { overflow-wrap: anywhere; } code { font-family: var(--__sl-font-mono); } :root { --astro-code-color-text: var(--sl-color-white); --astro-code-color-background: var(--sl-color-gray-6); --astro-code-token-constant: var(--sl-color-blue-high); --astro-code-token-string: var(--sl-color-green-high); --astro-code-token-comment: var(--sl-color-gray-2); --astro-code-token-keyword: var(--sl-color-purple-high); --astro-code-token-parameter: var(--sl-color-red-high); --astro-code-token-function: var(--sl-color-red-high); --astro-code-token-string-expression: var(--sl-color-green-high); --astro-code-token-punctuation: var(--sl-color-gray-2); --astro-code-token-link: var(--sl-color-blue-high); } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } .sl-hidden { display: none; } .sl-flex { display: flex; } .sl-block { display: block; } @media (min-width: 50rem) { .md\:sl-hidden { display: none; } .md\:sl-flex { display: flex; } .md\:sl-block { display: block; } } @media (min-width: 72rem) { .lg\:sl-hidden { display: none; } .lg\:sl-flex { display: flex; } .lg\:sl-block { display: block; } } [data-theme="light"] .light\:sl-hidden { display: none; } [data-theme="dark"] .dark\:sl-hidden { display: none; } /* Flip an element around the y-axis when in an RTL context. Primarily useful for things where we can’t rely on writing direction like icons. In a LTR context: → In a RTL context: ← */ [dir="rtl"] .rtl\:flip:not(:where([dir="rtl"] [dir="ltr"] *)) { transform: matrix(-1, 0, 0, 1, 0, 0); } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-Thin.woff2") format("woff2"); font-weight: 100; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-ThinItalic.woff2") format("woff2"); font-weight: 100; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-ExtraLight.woff2") format("woff2"); font-weight: 200; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-ExtraLightItalic.woff2") format("woff2"); font-weight: 200; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-Light.woff2") format("woff2"); font-weight: 300; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-LightItalic.woff2") format("woff2"); font-weight: 300; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-Italic.woff2") format("woff2"); font-weight: 400; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-Medium.woff2") format("woff2"); font-weight: 500; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-MediumItalic.woff2") format("woff2"); font-weight: 500; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-SemiBold.woff2") format("woff2"); font-weight: 600; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-SemiBoldItalic.woff2") format("woff2"); font-weight: 600; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-Bold.woff2") format("woff2"); font-weight: 700; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-BoldItalic.woff2") format("woff2"); font-weight: 700; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-ExtraBold.woff2") format("woff2"); font-weight: 800; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-ExtraBoldItalic.woff2") format("woff2"); font-weight: 800; font-style: italic; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-ExtraBold.woff2") format("woff2"); font-weight: 900; font-style: normal; } @font-face { font-family: "Jetbrains Mono"; src: url("/fonts/JetBrainsMono-ExtraBoldItalic.woff2") format("woff2"); font-weight: 900; font-style: italic; } @font-face { font-family: 'Monocraft'; src: url('/fonts/Monocraft.ttf') format('truetype'); font-weight: 400; font-style: normal; font-display: swap; } :root { --primary: #54d7a9; --secondary: #9b7eca; --accent: #7ed4fb; --extra: #8fcc75; --text: #ffffff; --alt: #999999; --background: #000000; --font: "Jetbrains Mono", monospace; } * { margin: 0; padding: 0; } html { background-color: var(--background); color: var(--text); font-family: var(--font); } body { zoom: 1; } h1, h2, h3 { font-size: 1rem; font-weight: bold; } a { font-weight: 300; } a:link, a:visited, a:active { background-color: transparent; text-decoration: none; color: var(--secondary); } a:hover { transition-duration: 0.2s; transition-property: color, text-decoration; text-decoration: underline; color: var(--accent); } main { display: block; position: absolute; padding-top: 10%; padding-bottom: 10%; left: 50%; transform: translate(-50%, 0%); background: var(--background); font-weight: 300; } `