├── .gitignore ├── Makefile ├── README.MD ├── components ├── App.templ ├── App_templ.go ├── Contact.templ ├── Contact_templ.go ├── Examples.templ ├── Examples_templ.go ├── Home.templ └── Home_templ.go ├── input.css ├── main.go ├── package.json ├── public ├── contact.html ├── examples.html ├── index.html └── static │ ├── assets │ └── logo.png │ ├── css │ └── styles.css │ └── js │ └── alpine.min.js ├── tailwind.config.js └── thumb.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | bun.lockb 4 | 5 | # macOS 6 | .DS_Store 7 | 8 | # Windows 9 | Thumbs.db 10 | 11 | # Android 12 | *.lock 13 | 14 | 15 | go.sum 16 | go.mod 17 | public/static/assets/demo.mov 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Original commands 2 | run: 3 | go run main.go 4 | 5 | compile: tailwindcss templ run 6 | 7 | vite: 8 | bun run vite ./public 9 | 10 | watch: 11 | find . -name "*.templ" | entr -r make compile 12 | 13 | test: 14 | go test -v ./... -count=1 15 | 16 | tailwindcss: 17 | bun run tailwindcss --config tailwind.config.js -i input.css -o ./public/static/css/styles.css 18 | 19 | templ: 20 | ~/go/bin/templ generate ./components 21 | 22 | .PHONY: run compile vite watch test tailwindcss templ 23 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # GOAT 2 | 3 | ![Banner](./thumb.png) 4 | 5 | This project is a starting point for developing web apps using [a-h/templ](https://github.com/a-h/templ). 6 | 7 | ## Tech Stack 8 | - [Go](https://golang.org/) (with [Templ](https://github.com/a-h/templ) for logic behind templating) 9 | - [Alpine.js](https://alpinejs.dev/) (JavaScript framework) 10 | - [Tailwind CSS](https://tailwindcss.com/) (CSS framework) 11 | - [Vite](https://vitejs.dev/) (optional, for hot reloading) 12 | 13 | ## Dependencies 14 | - Alpine.js (as a file under /public/static/js/alpine.min.js) 15 | - [Bun](https://bun.sh/) (or [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/)) used to install/run Vite and Tailwind CSS 16 | - Go 17 | - Templ CLI (install using `go install github.com/a-h/templ/cmd/templ@latest`) 18 | - [Make](https://www.gnu.org/software/make/) 19 | - [entr](https://github.com/eradman/entr) 20 | 21 | ## Usage 22 | 1. Clone the repo: 23 | ``` 24 | git clone https://github.com/morethancoder/goat.git 25 | ``` 26 | 27 | 2. Initialize the project: 28 | ``` 29 | go mod init yourprojectname && go mod tidy 30 | ``` 31 | 32 | 3. Change the import in main.go: 33 | ```go 34 | // Change this: 35 | "morethancoder/goat/components" 36 | // To: 37 | "yourprojectname/components" 38 | ``` 39 | 40 | 4. Install required Node modules: 41 | ``` 42 | bun install 43 | ``` 44 | 45 | 5. Start the Vite dev server: 46 | ``` 47 | make vite 48 | ``` 49 | 50 | 6. Start file watching & compiling: 51 | ``` 52 | make watch 53 | ``` 54 | 55 | 7. Star the repo! 56 | -------------------------------------------------------------------------------- /components/App.templ: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | templ App(route string) { 4 | 5 | 6 | 7 | 8 | 9 | 10 | GOAT Stack 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 32 | 33 | @Header() 34 | switch route { 35 | case "/": 36 | @Home() 37 | case "/examples": 38 | @Examples() 39 | case "/contact": 40 | @Contact() 41 | default: 42 | @Home() 43 | } 44 | @Footer() 45 | 46 | 47 | 48 | } 49 | 50 | templ Header() { 51 |
54 | 56 | Logo 57 | 58 | 59 | 78 |
79 | } 80 | 81 | templ IconPuzzle() { 82 | 84 | 86 | 87 | 88 | 90 | 92 | 93 | 94 | } 95 | 96 | templ IconContact() { 97 | 99 | 101 | 102 | 103 | 105 | 106 | 107 | 108 | } 109 | 110 | templ IconGithub() { 111 | 112 | 115 | 116 | } 117 | 118 | templ Footer() { 119 | 135 | } 136 | -------------------------------------------------------------------------------- /components/App_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.2.680 4 | package components 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import "context" 10 | import "io" 11 | import "bytes" 12 | 13 | func App(route string) templ.Component { 14 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 15 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 16 | if !templ_7745c5c3_IsBuffer { 17 | templ_7745c5c3_Buffer = templ.GetBuffer() 18 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 19 | } 20 | ctx = templ.InitializeContext(ctx) 21 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 22 | if templ_7745c5c3_Var1 == nil { 23 | templ_7745c5c3_Var1 = templ.NopComponent 24 | } 25 | ctx = templ.ClearChildren(ctx) 26 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("GOAT Stack") 27 | if templ_7745c5c3_Err != nil { 28 | return templ_7745c5c3_Err 29 | } 30 | templ_7745c5c3_Err = Header().Render(ctx, templ_7745c5c3_Buffer) 31 | if templ_7745c5c3_Err != nil { 32 | return templ_7745c5c3_Err 33 | } 34 | switch route { 35 | case "/": 36 | templ_7745c5c3_Err = Home().Render(ctx, templ_7745c5c3_Buffer) 37 | if templ_7745c5c3_Err != nil { 38 | return templ_7745c5c3_Err 39 | } 40 | case "/examples": 41 | templ_7745c5c3_Err = Examples().Render(ctx, templ_7745c5c3_Buffer) 42 | if templ_7745c5c3_Err != nil { 43 | return templ_7745c5c3_Err 44 | } 45 | case "/contact": 46 | templ_7745c5c3_Err = Contact().Render(ctx, templ_7745c5c3_Buffer) 47 | if templ_7745c5c3_Err != nil { 48 | return templ_7745c5c3_Err 49 | } 50 | default: 51 | templ_7745c5c3_Err = Home().Render(ctx, templ_7745c5c3_Buffer) 52 | if templ_7745c5c3_Err != nil { 53 | return templ_7745c5c3_Err 54 | } 55 | } 56 | templ_7745c5c3_Err = Footer().Render(ctx, templ_7745c5c3_Buffer) 57 | if templ_7745c5c3_Err != nil { 58 | return templ_7745c5c3_Err 59 | } 60 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 61 | if templ_7745c5c3_Err != nil { 62 | return templ_7745c5c3_Err 63 | } 64 | if !templ_7745c5c3_IsBuffer { 65 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 66 | } 67 | return templ_7745c5c3_Err 68 | }) 69 | } 70 | 71 | func Header() templ.Component { 72 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 73 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 74 | if !templ_7745c5c3_IsBuffer { 75 | templ_7745c5c3_Buffer = templ.GetBuffer() 76 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 77 | } 78 | ctx = templ.InitializeContext(ctx) 79 | templ_7745c5c3_Var2 := templ.GetChildren(ctx) 80 | if templ_7745c5c3_Var2 == nil { 81 | templ_7745c5c3_Var2 = templ.NopComponent 82 | } 83 | ctx = templ.ClearChildren(ctx) 84 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
\"Logo\" GOAT Stack
") 109 | if templ_7745c5c3_Err != nil { 110 | return templ_7745c5c3_Err 111 | } 112 | if !templ_7745c5c3_IsBuffer { 113 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 114 | } 115 | return templ_7745c5c3_Err 116 | }) 117 | } 118 | 119 | func IconPuzzle() templ.Component { 120 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 121 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 122 | if !templ_7745c5c3_IsBuffer { 123 | templ_7745c5c3_Buffer = templ.GetBuffer() 124 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 125 | } 126 | ctx = templ.InitializeContext(ctx) 127 | templ_7745c5c3_Var3 := templ.GetChildren(ctx) 128 | if templ_7745c5c3_Var3 == nil { 129 | templ_7745c5c3_Var3 = templ.NopComponent 130 | } 131 | ctx = templ.ClearChildren(ctx) 132 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") 133 | if templ_7745c5c3_Err != nil { 134 | return templ_7745c5c3_Err 135 | } 136 | if !templ_7745c5c3_IsBuffer { 137 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 138 | } 139 | return templ_7745c5c3_Err 140 | }) 141 | } 142 | 143 | func IconContact() templ.Component { 144 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 145 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 146 | if !templ_7745c5c3_IsBuffer { 147 | templ_7745c5c3_Buffer = templ.GetBuffer() 148 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 149 | } 150 | ctx = templ.InitializeContext(ctx) 151 | templ_7745c5c3_Var4 := templ.GetChildren(ctx) 152 | if templ_7745c5c3_Var4 == nil { 153 | templ_7745c5c3_Var4 = templ.NopComponent 154 | } 155 | ctx = templ.ClearChildren(ctx) 156 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") 157 | if templ_7745c5c3_Err != nil { 158 | return templ_7745c5c3_Err 159 | } 160 | if !templ_7745c5c3_IsBuffer { 161 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 162 | } 163 | return templ_7745c5c3_Err 164 | }) 165 | } 166 | 167 | func IconGithub() templ.Component { 168 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 169 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 170 | if !templ_7745c5c3_IsBuffer { 171 | templ_7745c5c3_Buffer = templ.GetBuffer() 172 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 173 | } 174 | ctx = templ.InitializeContext(ctx) 175 | templ_7745c5c3_Var5 := templ.GetChildren(ctx) 176 | if templ_7745c5c3_Var5 == nil { 177 | templ_7745c5c3_Var5 = templ.NopComponent 178 | } 179 | ctx = templ.ClearChildren(ctx) 180 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 181 | if templ_7745c5c3_Err != nil { 182 | return templ_7745c5c3_Err 183 | } 184 | if !templ_7745c5c3_IsBuffer { 185 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 186 | } 187 | return templ_7745c5c3_Err 188 | }) 189 | } 190 | 191 | func Footer() templ.Component { 192 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 193 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 194 | if !templ_7745c5c3_IsBuffer { 195 | templ_7745c5c3_Buffer = templ.GetBuffer() 196 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 197 | } 198 | ctx = templ.InitializeContext(ctx) 199 | templ_7745c5c3_Var6 := templ.GetChildren(ctx) 200 | if templ_7745c5c3_Var6 == nil { 201 | templ_7745c5c3_Var6 = templ.NopComponent 202 | } 203 | ctx = templ.ClearChildren(ctx) 204 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 205 | if templ_7745c5c3_Err != nil { 206 | return templ_7745c5c3_Err 207 | } 208 | if !templ_7745c5c3_IsBuffer { 209 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 210 | } 211 | return templ_7745c5c3_Err 212 | }) 213 | } 214 | -------------------------------------------------------------------------------- /components/Contact.templ: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | templ Contact() { 4 |
5 |
6 |

Contact Us

7 |

Have questions or need support?

8 | 10 | Send an Email 11 | 12 |

Or email us directly at:

13 |

support@morethancoder.com

14 |
15 | 27 |
28 | } 29 | -------------------------------------------------------------------------------- /components/Contact_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.2.680 4 | package components 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import "context" 10 | import "io" 11 | import "bytes" 12 | 13 | func Contact() templ.Component { 14 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 15 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 16 | if !templ_7745c5c3_IsBuffer { 17 | templ_7745c5c3_Buffer = templ.GetBuffer() 18 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 19 | } 20 | ctx = templ.InitializeContext(ctx) 21 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 22 | if templ_7745c5c3_Var1 == nil { 23 | templ_7745c5c3_Var1 = templ.NopComponent 24 | } 25 | ctx = templ.ClearChildren(ctx) 26 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Contact Us

Have questions or need support?

Send an Email

Or email us directly at:

support@morethancoder.com

") 27 | if templ_7745c5c3_Err != nil { 28 | return templ_7745c5c3_Err 29 | } 30 | if !templ_7745c5c3_IsBuffer { 31 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 32 | } 33 | return templ_7745c5c3_Err 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /components/Examples.templ: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | templ Examples() { 4 |
5 |
6 |

GOAT Stack Examples

7 |

Explore the power of Go templates with Alpine.js and Tailwind CSS

8 |
9 | 10 |
11 |

Button Component

12 |
13 | @Button("Primary", "bg-yellow-500 text-white", templ.Attributes{ "type": "submit" }) 14 | @Button("Secondary", "bg-stone-200 text-stone-800 dark:bg-stone-700 dark:text-white", templ.Attributes{ "type": "button" }) 15 | @Button("Outline", "border border-stone-800 text-stone-800 dark:border-stone-200 dark:text-stone-200", templ.Attributes{ "type": "button" }) 16 |
17 | @CodeBlock(buttonCode) 18 |
19 | 20 |
21 |

Toggle Switch

22 |
23 | @ToggleSwitch("Toggle Me") 24 |
25 | @CodeBlock(toggleSwitchCode) 26 |
27 | 28 |
29 |

Counter

30 |
31 | @Counter() 32 |
33 | @CodeBlock(counterCode) 34 |
35 | 36 |
37 |

Modal Dialog

38 |
39 | @Modal("Open Modal", "Modal Title", "This is the modal content.") 40 |
41 | @CodeBlock(modalCode) 42 |
43 |
44 | } 45 | 46 | templ Button(text string, classes string, attrs templ.Attributes) { 47 | 48 | } 49 | 50 | templ ToggleSwitch(label string) { 51 |
52 | { label } 53 | 66 | 67 |
68 | } 69 | 70 | templ Counter() { 71 |
72 |

Count:

73 |
74 | @Button("Increment", "bg-yellow-500 text-white", templ.Attributes{ "@click": "count++", "type": "submit" }) 75 | @Button("Decrement", "bg-stone-200 text-stone-800 dark:bg-stone-700 dark:text-white", templ.Attributes{ "@click": "count--", "type": "button" }) 76 | 77 |
78 |
79 | } 80 | 81 | templ Modal(buttonText, title, content string) { 82 |
83 | 85 | 86 | 94 |
95 | } 96 | 97 | templ CodeBlock(code string) { 98 |
99 |
100 |             { code }
101 |         
102 | @CopyButton(code) 103 |
104 | } 105 | 106 | 107 | templ CopyButton(code string) { 108 | 120 | } 121 | 122 | 123 | const ( 124 | buttonCode = `templ Button(text string, classes string) { 125 | 126 | } 127 | 128 | // Usage in another template: 129 | @Button("Primary", "bg-blue-500 text-white") 130 | @Button("Secondary", "bg-gray-200 text-gray-800") 131 | @Button("Outline", "border border-gray-300 text-gray-700")` 132 | 133 | toggleSwitchCode = `templ ToggleSwitch(label string) { 134 |
135 | { label } 136 | 146 | 147 |
148 | } 149 | 150 | // Usage in another template: 151 | @ToggleSwitch("Toggle Me")` 152 | 153 | counterCode = `templ Counter() { 154 |
155 |

Count:

156 |
157 | 158 | 159 |
160 |
161 | } 162 | 163 | // Usage in another template: 164 | @Counter()` 165 | 166 | modalCode = `templ Modal(buttonText, title, content string) { 167 |
168 | 169 | 170 | 177 |
178 | } 179 | 180 | // Usage in another template: 181 | @Modal("Open Modal", "Modal Title", "This is the modal content.")` 182 | ) 183 | -------------------------------------------------------------------------------- /components/Examples_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.2.680 4 | package components 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import "context" 10 | import "io" 11 | import "bytes" 12 | 13 | func Examples() templ.Component { 14 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 15 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 16 | if !templ_7745c5c3_IsBuffer { 17 | templ_7745c5c3_Buffer = templ.GetBuffer() 18 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 19 | } 20 | ctx = templ.InitializeContext(ctx) 21 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 22 | if templ_7745c5c3_Var1 == nil { 23 | templ_7745c5c3_Var1 = templ.NopComponent 24 | } 25 | ctx = templ.ClearChildren(ctx) 26 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

GOAT Stack Examples

Explore the power of Go templates with Alpine.js and Tailwind CSS

Button Component

") 27 | if templ_7745c5c3_Err != nil { 28 | return templ_7745c5c3_Err 29 | } 30 | templ_7745c5c3_Err = Button("Primary", "bg-yellow-500 text-white", templ.Attributes{"type": "submit"}).Render(ctx, templ_7745c5c3_Buffer) 31 | if templ_7745c5c3_Err != nil { 32 | return templ_7745c5c3_Err 33 | } 34 | templ_7745c5c3_Err = Button("Secondary", "bg-stone-200 text-stone-800 dark:bg-stone-700 dark:text-white", templ.Attributes{"type": "button"}).Render(ctx, templ_7745c5c3_Buffer) 35 | if templ_7745c5c3_Err != nil { 36 | return templ_7745c5c3_Err 37 | } 38 | templ_7745c5c3_Err = Button("Outline", "border border-stone-800 text-stone-800 dark:border-stone-200 dark:text-stone-200", templ.Attributes{"type": "button"}).Render(ctx, templ_7745c5c3_Buffer) 39 | if templ_7745c5c3_Err != nil { 40 | return templ_7745c5c3_Err 41 | } 42 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 43 | if templ_7745c5c3_Err != nil { 44 | return templ_7745c5c3_Err 45 | } 46 | templ_7745c5c3_Err = CodeBlock(buttonCode).Render(ctx, templ_7745c5c3_Buffer) 47 | if templ_7745c5c3_Err != nil { 48 | return templ_7745c5c3_Err 49 | } 50 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Toggle Switch

") 51 | if templ_7745c5c3_Err != nil { 52 | return templ_7745c5c3_Err 53 | } 54 | templ_7745c5c3_Err = ToggleSwitch("Toggle Me").Render(ctx, templ_7745c5c3_Buffer) 55 | if templ_7745c5c3_Err != nil { 56 | return templ_7745c5c3_Err 57 | } 58 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 59 | if templ_7745c5c3_Err != nil { 60 | return templ_7745c5c3_Err 61 | } 62 | templ_7745c5c3_Err = CodeBlock(toggleSwitchCode).Render(ctx, templ_7745c5c3_Buffer) 63 | if templ_7745c5c3_Err != nil { 64 | return templ_7745c5c3_Err 65 | } 66 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Counter

") 67 | if templ_7745c5c3_Err != nil { 68 | return templ_7745c5c3_Err 69 | } 70 | templ_7745c5c3_Err = Counter().Render(ctx, templ_7745c5c3_Buffer) 71 | if templ_7745c5c3_Err != nil { 72 | return templ_7745c5c3_Err 73 | } 74 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 75 | if templ_7745c5c3_Err != nil { 76 | return templ_7745c5c3_Err 77 | } 78 | templ_7745c5c3_Err = CodeBlock(counterCode).Render(ctx, templ_7745c5c3_Buffer) 79 | if templ_7745c5c3_Err != nil { 80 | return templ_7745c5c3_Err 81 | } 82 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Modal Dialog

") 83 | if templ_7745c5c3_Err != nil { 84 | return templ_7745c5c3_Err 85 | } 86 | templ_7745c5c3_Err = Modal("Open Modal", "Modal Title", "This is the modal content.").Render(ctx, templ_7745c5c3_Buffer) 87 | if templ_7745c5c3_Err != nil { 88 | return templ_7745c5c3_Err 89 | } 90 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 91 | if templ_7745c5c3_Err != nil { 92 | return templ_7745c5c3_Err 93 | } 94 | templ_7745c5c3_Err = CodeBlock(modalCode).Render(ctx, templ_7745c5c3_Buffer) 95 | if templ_7745c5c3_Err != nil { 96 | return templ_7745c5c3_Err 97 | } 98 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 99 | if templ_7745c5c3_Err != nil { 100 | return templ_7745c5c3_Err 101 | } 102 | if !templ_7745c5c3_IsBuffer { 103 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 104 | } 105 | return templ_7745c5c3_Err 106 | }) 107 | } 108 | 109 | func Button(text string, classes string, attrs templ.Attributes) templ.Component { 110 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 111 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 112 | if !templ_7745c5c3_IsBuffer { 113 | templ_7745c5c3_Buffer = templ.GetBuffer() 114 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 115 | } 116 | ctx = templ.InitializeContext(ctx) 117 | templ_7745c5c3_Var2 := templ.GetChildren(ctx) 118 | if templ_7745c5c3_Var2 == nil { 119 | templ_7745c5c3_Var2 = templ.NopComponent 120 | } 121 | ctx = templ.ClearChildren(ctx) 122 | var templ_7745c5c3_Var3 = []any{"px-4 py-2 rounded hover:opacity-80 transition duration-300 " + classes} 123 | templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...) 124 | if templ_7745c5c3_Err != nil { 125 | return templ_7745c5c3_Err 126 | } 127 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 149 | if templ_7745c5c3_Err != nil { 150 | return templ_7745c5c3_Err 151 | } 152 | var templ_7745c5c3_Var5 string 153 | templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(text) 154 | if templ_7745c5c3_Err != nil { 155 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/Examples.templ`, Line: 47, Col: 113} 156 | } 157 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) 158 | if templ_7745c5c3_Err != nil { 159 | return templ_7745c5c3_Err 160 | } 161 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 162 | if templ_7745c5c3_Err != nil { 163 | return templ_7745c5c3_Err 164 | } 165 | if !templ_7745c5c3_IsBuffer { 166 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 167 | } 168 | return templ_7745c5c3_Err 169 | }) 170 | } 171 | 172 | func ToggleSwitch(label string) templ.Component { 173 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 174 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 175 | if !templ_7745c5c3_IsBuffer { 176 | templ_7745c5c3_Buffer = templ.GetBuffer() 177 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 178 | } 179 | ctx = templ.InitializeContext(ctx) 180 | templ_7745c5c3_Var6 := templ.GetChildren(ctx) 181 | if templ_7745c5c3_Var6 == nil { 182 | templ_7745c5c3_Var6 = templ.NopComponent 183 | } 184 | ctx = templ.ClearChildren(ctx) 185 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 186 | if templ_7745c5c3_Err != nil { 187 | return templ_7745c5c3_Err 188 | } 189 | var templ_7745c5c3_Var7 string 190 | templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(label) 191 | if templ_7745c5c3_Err != nil { 192 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/Examples.templ`, Line: 52, Col: 84} 193 | } 194 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) 195 | if templ_7745c5c3_Err != nil { 196 | return templ_7745c5c3_Err 197 | } 198 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 199 | if templ_7745c5c3_Err != nil { 200 | return templ_7745c5c3_Err 201 | } 202 | if !templ_7745c5c3_IsBuffer { 203 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 204 | } 205 | return templ_7745c5c3_Err 206 | }) 207 | } 208 | 209 | func Counter() templ.Component { 210 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 211 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 212 | if !templ_7745c5c3_IsBuffer { 213 | templ_7745c5c3_Buffer = templ.GetBuffer() 214 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 215 | } 216 | ctx = templ.InitializeContext(ctx) 217 | templ_7745c5c3_Var8 := templ.GetChildren(ctx) 218 | if templ_7745c5c3_Var8 == nil { 219 | templ_7745c5c3_Var8 = templ.NopComponent 220 | } 221 | ctx = templ.ClearChildren(ctx) 222 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Count:

") 223 | if templ_7745c5c3_Err != nil { 224 | return templ_7745c5c3_Err 225 | } 226 | templ_7745c5c3_Err = Button("Increment", "bg-yellow-500 text-white", templ.Attributes{"@click": "count++", "type": "submit"}).Render(ctx, templ_7745c5c3_Buffer) 227 | if templ_7745c5c3_Err != nil { 228 | return templ_7745c5c3_Err 229 | } 230 | templ_7745c5c3_Err = Button("Decrement", "bg-stone-200 text-stone-800 dark:bg-stone-700 dark:text-white", templ.Attributes{"@click": "count--", "type": "button"}).Render(ctx, templ_7745c5c3_Buffer) 231 | if templ_7745c5c3_Err != nil { 232 | return templ_7745c5c3_Err 233 | } 234 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 235 | if templ_7745c5c3_Err != nil { 236 | return templ_7745c5c3_Err 237 | } 238 | if !templ_7745c5c3_IsBuffer { 239 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 240 | } 241 | return templ_7745c5c3_Err 242 | }) 243 | } 244 | 245 | func Modal(buttonText, title, content string) templ.Component { 246 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 247 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 248 | if !templ_7745c5c3_IsBuffer { 249 | templ_7745c5c3_Buffer = templ.GetBuffer() 250 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 251 | } 252 | ctx = templ.InitializeContext(ctx) 253 | templ_7745c5c3_Var9 := templ.GetChildren(ctx) 254 | if templ_7745c5c3_Var9 == nil { 255 | templ_7745c5c3_Var9 = templ.NopComponent 256 | } 257 | ctx = templ.ClearChildren(ctx) 258 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") 272 | if templ_7745c5c3_Err != nil { 273 | return templ_7745c5c3_Err 274 | } 275 | var templ_7745c5c3_Var11 string 276 | templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(title) 277 | if templ_7745c5c3_Err != nil { 278 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/Examples.templ`, Line: 88, Col: 62} 279 | } 280 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) 281 | if templ_7745c5c3_Err != nil { 282 | return templ_7745c5c3_Err 283 | } 284 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") 285 | if templ_7745c5c3_Err != nil { 286 | return templ_7745c5c3_Err 287 | } 288 | var templ_7745c5c3_Var12 string 289 | templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(content) 290 | if templ_7745c5c3_Err != nil { 291 | return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/Examples.templ`, Line: 89, Col: 41} 292 | } 293 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) 294 | if templ_7745c5c3_Err != nil { 295 | return templ_7745c5c3_Err 296 | } 297 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") 298 | if templ_7745c5c3_Err != nil { 299 | return templ_7745c5c3_Err 300 | } 301 | if !templ_7745c5c3_IsBuffer { 302 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 303 | } 304 | return templ_7745c5c3_Err 305 | }) 306 | } 307 | 308 | func CodeBlock(code string) templ.Component { 309 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 310 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 311 | if !templ_7745c5c3_IsBuffer { 312 | templ_7745c5c3_Buffer = templ.GetBuffer() 313 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 314 | } 315 | ctx = templ.InitializeContext(ctx) 316 | templ_7745c5c3_Var13 := templ.GetChildren(ctx) 317 | if templ_7745c5c3_Var13 == nil { 318 | templ_7745c5c3_Var13 = templ.NopComponent 319 | } 320 | ctx = templ.ClearChildren(ctx) 321 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
322 | 		if templ_7745c5c3_Err != nil {
323 | 			return templ_7745c5c3_Err
324 | 		}
325 | 		var templ_7745c5c3_Var14 string
326 | 		templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(code)
327 | 		if templ_7745c5c3_Err != nil {
328 | 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/Examples.templ`, Line: 100, Col: 60}
329 | 		}
330 | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
331 | 		if templ_7745c5c3_Err != nil {
332 | 			return templ_7745c5c3_Err
333 | 		}
334 | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 335 | if templ_7745c5c3_Err != nil { 336 | return templ_7745c5c3_Err 337 | } 338 | templ_7745c5c3_Err = CopyButton(code).Render(ctx, templ_7745c5c3_Buffer) 339 | if templ_7745c5c3_Err != nil { 340 | return templ_7745c5c3_Err 341 | } 342 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") 343 | if templ_7745c5c3_Err != nil { 344 | return templ_7745c5c3_Err 345 | } 346 | if !templ_7745c5c3_IsBuffer { 347 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 348 | } 349 | return templ_7745c5c3_Err 350 | }) 351 | } 352 | 353 | func CopyButton(code string) templ.Component { 354 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 355 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 356 | if !templ_7745c5c3_IsBuffer { 357 | templ_7745c5c3_Buffer = templ.GetBuffer() 358 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 359 | } 360 | ctx = templ.InitializeContext(ctx) 361 | templ_7745c5c3_Var15 := templ.GetChildren(ctx) 362 | if templ_7745c5c3_Var15 == nil { 363 | templ_7745c5c3_Var15 = templ.NopComponent 364 | } 365 | ctx = templ.ClearChildren(ctx) 366 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 388 | if templ_7745c5c3_Err != nil { 389 | return templ_7745c5c3_Err 390 | } 391 | if !templ_7745c5c3_IsBuffer { 392 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 393 | } 394 | return templ_7745c5c3_Err 395 | }) 396 | } 397 | 398 | const ( 399 | buttonCode = `templ Button(text string, classes string) { 400 | 401 | } 402 | 403 | // Usage in another template: 404 | @Button("Primary", "bg-blue-500 text-white") 405 | @Button("Secondary", "bg-gray-200 text-gray-800") 406 | @Button("Outline", "border border-gray-300 text-gray-700")` 407 | 408 | toggleSwitchCode = `templ ToggleSwitch(label string) { 409 |
410 | { label } 411 | 421 | 422 |
423 | } 424 | 425 | // Usage in another template: 426 | @ToggleSwitch("Toggle Me")` 427 | 428 | counterCode = `templ Counter() { 429 |
430 |

