├── 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 |
14 |
15 |

Stylz

16 |

Manage your state, the easy way

17 |
18 |
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 | } --------------------------------------------------------------------------------