├── static
├── css
│ ├── index.css
│ ├── input.css
│ └── output.css
└── js
│ └── index.js
├── html
├── components
│ └── home.html
└── templates
│ └── base.html
├── internal
├── stypes
│ └── stypes.go
├── middleware
│ └── middleware.go
└── filehandler
│ └── filehandler.go
├── tailwind.config.js
├── go.mod
├── go.sum
├── main.go
└── README.md
/static/css/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/html/components/home.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/js/index.js:
--------------------------------------------------------------------------------
1 | console.log("connected!")
--------------------------------------------------------------------------------
/static/css/input.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/internal/stypes/stypes.go:
--------------------------------------------------------------------------------
1 | package stypes
2 |
3 | import (
4 | "html/template"
5 | )
6 |
7 | type BasePageData struct {
8 | Title string
9 | Content template.HTML
10 | }
11 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./**/*.{html, go, js, ts}",
5 |
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module go-quickstart
2 |
3 | go 1.22.1
4 |
5 | require (
6 | github.com/alecthomas/chroma/v2 v2.2.0 // indirect
7 | github.com/dlclark/regexp2 v1.7.0 // indirect
8 | github.com/yuin/goldmark v1.7.1 // indirect
9 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
10 | )
11 |
--------------------------------------------------------------------------------
/html/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ .Title }}
10 |
11 |
12 |
13 |
19 |
20 | {{ .Content }}
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/internal/middleware/middleware.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "html/template"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | type CustomContext struct {
12 | context.Context
13 | Templates *template.Template
14 | StartTime time.Time
15 | }
16 |
17 | type CustomHandler func(ctx *CustomContext, w http.ResponseWriter, r *http.Request)
18 | type CustomMiddleware func(ctx *CustomContext, w http.ResponseWriter, r *http.Request) error
19 |
20 | func Chain(w http.ResponseWriter, r *http.Request, templates *template.Template, handler CustomHandler, middleware ...CustomMiddleware) {
21 | customContext := &CustomContext{
22 | Context: context.Background(),
23 | Templates: templates,
24 | StartTime: time.Now(),
25 | }
26 | for _, mw := range middleware {
27 | err := mw(customContext, w, r)
28 | if err != nil {
29 | return
30 | }
31 | }
32 | handler(customContext, w, r)
33 | Log(customContext, w, r)
34 | }
35 |
36 | func Log(ctx *CustomContext, w http.ResponseWriter, r *http.Request) error {
37 | elapsedTime := time.Since(ctx.StartTime)
38 | formattedTime := time.Now().Format("2006-01-02 15:04:05")
39 | fmt.Printf("[%s] [%s] [%s] [%s]\n", formattedTime, r.Method, r.URL.Path, elapsedTime)
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
2 | github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
3 | github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
7 | github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
8 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
11 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
12 | github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
13 | github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
14 | github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
15 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
16 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
19 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "go-quickstart/internal/filehandler"
6 | "go-quickstart/internal/middleware"
7 | "go-quickstart/internal/stypes"
8 | "net/http"
9 | "path/filepath"
10 | )
11 |
12 | func main() {
13 |
14 | port := "8080"
15 | templates, err := filehandler.ParseTemplates()
16 | if err != nil {
17 | fmt.Println(err)
18 | return
19 | }
20 |
21 | mux := http.NewServeMux()
22 |
23 | mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
24 | if r.URL.Path != "/" {
25 | http.NotFound(w, r)
26 | return
27 | }
28 | middleware.Chain(w, r, templates, HandleHome)
29 | })
30 |
31 | mux.HandleFunc("GET /favicon.ico", func(w http.ResponseWriter, r *http.Request) {
32 | middleware.Chain(w, r, templates, HandleFavicon)
33 | })
34 |
35 | mux.HandleFunc("GET /static/", func(w http.ResponseWriter, r *http.Request) {
36 | middleware.Chain(w, r, templates, HandleStatic)
37 | })
38 |
39 | fmt.Println("Running Development Server on localhost:" + port)
40 | http.ListenAndServe(":"+port, mux)
41 | }
42 |
43 | func HandleFavicon(customContext *middleware.CustomContext, w http.ResponseWriter, r *http.Request) {
44 | filePath := "favicon.ico"
45 | fullPath := filepath.Join(".", ".", filePath)
46 | http.ServeFile(w, r, fullPath)
47 | }
48 |
49 | func HandleStatic(customContext *middleware.CustomContext, w http.ResponseWriter, r *http.Request) {
50 | filePath := r.URL.Path[len("/static/"):]
51 | fullPath := filepath.Join(".", "static", filePath)
52 | http.ServeFile(w, r, fullPath)
53 | }
54 |
55 | func HandleHome(customContext *middleware.CustomContext, w http.ResponseWriter, r *http.Request) {
56 | err := customContext.Templates.ExecuteTemplate(w, "base.html", stypes.BasePageData{
57 | Title: "Stylz | Home",
58 | Content: filehandler.ExecuteMarkdown("README.md"),
59 | })
60 | if err != nil {
61 | fmt.Println(err)
62 | http.Error(w, "Internal Server Error", http.StatusInternalServerError)
63 | return
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/internal/filehandler/filehandler.go:
--------------------------------------------------------------------------------
1 | package filehandler
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "html/template"
7 | "os"
8 | "path/filepath"
9 |
10 | chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
11 | "github.com/yuin/goldmark"
12 | highlighting "github.com/yuin/goldmark-highlighting/v2"
13 | )
14 |
15 | func ParseTemplates() (*template.Template, error) {
16 | templates := template.New("")
17 | err := filepath.Walk("./html", func(path string, info os.FileInfo, err error) error {
18 | if err != nil {
19 | return err
20 | }
21 | if !info.IsDir() && filepath.Ext(path) == ".html" {
22 | _, err := templates.ParseFiles(path)
23 | if err != nil {
24 | return err
25 | }
26 | }
27 | return nil
28 | })
29 | if err != nil {
30 | return nil, err
31 | }
32 | return templates, nil
33 | }
34 |
35 | func ExecuteTemplate(t *template.Template, name string, data interface{}) template.HTML {
36 | var templateContent bytes.Buffer
37 | err := t.ExecuteTemplate(&templateContent, name, data)
38 | if err != nil {
39 | panic(err)
40 | }
41 | return template.HTML(templateContent.String())
42 | }
43 |
44 | func ExecuteMarkdown(filepath string) template.HTML {
45 | file, err := os.Open(filepath)
46 | if err != nil {
47 | panic(err)
48 | }
49 | defer file.Close()
50 | fileBytes := new(bytes.Buffer)
51 | fileBytes.ReadFrom(file)
52 | md := goldmark.New(
53 | goldmark.WithExtensions(
54 | highlighting.NewHighlighting(
55 | highlighting.WithStyle("github"),
56 | highlighting.WithGuessLanguage(true),
57 | highlighting.WithFormatOptions(
58 | chromahtml.WithLineNumbers(true),
59 | ),
60 | ),
61 | ),
62 | )
63 | var output bytes.Buffer
64 | if err := md.Convert(fileBytes.Bytes(), &output); err != nil {
65 | panic(err)
66 | }
67 | outputString := output.String()
68 | for i := 0; i < len(outputString); i++ {
69 | chunkSize := 6
70 | atEnd := i+chunkSize >= len(outputString)
71 | if atEnd {
72 | break
73 | }
74 | chunck := outputString[i : i+chunkSize]
75 | if chunck == "" {
76 | fmt.Println("hit")
77 | // insertIndex := i - 1
78 | // copySvg := `
79 | //
80 | //
83 | //
84 | // `
85 | // outputString = outputString[:insertIndex] + copySvg + outputString[insertIndex:]
86 | }
87 | }
88 | return template.HTML(outputString)
89 | }
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-quickstart
2 |
3 | go-quickstart is intended to get you a Go server ASAP.
4 |
5 | ## Requirements
6 |
7 | go-quickstart requires Go version 1.22.0 or greater
8 |
9 | ## Installation
10 |
11 | To install go-quickstart, do the following:
12 |
13 | 1. Create a directory
14 |
15 | ```bash
16 | mkdir myapp
17 | cd myapp
18 | ```
19 |
20 | 2. Clone the repo
21 |
22 | ```bash
23 | git clone https://github.com/phillip-england/go-quickstart .
24 | ```
25 |
26 | 3. Run the server
27 |
28 | ```bash
29 | go run main.go
30 | ```
31 |
32 | 4. Visit localhost:8080
33 |
34 | That's it, you're up and running!
35 |
36 | ## Serving
37 |
38 | To serve the application, simply run:
39 |
40 | ```bash
41 | go run main.go
42 | ```
43 |
44 | ## Custom Middleware
45 |
46 | go-quickstart comes with a custom middleware implemenatation. Instead of deeply nesting all of your middleware logic, go-quickstart enables you to chain middleware to your routes using the `Chain` func found in `/internal/middleware/middleware.go`.
47 |
48 | ### Creating Custom Middleware
49 |
50 | To create a new custom middleware, let's head over to `/internal/middleware/middleware.go`.
51 |
52 | ```go
53 | type CustomContext struct {
54 | context.Context
55 | Templates *template.Template
56 | StartTime time.Time
57 | }
58 |
59 | type CustomHandler func(ctx *CustomContext, w http.ResponseWriter, r *http.Request)
60 | type CustomMiddleware func(ctx *CustomContext, w http.ResponseWriter, r *http.Request) error
61 |
62 | func Chain(w http.ResponseWriter, r *http.Request, templates *template.Template, handler CustomHandler, middleware ...CustomMiddleware) {
63 | customContext := &CustomContext{
64 | Context: context.Background(),
65 | Templates: templates,
66 | StartTime: time.Now(),
67 | }
68 | for _, mw := range middleware {
69 | err := mw(customContext, w, r)
70 | if err != nil {
71 | return
72 | }
73 | }
74 | handler(customContext, w, r)
75 | Log(customContext, w, r)
76 | }
77 |
78 | func Log(ctx *CustomContext, w http.ResponseWriter, r *http.Request) error {
79 | elapsedTime := time.Since(ctx.StartTime)
80 | formattedTime := time.Now().Format("2006-01-02 15:04:05")
81 | fmt.Printf("[%s] [%s] [%s] [%s]\n", formattedTime, r.Method, r.URL.Path, elapsedTime)
82 | return nil
83 | }
84 | ```
85 |
86 | You can already see the middleware func `Log` defined at the bottom of the file. Let's define a new middleware called `HelloWorld`.
87 |
88 | At the bottom of the file, write:
89 |
90 | ```go
91 | func HelloWorld(ctx *CustomContext, w http.ResponseWriter, r *http.Request) error {
92 | fmt.Println("Hello, World")
93 | return nil
94 | }
95 | ```
96 |
97 | Now, take note. All custom middleware must take in `*CustomContext`, `http.ResponseWriter`, and `*http.Request` as parameters. This is what defines the func as a custom middleware.
98 |
99 | ### Using Custom Middleware
100 |
101 | To use the custom middleware, lets take a look at `main.go`:
102 |
103 | ```go
104 | func main() {
105 | // ...
106 | mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
107 | // handles 404 errors
108 | // only required on the "/" endpoint
109 | if r.URL.Path != "/" {
110 | http.NotFound(w, r)
111 | return
112 | }
113 | middleware.Chain(w, r, templates, HandlerHome)
114 | })
115 | }
116 | ```
117 |
118 | Now, let's add our new `HelloWorld` middleware to the chain.
119 |
120 | ```go
121 | middleware.Chain(w, r, templates, HandlerHome, middleware.HelloWorld)
122 | ```
123 |
124 | That's it! You've created a new custom middleware and added it to the "/" endpoint.
125 |
126 | Serve the application with `go run main.go` and visit localhost:8080. You should see the following output printed to the console:
127 |
128 | ```bash
129 | Hello, World
130 | ```
131 |
132 | ### Middleware Errors
133 |
134 | If a middleware returns an error, it will be handled in the `Chain` func. This enables you to stop execution and handle errors with ease.
135 |
136 | Let's look at the `Chain` func in, `/internal/middleware/middleware.go`:
137 |
138 | ```go
139 | func Chain(w http.ResponseWriter, r *http.Request, templates *template.Template, handler CustomHandler, middleware ...CustomMiddleware) {
140 | customContext := &CustomContext{
141 | Context: context.Background(),
142 | Templates: templates,
143 | StartTime: time.Now(),
144 | }
145 | for _, mw := range middleware {
146 | err := mw(customContext, w, r)
147 | if err != nil {
148 | return
149 | }
150 | }
151 | handler(customContext, w, r)
152 | Log(customContext, w, r)
153 | }
154 | ```
155 |
156 | Zooming in to where we handle potential middleware errors:
157 |
158 | ```go
159 | for _, mw := range middleware {
160 | err := mw(customContext, w, r)
161 | if err != nil {
162 | return // if an error occurs, do not run the handler
163 | }
164 | handler(customContext, w, r)
165 | }
166 | ```
167 |
168 | ## Parsing Templates
169 |
170 | go-quickstart already handles parsing all of your templates at `/html`. It does so with the `ParseTemplates` func found at `/internal/filehandler/filehandler.go`.
171 |
172 | Just stash new templates anywhere in `/html` and they will be parsed at the start of your programs execution.
173 |
174 |
175 |
--------------------------------------------------------------------------------
/static/css/output.css:
--------------------------------------------------------------------------------
1 | /*
2 | ! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com
3 | */
4 |
5 | /*
6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
8 | */
9 |
10 | *,
11 | ::before,
12 | ::after {
13 | box-sizing: border-box;
14 | /* 1 */
15 | border-width: 0;
16 | /* 2 */
17 | border-style: solid;
18 | /* 2 */
19 | border-color: #e5e7eb;
20 | /* 2 */
21 | }
22 |
23 | ::before,
24 | ::after {
25 | --tw-content: '';
26 | }
27 |
28 | /*
29 | 1. Use a consistent sensible line-height in all browsers.
30 | 2. Prevent adjustments of font size after orientation changes in iOS.
31 | 3. Use a more readable tab size.
32 | 4. Use the user's configured `sans` font-family by default.
33 | 5. Use the user's configured `sans` font-feature-settings by default.
34 | 6. Use the user's configured `sans` font-variation-settings by default.
35 | 7. Disable tap highlights on iOS
36 | */
37 |
38 | html,
39 | :host {
40 | line-height: 1.5;
41 | /* 1 */
42 | -webkit-text-size-adjust: 100%;
43 | /* 2 */
44 | -moz-tab-size: 4;
45 | /* 3 */
46 | -o-tab-size: 4;
47 | tab-size: 4;
48 | /* 3 */
49 | font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
50 | /* 4 */
51 | font-feature-settings: normal;
52 | /* 5 */
53 | font-variation-settings: normal;
54 | /* 6 */
55 | -webkit-tap-highlight-color: transparent;
56 | /* 7 */
57 | }
58 |
59 | /*
60 | 1. Remove the margin in all browsers.
61 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
62 | */
63 |
64 | body {
65 | margin: 0;
66 | /* 1 */
67 | line-height: inherit;
68 | /* 2 */
69 | }
70 |
71 | /*
72 | 1. Add the correct height in Firefox.
73 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
74 | 3. Ensure horizontal rules are visible by default.
75 | */
76 |
77 | hr {
78 | height: 0;
79 | /* 1 */
80 | color: inherit;
81 | /* 2 */
82 | border-top-width: 1px;
83 | /* 3 */
84 | }
85 |
86 | /*
87 | Add the correct text decoration in Chrome, Edge, and Safari.
88 | */
89 |
90 | abbr:where([title]) {
91 | -webkit-text-decoration: underline dotted;
92 | text-decoration: underline dotted;
93 | }
94 |
95 | /*
96 | Remove the default font size and weight for headings.
97 | */
98 |
99 | h1,
100 | h2,
101 | h3,
102 | h4,
103 | h5,
104 | h6 {
105 | font-size: inherit;
106 | font-weight: inherit;
107 | }
108 |
109 | /*
110 | Reset links to optimize for opt-in styling instead of opt-out.
111 | */
112 |
113 | a {
114 | color: inherit;
115 | text-decoration: inherit;
116 | }
117 |
118 | /*
119 | Add the correct font weight in Edge and Safari.
120 | */
121 |
122 | b,
123 | strong {
124 | font-weight: bolder;
125 | }
126 |
127 | /*
128 | 1. Use the user's configured `mono` font-family by default.
129 | 2. Use the user's configured `mono` font-feature-settings by default.
130 | 3. Use the user's configured `mono` font-variation-settings by default.
131 | 4. Correct the odd `em` font sizing in all browsers.
132 | */
133 |
134 | code,
135 | kbd,
136 | samp,
137 | pre {
138 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
139 | /* 1 */
140 | font-feature-settings: normal;
141 | /* 2 */
142 | font-variation-settings: normal;
143 | /* 3 */
144 | font-size: 1em;
145 | /* 4 */
146 | }
147 |
148 | /*
149 | Add the correct font size in all browsers.
150 | */
151 |
152 | small {
153 | font-size: 80%;
154 | }
155 |
156 | /*
157 | Prevent `sub` and `sup` elements from affecting the line height in all browsers.
158 | */
159 |
160 | sub,
161 | sup {
162 | font-size: 75%;
163 | line-height: 0;
164 | position: relative;
165 | vertical-align: baseline;
166 | }
167 |
168 | sub {
169 | bottom: -0.25em;
170 | }
171 |
172 | sup {
173 | top: -0.5em;
174 | }
175 |
176 | /*
177 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
178 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
179 | 3. Remove gaps between table borders by default.
180 | */
181 |
182 | table {
183 | text-indent: 0;
184 | /* 1 */
185 | border-color: inherit;
186 | /* 2 */
187 | border-collapse: collapse;
188 | /* 3 */
189 | }
190 |
191 | /*
192 | 1. Change the font styles in all browsers.
193 | 2. Remove the margin in Firefox and Safari.
194 | 3. Remove default padding in all browsers.
195 | */
196 |
197 | button,
198 | input,
199 | optgroup,
200 | select,
201 | textarea {
202 | font-family: inherit;
203 | /* 1 */
204 | font-feature-settings: inherit;
205 | /* 1 */
206 | font-variation-settings: inherit;
207 | /* 1 */
208 | font-size: 100%;
209 | /* 1 */
210 | font-weight: inherit;
211 | /* 1 */
212 | line-height: inherit;
213 | /* 1 */
214 | letter-spacing: inherit;
215 | /* 1 */
216 | color: inherit;
217 | /* 1 */
218 | margin: 0;
219 | /* 2 */
220 | padding: 0;
221 | /* 3 */
222 | }
223 |
224 | /*
225 | Remove the inheritance of text transform in Edge and Firefox.
226 | */
227 |
228 | button,
229 | select {
230 | text-transform: none;
231 | }
232 |
233 | /*
234 | 1. Correct the inability to style clickable types in iOS and Safari.
235 | 2. Remove default button styles.
236 | */
237 |
238 | button,
239 | input:where([type='button']),
240 | input:where([type='reset']),
241 | input:where([type='submit']) {
242 | -webkit-appearance: button;
243 | /* 1 */
244 | background-color: transparent;
245 | /* 2 */
246 | background-image: none;
247 | /* 2 */
248 | }
249 |
250 | /*
251 | Use the modern Firefox focus style for all focusable elements.
252 | */
253 |
254 | :-moz-focusring {
255 | outline: auto;
256 | }
257 |
258 | /*
259 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
260 | */
261 |
262 | :-moz-ui-invalid {
263 | box-shadow: none;
264 | }
265 |
266 | /*
267 | Add the correct vertical alignment in Chrome and Firefox.
268 | */
269 |
270 | progress {
271 | vertical-align: baseline;
272 | }
273 |
274 | /*
275 | Correct the cursor style of increment and decrement buttons in Safari.
276 | */
277 |
278 | ::-webkit-inner-spin-button,
279 | ::-webkit-outer-spin-button {
280 | height: auto;
281 | }
282 |
283 | /*
284 | 1. Correct the odd appearance in Chrome and Safari.
285 | 2. Correct the outline style in Safari.
286 | */
287 |
288 | [type='search'] {
289 | -webkit-appearance: textfield;
290 | /* 1 */
291 | outline-offset: -2px;
292 | /* 2 */
293 | }
294 |
295 | /*
296 | Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | ::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /*
304 | 1. Correct the inability to style clickable types in iOS and Safari.
305 | 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button;
310 | /* 1 */
311 | font: inherit;
312 | /* 2 */
313 | }
314 |
315 | /*
316 | Add the correct display in Chrome and Safari.
317 | */
318 |
319 | summary {
320 | display: list-item;
321 | }
322 |
323 | /*
324 | Removes the default spacing and border for appropriate elements.
325 | */
326 |
327 | blockquote,
328 | dl,
329 | dd,
330 | h1,
331 | h2,
332 | h3,
333 | h4,
334 | h5,
335 | h6,
336 | hr,
337 | figure,
338 | p,
339 | pre {
340 | margin: 0;
341 | }
342 |
343 | fieldset {
344 | margin: 0;
345 | padding: 0;
346 | }
347 |
348 | legend {
349 | padding: 0;
350 | }
351 |
352 | ol,
353 | ul,
354 | menu {
355 | list-style: none;
356 | margin: 0;
357 | padding: 0;
358 | }
359 |
360 | /*
361 | Reset default styling for dialogs.
362 | */
363 |
364 | dialog {
365 | padding: 0;
366 | }
367 |
368 | /*
369 | Prevent resizing textareas horizontally by default.
370 | */
371 |
372 | textarea {
373 | resize: vertical;
374 | }
375 |
376 | /*
377 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
378 | 2. Set the default placeholder color to the user's configured gray 400 color.
379 | */
380 |
381 | input::-moz-placeholder, textarea::-moz-placeholder {
382 | opacity: 1;
383 | /* 1 */
384 | color: #9ca3af;
385 | /* 2 */
386 | }
387 |
388 | input::placeholder,
389 | textarea::placeholder {
390 | opacity: 1;
391 | /* 1 */
392 | color: #9ca3af;
393 | /* 2 */
394 | }
395 |
396 | /*
397 | Set the default cursor for buttons.
398 | */
399 |
400 | button,
401 | [role="button"] {
402 | cursor: pointer;
403 | }
404 |
405 | /*
406 | Make sure disabled buttons don't get the pointer cursor.
407 | */
408 |
409 | :disabled {
410 | cursor: default;
411 | }
412 |
413 | /*
414 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
415 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
416 | This can trigger a poorly considered lint error in some tools but is included by design.
417 | */
418 |
419 | img,
420 | svg,
421 | video,
422 | canvas,
423 | audio,
424 | iframe,
425 | embed,
426 | object {
427 | display: block;
428 | /* 1 */
429 | vertical-align: middle;
430 | /* 2 */
431 | }
432 |
433 | /*
434 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
435 | */
436 |
437 | img,
438 | video {
439 | max-width: 100%;
440 | height: auto;
441 | }
442 |
443 | /* Make elements with the HTML hidden attribute stay hidden by default */
444 |
445 | [hidden] {
446 | display: none;
447 | }
448 |
449 | *, ::before, ::after {
450 | --tw-border-spacing-x: 0;
451 | --tw-border-spacing-y: 0;
452 | --tw-translate-x: 0;
453 | --tw-translate-y: 0;
454 | --tw-rotate: 0;
455 | --tw-skew-x: 0;
456 | --tw-skew-y: 0;
457 | --tw-scale-x: 1;
458 | --tw-scale-y: 1;
459 | --tw-pan-x: ;
460 | --tw-pan-y: ;
461 | --tw-pinch-zoom: ;
462 | --tw-scroll-snap-strictness: proximity;
463 | --tw-gradient-from-position: ;
464 | --tw-gradient-via-position: ;
465 | --tw-gradient-to-position: ;
466 | --tw-ordinal: ;
467 | --tw-slashed-zero: ;
468 | --tw-numeric-figure: ;
469 | --tw-numeric-spacing: ;
470 | --tw-numeric-fraction: ;
471 | --tw-ring-inset: ;
472 | --tw-ring-offset-width: 0px;
473 | --tw-ring-offset-color: #fff;
474 | --tw-ring-color: rgb(59 130 246 / 0.5);
475 | --tw-ring-offset-shadow: 0 0 #0000;
476 | --tw-ring-shadow: 0 0 #0000;
477 | --tw-shadow: 0 0 #0000;
478 | --tw-shadow-colored: 0 0 #0000;
479 | --tw-blur: ;
480 | --tw-brightness: ;
481 | --tw-contrast: ;
482 | --tw-grayscale: ;
483 | --tw-hue-rotate: ;
484 | --tw-invert: ;
485 | --tw-saturate: ;
486 | --tw-sepia: ;
487 | --tw-drop-shadow: ;
488 | --tw-backdrop-blur: ;
489 | --tw-backdrop-brightness: ;
490 | --tw-backdrop-contrast: ;
491 | --tw-backdrop-grayscale: ;
492 | --tw-backdrop-hue-rotate: ;
493 | --tw-backdrop-invert: ;
494 | --tw-backdrop-opacity: ;
495 | --tw-backdrop-saturate: ;
496 | --tw-backdrop-sepia: ;
497 | --tw-contain-size: ;
498 | --tw-contain-layout: ;
499 | --tw-contain-paint: ;
500 | --tw-contain-style: ;
501 | }
502 |
503 | ::backdrop {
504 | --tw-border-spacing-x: 0;
505 | --tw-border-spacing-y: 0;
506 | --tw-translate-x: 0;
507 | --tw-translate-y: 0;
508 | --tw-rotate: 0;
509 | --tw-skew-x: 0;
510 | --tw-skew-y: 0;
511 | --tw-scale-x: 1;
512 | --tw-scale-y: 1;
513 | --tw-pan-x: ;
514 | --tw-pan-y: ;
515 | --tw-pinch-zoom: ;
516 | --tw-scroll-snap-strictness: proximity;
517 | --tw-gradient-from-position: ;
518 | --tw-gradient-via-position: ;
519 | --tw-gradient-to-position: ;
520 | --tw-ordinal: ;
521 | --tw-slashed-zero: ;
522 | --tw-numeric-figure: ;
523 | --tw-numeric-spacing: ;
524 | --tw-numeric-fraction: ;
525 | --tw-ring-inset: ;
526 | --tw-ring-offset-width: 0px;
527 | --tw-ring-offset-color: #fff;
528 | --tw-ring-color: rgb(59 130 246 / 0.5);
529 | --tw-ring-offset-shadow: 0 0 #0000;
530 | --tw-ring-shadow: 0 0 #0000;
531 | --tw-shadow: 0 0 #0000;
532 | --tw-shadow-colored: 0 0 #0000;
533 | --tw-blur: ;
534 | --tw-brightness: ;
535 | --tw-contrast: ;
536 | --tw-grayscale: ;
537 | --tw-hue-rotate: ;
538 | --tw-invert: ;
539 | --tw-saturate: ;
540 | --tw-sepia: ;
541 | --tw-drop-shadow: ;
542 | --tw-backdrop-blur: ;
543 | --tw-backdrop-brightness: ;
544 | --tw-backdrop-contrast: ;
545 | --tw-backdrop-grayscale: ;
546 | --tw-backdrop-hue-rotate: ;
547 | --tw-backdrop-invert: ;
548 | --tw-backdrop-opacity: ;
549 | --tw-backdrop-saturate: ;
550 | --tw-backdrop-sepia: ;
551 | --tw-contain-size: ;
552 | --tw-contain-layout: ;
553 | --tw-contain-paint: ;
554 | --tw-contain-style: ;
555 | }
556 |
557 | .flex {
558 | display: flex;
559 | }
560 |
561 | .flex-col {
562 | flex-direction: column;
563 | }
564 |
565 | .border {
566 | border-width: 1px;
567 | }
568 |
569 | .border-b {
570 | border-bottom-width: 1px;
571 | }
572 |
573 | .p-6 {
574 | padding: 1.5rem;
575 | }
576 |
577 | .text-2xl {
578 | font-size: 1.5rem;
579 | line-height: 2rem;
580 | }
--------------------------------------------------------------------------------