Count:

431 |
432 | 433 | 434 |
435 |
436 | } 437 | 438 | // Usage in another template: 439 | @Counter()` 440 | 441 | modalCode = `templ Modal(buttonText, title, content string) { 442 |
443 | 444 | 445 | 452 |
453 | } 454 | 455 | // Usage in another template: 456 | @Modal("Open Modal", "Modal Title", "This is the modal content.")` 457 | ) 458 | -------------------------------------------------------------------------------- /components/Home.templ: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | templ Home() { 4 |
5 |
6 |

GOAT Stack

7 |

Blazing fast web development with Go, Alpine.js, and Tailwind CSS

8 |
12 | 28 |
36 | 43 |
44 |
45 | 46 |
47 |
48 | 49 | 61 |
62 |
63 |

Tech Stack

64 |
65 |
68 |
69 | @IconLayers() 70 |

Go with Templ

71 |
72 |

Powerful backend for server-side rendering and rapid static page generation.

73 |
74 |
77 |
78 | @IconLightning() 79 |

Alpine.js

80 |
81 |

Lightweight JavaScript framework for smooth interactivity.

82 |
83 |
86 |
87 | @IconPaintbrush() 88 |

Tailwind CSS

89 |
90 |

Utility-first CSS framework for rapid UI development.

91 |
92 |
95 |
96 | @IconRocket() 97 |

Vite (optional)

98 |
99 |

Next-generation frontend tooling for fast hot reloading.

100 |
101 |
102 |
103 |
104 |

Get Started

105 |
    106 |
  1. 107 |
    108 | @IconGitBranch() 109 |
    110 |
    111 |

    Clone the repo:

    112 |
    113 |
    114 |                             
    115 |                                 git clone https://github.com/morethancoder/goat.git yourprojectname
    116 |                             
    117 |                         
    118 | @CopyButton("git clone https://github.com/morethancoder/goat.git yourprojectname") 119 |
    120 |
    121 |
  2. 122 |
  3. 123 |
    124 | @IconCode() 125 |
    126 |
    127 |

    Change into the project directory:

    128 |
    129 |
    130 |                             
    131 |                                 cd yourprojectname
    132 |                             
    133 |                         
    134 | @CopyButton(`cd yourprojectname`) 135 |
    136 |
    137 |
  4. 138 |
  5. 139 |
    140 | @IconPackage() 141 |
    142 |
    143 |

    Initialize the project:

    144 |
    145 |
    146 |                             
    147 |                                 go mod init yourprojectname && go mod tidy
    148 |                             
    149 |                         
    150 | @CopyButton("go mod init yourprojectname && go mod tidy") 151 |
    152 |
    153 |
  6. 154 |
  7. 155 |
    156 | @IconCode() 157 |
    158 |
    159 |

    Change the import in main.go:

    160 |
    161 |
    162 |                             
    163 |                                 { `// Change this:
    164 | "morethancoder/goat/components"
    165 | // To:
    166 | "yourprojectname/components"` }
    167 |                             
    168 |                         
    169 | @CopyButton(`// Change this: 170 | "morethancoder/goat/components" 171 | // To: 172 | "yourprojectname/components"`) 173 |
    174 |
    175 |
  8. 176 |
  9. 177 |
    178 | @IconDownload() 179 |
    180 |
    181 |

    Install required Node modules:

    182 |
    183 |
    184 |                             
    185 |                                 bun install
    186 |                             
    187 |                         
    188 | @CopyButton("bun install") 189 |
    190 |
    191 |
  10. 192 |
  11. 193 |
    194 | @IconPlay() 195 |
    196 |
    197 |

    Start the Vite dev server:

    198 |
    199 |
    200 |                             
    201 |                                 make vite
    202 |                             
    203 |                         
    204 | @CopyButton("make vite") 205 |
    206 |
    207 |
  12. 208 |
  13. 209 |
    210 | @IconEye() 211 |
    212 |
    213 |

    Start file watching & compiling:

    214 |
    215 |
    216 |                             
    217 |                                 make watch
    218 |                             
    219 |                         
    220 | @CopyButton("make watch") 221 |
    222 |
    223 |
  14. 224 |
  15. 225 |
    226 | @IconStar() 227 |
    228 |
    229 |

    Star the repo!

    230 |

    231 | If you find this project helpful, please consider giving it a star on 232 | 234 | GitHub. 235 | 236 |

    237 |
    238 |
  16. 239 |
240 |
241 |
242 | } 243 | 244 | templ IconPlay() { 245 | 247 | 249 | 250 | 251 | } 252 | 253 | templ IconPaintbrush() { 254 | 256 | 258 | 259 | 260 | } 261 | 262 | templ IconRocket() { 263 | 265 | 267 | 268 | 269 | } 270 | 271 | templ IconCode() { 272 | 274 | 276 | 277 | } 278 | 279 | templ IconStar() { 280 | 282 | 284 | 285 | 286 | } 287 | 288 | templ IconBook() { 289 | 291 | 293 | 294 | 295 | } 296 | 297 | templ IconLightning() { 298 | 300 | 302 | 303 | } 304 | 305 | templ IconCloud() { 306 | 308 | 310 | 311 | 312 | } 313 | 314 | templ IconLayers() { 315 | 317 | 319 | 320 | 321 | } 322 | 323 | templ IconGitBranch() { 324 | 326 | 328 | 329 | } 330 | 331 | templ IconPackage() { 332 | 334 | 336 | 337 | 338 | } 339 | 340 | templ IconDownload() { 341 | 343 | 345 | 346 | 347 | } 348 | 349 | templ IconEye() { 350 | 352 | 354 | 355 | 356 | 357 | } 358 | 359 | templ IconCopy() { 360 | 362 | 364 | 365 | 366 | } 367 | -------------------------------------------------------------------------------- /components/Home_templ.go: -------------------------------------------------------------------------------- 1 | // Code generated by templ - DO NOT EDIT. 2 | 3 | // templ: version: v0.2.680 4 | package components 5 | 6 | //lint:file-ignore SA4006 This context is only used if a nested component is present. 7 | 8 | import "github.com/a-h/templ" 9 | import "context" 10 | import "io" 11 | import "bytes" 12 | 13 | func Home() templ.Component { 14 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 15 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 16 | if !templ_7745c5c3_IsBuffer { 17 | templ_7745c5c3_Buffer = templ.GetBuffer() 18 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 19 | } 20 | ctx = templ.InitializeContext(ctx) 21 | templ_7745c5c3_Var1 := templ.GetChildren(ctx) 22 | if templ_7745c5c3_Var1 == nil { 23 | templ_7745c5c3_Var1 = templ.NopComponent 24 | } 25 | ctx = templ.ClearChildren(ctx) 26 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

GOAT Stack

Blazing fast web development with Go, Alpine.js, and Tailwind CSS

Tech Stack

") 43 | if templ_7745c5c3_Err != nil { 44 | return templ_7745c5c3_Err 45 | } 46 | templ_7745c5c3_Err = IconLayers().Render(ctx, templ_7745c5c3_Buffer) 47 | if templ_7745c5c3_Err != nil { 48 | return templ_7745c5c3_Err 49 | } 50 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Go with Templ

Powerful backend for server-side rendering and rapid static page generation.

") 51 | if templ_7745c5c3_Err != nil { 52 | return templ_7745c5c3_Err 53 | } 54 | templ_7745c5c3_Err = IconLightning().Render(ctx, templ_7745c5c3_Buffer) 55 | if templ_7745c5c3_Err != nil { 56 | return templ_7745c5c3_Err 57 | } 58 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Alpine.js

Lightweight JavaScript framework for smooth interactivity.

") 59 | if templ_7745c5c3_Err != nil { 60 | return templ_7745c5c3_Err 61 | } 62 | templ_7745c5c3_Err = IconPaintbrush().Render(ctx, templ_7745c5c3_Buffer) 63 | if templ_7745c5c3_Err != nil { 64 | return templ_7745c5c3_Err 65 | } 66 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Tailwind CSS

Utility-first CSS framework for rapid UI development.

") 67 | if templ_7745c5c3_Err != nil { 68 | return templ_7745c5c3_Err 69 | } 70 | templ_7745c5c3_Err = IconRocket().Render(ctx, templ_7745c5c3_Buffer) 71 | if templ_7745c5c3_Err != nil { 72 | return templ_7745c5c3_Err 73 | } 74 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Vite (optional)

Next-generation frontend tooling for fast hot reloading.

Get Started

  1. ") 75 | if templ_7745c5c3_Err != nil { 76 | return templ_7745c5c3_Err 77 | } 78 | templ_7745c5c3_Err = IconGitBranch().Render(ctx, templ_7745c5c3_Buffer) 79 | if templ_7745c5c3_Err != nil { 80 | return templ_7745c5c3_Err 81 | } 82 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Clone the repo:

    git clone https://github.com/morethancoder/goat.git yourprojectname
    ") 83 | if templ_7745c5c3_Err != nil { 84 | return templ_7745c5c3_Err 85 | } 86 | templ_7745c5c3_Err = CopyButton("git clone https://github.com/morethancoder/goat.git yourprojectname").Render(ctx, templ_7745c5c3_Buffer) 87 | if templ_7745c5c3_Err != nil { 88 | return templ_7745c5c3_Err 89 | } 90 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  2. ") 91 | if templ_7745c5c3_Err != nil { 92 | return templ_7745c5c3_Err 93 | } 94 | templ_7745c5c3_Err = IconCode().Render(ctx, templ_7745c5c3_Buffer) 95 | if templ_7745c5c3_Err != nil { 96 | return templ_7745c5c3_Err 97 | } 98 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Change into the project directory:

    cd yourprojectname
    ") 99 | if templ_7745c5c3_Err != nil { 100 | return templ_7745c5c3_Err 101 | } 102 | templ_7745c5c3_Err = CopyButton(`cd yourprojectname`).Render(ctx, templ_7745c5c3_Buffer) 103 | if templ_7745c5c3_Err != nil { 104 | return templ_7745c5c3_Err 105 | } 106 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  3. ") 107 | if templ_7745c5c3_Err != nil { 108 | return templ_7745c5c3_Err 109 | } 110 | templ_7745c5c3_Err = IconPackage().Render(ctx, templ_7745c5c3_Buffer) 111 | if templ_7745c5c3_Err != nil { 112 | return templ_7745c5c3_Err 113 | } 114 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Initialize the project:

    go mod init yourprojectname && go mod tidy
    ") 115 | if templ_7745c5c3_Err != nil { 116 | return templ_7745c5c3_Err 117 | } 118 | templ_7745c5c3_Err = CopyButton("go mod init yourprojectname && go mod tidy").Render(ctx, templ_7745c5c3_Buffer) 119 | if templ_7745c5c3_Err != nil { 120 | return templ_7745c5c3_Err 121 | } 122 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  4. ") 123 | if templ_7745c5c3_Err != nil { 124 | return templ_7745c5c3_Err 125 | } 126 | templ_7745c5c3_Err = IconCode().Render(ctx, templ_7745c5c3_Buffer) 127 | if templ_7745c5c3_Err != nil { 128 | return templ_7745c5c3_Err 129 | } 130 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Change the import in main.go:

    ")
    131 | 		if templ_7745c5c3_Err != nil {
    132 | 			return templ_7745c5c3_Err
    133 | 		}
    134 | 		var templ_7745c5c3_Var2 string
    135 | 		templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(`// Change this:
    136 | "morethancoder/goat/components"
    137 | // To:
    138 | "yourprojectname/components"`)
    139 | 		if templ_7745c5c3_Err != nil {
    140 | 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/Home.templ`, Line: 166, Col: 29}
    141 | 		}
    142 | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
    143 | 		if templ_7745c5c3_Err != nil {
    144 | 			return templ_7745c5c3_Err
    145 | 		}
    146 | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") 147 | if templ_7745c5c3_Err != nil { 148 | return templ_7745c5c3_Err 149 | } 150 | templ_7745c5c3_Err = CopyButton(`// Change this: 151 | "morethancoder/goat/components" 152 | // To: 153 | "yourprojectname/components"`).Render(ctx, templ_7745c5c3_Buffer) 154 | if templ_7745c5c3_Err != nil { 155 | return templ_7745c5c3_Err 156 | } 157 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  5. ") 158 | if templ_7745c5c3_Err != nil { 159 | return templ_7745c5c3_Err 160 | } 161 | templ_7745c5c3_Err = IconDownload().Render(ctx, templ_7745c5c3_Buffer) 162 | if templ_7745c5c3_Err != nil { 163 | return templ_7745c5c3_Err 164 | } 165 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Install required Node modules:

    bun install
    ") 166 | if templ_7745c5c3_Err != nil { 167 | return templ_7745c5c3_Err 168 | } 169 | templ_7745c5c3_Err = CopyButton("bun install").Render(ctx, templ_7745c5c3_Buffer) 170 | if templ_7745c5c3_Err != nil { 171 | return templ_7745c5c3_Err 172 | } 173 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  6. ") 174 | if templ_7745c5c3_Err != nil { 175 | return templ_7745c5c3_Err 176 | } 177 | templ_7745c5c3_Err = IconPlay().Render(ctx, templ_7745c5c3_Buffer) 178 | if templ_7745c5c3_Err != nil { 179 | return templ_7745c5c3_Err 180 | } 181 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Start the Vite dev server:

    make vite
    ") 182 | if templ_7745c5c3_Err != nil { 183 | return templ_7745c5c3_Err 184 | } 185 | templ_7745c5c3_Err = CopyButton("make vite").Render(ctx, templ_7745c5c3_Buffer) 186 | if templ_7745c5c3_Err != nil { 187 | return templ_7745c5c3_Err 188 | } 189 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  7. ") 190 | if templ_7745c5c3_Err != nil { 191 | return templ_7745c5c3_Err 192 | } 193 | templ_7745c5c3_Err = IconEye().Render(ctx, templ_7745c5c3_Buffer) 194 | if templ_7745c5c3_Err != nil { 195 | return templ_7745c5c3_Err 196 | } 197 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Start file watching & compiling:

    make watch
    ") 198 | if templ_7745c5c3_Err != nil { 199 | return templ_7745c5c3_Err 200 | } 201 | templ_7745c5c3_Err = CopyButton("make watch").Render(ctx, templ_7745c5c3_Buffer) 202 | if templ_7745c5c3_Err != nil { 203 | return templ_7745c5c3_Err 204 | } 205 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  8. ") 206 | if templ_7745c5c3_Err != nil { 207 | return templ_7745c5c3_Err 208 | } 209 | templ_7745c5c3_Err = IconStar().Render(ctx, templ_7745c5c3_Buffer) 210 | if templ_7745c5c3_Err != nil { 211 | return templ_7745c5c3_Err 212 | } 213 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

    Star the repo!

    If you find this project helpful, please consider giving it a star on GitHub.

") 214 | if templ_7745c5c3_Err != nil { 215 | return templ_7745c5c3_Err 216 | } 217 | if !templ_7745c5c3_IsBuffer { 218 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 219 | } 220 | return templ_7745c5c3_Err 221 | }) 222 | } 223 | 224 | func IconPlay() templ.Component { 225 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 226 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 227 | if !templ_7745c5c3_IsBuffer { 228 | templ_7745c5c3_Buffer = templ.GetBuffer() 229 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 230 | } 231 | ctx = templ.InitializeContext(ctx) 232 | templ_7745c5c3_Var3 := templ.GetChildren(ctx) 233 | if templ_7745c5c3_Var3 == nil { 234 | templ_7745c5c3_Var3 = templ.NopComponent 235 | } 236 | ctx = templ.ClearChildren(ctx) 237 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 238 | if templ_7745c5c3_Err != nil { 239 | return templ_7745c5c3_Err 240 | } 241 | if !templ_7745c5c3_IsBuffer { 242 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 243 | } 244 | return templ_7745c5c3_Err 245 | }) 246 | } 247 | 248 | func IconPaintbrush() templ.Component { 249 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 250 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 251 | if !templ_7745c5c3_IsBuffer { 252 | templ_7745c5c3_Buffer = templ.GetBuffer() 253 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 254 | } 255 | ctx = templ.InitializeContext(ctx) 256 | templ_7745c5c3_Var4 := templ.GetChildren(ctx) 257 | if templ_7745c5c3_Var4 == nil { 258 | templ_7745c5c3_Var4 = templ.NopComponent 259 | } 260 | ctx = templ.ClearChildren(ctx) 261 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 262 | if templ_7745c5c3_Err != nil { 263 | return templ_7745c5c3_Err 264 | } 265 | if !templ_7745c5c3_IsBuffer { 266 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 267 | } 268 | return templ_7745c5c3_Err 269 | }) 270 | } 271 | 272 | func IconRocket() templ.Component { 273 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 274 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 275 | if !templ_7745c5c3_IsBuffer { 276 | templ_7745c5c3_Buffer = templ.GetBuffer() 277 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 278 | } 279 | ctx = templ.InitializeContext(ctx) 280 | templ_7745c5c3_Var5 := templ.GetChildren(ctx) 281 | if templ_7745c5c3_Var5 == nil { 282 | templ_7745c5c3_Var5 = templ.NopComponent 283 | } 284 | ctx = templ.ClearChildren(ctx) 285 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 286 | if templ_7745c5c3_Err != nil { 287 | return templ_7745c5c3_Err 288 | } 289 | if !templ_7745c5c3_IsBuffer { 290 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 291 | } 292 | return templ_7745c5c3_Err 293 | }) 294 | } 295 | 296 | func IconCode() templ.Component { 297 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 298 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 299 | if !templ_7745c5c3_IsBuffer { 300 | templ_7745c5c3_Buffer = templ.GetBuffer() 301 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 302 | } 303 | ctx = templ.InitializeContext(ctx) 304 | templ_7745c5c3_Var6 := templ.GetChildren(ctx) 305 | if templ_7745c5c3_Var6 == nil { 306 | templ_7745c5c3_Var6 = templ.NopComponent 307 | } 308 | ctx = templ.ClearChildren(ctx) 309 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 310 | if templ_7745c5c3_Err != nil { 311 | return templ_7745c5c3_Err 312 | } 313 | if !templ_7745c5c3_IsBuffer { 314 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 315 | } 316 | return templ_7745c5c3_Err 317 | }) 318 | } 319 | 320 | func IconStar() templ.Component { 321 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 322 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 323 | if !templ_7745c5c3_IsBuffer { 324 | templ_7745c5c3_Buffer = templ.GetBuffer() 325 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 326 | } 327 | ctx = templ.InitializeContext(ctx) 328 | templ_7745c5c3_Var7 := templ.GetChildren(ctx) 329 | if templ_7745c5c3_Var7 == nil { 330 | templ_7745c5c3_Var7 = templ.NopComponent 331 | } 332 | ctx = templ.ClearChildren(ctx) 333 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 334 | if templ_7745c5c3_Err != nil { 335 | return templ_7745c5c3_Err 336 | } 337 | if !templ_7745c5c3_IsBuffer { 338 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 339 | } 340 | return templ_7745c5c3_Err 341 | }) 342 | } 343 | 344 | func IconBook() templ.Component { 345 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 346 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 347 | if !templ_7745c5c3_IsBuffer { 348 | templ_7745c5c3_Buffer = templ.GetBuffer() 349 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 350 | } 351 | ctx = templ.InitializeContext(ctx) 352 | templ_7745c5c3_Var8 := templ.GetChildren(ctx) 353 | if templ_7745c5c3_Var8 == nil { 354 | templ_7745c5c3_Var8 = templ.NopComponent 355 | } 356 | ctx = templ.ClearChildren(ctx) 357 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 358 | if templ_7745c5c3_Err != nil { 359 | return templ_7745c5c3_Err 360 | } 361 | if !templ_7745c5c3_IsBuffer { 362 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 363 | } 364 | return templ_7745c5c3_Err 365 | }) 366 | } 367 | 368 | func IconLightning() templ.Component { 369 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 370 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 371 | if !templ_7745c5c3_IsBuffer { 372 | templ_7745c5c3_Buffer = templ.GetBuffer() 373 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 374 | } 375 | ctx = templ.InitializeContext(ctx) 376 | templ_7745c5c3_Var9 := templ.GetChildren(ctx) 377 | if templ_7745c5c3_Var9 == nil { 378 | templ_7745c5c3_Var9 = templ.NopComponent 379 | } 380 | ctx = templ.ClearChildren(ctx) 381 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 382 | if templ_7745c5c3_Err != nil { 383 | return templ_7745c5c3_Err 384 | } 385 | if !templ_7745c5c3_IsBuffer { 386 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 387 | } 388 | return templ_7745c5c3_Err 389 | }) 390 | } 391 | 392 | func IconCloud() templ.Component { 393 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 394 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 395 | if !templ_7745c5c3_IsBuffer { 396 | templ_7745c5c3_Buffer = templ.GetBuffer() 397 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 398 | } 399 | ctx = templ.InitializeContext(ctx) 400 | templ_7745c5c3_Var10 := templ.GetChildren(ctx) 401 | if templ_7745c5c3_Var10 == nil { 402 | templ_7745c5c3_Var10 = templ.NopComponent 403 | } 404 | ctx = templ.ClearChildren(ctx) 405 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 406 | if templ_7745c5c3_Err != nil { 407 | return templ_7745c5c3_Err 408 | } 409 | if !templ_7745c5c3_IsBuffer { 410 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 411 | } 412 | return templ_7745c5c3_Err 413 | }) 414 | } 415 | 416 | func IconLayers() templ.Component { 417 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 418 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 419 | if !templ_7745c5c3_IsBuffer { 420 | templ_7745c5c3_Buffer = templ.GetBuffer() 421 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 422 | } 423 | ctx = templ.InitializeContext(ctx) 424 | templ_7745c5c3_Var11 := templ.GetChildren(ctx) 425 | if templ_7745c5c3_Var11 == nil { 426 | templ_7745c5c3_Var11 = templ.NopComponent 427 | } 428 | ctx = templ.ClearChildren(ctx) 429 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 430 | if templ_7745c5c3_Err != nil { 431 | return templ_7745c5c3_Err 432 | } 433 | if !templ_7745c5c3_IsBuffer { 434 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 435 | } 436 | return templ_7745c5c3_Err 437 | }) 438 | } 439 | 440 | func IconGitBranch() templ.Component { 441 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 442 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 443 | if !templ_7745c5c3_IsBuffer { 444 | templ_7745c5c3_Buffer = templ.GetBuffer() 445 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 446 | } 447 | ctx = templ.InitializeContext(ctx) 448 | templ_7745c5c3_Var12 := templ.GetChildren(ctx) 449 | if templ_7745c5c3_Var12 == nil { 450 | templ_7745c5c3_Var12 = templ.NopComponent 451 | } 452 | ctx = templ.ClearChildren(ctx) 453 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 454 | if templ_7745c5c3_Err != nil { 455 | return templ_7745c5c3_Err 456 | } 457 | if !templ_7745c5c3_IsBuffer { 458 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 459 | } 460 | return templ_7745c5c3_Err 461 | }) 462 | } 463 | 464 | func IconPackage() templ.Component { 465 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 466 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 467 | if !templ_7745c5c3_IsBuffer { 468 | templ_7745c5c3_Buffer = templ.GetBuffer() 469 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 470 | } 471 | ctx = templ.InitializeContext(ctx) 472 | templ_7745c5c3_Var13 := templ.GetChildren(ctx) 473 | if templ_7745c5c3_Var13 == nil { 474 | templ_7745c5c3_Var13 = templ.NopComponent 475 | } 476 | ctx = templ.ClearChildren(ctx) 477 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 478 | if templ_7745c5c3_Err != nil { 479 | return templ_7745c5c3_Err 480 | } 481 | if !templ_7745c5c3_IsBuffer { 482 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 483 | } 484 | return templ_7745c5c3_Err 485 | }) 486 | } 487 | 488 | func IconDownload() templ.Component { 489 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 490 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 491 | if !templ_7745c5c3_IsBuffer { 492 | templ_7745c5c3_Buffer = templ.GetBuffer() 493 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 494 | } 495 | ctx = templ.InitializeContext(ctx) 496 | templ_7745c5c3_Var14 := templ.GetChildren(ctx) 497 | if templ_7745c5c3_Var14 == nil { 498 | templ_7745c5c3_Var14 = templ.NopComponent 499 | } 500 | ctx = templ.ClearChildren(ctx) 501 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 502 | if templ_7745c5c3_Err != nil { 503 | return templ_7745c5c3_Err 504 | } 505 | if !templ_7745c5c3_IsBuffer { 506 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 507 | } 508 | return templ_7745c5c3_Err 509 | }) 510 | } 511 | 512 | func IconEye() templ.Component { 513 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 514 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 515 | if !templ_7745c5c3_IsBuffer { 516 | templ_7745c5c3_Buffer = templ.GetBuffer() 517 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 518 | } 519 | ctx = templ.InitializeContext(ctx) 520 | templ_7745c5c3_Var15 := templ.GetChildren(ctx) 521 | if templ_7745c5c3_Var15 == nil { 522 | templ_7745c5c3_Var15 = templ.NopComponent 523 | } 524 | ctx = templ.ClearChildren(ctx) 525 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") 526 | if templ_7745c5c3_Err != nil { 527 | return templ_7745c5c3_Err 528 | } 529 | if !templ_7745c5c3_IsBuffer { 530 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 531 | } 532 | return templ_7745c5c3_Err 533 | }) 534 | } 535 | 536 | func IconCopy() templ.Component { 537 | return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 538 | templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) 539 | if !templ_7745c5c3_IsBuffer { 540 | templ_7745c5c3_Buffer = templ.GetBuffer() 541 | defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) 542 | } 543 | ctx = templ.InitializeContext(ctx) 544 | templ_7745c5c3_Var16 := templ.GetChildren(ctx) 545 | if templ_7745c5c3_Var16 == nil { 546 | templ_7745c5c3_Var16 = templ.NopComponent 547 | } 548 | ctx = templ.ClearChildren(ctx) 549 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") 550 | if templ_7745c5c3_Err != nil { 551 | return templ_7745c5c3_Err 552 | } 553 | if !templ_7745c5c3_IsBuffer { 554 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) 555 | } 556 | return templ_7745c5c3_Err 557 | }) 558 | } 559 | -------------------------------------------------------------------------------- /input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "morethancoder/goat/components" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "time" 11 | 12 | "github.com/charmbracelet/lipgloss" 13 | "github.com/charmbracelet/log" 14 | ) 15 | 16 | var ( 17 | // Define App Routes & Public Directory 18 | routes = []string{"/", "/examples", "/contact"} 19 | publicDir = "public" 20 | 21 | // Logger & Styles 22 | gopherStyle = lipgloss.NewStyle(). 23 | Bold(true). 24 | Foreground(lipgloss.Color("#00FFFF")) 25 | goatStyle = lipgloss.NewStyle(). 26 | Bold(true). 27 | Foreground(lipgloss.Color("#FFFF00")) 28 | 29 | logger = log.NewWithOptions(os.Stderr, log.Options{ 30 | ReportCaller: true, 31 | ReportTimestamp: true, 32 | TimeFormat: time.Kitchen, 33 | Prefix: goatStyle.Render("Maa 🐐 "), 34 | }) 35 | styles = log.DefaultStyles() 36 | ) 37 | 38 | func main() { 39 | // Fancy logging 40 | styles.Levels[log.InfoLevel] = lipgloss.NewStyle().Bold(true). 41 | SetString("INFO"). 42 | Padding(0, 1, 0, 1). 43 | Background(lipgloss.Color("#00FFFF")). 44 | Foreground(lipgloss.Color("0")) 45 | logger.SetStyles(styles) 46 | 47 | // Checking public directory existence 48 | if err := os.MkdirAll(publicDir, 0755); err != nil { 49 | logger.Fatal("Failed to create public directory", "error", err) 50 | } 51 | 52 | // Clear the terminal 53 | fmt.Print("\033[H\033[2J") 54 | 55 | // Generate HTML for each route 56 | for _, route := range routes { 57 | if err := generateHTML(route); err != nil { 58 | logger.Fatal("Failed to generate HTML", "route", route, "error", err) 59 | } 60 | } 61 | 62 | // Clean up obsolete files 63 | if err := cleanUp(); err != nil { 64 | logger.Fatal("Failed to clean up obsolete files", "error", err) 65 | } 66 | } 67 | 68 | func generateHTML(route string) error { 69 | filename := "index.html" 70 | if route != "/" { 71 | filename = route[1:] + ".html" 72 | } 73 | filePath := filepath.Join(publicDir, filename) 74 | 75 | file, err := os.Create(filePath) 76 | if err != nil { 77 | return fmt.Errorf("failed to create file %s: %w", filePath, err) 78 | } 79 | defer file.Close() 80 | 81 | if err := components.App(route).Render(context.Background(), file); err != nil { 82 | return fmt.Errorf("failed to render HTML for route %s: %w", route, err) 83 | } 84 | 85 | logger.Infof("Successfully generated file %s", gopherStyle.Render(filePath)) 86 | return nil 87 | } 88 | 89 | func routeExists(slice []string, item string) bool { 90 | for _, s := range slice { 91 | if s == item { 92 | return true 93 | } 94 | } 95 | return false 96 | } 97 | 98 | func cleanUp() error { 99 | files, err := os.ReadDir(publicDir) 100 | if err != nil { 101 | return fmt.Errorf("error reading directory: %w", err) 102 | } 103 | 104 | for _, file := range files { 105 | if file.IsDir() { 106 | continue // Skip directories 107 | } 108 | filename := file.Name() 109 | route := "/" + strings.TrimSuffix(filename, ".html") 110 | if filename == "index.html" { 111 | route = "/" 112 | } 113 | if !routeExists(routes, route) { 114 | filePath := filepath.Join(publicDir, filename) 115 | if err := os.Remove(filePath); err != nil { 116 | logger.Error("Failed to remove file", "file", filePath, "error", err) 117 | } else { 118 | logger.Info("Removed obsolete file", "file", filePath) 119 | } 120 | } 121 | } 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "tailwindcss": "^3.4.9", 4 | "vite": "^5.4.0" 5 | }, 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /public/contact.html: -------------------------------------------------------------------------------- 1 | GOAT Stack
Logo

Contact Us

Have questions or need support?

Send an Email

Or email us directly at:

support@morethancoder.com

-------------------------------------------------------------------------------- /public/examples.html: -------------------------------------------------------------------------------- 1 | GOAT Stack
Logo

GOAT Stack Examples

Explore the power of Go templates with Alpine.js and Tailwind CSS

Button Component

templ Button(text string, classes string) {
  8 |     <button class={ "px-4 py-2 rounded hover:opacity-80 transition duration-300 " + classes }>{ text }</button>
  9 | }
 10 | 
 11 | // Usage in another template:
 12 | @Button("Primary", "bg-blue-500 text-white")
 13 | @Button("Secondary", "bg-gray-200 text-gray-800")
 14 | @Button("Outline", "border border-gray-300 text-gray-700")

Toggle Switch

Toggle Me
templ ToggleSwitch(label string) {
 26 |     <div x-data="{ isOn: false }" class="flex items-center space-x-4">
 27 |         <span class="text-sm font-medium text-gray-900 dark:text-gray-300">{ label }</span>
 28 |         <button 
 29 |             @click="isOn = !isOn" 
 30 |             :class="isOn ? 'bg-blue-600' : 'bg-gray-200'"
 31 |             class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
 32 |         >
 33 |             <span 
 34 |                 :class="isOn ? 'translate-x-5' : 'translate-x-0'"
 35 |                 class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
 36 |             ></span>
 37 |         </button>
 38 |         <span x-text="isOn ? 'ON' : 'OFF'" class="text-sm font-medium text-gray-900 dark:text-gray-300"></span>
 39 |     </div>
 40 | }
 41 | 
 42 | // Usage in another template:
 43 | @ToggleSwitch("Toggle Me")

Counter

Count:

templ Counter() {
 63 |     <div x-data="{ count: 0 }" class="text-center">
 64 |         <p class="text-2xl font-bold mb-4">Count: <span x-text="count"></span></p>
 65 |         <div class="space-x-2">
 66 |             <button @click="count++" class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 transition duration-300">Increment</button>
 67 |             <button @click="count--" class="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition duration-300">Decrement</button>
 68 |         </div>
 69 |     </div>
 70 | }
 71 | 
 72 | // Usage in another template:
 73 | @Counter()

Modal Dialog

templ Modal(buttonText, title, content string) {
 86 |     <div x-data="{ isOpen: false }">
 87 |         <button @click="isOpen = true" class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600 transition duration-300">{ buttonText }</button>
 88 |         
 89 |         <div x-show="isOpen" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center" style="display: none;">
 90 |             <div class="bg-white p-6 rounded-lg shadow-lg">
 91 |                 <h3 class="text-lg font-semibold mb-2">{ title }</h3>
 92 |                 <p class="mb-4">{ content }</p>
 93 |                 <button @click="isOpen = false" class="bg-gray-300 text-gray-800 px-4 py-2 rounded hover:bg-gray-400 transition duration-300">Close</button>
 94 |             </div>
 95 |         </div>
 96 |     </div>
 97 | }
 98 | 
 99 | // Usage in another template:
100 | @Modal("Open Modal", "Modal Title", "This is the modal content.")
-------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | GOAT Stack
Logo

GOAT Stack

Blazing fast web development with Go, Alpine.js, and Tailwind CSS

Tech Stack

Go with Templ

Powerful backend for server-side rendering and rapid static page generation.

Alpine.js

Lightweight JavaScript framework for smooth interactivity.

Tailwind CSS

Utility-first CSS framework for rapid UI development.

Vite (optional)

Next-generation frontend tooling for fast hot reloading.

Get Started

  1. Clone the repo:

    git clone https://github.com/morethancoder/goat.git yourprojectname
  2. Change into the project directory:

    cd yourprojectname
  3. Initialize the project:

    go mod init yourprojectname && go mod tidy
  4. Change the import in main.go:

    // Change this:
    19 | "morethancoder/goat/components"
    20 | // To:
    21 | "yourprojectname/components"
  5. Install required Node modules:

    bun install
  6. Start the Vite dev server:

    make vite
  7. Start file watching & compiling:

    make watch
  8. Star the repo!

    If you find this project helpful, please consider giving it a star on GitHub.

-------------------------------------------------------------------------------- /public/static/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morethancoder/goat/416fc3c35c770efefd1b3945304a6f4def17f1f8/public/static/assets/logo.png -------------------------------------------------------------------------------- /public/static/css/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | ! tailwindcss v3.4.9 | 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 | .pointer-events-none { 558 | pointer-events: none; 559 | } 560 | 561 | .static { 562 | position: static; 563 | } 564 | 565 | .fixed { 566 | position: fixed; 567 | } 568 | 569 | .absolute { 570 | position: absolute; 571 | } 572 | 573 | .relative { 574 | position: relative; 575 | } 576 | 577 | .sticky { 578 | position: sticky; 579 | } 580 | 581 | .inset-0 { 582 | inset: 0px; 583 | } 584 | 585 | .bottom-4 { 586 | bottom: 1rem; 587 | } 588 | 589 | .left-4 { 590 | left: 1rem; 591 | } 592 | 593 | .right-2 { 594 | right: 0.5rem; 595 | } 596 | 597 | .right-4 { 598 | right: 1rem; 599 | } 600 | 601 | .top-0 { 602 | top: 0px; 603 | } 604 | 605 | .top-2 { 606 | top: 0.5rem; 607 | } 608 | 609 | .top-4 { 610 | top: 1rem; 611 | } 612 | 613 | .z-10 { 614 | z-index: 10; 615 | } 616 | 617 | .mx-1 { 618 | margin-left: 0.25rem; 619 | margin-right: 0.25rem; 620 | } 621 | 622 | .mx-auto { 623 | margin-left: auto; 624 | margin-right: auto; 625 | } 626 | 627 | .mb-2 { 628 | margin-bottom: 0.5rem; 629 | } 630 | 631 | .mb-4 { 632 | margin-bottom: 1rem; 633 | } 634 | 635 | .mb-6 { 636 | margin-bottom: 1.5rem; 637 | } 638 | 639 | .mb-8 { 640 | margin-bottom: 2rem; 641 | } 642 | 643 | .ml-1 { 644 | margin-left: 0.25rem; 645 | } 646 | 647 | .ml-2 { 648 | margin-left: 0.5rem; 649 | } 650 | 651 | .mr-2 { 652 | margin-right: 0.5rem; 653 | } 654 | 655 | .mt-16 { 656 | margin-top: 4rem; 657 | } 658 | 659 | .mt-8 { 660 | margin-top: 2rem; 661 | } 662 | 663 | .block { 664 | display: block; 665 | } 666 | 667 | .inline-block { 668 | display: inline-block; 669 | } 670 | 671 | .flex { 672 | display: flex; 673 | } 674 | 675 | .inline-flex { 676 | display: inline-flex; 677 | } 678 | 679 | .grid { 680 | display: grid; 681 | } 682 | 683 | .hidden { 684 | display: none; 685 | } 686 | 687 | .size-5 { 688 | width: 1.25rem; 689 | height: 1.25rem; 690 | } 691 | 692 | .h-10 { 693 | height: 2.5rem; 694 | } 695 | 696 | .h-4 { 697 | height: 1rem; 698 | } 699 | 700 | .h-5 { 701 | height: 1.25rem; 702 | } 703 | 704 | .h-6 { 705 | height: 1.5rem; 706 | } 707 | 708 | .h-\[70svh\] { 709 | height: 70svh; 710 | } 711 | 712 | .min-h-screen { 713 | min-height: 100vh; 714 | } 715 | 716 | .w-10 { 717 | width: 2.5rem; 718 | } 719 | 720 | .w-11 { 721 | width: 2.75rem; 722 | } 723 | 724 | .w-4 { 725 | width: 1rem; 726 | } 727 | 728 | .w-5 { 729 | width: 1.25rem; 730 | } 731 | 732 | .w-6 { 733 | width: 1.5rem; 734 | } 735 | 736 | .w-full { 737 | width: 100%; 738 | } 739 | 740 | .max-w-2xl { 741 | max-width: 42rem; 742 | } 743 | 744 | .max-w-4xl { 745 | max-width: 56rem; 746 | } 747 | 748 | .max-w-md { 749 | max-width: 28rem; 750 | } 751 | 752 | .max-w-xl { 753 | max-width: 36rem; 754 | } 755 | 756 | .max-w-xs { 757 | max-width: 20rem; 758 | } 759 | 760 | .flex-shrink-0 { 761 | flex-shrink: 0; 762 | } 763 | 764 | .flex-grow { 765 | flex-grow: 1; 766 | } 767 | 768 | .translate-x-0 { 769 | --tw-translate-x: 0px; 770 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 771 | } 772 | 773 | .translate-x-5 { 774 | --tw-translate-x: 1.25rem; 775 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 776 | } 777 | 778 | .translate-y-0 { 779 | --tw-translate-y: 0px; 780 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 781 | } 782 | 783 | .translate-y-2 { 784 | --tw-translate-y: 0.5rem; 785 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 786 | } 787 | 788 | .transform { 789 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 790 | } 791 | 792 | .cursor-pointer { 793 | cursor: pointer; 794 | } 795 | 796 | .select-none { 797 | -webkit-user-select: none; 798 | -moz-user-select: none; 799 | user-select: none; 800 | } 801 | 802 | .list-none { 803 | list-style-type: none; 804 | } 805 | 806 | .grid-cols-1 { 807 | grid-template-columns: repeat(1, minmax(0, 1fr)); 808 | } 809 | 810 | .flex-col { 811 | flex-direction: column; 812 | } 813 | 814 | .items-end { 815 | align-items: flex-end; 816 | } 817 | 818 | .items-center { 819 | align-items: center; 820 | } 821 | 822 | .justify-start { 823 | justify-content: flex-start; 824 | } 825 | 826 | .justify-center { 827 | justify-content: center; 828 | } 829 | 830 | .justify-between { 831 | justify-content: space-between; 832 | } 833 | 834 | .gap-6 { 835 | gap: 1.5rem; 836 | } 837 | 838 | .space-x-1 > :not([hidden]) ~ :not([hidden]) { 839 | --tw-space-x-reverse: 0; 840 | margin-right: calc(0.25rem * var(--tw-space-x-reverse)); 841 | margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); 842 | } 843 | 844 | .space-x-2 > :not([hidden]) ~ :not([hidden]) { 845 | --tw-space-x-reverse: 0; 846 | margin-right: calc(0.5rem * var(--tw-space-x-reverse)); 847 | margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); 848 | } 849 | 850 | .space-x-4 > :not([hidden]) ~ :not([hidden]) { 851 | --tw-space-x-reverse: 0; 852 | margin-right: calc(1rem * var(--tw-space-x-reverse)); 853 | margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); 854 | } 855 | 856 | .space-y-12 > :not([hidden]) ~ :not([hidden]) { 857 | --tw-space-y-reverse: 0; 858 | margin-top: calc(3rem * calc(1 - var(--tw-space-y-reverse))); 859 | margin-bottom: calc(3rem * var(--tw-space-y-reverse)); 860 | } 861 | 862 | .space-y-2 > :not([hidden]) ~ :not([hidden]) { 863 | --tw-space-y-reverse: 0; 864 | margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); 865 | margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); 866 | } 867 | 868 | .space-y-6 > :not([hidden]) ~ :not([hidden]) { 869 | --tw-space-y-reverse: 0; 870 | margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); 871 | margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); 872 | } 873 | 874 | .space-y-8 > :not([hidden]) ~ :not([hidden]) { 875 | --tw-space-y-reverse: 0; 876 | margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); 877 | margin-bottom: calc(2rem * var(--tw-space-y-reverse)); 878 | } 879 | 880 | .overflow-hidden { 881 | overflow: hidden; 882 | } 883 | 884 | .overflow-x-auto { 885 | overflow-x: auto; 886 | } 887 | 888 | .rounded { 889 | border-radius: 0.25rem; 890 | } 891 | 892 | .rounded-full { 893 | border-radius: 9999px; 894 | } 895 | 896 | .rounded-lg { 897 | border-radius: 0.5rem; 898 | } 899 | 900 | .rounded-md { 901 | border-radius: 0.375rem; 902 | } 903 | 904 | .border { 905 | border-width: 1px; 906 | } 907 | 908 | .border-2 { 909 | border-width: 2px; 910 | } 911 | 912 | .border-current { 913 | border-color: currentColor; 914 | } 915 | 916 | .border-gray-300 { 917 | --tw-border-opacity: 1; 918 | border-color: rgb(209 213 219 / var(--tw-border-opacity)); 919 | } 920 | 921 | .border-stone-800 { 922 | --tw-border-opacity: 1; 923 | border-color: rgb(41 37 36 / var(--tw-border-opacity)); 924 | } 925 | 926 | .border-transparent { 927 | border-color: transparent; 928 | } 929 | 930 | .bg-black { 931 | --tw-bg-opacity: 1; 932 | background-color: rgb(0 0 0 / var(--tw-bg-opacity)); 933 | } 934 | 935 | .bg-blue-500 { 936 | --tw-bg-opacity: 1; 937 | background-color: rgb(59 130 246 / var(--tw-bg-opacity)); 938 | } 939 | 940 | .bg-blue-600 { 941 | --tw-bg-opacity: 1; 942 | background-color: rgb(37 99 235 / var(--tw-bg-opacity)); 943 | } 944 | 945 | .bg-gray-200 { 946 | --tw-bg-opacity: 1; 947 | background-color: rgb(229 231 235 / var(--tw-bg-opacity)); 948 | } 949 | 950 | .bg-gray-300 { 951 | --tw-bg-opacity: 1; 952 | background-color: rgb(209 213 219 / var(--tw-bg-opacity)); 953 | } 954 | 955 | .bg-green-500 { 956 | --tw-bg-opacity: 1; 957 | background-color: rgb(34 197 94 / var(--tw-bg-opacity)); 958 | } 959 | 960 | .bg-purple-500 { 961 | --tw-bg-opacity: 1; 962 | background-color: rgb(168 85 247 / var(--tw-bg-opacity)); 963 | } 964 | 965 | .bg-red-500 { 966 | --tw-bg-opacity: 1; 967 | background-color: rgb(239 68 68 / var(--tw-bg-opacity)); 968 | } 969 | 970 | .bg-stone-100 { 971 | --tw-bg-opacity: 1; 972 | background-color: rgb(245 245 244 / var(--tw-bg-opacity)); 973 | } 974 | 975 | .bg-stone-200 { 976 | --tw-bg-opacity: 1; 977 | background-color: rgb(231 229 228 / var(--tw-bg-opacity)); 978 | } 979 | 980 | .bg-stone-50 { 981 | --tw-bg-opacity: 1; 982 | background-color: rgb(250 250 249 / var(--tw-bg-opacity)); 983 | } 984 | 985 | .bg-stone-800 { 986 | --tw-bg-opacity: 1; 987 | background-color: rgb(41 37 36 / var(--tw-bg-opacity)); 988 | } 989 | 990 | .bg-white { 991 | --tw-bg-opacity: 1; 992 | background-color: rgb(255 255 255 / var(--tw-bg-opacity)); 993 | } 994 | 995 | .bg-yellow-500 { 996 | --tw-bg-opacity: 1; 997 | background-color: rgb(234 179 8 / var(--tw-bg-opacity)); 998 | } 999 | 1000 | .bg-opacity-50 { 1001 | --tw-bg-opacity: 0.5; 1002 | } 1003 | 1004 | .p-1 { 1005 | padding: 0.25rem; 1006 | } 1007 | 1008 | .p-2 { 1009 | padding: 0.5rem; 1010 | } 1011 | 1012 | .p-4 { 1013 | padding: 1rem; 1014 | } 1015 | 1016 | .p-6 { 1017 | padding: 1.5rem; 1018 | } 1019 | 1020 | .p-8 { 1021 | padding: 2rem; 1022 | } 1023 | 1024 | .px-2 { 1025 | padding-left: 0.5rem; 1026 | padding-right: 0.5rem; 1027 | } 1028 | 1029 | .px-3 { 1030 | padding-left: 0.75rem; 1031 | padding-right: 0.75rem; 1032 | } 1033 | 1034 | .px-4 { 1035 | padding-left: 1rem; 1036 | padding-right: 1rem; 1037 | } 1038 | 1039 | .py-1 { 1040 | padding-top: 0.25rem; 1041 | padding-bottom: 0.25rem; 1042 | } 1043 | 1044 | .py-2 { 1045 | padding-top: 0.5rem; 1046 | padding-bottom: 0.5rem; 1047 | } 1048 | 1049 | .py-3 { 1050 | padding-top: 0.75rem; 1051 | padding-bottom: 0.75rem; 1052 | } 1053 | 1054 | .text-center { 1055 | text-align: center; 1056 | } 1057 | 1058 | .font-mono { 1059 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 1060 | } 1061 | 1062 | .text-2xl { 1063 | font-size: 1.5rem; 1064 | line-height: 2rem; 1065 | } 1066 | 1067 | .text-3xl { 1068 | font-size: 1.875rem; 1069 | line-height: 2.25rem; 1070 | } 1071 | 1072 | .text-4xl { 1073 | font-size: 2.25rem; 1074 | line-height: 2.5rem; 1075 | } 1076 | 1077 | .text-lg { 1078 | font-size: 1.125rem; 1079 | line-height: 1.75rem; 1080 | } 1081 | 1082 | .text-sm { 1083 | font-size: 0.875rem; 1084 | line-height: 1.25rem; 1085 | } 1086 | 1087 | .text-xl { 1088 | font-size: 1.25rem; 1089 | line-height: 1.75rem; 1090 | } 1091 | 1092 | .text-xs { 1093 | font-size: 0.75rem; 1094 | line-height: 1rem; 1095 | } 1096 | 1097 | .font-bold { 1098 | font-weight: 700; 1099 | } 1100 | 1101 | .font-medium { 1102 | font-weight: 500; 1103 | } 1104 | 1105 | .font-semibold { 1106 | font-weight: 600; 1107 | } 1108 | 1109 | .text-black { 1110 | --tw-text-opacity: 1; 1111 | color: rgb(0 0 0 / var(--tw-text-opacity)); 1112 | } 1113 | 1114 | .text-gray-700 { 1115 | --tw-text-opacity: 1; 1116 | color: rgb(55 65 81 / var(--tw-text-opacity)); 1117 | } 1118 | 1119 | .text-gray-800 { 1120 | --tw-text-opacity: 1; 1121 | color: rgb(31 41 55 / var(--tw-text-opacity)); 1122 | } 1123 | 1124 | .text-gray-900 { 1125 | --tw-text-opacity: 1; 1126 | color: rgb(17 24 39 / var(--tw-text-opacity)); 1127 | } 1128 | 1129 | .text-green-500 { 1130 | --tw-text-opacity: 1; 1131 | color: rgb(34 197 94 / var(--tw-text-opacity)); 1132 | } 1133 | 1134 | .text-red-500 { 1135 | --tw-text-opacity: 1; 1136 | color: rgb(239 68 68 / var(--tw-text-opacity)); 1137 | } 1138 | 1139 | .text-stone-800 { 1140 | --tw-text-opacity: 1; 1141 | color: rgb(41 37 36 / var(--tw-text-opacity)); 1142 | } 1143 | 1144 | .text-stone-900 { 1145 | --tw-text-opacity: 1; 1146 | color: rgb(28 25 23 / var(--tw-text-opacity)); 1147 | } 1148 | 1149 | .text-white { 1150 | --tw-text-opacity: 1; 1151 | color: rgb(255 255 255 / var(--tw-text-opacity)); 1152 | } 1153 | 1154 | .text-yellow-500 { 1155 | --tw-text-opacity: 1; 1156 | color: rgb(234 179 8 / var(--tw-text-opacity)); 1157 | } 1158 | 1159 | .underline { 1160 | text-decoration-line: underline; 1161 | } 1162 | 1163 | .opacity-0 { 1164 | opacity: 0; 1165 | } 1166 | 1167 | .opacity-100 { 1168 | opacity: 1; 1169 | } 1170 | 1171 | .opacity-90 { 1172 | opacity: 0.9; 1173 | } 1174 | 1175 | .shadow { 1176 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 1177 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 1178 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1179 | } 1180 | 1181 | .shadow-lg { 1182 | --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 1183 | --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); 1184 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1185 | } 1186 | 1187 | .shadow-md { 1188 | --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 1189 | --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); 1190 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1191 | } 1192 | 1193 | .shadow-sm { 1194 | --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); 1195 | --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); 1196 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1197 | } 1198 | 1199 | .ring-0 { 1200 | --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); 1201 | --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); 1202 | box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); 1203 | } 1204 | 1205 | .transition { 1206 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; 1207 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; 1208 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; 1209 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1210 | transition-duration: 150ms; 1211 | } 1212 | 1213 | .transition-all { 1214 | transition-property: all; 1215 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1216 | transition-duration: 150ms; 1217 | } 1218 | 1219 | .transition-colors { 1220 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; 1221 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1222 | transition-duration: 150ms; 1223 | } 1224 | 1225 | .transition-opacity { 1226 | transition-property: opacity; 1227 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1228 | transition-duration: 150ms; 1229 | } 1230 | 1231 | .duration-200 { 1232 | transition-duration: 200ms; 1233 | } 1234 | 1235 | .duration-300 { 1236 | transition-duration: 300ms; 1237 | } 1238 | 1239 | .ease-in { 1240 | transition-timing-function: cubic-bezier(0.4, 0, 1, 1); 1241 | } 1242 | 1243 | .ease-in-out { 1244 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1245 | } 1246 | 1247 | .ease-out { 1248 | transition-timing-function: cubic-bezier(0, 0, 0.2, 1); 1249 | } 1250 | 1251 | .hover\:cursor-pointer:hover { 1252 | cursor: pointer; 1253 | } 1254 | 1255 | .hover\:bg-gray-400:hover { 1256 | --tw-bg-opacity: 1; 1257 | background-color: rgb(156 163 175 / var(--tw-bg-opacity)); 1258 | } 1259 | 1260 | .hover\:bg-green-600:hover { 1261 | --tw-bg-opacity: 1; 1262 | background-color: rgb(22 163 74 / var(--tw-bg-opacity)); 1263 | } 1264 | 1265 | .hover\:bg-purple-600:hover { 1266 | --tw-bg-opacity: 1; 1267 | background-color: rgb(147 51 234 / var(--tw-bg-opacity)); 1268 | } 1269 | 1270 | .hover\:bg-red-600:hover { 1271 | --tw-bg-opacity: 1; 1272 | background-color: rgb(220 38 38 / var(--tw-bg-opacity)); 1273 | } 1274 | 1275 | .hover\:bg-stone-100:hover { 1276 | --tw-bg-opacity: 1; 1277 | background-color: rgb(245 245 244 / var(--tw-bg-opacity)); 1278 | } 1279 | 1280 | .hover\:bg-yellow-400:hover { 1281 | --tw-bg-opacity: 1; 1282 | background-color: rgb(250 204 21 / var(--tw-bg-opacity)); 1283 | } 1284 | 1285 | .hover\:bg-yellow-600:hover { 1286 | --tw-bg-opacity: 1; 1287 | background-color: rgb(202 138 4 / var(--tw-bg-opacity)); 1288 | } 1289 | 1290 | .hover\:text-yellow-500:hover { 1291 | --tw-text-opacity: 1; 1292 | color: rgb(234 179 8 / var(--tw-text-opacity)); 1293 | } 1294 | 1295 | .hover\:decoration-yellow-500:hover { 1296 | text-decoration-color: #eab308; 1297 | } 1298 | 1299 | .hover\:opacity-80:hover { 1300 | opacity: 0.8; 1301 | } 1302 | 1303 | .focus\:outline-none:focus { 1304 | outline: 2px solid transparent; 1305 | outline-offset: 2px; 1306 | } 1307 | 1308 | .focus\:ring-2:focus { 1309 | --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); 1310 | --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); 1311 | box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); 1312 | } 1313 | 1314 | .focus\:ring-blue-500:focus { 1315 | --tw-ring-opacity: 1; 1316 | --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); 1317 | } 1318 | 1319 | .focus\:ring-yellow-500:focus { 1320 | --tw-ring-opacity: 1; 1321 | --tw-ring-color: rgb(234 179 8 / var(--tw-ring-opacity)); 1322 | } 1323 | 1324 | .focus\:ring-offset-2:focus { 1325 | --tw-ring-offset-width: 2px; 1326 | } 1327 | 1328 | .active\:scale-95:active { 1329 | --tw-scale-x: .95; 1330 | --tw-scale-y: .95; 1331 | transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); 1332 | } 1333 | 1334 | .group:hover .group-hover\:opacity-100 { 1335 | opacity: 1; 1336 | } 1337 | 1338 | @media (min-width: 640px) { 1339 | .sm\:mb-0 { 1340 | margin-bottom: 0px; 1341 | } 1342 | 1343 | .sm\:inline { 1344 | display: inline; 1345 | } 1346 | 1347 | .sm\:flex-row { 1348 | flex-direction: row; 1349 | } 1350 | 1351 | .sm\:items-start { 1352 | align-items: flex-start; 1353 | } 1354 | 1355 | .sm\:border-0 { 1356 | border-width: 0px; 1357 | } 1358 | 1359 | .sm\:bg-stone-100 { 1360 | --tw-bg-opacity: 1; 1361 | background-color: rgb(245 245 244 / var(--tw-bg-opacity)); 1362 | } 1363 | 1364 | .sm\:p-8 { 1365 | padding: 2rem; 1366 | } 1367 | 1368 | .sm\:px-3 { 1369 | padding-left: 0.75rem; 1370 | padding-right: 0.75rem; 1371 | } 1372 | 1373 | .sm\:py-1 { 1374 | padding-top: 0.25rem; 1375 | padding-bottom: 0.25rem; 1376 | } 1377 | } 1378 | 1379 | @media (min-width: 768px) { 1380 | .md\:grid-cols-2 { 1381 | grid-template-columns: repeat(2, minmax(0, 1fr)); 1382 | } 1383 | } 1384 | 1385 | @media (prefers-color-scheme: dark) { 1386 | .dark\:border-stone-200 { 1387 | --tw-border-opacity: 1; 1388 | border-color: rgb(231 229 228 / var(--tw-border-opacity)); 1389 | } 1390 | 1391 | .dark\:bg-black { 1392 | --tw-bg-opacity: 1; 1393 | background-color: rgb(0 0 0 / var(--tw-bg-opacity)); 1394 | } 1395 | 1396 | .dark\:bg-stone-700 { 1397 | --tw-bg-opacity: 1; 1398 | background-color: rgb(68 64 60 / var(--tw-bg-opacity)); 1399 | } 1400 | 1401 | .dark\:bg-stone-800 { 1402 | --tw-bg-opacity: 1; 1403 | background-color: rgb(41 37 36 / var(--tw-bg-opacity)); 1404 | } 1405 | 1406 | .dark\:text-gray-300 { 1407 | --tw-text-opacity: 1; 1408 | color: rgb(209 213 219 / var(--tw-text-opacity)); 1409 | } 1410 | 1411 | .dark\:text-stone-100 { 1412 | --tw-text-opacity: 1; 1413 | color: rgb(245 245 244 / var(--tw-text-opacity)); 1414 | } 1415 | 1416 | .dark\:text-stone-200 { 1417 | --tw-text-opacity: 1; 1418 | color: rgb(231 229 228 / var(--tw-text-opacity)); 1419 | } 1420 | 1421 | .dark\:text-stone-300 { 1422 | --tw-text-opacity: 1; 1423 | color: rgb(214 211 209 / var(--tw-text-opacity)); 1424 | } 1425 | 1426 | .dark\:text-white { 1427 | --tw-text-opacity: 1; 1428 | color: rgb(255 255 255 / var(--tw-text-opacity)); 1429 | } 1430 | 1431 | .dark\:shadow-none { 1432 | --tw-shadow: 0 0 #0000; 1433 | --tw-shadow-colored: 0 0 #0000; 1434 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1435 | } 1436 | 1437 | .dark\:hover\:bg-stone-800:hover { 1438 | --tw-bg-opacity: 1; 1439 | background-color: rgb(41 37 36 / var(--tw-bg-opacity)); 1440 | } 1441 | 1442 | .dark\:hover\:text-yellow-400:hover { 1443 | --tw-text-opacity: 1; 1444 | color: rgb(250 204 21 / var(--tw-text-opacity)); 1445 | } 1446 | } 1447 | 1448 | @media (min-width: 640px) { 1449 | @media (prefers-color-scheme: dark) { 1450 | .sm\:dark\:bg-stone-800 { 1451 | --tw-bg-opacity: 1; 1452 | background-color: rgb(41 37 36 / var(--tw-bg-opacity)); 1453 | } 1454 | } 1455 | } 1456 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./components/*.templ"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morethancoder/goat/416fc3c35c770efefd1b3945304a6f4def17f1f8/thumb.png --------------------------------------------------------------------------------