Changed: DB Params

This commit is contained in:
2025-03-20 12:35:13 +01:00
parent 8640a12439
commit b71b3d12ca
822 changed files with 134218 additions and 0 deletions

22
templ/docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/static/llms.md

41
templ/docs/README.md Normal file
View File

@@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -0,0 +1,138 @@
# Installation
## go install
With Go 1.23 or greater installed, run:
```bash
go install github.com/a-h/templ/cmd/templ@latest
```
## GitHub binaries
Download the latest release from https://github.com/a-h/templ/releases/latest
## Nix
templ provides a Nix flake with an exported package containing the binary at https://github.com/a-h/templ/blob/main/flake.nix
```bash
nix run github:a-h/templ
```
templ also provides a development shell which includes all of the tools required to build templ, e.g. go, gopls etc. but not templ itself.
```bash
nix develop github:a-h/templ
```
To install in your Nix Flake:
This flake exposes an overlay, so you can add it to your own Flake and/or NixOS system.
```nix
{
inputs = {
...
templ.url = "github:a-h/templ";
...
};
outputs = inputs@{
...
}:
# For NixOS configuration:
{
# Add the overlay,
nixpkgs.overlays = [
inputs.templ.overlays.default
];
# and install the package
environment.systemPackages = with pkgs; [
templ
];
};
# For a flake project:
let
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
inherit system;
pkgs = import nixpkgs { inherit system; };
});
templ = system: inputs.templ.packages.${system}.templ;
in {
packages = forAllSystems ({ pkgs, system }: {
myNewPackage = pkgs.buildGoModule {
...
preBuild = ''
${templ system}/bin/templ generate
'';
};
});
devShell = forAllSystems ({ pkgs, system }:
pkgs.mkShell {
buildInputs = with pkgs; [
go
(templ system)
];
};
});
}
```
## Docker
A Docker container is pushed on each release to https://github.com/a-h/templ/pkgs/container/templ
Pull the latest version with:
```bash
docker pull ghcr.io/a-h/templ:latest
```
To use the container, mount the source code of your application into the `/app` directory, set the working directory to the same directory and run `templ generate`, e.g. in a Linux or Mac shell, you can generate code for the current directory with:
```bash
docker run -v `pwd`:/app -w=/app ghcr.io/a-h/templ:latest generate
```
If you want to build templates using a multi-stage Docker build, you can use the `templ` image as a base image.
Here's an example multi-stage Dockerfile. Note that in the `generate-stage` the source code is copied into the container, and the `templ generate` command is run. The `build-stage` then copies the generated code into the container and builds the application.
The permissions of the source code are set to a user with a UID of 65532, which is the UID of the `nonroot` user in the `ghcr.io/a-h/templ:latest` image.
Note also the use of the `RUN ["templ", "generate"]` command instead of the common `RUN templ generate` command. This is because the templ Docker container does not contain a shell environment to keep its size minimal, so the command must be ran in the ["exec" form](https://docs.docker.com/reference/dockerfile/#shell-and-exec-form).
```Dockerfile
# Fetch
FROM golang:latest AS fetch-stage
COPY go.mod go.sum /app
WORKDIR /app
RUN go mod download
# Generate
FROM ghcr.io/a-h/templ:latest AS generate-stage
COPY --chown=65532:65532 . /app
WORKDIR /app
RUN ["templ", "generate"]
# Build
FROM golang:latest AS build-stage
COPY --from=generate-stage /app /app
WORKDIR /app
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/app
# Test
FROM build-stage AS test-stage
RUN go test -v ./...
# Deploy
FROM gcr.io/distroless/base-debian12 AS deploy-stage
WORKDIR /
COPY --from=build-stage /app/app /app
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/app"]
```

View File

@@ -0,0 +1,85 @@
# Creating a simple templ component
To create a templ component, first create a new Go project.
## Setup project
Create a new directory containing our project.
```bash
mkdir hello-world
```
Initialize a new Go project within it.
```bash
cd hello-world
go mod init github.com/a-h/templ-examples/hello-world
go get github.com/a-h/templ
```
## Create a templ file
To use it, create a `hello.templ` file containing a component.
Components are functions that contain templ elements, markup, and `if`, `switch`, and `for` Go expressions.
```templ title="hello.templ"
package main
templ hello(name string) {
<div>Hello, { name }</div>
}
```
## Generate Go code from the templ file
Run the `templ generate` command.
```bash
templ generate
```
templ will generate a `hello_templ.go` file containing Go code.
This file will contain a function called `hello` which takes `name` as an argument, and returns a `templ.Component` that renders HTML.
```go
func hello(name string) templ.Component {
// ...
}
```
## Write a program that renders to stdout
Create a `main.go` file.
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
component := hello("John")
component.Render(context.Background(), os.Stdout)
}
```
## Run the program
Running the code will render the component's HTML to stdout.
```bash
go run .
```
```html title="Output"
<div>Hello, John</div>
```
Instead of passing `os.Stdout` to the component's render function, you can pass any type that implements the `io.Writer` interface. This includes files, `bytes.Buffer`, and HTTP responses.
In this way, templ can be used to generate HTML files that can be hosted as static content in an S3 bucket, Google Cloud Storage, or used to generate HTML that is fed into PDF conversion processes, or sent via email.

View File

@@ -0,0 +1,47 @@
# Running your first templ application
Let's update the previous application to serve HTML over HTTP instead of writing it to the terminal.
## Create a web server
Update the `main.go` file.
templ components can be served as a standard HTTP handler using the `templ.Handler` function.
```go title="main.go"
package main
import (
"fmt"
"net/http"
"github.com/a-h/templ"
)
func main() {
component := hello("John")
http.Handle("/", templ.Handler(component))
fmt.Println("Listening on :3000")
http.ListenAndServe(":3000", nil)
}
```
## Run the program
Running the code will start a web server on port 3000.
```bash
go run *.go
```
If you run another terminal session and run `curl` you can see the exact HTML that is returned matches the `hello` component, with the name "John".
```bash
curl localhost:3000
```
```html name="Output"
<div>Hello, John</div>
```

View File

@@ -0,0 +1,4 @@
{
"position": 2,
"label": "Quick start"
}

View File

@@ -0,0 +1,48 @@
# Basic syntax
## Package name and imports
templ files start with a package name, followed by any required imports, just like Go.
```go
package main
import "fmt"
import "time"
```
## Components
templ files can also contain components. Components are markup and code that is compiled into functions that return a `templ.Component` interface by running the `templ generate` command.
Components can contain templ elements that render HTML, text, expressions that output text or include other templates, and branching statements such as `if` and `switch`, and `for` loops.
```templ name="header.templ"
package main
templ headerTemplate(name string) {
<header data-testid="headerTemplate">
<h1>{ name }</h1>
</header>
}
```
## Go code
Outside of templ Components, templ files are ordinary Go code.
```templ name="header.templ"
package main
// Ordinary Go code that we can use in our Component.
var greeting = "Welcome!"
// templ Component
templ headerTemplate(name string) {
<header>
<h1>{ name }</h1>
<h2>"{ greeting }" comes from ordinary Go code</h2>
</header>
}
```

View File

@@ -0,0 +1,81 @@
# Elements
templ elements are used to render HTML within templ components.
```templ title="button.templ"
package main
templ button(text string) {
<button class="button">{ text }</button>
}
```
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
button("Click me").Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<button class="button">
Click me
</button>
```
:::info
templ automatically minifies HTML responses, output is shown formatted for readability.
:::
## Tags must be closed
Unlike HTML, templ requires that all HTML elements are closed with either a closing tag (`</a>`), or by using a self-closing element (`<hr/>`).
templ is aware of which HTML elements are "void", and will not include the closing `/` in the output HTML.
```templ title="button.templ"
package main
templ component() {
<div>Test</div>
<img src="images/test.png"/>
<br/>
}
```
```templ title="Output"
<div>Test</div>
<img src="images/test.png">
<br>
```
## Attributes and elements can contain expressions
templ elements can contain placeholder expressions for attributes and content.
```templ title="button.templ"
package main
templ button(name string, content string) {
<button value={ name }>{ content }</button>
}
```
Rendering the component to stdout, we can see the results.
```go title="main.go"
func main() {
component := button("John", "Say Hello")
component.Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<button value="John">Say Hello</button>
```

View File

@@ -0,0 +1,256 @@
# Attributes
## Constant attributes
templ elements can have HTML attributes that use the double quote character `"`.
```templ
templ component() {
<p data-testid="paragraph">Text</p>
}
```
```html title="Output"
<p data-testid="paragraph">Text</p>
```
## String expression attributes
Element attributes can be set to Go strings.
```templ
templ component(testID string) {
<p data-testid={ testID }>Text</p>
}
templ page() {
@component("testid-123")
}
```
Rendering the `page` component results in:
```html title="Output"
<p data-testid="testid-123">Text</p>
```
:::note
String values are automatically HTML attribute encoded. This is a security measure, but may make the values (especially JSON appear) look strange to you, since some characters may be converted into HTML entities. However, it is correct HTML and won't affect the behavior.
:::
It's also possible to use function calls in string attribute expressions.
Here's a function that returns a string based on a boolean input.
```go
func testID(isTrue bool) string {
if isTrue {
return "testid-123"
}
return "testid-456"
}
```
```templ
templ component() {
<p data-testid={ testID(true) }>Text</p>
}
```
The result:
```html title="Output"
<p data-testid="testid-123">Text</p>
```
Functions in string attribute expressions can also return errors.
```go
func testID(isTrue bool) (string, error) {
if isTrue {
return "testid-123", nil
}
return "", fmt.Errorf("isTrue is false")
}
```
If the function returns an error, the `Render` method will return the error along with its location.
## Boolean attributes
Boolean attributes (see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes) where the presence of an attribute name without a value means true, and the attribute name not being present means false are supported.
```templ
templ component() {
<hr noshade/>
}
```
```html title="Output"
<hr noshade>
```
:::note
templ is aware that `<hr/>` is a void element, and renders `<hr>` instead.
:::
To set boolean attributes using variables or template parameters, a question mark after the attribute name is used to denote that the attribute is boolean.
```templ
templ component() {
<hr noshade?={ false } />
}
```
```html title="Output"
<hr>
```
## Conditional attributes
Use an `if` statement within a templ element to optionally add attributes to elements.
```templ
templ component() {
<hr style="padding: 10px"
if true {
class="itIsTrue"
}
/>
}
```
```html title="Output"
<hr style="padding: 10px" class="itIsTrue" />
```
## Spread attributes
Use the `{ attrMap... }` syntax in the open tag of an element to append a dynamic map of attributes to the element's attributes.
It's possible to spread any variable of type `templ.Attributes`. `templ.Attributes` is a `map[string]any` type definition.
* If the value is a `string`, the attribute is added with the string value, e.g. `<div name="value">`.
* If the value is a `bool`, the attribute is added as a boolean attribute if the value is true, e.g. `<div name>`.
* If the value is a `templ.KeyValue[string, bool]`, the attribute is added if the boolean is true, e.g. `<div name="value">`.
* If the value is a `templ.KeyValue[bool, bool]`, the attribute is added if both boolean values are true, as `<div name>`.
```templ
templ component(shouldBeUsed bool, attrs templ.Attributes) {
<p { attrs... }>Text</p>
<hr
if shouldBeUsed {
{ attrs... }
}
/>
}
templ usage() {
@component(false, templ.Attributes{"data-testid": "paragraph"})
}
```
```html title="Output"
<p data-testid="paragraph">Text</p>
<hr>
```
## URL attributes
The `<a>` element's `href` attribute is treated differently. templ expects you to provide a `templ.SafeURL` instead of a `string`.
Typically, you would do this by using the `templ.URL` function.
The `templ.URL` function sanitizes input URLs and checks that the protocol is `http`/`https`/`mailto` rather than `javascript` or another unexpected protocol.
```templ
templ component(p Person) {
<a href={ templ.URL(p.URL) }>{ strings.ToUpper(p.Name) }</a>
}
```
:::tip
In templ, all attributes are HTML-escaped. This means that:
- `&` characters in the URL are escaped to `&amp;`.
- `"` characters are escaped to `&quot;`.
- `'` characters are escaped to `&#39;`.
This done to prevent XSS attacks. For example, without escaping, if a string contained `http://google.com" onclick="alert('hello')"`, the browser would interpret this as a URL followed by an `onclick` attribute, which would execute JavaScript code.
The escaping does not change the URL's functionality.
Sanitization is the process of examining the URL scheme (protocol) and structure to ensure that it's safe to use, e.g. that it doesn't contain `javascript:` or other potentially harmful schemes. If a URL is not safe, templ will replace the URL with `about:invalid#TemplFailedSanitizationURL`.
:::
The `templ.URL` function only supports standard HTML elements and attributes (`<a href=""` and `<form action=""`).
For use on non-standard HTML elements (e.g. HTMX's `hx-*` attributes), convert the `templ.URL` to a `string` after sanitization.
```templ
templ component(contact model.Contact) {
<div hx-get={ string(templ.URL(fmt.Sprintf("/contacts/%s/email", contact.ID)))}>
{ contact.Name }
</div>
}
```
:::caution
If you need to bypass this sanitization, you can use `templ.SafeURL(myURL)` to mark that your string is safe to use.
This may introduce security vulnerabilities to your program.
:::
## JavaScript attributes
`onClick` and other `on*` handlers have special behaviour, they expect a reference to a `script` template.
:::info
This ensures that any client-side JavaScript that is required for a component to function is only emitted once, that script name collisions are not possible, and that script input parameters are properly sanitized.
:::
```templ
script withParameters(a string, b string, c int) {
console.log(a, b, c);
}
script withoutParameters() {
alert("hello");
}
templ Button(text string) {
<button onClick={ withParameters("test", text, 123) } onMouseover={ withoutParameters() } type="button">{ text }</button>
}
```
```html title="Output"
<script>
function __templ_withParameters_1056(a, b, c){console.log(a, b, c);}function __templ_withoutParameters_6bbf(){alert("hello");}
</script>
<button onclick="__templ_withParameters_1056("test","Say hello",123)" onmouseover="__templ_withoutParameters_6bbf()" type="button">
Say hello
</button>
```
## CSS attributes
CSS handling is discussed in detail in [CSS style management](/syntax-and-usage/css-style-management).
## JSON attributes
To set an attribute's value to a JSON string (e.g. for HTMX's [hx-vals](https://htmx.org/attributes/hx-vals) or Alpine's [x-data](https://alpinejs.dev/directives/data)), serialize the value to a string using a function.
```go
func countriesJSON() string {
countries := []string{"Czech Republic", "Slovakia", "United Kingdom", "Germany", "Austria", "Slovenia"}
bytes, _ := json.Marshal(countries)
return string(bytes)
}
```
```templ
templ SearchBox() {
<search-webcomponent suggestions={ countriesJSON() } />
}
```

View File

@@ -0,0 +1,103 @@
# Expressions
## String expressions
Within a templ element, expressions can be used to render strings. Content is automatically escaped using context-aware HTML encoding rules to protect against XSS and CSS injection attacks.
String literals, variables and functions that return a string can be used.
### Literals
You can use Go string literals.
```templ title="component.templ"
package main
templ component() {
<div>{ "print this" }</div>
<div>{ `and this` }</div>
}
```
```html title="Output"
<div>print this</div><div>and this</div>
```
### Variables
Any Go string variable can be used, for example:
* A string function parameter.
* A field on a struct.
* A variable or constant string that is in scope.
```templ title="/main.templ"
package main
templ greet(prefix string, p Person) {
<div>{ prefix } { p.Name }{ exclamation }</div>
}
```
```templ title="main.go"
package main
type Person struct {
Name string
}
const exclamation = "!"
func main() {
p := Person{ Name: "John" }
component := greet("Hello", p)
component.Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<div>Hello John!</div>
```
### Functions
Functions that return `string` or `(string, error)` can be used.
```templ title="component.templ"
package main
import "strings"
import "strconv"
func getString() (string, error) {
return "DEF", nil
}
templ component() {
<div>{ strings.ToUpper("abc") }</div>
<div>{ getString() }</div>
}
```
```html title="Output"
<div>ABC</div>
<div>DEF</div>
```
If the function returns an error, the `Render` function will return an error containing the location of the error and the underlying error.
### Escaping
templ automatically escapes strings using HTML escaping rules.
```templ title="component.templ"
package main
templ component() {
<div>{ `</div><script>alert('hello!')</script><div>` }</div>
}
```
```html title="Output"
<div>&lt;/div&gt;&lt;script&gt;alert(&#39;hello!&#39;)&lt;/script&gt;&lt;div&gt;</div>
```

View File

@@ -0,0 +1,101 @@
# Statements
## Control flow
Within a templ element, a subset of Go statements can be used directly.
These Go statements can be used to conditionally render child elements, or to iterate variables.
For individual implementation guides see:
* [if/else](/syntax-and-usage/if-else)
* [switch](/syntax-and-usage/switch)
* [for loops](/syntax-and-usage/loops)
## if/switch/for within text
Go statements can be used without any escaping to make it simple for developers to include them.
The templ parser assumes that text that starts with `if`, `switch` or `for` denotes the start of one of those expressions as per this example.
```templ title="show-hello.templ"
package main
templ showHelloIfTrue(b bool) {
<div>
if b {
<p>Hello</p>
}
</div>
}
```
If you need to start a text block with the words `if`, `switch`, or `for`:
* Use a Go string expression.
* Capitalise `if`, `switch`, or `for`.
```templ title="paragraph.templ"
package main
templ display(price float64, count int) {
<p>Switch to Linux</p>
<p>{ `switch to Linux` }</p>
<p>{ "for a day" }</p>
<p>{ fmt.Sprintf("%f", price) }{ "for" }{ fmt.Sprintf("%d", count) }</p>
<p>{ fmt.Sprintf("%f for %d", price, count) }</p>
}
```
## Design considerations
We decided to not require a special prefix for `if`, `switch` and `for` expressions on the basis that we were more likely to want to use a Go control statement than start a text run with those strings.
To reduce the risk of a broken control statement, resulting in printing out the source code of the application, templ will complain if a text run starts with `if`, `switch` or `for`, but no opening brace `{` is found.
For example, the following code causes the templ parser to return an error:
```templ title="broken-if.templ"
package main
templ showIfTrue(b bool) {
if b
<p>Hello</p>
}
}
```
:::note
Note the missing `{` on line 4.
:::
The following code also produces an error, since the text run starts with `if`, but no opening `{` is found.
```templ title="paragraph.templ"
package main
templ text(b bool) {
<p>if a tree fell in the woods</p>
}
```
:::note
This also applies to `for` and `switch` statements.
:::
To resolve the issue:
* Use a Go string expression.
* Capitalise `if`, `switch`, or `for`.
```templ title="paragraph.templ"
package main
templ display(price float64, count int) {
<p>Switch to Linux</p>
<p>{ `switch to Linux` }</p>
<p>{ "for a day" }</p>
<p>{ fmt.Sprintf("%f", price) }{ "for" }{ fmt.Sprintf("%d", count) }</p>
<p>{ fmt.Sprintf("%f for %d", price, count) }</p>
}
```

View File

@@ -0,0 +1,32 @@
# If/else
templ uses standard Go `if`/`else` statements which can be used to conditionally render components and elements.
```templ title="component.templ"
templ login(isLoggedIn bool) {
if isLoggedIn {
<div>Welcome back!</div>
} else {
<input name="login" type="button" value="Log in"/>
}
}
```
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
login(true).Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<div>
Welcome back!
</div>
```

View File

@@ -0,0 +1,37 @@
# Switch
templ uses standard Go `switch` statements which can be used to conditionally render components and elements.
```templ title="component.templ"
package main
templ userTypeDisplay(userType string) {
switch userType {
case "test":
<span>{ "Test user" }</span>
case "admin":
<span>{ "Admin user" }</span>
default:
<span>{ "Unknown user" }</span>
}
}
```
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
userTypeDisplay("Other").Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<span>
Unknown user
</span>
```

View File

@@ -0,0 +1,23 @@
# For loops
Use the standard Go `for` loop for iteration.
```templ title="component.templ"
package main
templ nameList(items []Item) {
<ul>
for _, item := range items {
<li>{ item.Name }</li>
}
</ul>
}
```
```html title="Output"
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
```

View File

@@ -0,0 +1,24 @@
# Raw Go
For some more advanced use cases it may be useful to write Go code statements in your template.
Use the `{{ ... }}` syntax for this.
## Variable declarations
Scoped variables can be created using this syntax, to reduce the need for multiple function calls.
```templ title="component.templ"
package main
templ nameList(items []Item) {
{{ first := items[0] }}
<p>
{ first.Name }
</p>
}
```
```html title="Output"
<p>A</p>
```

View File

@@ -0,0 +1,320 @@
# Template composition
Templates can be composed using the import expression.
```templ
templ showAll() {
@left()
@middle()
@right()
}
templ left() {
<div>Left</div>
}
templ middle() {
<div>Middle</div>
}
templ right() {
<div>Right</div>
}
```
```html title="Output"
<div>
Left
</div>
<div>
Middle
</div>
<div>
Right
</div>
```
## Children
Children can be passed to a component for it to wrap.
```templ
templ showAll() {
@wrapChildren() {
<div>Inserted from the top</div>
}
}
templ wrapChildren() {
<div id="wrapper">
{ children... }
</div>
}
```
:::note
The use of the `{ children... }` expression in the child component.
:::
```html title="output"
<div id="wrapper">
<div>
Inserted from the top
</div>
</div>
```
### Using children in code components
Children are passed to a component using the Go context. To pass children to a component using Go code, use the `templ.WithChildren` function.
```templ
package main
import (
"context"
"os"
"github.com/a-h/templ"
)
templ wrapChildren() {
<div id="wrapper">
{ children... }
</div>
}
func main() {
contents := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, "<div>Inserted from Go code</div>")
return err
})
ctx := templ.WithChildren(context.Background(), contents)
wrapChildren().Render(ctx, os.Stdout)
}
```
```html title="output"
<div id="wrapper">
<div>
Inserted from Go code
</div>
</div>
```
To get children from the context, use the `templ.GetChildren` function.
```templ
package main
import (
"context"
"os"
"github.com/a-h/templ"
)
func main() {
contents := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, "<div>Inserted from Go code</div>")
return err
})
wrapChildren := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
children := templ.GetChildren(ctx)
ctx = templ.ClearChildren(ctx)
_, err := io.WriteString(w, "<div id=\"wrapper\">")
if err != nil {
return err
}
err = children.Render(ctx, w)
if err != nil {
return err
}
_, err = io.WriteString(w, "</div>")
return err
})
```
:::note
The `templ.ClearChildren` function is used to stop passing the children down the tree.
:::
## Components as parameters
Components can also be passed as parameters and rendered using the `@component` expression.
```templ
package main
templ heading() {
<h1>Heading</h1>
}
templ layout(contents templ.Component) {
<div id="heading">
@heading()
</div>
<div id="contents">
@contents
</div>
}
templ paragraph(contents string) {
<p>{ contents }</p>
}
```
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
c := paragraph("Dynamic contents")
layout(c).Render(context.Background(), os.Stdout)
}
```
```html title="output"
<div id="heading">
<h1>Heading</h1>
</div>
<div id="contents">
<p>Dynamic contents</p>
</div>
```
You can pass `templ` components as parameters to other components within templates using standard Go function call syntax.
```templ
package main
templ heading() {
<h1>Heading</h1>
}
templ layout(contents templ.Component) {
<div id="heading">
@heading()
</div>
<div id="contents">
@contents
</div>
}
templ paragraph(contents string) {
<p>{ contents }</p>
}
templ root() {
@layout(paragraph("Dynamic contents"))
}
```
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
root().Render(context.Background(), os.Stdout)
}
```
```html title="output"
<div id="heading">
<h1>Heading</h1>
</div>
<div id="contents">
<p>Dynamic contents</p>
</div>
```
## Joining Components
Components can be aggregated into a single Component using `templ.Join`.
```templ
package main
templ hello() {
<span>hello</span>
}
templ world() {
<span>world</span>
}
templ helloWorld() {
@templ.Join(hello(), world())
}
```
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
helloWorld().Render(context.Background(), os.Stdout)
}
```
```html title="output"
<span>hello</span><span>world</span>
```
## Sharing and re-using components
Since templ components are compiled into Go functions by the `go generate` command, templ components follow the rules of Go, and are shared in exactly the same way as Go code.
templ files in the same directory can access each other's components. Components in different directories can be accessed by importing the package that contains the component, so long as the component is exported by capitalizing its name.
:::tip
In Go, a _package_ is a collection of Go source files in the same directory that are compiled together. All of the functions, types, variables, and constants defined in one source file in a package are available to all other source files in the same package.
Packages exist within a Go _module_, defined by the `go.mod` file.
:::
:::note
Go is structured differently to JavaScript, but uses similar terminology. A single `.js` or `.ts` _file_ is like a Go package, and an NPM package is like a Go module.
:::
### Exporting components
To make a templ component available to other packages, export it by capitalizing its name.
```templ
package components
templ Hello() {
<div>Hello</div>
}
```
### Importing components
To use a component in another package, import the package and use the component as you would any other Go function or type.
```templ
package main
import "github.com/a-h/templ/examples/counter/components"
templ Home() {
@components.Hello()
}
```
:::tip
To import a component from another Go module, you must first import the module by using the `go get <module>` command. Then, you can import the component as you would any other Go package.
:::

View File

@@ -0,0 +1,448 @@
# CSS style management
## HTML class and style attributes
The standard HTML `class` and `style` attributes can be added to components. Note the use of standard quotes to denote a static value.
```templ
templ button(text string) {
<button class="button is-primary" style="background-color: red">{ text }</button>
}
```
```html title="Output"
<button class="button is-primary" style="background-color: red">
Click me
</button>
```
## Style attribute
To use a variable in the style attribute, use braces to denote the Go expression.
```templ
templ button(style, text string) {
<button style={ style }>{ text }</button>
}
```
You can pass multiple values to the `style` attribute. The results are all added to the output.
```templ
templ button(style1, style2 string, text string) {
<button style={ style1, style2 }>{ text }</button>
}
```
The style attribute supports use of the following types:
* `string` - A string containing CSS properties, e.g. `background-color: red`.
* `templ.SafeCSS` - A value containing CSS properties and values that will not be sanitized, e.g. `background-color: red; text-decoration: underline`
* `map[string]string` - A map of string keys to string values, e.g. `map[string]string{"color": "red"}`
* `map[string]templ.SafeCSSProperty` - A map of string keys to values, where the values will not be sanitized.
* `templ.KeyValue[string, string]` - A single CSS key/value.
* `templ.KeyValue[string, templ.SafeCSSProperty` - A CSS key/value, but the value will not be sanitized.
* `templ.KeyValue[string, bool]` - A map where the CSS in the key is only included in the output if the boolean value is true.
* `templ.KeyValue[templ.SafeCSS, bool]` - A map where the CSS in the key is only included if the boolean value is true.
Finally, a function value that returns any of the above types can be used.
Go syntax allows you to pass a single function that returns a value and an error.
```templ
templ Page(userType string) {
<div style={ getStyle(userType) }>Styled</div>
}
func getStyle(userType string) (string, error) {
//TODO: Look up in something that might error.
return "background-color: red", errors.New("failed")
}
```
Or multiple functions and values that return a single type.
```templ
templ Page(userType string) {
<div style={ getStyle(userType), "color: blue" }>Styled</div>
}
func getStyle(userType string) (string) {
return "background-color: red"
}
```
### Style attribute examples
#### Maps
Maps are useful when styles need to be dynamically computed based on component state or external inputs.
```templ
func getProgressStyle(percent int) map[string]string {
return map[string]string{
"width": fmt.Sprintf("%d%%", percent),
"transition": "width 0.3s ease",
}
}
templ ProgressBar(percent int) {
<div style={ getProgressStyle(percent) } class="progress-bar">
<div class="progress-fill"></div>
</div>
}
```
```html title="Output (percent=75)"
<div style="transition:width 0.3s ease;width:75%;" class="progress-bar">
<div class="progress-fill"></div>
</div>
```
#### KeyValue pattern
The `templ.KV` helper provides conditional style application in a more compact syntax.
```templ
templ TextInput(value string, hasError bool) {
<input
type="text"
value={ value }
style={
templ.KV("border-color: #ff3860", hasError),
templ.KV("background-color: #fff5f7", hasError),
"padding: 0.5em 1em;",
}
>
}
```
```html title="Output (hasError=true)"
<input
type="text"
value=""
style="border-color: #ff3860; background-color: #fff5f7; padding: 0.5em 1em;">
```
#### Bypassing sanitization
By default, dynamic CSS values are sanitized to protect against dangerous CSS values that might introduce vulnerabilities into your application.
However, if you're sure, you can bypass sanitization by marking your content as safe with the `templ.SafeCSS` and `templ.SafeCSSProperty` types.
```templ
func calculatePositionStyles(x, y int) templ.SafeCSS {
return templ.SafeCSS(fmt.Sprintf(
"transform: translate(%dpx, %dpx);",
x*2, // Example calculation
y*2,
))
}
templ DraggableElement(x, y int) {
<div style={ calculatePositionStyles(x, y) }>
Drag me
</div>
}
```
```html title="Output (x=10, y=20)"
<div style="transform: translate(20px, 40px);">
Drag me
</div>
```
### Pattern use cases
| Pattern | Best For | Example Use Case |
|---------|----------|------------------|
| **Maps** | Dynamic style sets requiring multiple computed values | Progress indicators, theme switching |
| **KeyValue** | Conditional style toggling | Form validation, interactive states |
| **Functions** | Complex style generation | Animations, data visualizations |
| **Direct Strings** | Simple static styles | Basic formatting, utility classes |
### Sanitization behaviour
By default, dynamic CSS values are sanitized to protect against dangerous CSS values that might introduce vulnerabilities into your application.
```templ
templ UnsafeExample() {
<div style={ "background-image: url('javascript:alert(1)')" }>
Dangerous content
</div>
}
```
```html title="Output"
<div style="background-image:zTemplUnsafeCSSPropertyValue;">
Dangerous content
</div>
```
These protections can be bypassed with the `templ.SafeCSS` and `templ.SafeCSSProperty` types.
```templ
templ SafeEmbed() {
<div style={ templ.SafeCSS("background-image: url(/safe.png);") }>
Trusted content
</div>
}
```
```html title="Output"
<div style="background-image: url(/safe.png);">
Trusted content
</div>
```
:::note
HTML attribute escaping is not bypassed, so `<`, `>`, `&` and quotes will always appear as HTML entities (`&lt;` etc.) in attributes - this is good practice, and doesn't affect how browsers use the CSS.
:::
### Error Handling
Invalid values are automatically sanitized:
```templ
templ InvalidButton() {
<button style={
map[string]string{
"": "invalid-property",
"color": "</style>",
}
}>Click me</button>
}
```
```html title="Output"
<button style="zTemplUnsafeCSSPropertyName:zTemplUnsafeCSSPropertyValue;color:zTemplUnsafeCSSPropertyValue;">
Click me
</button>
```
Go's type system doesn't support union types, so it's not possible to limit the inputs to the style attribute to just the supported types.
As such, the attribute takes `any`, and executes type checks at runtime. Any invalid types will produce the CSS value `zTemplUnsupportedStyleAttributeValue:Invalid;`.
## Class attributes
To use a variable as the name of a CSS class, use a CSS expression.
```templ title="component.templ"
package main
templ button(text string, className string) {
<button class={ className }>{ text }</button>
}
```
The class expression can take an array of values.
```templ title="component.templ"
package main
templ button(text string, className string) {
<button class={ "button", className }>{ text }</button>
}
```
### Dynamic class names
Toggle addition of CSS classes to an element based on a boolean value by passing:
* A `string` containing the name of a class to apply.
* A `templ.KV` value containing the name of the class to add to the element, and a boolean that determines whether the class is added to the attribute at render time.
* `templ.KV("is-primary", true)`
* `templ.KV("hover:red", true)`
* A map of string class names to a boolean that determines if the class is added to the class attribute value at render time:
* `map[string]bool`
* `map[CSSClass]bool`
```templ title="component.templ"
package main
css red() {
background-color: #ff0000;
}
templ button(text string, isPrimary bool) {
<button class={ "button", templ.KV("is-primary", isPrimary), templ.KV(red(), isPrimary) }>{ text }</button>
}
```
```go title="main.go"
package main
import (
"context"
"os"
)
func main() {
button("Click me", false).Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<button class="button">
Click me
</button>
```
## CSS elements
The standard `<style>` element can be used within a template.
`<style>` element contents are rendered to the output without any changes.
```templ
templ page() {
<style type="text/css">
p {
font-family: sans-serif;
}
.button {
background-color: black;
foreground-color: white;
}
</style>
<p>
Paragraph contents.
</p>
}
```
```html title="Output"
<style type="text/css">
p {
font-family: sans-serif;
}
.button {
background-color: black;
foreground-color: white;
}
</style>
<p>
Paragraph contents.
</p>
```
:::tip
If you want to make sure that the CSS element is only output once, even if you use a template many times, use a CSS expression.
:::
## CSS components
When developing a component library, it may not be desirable to require that specific CSS classes are present when the HTML is rendered.
There may be CSS class name clashes, or developers may forget to include the required CSS.
To include CSS within a component library, use a CSS component.
CSS components can also be conditionally rendered.
```templ title="component.templ"
package main
var red = "#ff0000"
var blue = "#0000ff"
css primaryClassName() {
background-color: #ffffff;
color: { red };
}
css className() {
background-color: #ffffff;
color: { blue };
}
templ button(text string, isPrimary bool) {
<button class={ "button", className(), templ.KV(primaryClassName(), isPrimary) }>{ text }</button>
}
```
```html title="Output"
<style type="text/css">
.className_f179{background-color:#ffffff;color:#ff0000;}
</style>
<button class="button className_f179">
Click me
</button>
```
:::info
The CSS class is given a unique name the first time it is used, and only rendered once per HTTP request to save bandwidth.
:::
:::caution
The class name is autogenerated, don't rely on it being consistent.
:::
### CSS component arguments
CSS components can also require function arguments.
```templ title="component.templ"
package main
css loading(percent int) {
width: { fmt.Sprintf("%d%%", percent) };
}
templ index() {
<div class={ loading(50) }></div>
<div class={ loading(100) }></div>
}
```
```html title="Output"
<style type="text/css">
.loading_a3cc{width:50%;}
</style>
<div class="loading_a3cc"></div>
<style type="text/css">
.loading_9ccc{width:100%;}
</style>
<div class="loading_9ccc"></div>
```
### CSS Sanitization
To prevent CSS injection attacks, templ automatically sanitizes dynamic CSS property names and values using the `templ.SanitizeCSS` function. Internally, this uses a lightweight fork of Google's `safehtml` package to sanitize the value.
If a property name or value has been sanitized, it will be replaced with `zTemplUnsafeCSSPropertyName` for property names, or `zTemplUnsafeCSSPropertyValue` for property values.
To bypass this sanitization, e.g. for URL values of `background-image`, you can mark the value as safe using the `templ.SafeCSSProperty` type.
```templ
css windVaneRotation(degrees float64) {
transform: { templ.SafeCSSProperty(fmt.Sprintf("rotate(%ddeg)", int(math.Round(degrees)))) };
}
templ Rotate(degrees float64) {
<div class={ windVaneRotation(degrees) }>Rotate</div>
}
```
### CSS Middleware
The use of CSS templates means that `<style>` elements containing the CSS are rendered on each HTTP request.
To save bandwidth, templ can provide a global stylesheet that includes the output of CSS templates instead of including `<style>` tags in each HTTP request.
To provide a global stylesheet, use templ's CSS middleware, and register templ classes on application startup.
The middleware adds a HTTP route to the web server (`/styles/templ.css` by default) that renders the `text/css` classes that would otherwise be added to `<style>` tags when components are rendered.
For example, to stop the `className` CSS class from being added to the output, the HTTP middleware can be used.
```go
c1 := className()
handler := NewCSSMiddleware(httpRoutes, c1)
http.ListenAndServe(":8000", handler)
```
:::caution
Don't forget to add a `<link rel="stylesheet" href="/styles/templ.css">` to your HTML to include the generated CSS class names!
:::

View File

@@ -0,0 +1,522 @@
# Using JavaScript with templ
## Script tags
Use standard `<script>` tags, and standard HTML attributes to run JavaScript on the client.
```templ
templ body() {
<script>
function handleClick(event) {
alert(event + ' clicked');
}
</script>
<button onclick="handleClick(this)">Click me</button>
}
```
:::tip
To ensure that a `<script>` tag within a templ component is only rendered once per HTTP response (or context), use a [templ.OnceHandle](18-render-once.md).
Using a `templ.OnceHandle` allows a component to define global client-side scripts that it needs to run without including the scripts multiple times in the response.
:::
## Pass Go data to JavaScript
### Pass Go data to a JavaScript event handler
Use `templ.JSFuncCall` to pass server-side data to client-side scripts by calling a JavaScript function.
```templ title="input.templ"
templ Component(data CustomType) {
<button onclick={ templ.JSFuncCall("alert", data.Message) }>Show alert</button>
}
```
The data passed to the `alert` function is JSON encoded, so if `data.Message` was the string value of `Hello, from the JSFuncCall data`, the output would be:
```html title="output.html"
<button onclick="alert('Hello, from the JSFuncCall data')">Show alert</button>
```
### Pass event objects to an Event Handler
HTML element `on*` attributes pass an event object to the function. To pass the event object to a function, use `templ.JSExpression`.
:::warning
`templ.JSExpression` bypasses JSON encoding, so the string value is output directly to the HTML - this can be a security risk if the data is not trusted, e.g. the data is user input, not a compile-time constant.
:::
```templ title="input.templ"
<script>
function clickHandler(event, message) {
alert(message);
event.preventDefault();
}
</script>
<button onclick={ templ.JSFuncCall("clickHandler", templ.JSExpression("event"), "message from Go") }>Show event</button>
```
The output would be:
```html title="output.html"
<script>
function clickHandler(event, message) {
alert(message);
event.preventDefault();
}
</script>
<button onclick="clickHandler(event, 'message from Go')">Show event</button>
```
### Call client side functions with server side data
Use `templ.JSFuncCall` to call a client-side function with server-side data.
`templ.JSFuncCall` takes a function name and a variadic list of arguments. The arguments are JSON encoded and passed to the function.
In the case that the function name is invalid (e.g. contains `</script>` or is a JavaScript expression, not a function name), the function name will be sanitized to `__templ_invalid_function_name`.
```templ title="components.templ"
templ InitializeClientSideScripts(data CustomType) {
@templ.JSFuncCall("functionToCall", data.Name, data.Age)
}
```
This will output a `<script>` tag that calls the `functionToCall` function with the `Name` and `Age` properties of the `data` object.
```html title="output.html"
<script>
functionToCall("John", 42);
</script>
```
:::tip
If you want to write out an arbitrary string containing JavaScript, and are sure it is safe, you can use `templ.JSUnsafeFuncCall` to bypass script sanitization.
Whatever string you pass to `templ.JSUnsafeFuncCall` will be output directly to the HTML, so be sure to validate the input.
:::
### Pass server-side data to the client in a HTML attribute
A common approach used by libraries like alpine.js is to pass data to the client in a HTML attribute.
To pass server-side data to the client in a HTML attribute, use `templ.JSONString` to encode the data as a JSON string.
```templ title="input.templ"
templ body(data any) {
<button id="alerter" alert-data={ templ.JSONString(data) }>Show alert</button>
}
```
```html title="output.html"
<button id="alerter" alert-data="{&quot;msg&quot;:&quot;Hello, from the attribute data&quot;}">Show alert</button>
```
The data in the attribute can then be accessed from client-side JavaScript.
```javascript
const button = document.getElementById('alerter');
const data = JSON.parse(button.getAttribute('alert-data'));
```
[alpine.js](https://alpinejs.dev/) uses `x-*` attributes to pass data to the client:
```templ
templ DataDisplay(data DataType) {
<div x-data={ templ.JSONString(data) }>
...
</div>
}
```
### Pass server-side data to the client in a script element
In addition to passing data in HTML attributes, you can also pass data to the client in a `<script>` element.
```templ title="input.templ"
templ body(data any) {
@templ.JSONScript("id", data)
}
```
```html title="output.html"
<script id="id" type="application/json">{"msg":"Hello, from the script data"}</script>
```
The data in the script tag can then be accessed from client-side JavaScript.
```javascript
const data = JSON.parse(document.getElementById('id').textContent);
```
## Avoiding inline event handlers
According to Mozilla, [inline event handlers are considered bad practice](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Events#inline_event_handlers_%E2%80%94_dont_use_these).
This example demonstrates how to add client-side behaviour to a component using a script tag.
The example uses a `templ.OnceHandle` to define global client-side scripts that are required, without rendering the scripts multiple times in the response.
```templ title="component.templ"
package main
import "net/http"
var helloHandle = templ.NewOnceHandle()
templ hello(label, name string) {
// This script is only rendered once per HTTP request.
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
<div>
<input type="button" value={ label } data-name={ name }/>
<script>
// To prevent the variables from leaking into the global scope,
// this script is wrapped in an IIFE (Immediately Invoked Function Expression).
(() => {
let scriptElement = document.currentScript;
let parent = scriptElement.closest('div');
let nearestButtonWithName = parent.querySelector('input[data-name]');
nearestButtonWithName.addEventListener('click', function() {
let name = nearestButtonWithName.getAttribute('data-name');
hello(name);
})
})()
</script>
</div>
}
templ page() {
@hello("Hello User", "user")
@hello("Hello World", "world")
}
func main() {
http.Handle("/", templ.Handler(page()))
http.ListenAndServe("127.0.0.1:8080", nil)
}
```
:::tip
You might find libraries like [surreal](https://github.com/gnat/surreal) useful for reducing boilerplate.
```templ
var helloHandle = templ.NewOnceHandle()
var surrealHandle = templ.NewOnceHandle()
templ hello(label, name string) {
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
@surrealHandle.Once() {
<script src="https://cdn.jsdelivr.net/gh/gnat/surreal@3b4572dd0938ce975225ee598a1e7381cb64ffd8/surreal.js"></script>
}
<div>
<input type="button" value={ label } data-name={ name }/>
<script>
// me("-") returns the previous sibling element.
me("-").addEventListener('click', function() {
let name = this.getAttribute('data-name');
hello(name);
})
</script>
</div>
}
```
:::
## Importing scripts
Use standard `<script>` tags to load JavaScript from a URL.
```templ
templ head() {
<head>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
}
```
And use the imported JavaScript directly in templ via `<script>` tags.
```templ
templ body() {
<script>
const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData([
{ time: '2019-04-11', value: 80.01 },
{ time: '2019-04-12', value: 96.63 },
{ time: '2019-04-13', value: 76.64 },
{ time: '2019-04-14', value: 81.89 },
{ time: '2019-04-15', value: 74.43 },
{ time: '2019-04-16', value: 80.01 },
{ time: '2019-04-17', value: 96.63 },
{ time: '2019-04-18', value: 76.64 },
{ time: '2019-04-19', value: 81.89 },
{ time: '2019-04-20', value: 74.43 },
]);
</script>
}
```
:::tip
You can use a CDN to serve 3rd party scripts, or serve your own and 3rd party scripts from your server using a `http.FileServer`.
```go
mux := http.NewServeMux()
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))
http.ListenAndServe("localhost:8080", mux)
```
:::
## Working with NPM projects
https://github.com/a-h/templ/tree/main/examples/typescript contains a TypeScript example that uses `esbuild` to transpile TypeScript into plain JavaScript, along with any required `npm` modules.
After transpilation and bundling, the output JavaScript code can be used in a web page by including a `<script>` tag.
### Creating a TypeScript project
Create a new TypeScript project with `npm`, and install TypeScript and `esbuild` as development dependencies.
```bash
mkdir ts
cd ts
npm init
npm install --save-dev typescript esbuild
```
Create a `src` directory to hold the TypeScript code.
```bash
mkdir src
```
And add a TypeScript file to the `src` directory.
```typescript title="ts/src/index.ts"
function hello() {
console.log('Hello, from TypeScript');
}
```
### Bundling TypeScript code
Add a script to build the TypeScript code in `index.ts` and copy it to an output directory (in this case `./assets/js/index.js`).
```json title="ts/package.json"
{
"name": "ts",
"version": "1.0.0",
"scripts": {
"build": "esbuild --bundle --minify --outfile=../assets/js/index.js ./src/index.ts"
},
"devDependencies": {
"esbuild": "0.21.3",
"typescript": "^5.4.5"
}
}
```
After running `npm build` in the `ts` directory, the TypeScript code is transpiled into JavaScript and copied to the output directory.
### Using the output JavaScript
The output file `../assets/js/index.js` can then be used in a templ project.
```templ title="components/head.templ"
templ head() {
<head>
<script src="/assets/js/index.js"></script>
</head>
}
```
You will need to configure your Go web server to serve the static content.
```go title="main.go"
func main() {
mux := http.NewServeMux()
// Serve the JS bundle.
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))
// Serve components.
data := map[string]any{"msg": "Hello, World!"}
h := templ.Handler(components.Page(data))
mux.Handle("/", h)
fmt.Println("Listening on http://localhost:8080")
http.ListenAndServe("localhost:8080", mux)
}
```
## Script templates
:::warning
Script templates are a legacy feature and are not recommended for new projects.
Use the `templ.JSFuncCall`, `templ.JSONString` and other features of templ alongside standard `<script>` tags to import standalone JavaScript files, optionally created by a bundler like `esbuild`.
:::
If you need to pass Go data to scripts, you can use a script template.
Here, the `page` HTML template includes a `script` element that loads a charting library, which is then used by the `body` element to render some data.
```templ
package main
script graph(data []TimeValue) {
const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData(data);
}
templ page(data []TimeValue) {
<html>
<head>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
<body onload={ graph(data) }></body>
</html>
}
```
The data is loaded by the backend into the template. This example uses a constant, but it could easily have collected the `[]TimeValue` from a database.
```go title="main.go"
package main
import (
"fmt"
"log"
"net/http"
)
type TimeValue struct {
Time string `json:"time"`
Value float64 `json:"value"`
}
func main() {
mux := http.NewServeMux()
// Handle template.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := []TimeValue{
{Time: "2019-04-11", Value: 80.01},
{Time: "2019-04-12", Value: 96.63},
{Time: "2019-04-13", Value: 76.64},
{Time: "2019-04-14", Value: 81.89},
{Time: "2019-04-15", Value: 74.43},
{Time: "2019-04-16", Value: 80.01},
{Time: "2019-04-17", Value: 96.63},
{Time: "2019-04-18", Value: 76.64},
{Time: "2019-04-19", Value: 81.89},
{Time: "2019-04-20", Value: 74.43},
}
page(data).Render(r.Context(), w)
})
// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}
```
`script` elements are templ Components, so you can also directly render the Javascript function, passing in Go data, using the `@` expression:
```templ
package main
import "fmt"
script printToConsole(content string) {
console.log(content)
}
templ page(content string) {
<html>
<body>
@printToConsole(content)
@printToConsole(fmt.Sprintf("Again: %s", content))
</body>
</html>
}
```
The data passed into the Javascript function will be JSON encoded, which then can be used inside the function.
```go title="main.go"
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
// Handle template.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Format the current time and pass it into our template
page(time.Now().String()).Render(r.Context(), w)
})
// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}
```
After building and running the executable, running `curl http://localhost:8080/` would render:
```html title="Output"
<html>
<body>
<script>function __templ_printToConsole_5a85(content){console.log(content)}</script>
<script>__templ_printToConsole_5a85("2023-11-11 01:01:40.983381358 +0000 UTC")</script>
<script>__templ_printToConsole_5a85("Again: 2023-11-11 01:01:40.983381358 +0000 UTC")</script>
</body>
</html>
```
The `JSExpression` type is used to pass arbitrary JavaScript expressions to a templ script template.
A common use case is to pass the `event` or `this` objects to an event handler.
```templ
package main
script showButtonWasClicked(event templ.JSExpression) {
const originalButtonText = event.target.innerText
event.target.innerText = "I was Clicked!"
setTimeout(() => event.target.innerText = originalButtonText, 2000)
}
templ page() {
<html>
<body>
<button type="button" onclick={ showButtonWasClicked(templ.JSExpression("event")) }>Click Me</button>
</body>
</html>
}
```

View File

@@ -0,0 +1,40 @@
# Comments
# HTML comments
Inside templ statements, use HTML comments.
```templ title="template.templ"
templ template() {
<!-- Single line -->
<!--
Single or multiline.
-->
}
```
Comments are rendered to the template output.
```html title="Output"
<!-- Single line -->
<!--
Single or multiline.
-->
```
As per HTML, nested comments are not supported.
# Go comments
Outside of templ statements, use Go comments.
```templ
package main
// Use standard Go comments outside templ statements.
var greeting = "Hello!"
templ hello(name string) {
<p>{greeting} { name }</p>
}
```

View File

@@ -0,0 +1,181 @@
# Context
## What problems does `context` solve?
### "Prop drilling"
It can be cumbersome to pass data from parents through to children components, since this means that every component in the hierarchy has to accept parameters and pass them through to children.
The technique of passing data through a stack of components is sometimes called "prop drilling".
In this example, the `middle` component doesn't use the `name` parameter, but must accept it as a parameter in order to pass it to the `bottom` component.
```templ title="component.templ"
package main
templ top(name string) {
<div>
@middle(name)
</div>
}
templ middle(name string) {
<ul>
@bottom(name)
</ul>
}
templ bottom(name string) {
<li>{ name }</li>
}
```
:::tip
In many cases, prop drilling is the best way to pass data because it's simple and reliable.
Context is not strongly typed, and errors only show at runtime, not compile time, so it should be used sparingly in your application.
:::
### Coupling
Some data is useful for many components throughout the hierarchy, for example:
* Whether the current user is logged in or not.
* The username of the current user.
* The locale of the user (used for localization).
* Theme preferences (e.g. light vs dark).
One way to pass this information is to create a `Settings` struct and pass it through the stack as a parameter.
```templ title="component.templ"
package main
type Settings struct {
Username string
Locale string
Theme string
}
templ top(settings Settings) {
<div>
@middle(settings)
</div>
}
templ middle(settings Settings) {
<ul>
@bottom(settings)
</ul>
}
templ bottom(settings Settings) {
<li>{ settings.Theme }</li>
}
```
However, this `Settings` struct may be unique to a single website, and reduce the ability to reuse a component in another website, due to its tight coupling with the `Settings` struct.
## Using `context`
:::info
templ components have an implicit `ctx` variable within the scope. This `ctx` variable is the variable that is passed to the `templ.Component`'s `Render` method.
:::
To allow data to be accessible at any level in the hierarchy, we can use Go's built in `context` package.
Within templ components, use the implicit `ctx` variable to access the context.
```templ title="component.templ"
templ themeName() {
<div>{ ctx.Value(themeContextKey).(string) }</div>
}
```
To allow the template to get the `themeContextKey` from the context, create a context, and pass it to the component's `Render` function.
```templ title="main.go"
// Define the context key type.
type contextKey string
// Create a context key for the theme.
var themeContextKey contextKey = "theme"
// Create a context variable that inherits from a parent, and sets the value "test".
ctx := context.WithValue(context.Background(), themeContextKey, "test")
// Pass the ctx variable to the render function.
themeName().Render(ctx, w)
```
:::warning
Attempting to access a context key that doesn't exist, or using an invalid type assertion will trigger a panic.
:::
### Tidying up
Rather than read from the context object directly, it's common to implement a type-safe function instead.
This is also required when the type of the context key is in a different package to the consumer of the context, and the type is private (which is usually the case).
```templ title="main.go"
func GetTheme(ctx context.Context) string {
if theme, ok := ctx.Value(themeContextKey).(string); ok {
return theme
}
return ""
}
```
This minor change makes the template code a little tidier.
```templ title="component.templ"
templ themeName() {
<div>{ GetTheme(ctx) }</div>
}
```
:::note
As of v0.2.731, Go's built in `context` package is no longer implicitly imported into .templ files.
:::
## Using `context` with HTTP middleware
In HTTP applications, a common pattern is to insert HTTP middleware into the request/response chain.
Middleware can be used to update the context that is passed to other components. Common use cases for middleware include authentication, and theming.
By inserting HTTP middleware, you can set values in the context that can be read by any templ component in the stack for the duration of that HTTP request.
```templ title="component.templ"
type contextKey string
var contextClass = contextKey("class")
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request ) {
ctx := context.WithValue(r.Context(), contextClass, "red")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
templ Page() {
@Show()
}
templ Show() {
<div class={ ctx.Value(contextClass) }>Display</div>
}
func main() {
h := templ.Handler(Page())
withMiddleware := Middleware(h)
http.Handle("/", withMiddleware)
http.ListenAndServe(":8080", h)
}
```
:::warning
If you write a component that relies on a context variable that doesn't exist, or is an unexpected type, your component will panic at runtime.
This means that if your component relies on HTTP middleware that sets the context, and you forget to add it, your component will panic at runtime.
:::

View File

@@ -0,0 +1,89 @@
# Using with `html/template`
Templ components can be used with the Go standard library [`html/template`](https://pkg.go.dev/html/template) package.
## Using `html/template` in a templ component
To use an existing `html/template` in a templ component, use the `templ.FromGoHTML` function.
```templ title="component.templ"
package testgotemplates
import "html/template"
var goTemplate = template.Must(template.New("example").Parse("<div>{{ . }}</div>"))
templ Example() {
<!DOCTYPE html>
<html>
<body>
@templ.FromGoHTML(goTemplate, "Hello, World!")
</body>
</html>
}
```
```go title="main.go"
func main() {
Example.Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<!DOCTYPE html>
<html>
<body>
<div>Hello, World!</div>
</body>
</html>
```
## Using a templ component with `html/template`
To use a templ component within a `html/template`, use the `templ.ToGoHTML` function to render the component into a `template.HTML value`.
```templ title="component.html"
package testgotemplates
import "html/template"
var example = template.Must(template.New("example").Parse(`<!DOCTYPE html>
<html>
<body>
{{ . }}
</body>
</html>
`))
templ greeting() {
<div>Hello, World!</div>
}
```
```go title="main.go"
func main() {
// Create the templ component.
templComponent := greeting()
// Render the templ component to a `template.HTML` value.
html, err := templ.ToGoHTML(context.Background(), templComponent)
if err != nil {
t.Fatalf("failed to convert to html: %v", err)
}
// Use the `template.HTML` value within the text/html template.
err = example.Execute(os.Stdout, html)
if err != nil {
t.Fatalf("failed to execute template: %v", err)
}
}
```
```html title="Output"
<!DOCTYPE html>
<html>
<body>
<div>Hello, World!</div>
</body>
</html>
```

View File

@@ -0,0 +1,31 @@
# Rendering raw HTML
To render HTML that has come from a trusted source, bypassing all HTML escaping and security mechanisms that templ includes, use the `templ.Raw` function.
:::info
Only include HTML that comes from a trusted source.
:::
:::warning
Use of this function may introduce security vulnerabilities to your program.
:::
```templ title="component.templ"
templ Example() {
<!DOCTYPE html>
<html>
<body>
@templ.Raw("<div>Hello, World!</div>")
</body>
</html>
}
```
```html title="Output"
<!DOCTYPE html>
<html>
<body>
<div>Hello, World!</div>
</body>
</html>
```

View File

@@ -0,0 +1,280 @@
# Using React with templ
templ is great for server-side rendering. Combined with [HTMX](https://htmx.org/), it's even more powerful, since HTMX can be used to replace elements within the page with updated HTML fetched from the server, providing many of the benefits of React with reduced overall complexity. See [/server-side-rendering/htmx](/server-side-rendering/htmx) for an example.
However, React has a huge ecosystem of rich interactive components, so being able to tap into the ecosystem is very useful.
With templ, it's more likely that you will use React components as [islands of interactivity](https://www.patterns.dev/vanilla/islands-architecture/) rather than taking over all aspects of displaying your app, with templ taking over server-side rendering, but using React to provide specific features on the client side.
## Using React components
First, lets start by rendering simple React components.
### Create React components
To use React components in your templ app, create your React components using TSX (TypeScript) or JSX as usual.
```tsx title="react/components.tsx"
export const Header = () => (<h1>React component Header</h1>);
export const Body = () => (<div>This is client-side content from React</div>);
```
### Create a templ page
Next, use templ to create a page containing HTML elements with specific IDs.
:::note
This page defines elements with ids of `react-header` and `react-content`.
A `<script>` element loads in a JavaScript bundle that we haven't created yet.
:::
```templ title="components.templ"
package main
templ page() {
<html>
<body>
<div id="react-header"></div>
<div id="react-content"></div>
<div>This is server-side content from templ.</div>
<!-- Load the React bundle created using esbuild -->
<script src="static/index.js"></script>
</body>
</html>
}
```
:::tip
Remember to run `templ generate` when you've finished writing your templ file.
:::
### Render React components into the IDs
Write TypeScript or JavaScript to render the React components into the HTML elements that are rendered by templ.
```typescript title="react/index.ts"
import { createRoot } from 'react-dom/client';
import { Header, Body } from './components';
// Render the React component into the templ page at the react-header.
const headerRoot = document.getElementById('react-header');
if (!headerRoot) {
throw new Error('Could not find element with id react-header');
}
const headerReactRoot = createRoot(headerRoot);
headerReactRoot.render(Header());
// Add the body React component.
const contentRoot = document.getElementById('react-content');
if (!contentRoot) {
throw new Error('Could not find element with id react-content');
}
const contentReactRoot = createRoot(contentRoot);
contentReactRoot.render(Body());
```
### Create a client-side bundle
To turn the JSX, TSX, TypeScript and JavaScript code into a bundle that can run in the browser, use a bundling tool.
https://esbuild.github.io/ is commonly used for this task. It's fast, it's easy to use, and it's written in Go.
Executing `esbuild` with the following arguments creates an `index.js` file in the static directory.
```bash
esbuild --bundle index.ts --outdir=../static --minify
```
### Serve the templ component and client side bundle
To serve the server-side rendered templ template, and the client-side JavaScript bundle created in the previous step, setup a Go web server.
```go title="main.go"
package main
import (
"fmt"
"log"
"net/http"
"github.com/a-h/templ"
)
func main() {
mux := http.NewServeMux()
// Serve the templ page.
mux.Handle("/", templ.Handler(page()))
// Serve static content.
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// Start the server.
fmt.Println("listening on localhost:8080")
if err := http.ListenAndServe("localhost:8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}
```
### Results
Putting this together results in a web page that renders server-side HTML using templ. The server-side HTML includes a link to the static React bundle.
```mermaid
sequenceDiagram
browser->>app: GET /
activate app
app->>templ_component: Render
activate templ_component
templ_component->>app: HTML
deactivate templ_component
app->>browser: HTML
deactivate app
browser->>app: GET /static/index.js
activate app
app->>browser: JS bundle containing React components
deactivate app
browser->>browser: render components into react-header and react-content
```
## Passing server-side data to React components
Moving on from the previous example, it's possible to pass data to client-side React components.
### Add a React component that accepts data arguments
First, add a new component.
```tsx title="react/components.tsx"
export const Hello = (name: string) => (
<div>Hello {name} (Client-side React, rendering server-side data)</div>
);
```
### Export a JavaScript function that renders the React component to a HTML element
```typescript title="react/index.ts"
// Update the import to add the new Hello React component.
import { Header, Body, Hello } from './components';
// Previous script contents...
export function renderHello(e: HTMLElement) {
const name = e.getAttribute('data-name') ?? "";
createRoot(e).render(Hello(name));
}
```
### Update the templ component to use the new function
Now that we have a `renderHello` function that will render the React component to the given element, we can update the templ components to use it.
In templ, we can add a `Hello` component that does two things:
1. Renders an element for the React component to be loaded into that sets the `data-name` attribute to the value of the server-side `name` field.
2. Writes out JS that calls the `renderHello` function to mount the React component into the element.
:::note
The template renders three copies of the `Hello` React component, passing in a distinct `name` parameter ("Alice", "Bob" and "Charlie").
:::
```templ title="components.templ"
package main
import "fmt"
templ Hello(name string) {
<div data-name={ name }>
<script>
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
}
templ page() {
<html>
<head>
<title>React integration</title>
</head>
<body>
<div id="react-header"></div>
<div id="react-content"></div>
<div>
This is server-side content from templ.
</div>
<!-- Load the React bundle that was created using esbuild -->
<!-- Since the bundle was coded to expect the react-header and react-content elements to exist already, in this case, the script has to be loaded after the elements are on the page -->
<script src="static/index.js"></script>
<!-- Now that the React bundle is loaded, we can use the functions that are in it -->
<!-- the renderName function in the bundle can be used, but we want to pass it some server-side data -->
for _, name := range []string{"Alice", "Bob", "Charlie"} {
@Hello(name)
}
</body>
</html>
}
```
### Update the `esbuild` command
The `bundle` namespace in JavaScript is created by adding a `--global-name` argument to `esbuild`. The argument causes any exported functions in `index.ts` to be added to that namespace.
```bash
esbuild --bundle index.ts --outdir=../static --minify --global-name=bundle
```
### Results
The HTML that's rendered is:
```html
<html>
<head>
<title>React integration</title>
</head>
<body>
<div id="react-header"></div>
<div id="react-content"></div>
<div>This is server-side content from templ.</div>
<script src="static/index.js"></script>
<div data-name="Alice">
<script>
// Place the React component into the parent div.
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
<div data-name="Bob">
<script>
// Place the React component into the parent div.
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
<div data-name="Charlie">
<script>
// Place the React component into the parent div.
bundle.renderHello(document.currentScript.closest('div'));
</script>
</div>
</body>
</html>
```
And the browser shows the expected content after rendering the client side React components.
```
React component Header
This is client-side content from React
This is server-side content from templ.
Hello Alice (Client-side React, rendering server-side data)
Hello Bob (Client-side React, rendering server-side data)
Hello Charlie (Client-side React, rendering server-side data)
```
## Example code
See https://github.com/a-h/templ/tree/main/examples/integration-react for a complete example.

View File

@@ -0,0 +1,102 @@
# Render once
If you need to render something to the page once per page, you can create a `*OnceHandler` with `templ.NewOnceHandler()` and use its `Once()` method.
The `*OnceHandler.Once()` method ensures that the content is only rendered once per distinct context passed to the component's `Render` method, even if the component is rendered multiple times.
## Example
The `hello` JavaScript function is only rendered once, even though the `hello` component is rendered twice.
:::warning
Dont write `@templ.NewOnceHandle().Once()` - this creates a new `*OnceHandler` each time the `Once` method is called, and will result in the content being rendered multiple times.
:::
```templ title="component.templ"
package once
var helloHandle = templ.NewOnceHandle()
templ hello(label, name string) {
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
<input type="button" value={ label } data-name={ name } onclick="hello(this.getAttribute('data-name'))"/>
}
templ page() {
@hello("Hello User", "user")
@hello("Hello World", "world")
}
```
```html title="Output"
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
<input type="button" value="Hello User" data-name="user" onclick="hello(this.getAttribute('data-name'))">
<input type="button" value="Hello World" data-name="world" onclick="hello(this.getAttribute('data-name'))">
```
:::tip
Note the use of the `data-name` attribute to pass the `name` value from server-side Go code to the client-side JavaScript code.
The value of `name` is collected by the `onclick` handler, and passed to the `hello` function.
To pass complex data structures, consider using a `data-` attribute to pass a JSON string using the `templ.JSONString` function, or use the `templ.JSONScript` function to create a templ component that creates a `<script>` element containing JSON data.
:::
## Common use cases
- Rendering a `<style>` tag that contains CSS classes required by a component.
- Rendering a `<script>` tag that contains JavaScript required by a component.
- Rendering a `<link>` tag that contains a reference to a stylesheet.
## Usage across packages
Export a component that contains the `*OnceHandler` and the content to be rendered once.
For example, create a `deps` package that contains a `JQuery` component that renders a `<script>` tag that references the jQuery library.
```templ title="deps/deps.templ"
package deps
var jqueryHandle = templ.NewOnceHandle()
templ JQuery() {
@jqueryHandle.Once() {
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
}
}
```
You can then use the `JQuery` component in other packages, and the jQuery library will only be included once in the rendered HTML.
```templ title="main.templ"
package main
import "deps"
templ page() {
<html>
<head>
@deps.JQuery()
</head>
<body>
<h1>Hello, World!</h1>
@button()
</body>
</html>
}
templ button() {
@deps.JQuery()
<button>Click me</button>
}
```

View File

@@ -0,0 +1,4 @@
{
"position": 3,
"label": "Syntax and usage"
}

View File

@@ -0,0 +1,132 @@
# Components
templ Components are markup and code that is compiled into functions that return a `templ.Component` interface by running the `templ generate` command.
Components can contain templ elements that render HTML, text, expressions that output text or include other templates, and branching statements such as `if` and `switch`, and `for` loops.
```templ title="header.templ"
package main
templ headerTemplate(name string) {
<header data-testid="headerTemplate">
<h1>{ name }</h1>
</header>
}
```
The generated code is a Go function that returns a `templ.Component`.
```go title="header_templ.go"
func headerTemplate(name string) templ.Component {
// Generated contents
}
```
`templ.Component` is an interface that has a `Render` method on it that is used to render the component to an `io.Writer`.
```go
type Component interface {
Render(ctx context.Context, w io.Writer) error
}
```
:::tip
Since templ produces Go code, you can share templates the same way that you share Go code - by sharing your Go module.
templ follows the same rules as Go. If a `templ` block starts with an uppercase letter, then it is public, otherwise, it is private.
A `templ.Component` may write partial output to the `io.Writer` if it returns an error. If you want to ensure you only get complete output or nothing, write to a buffer first and then write the buffer to an `io.Writer`.
:::
## Code-only components
Since templ Components ultimately implement the `templ.Component` interface, any code that implements the interface can be used in place of a templ component generated from a `*.templ` file.
```go
package main
import (
"context"
"io"
"os"
"github.com/a-h/templ"
)
func button(text string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, "<button>"+text+"</button>")
return err
})
}
func main() {
button("Click me").Render(context.Background(), os.Stdout)
}
```
```html title="Output"
<button>
Click me
</button>
```
:::warning
This code is unsafe! In code-only components, you're responsible for escaping the HTML content yourself, e.g. with the `templ.EscapeString` function.
:::
## Method components
templ components can be returned from methods (functions attached to types).
Go code:
```templ
package main
import "os"
type Data struct {
message string
}
templ (d Data) Method() {
<div>{ d.message }</div>
}
func main() {
d := Data{
message: "You can implement methods on a type.",
}
d.Method().Render(context.Background(), os.Stdout)
}
```
It is also possible to initialize a struct and call its component method inline.
```templ
package main
import "os"
type Data struct {
message string
}
templ (d Data) Method() {
<div>{ d.message }</div>
}
templ Message() {
<div>
@Data{
message: "You can implement methods on a type.",
}.Method()
</div>
}
func main() {
Message().Render(context.Background(), os.Stdout)
}
```

View File

@@ -0,0 +1,95 @@
# Template generation
To generate Go code from `*.templ` files, use the `templ` command line tool.
```
templ generate
```
The `templ generate` recurses into subdirectories and generates Go code for each `*.templ` file it finds.
The command outputs warnings, and a summary of updates.
```
(!) void element <input> should not have child content [ from=12:2 to=12:7 ]
(✓) Complete [ updates=62 duration=144.677334ms ]
```
## Advanced options
The `templ generate` command has a `--help` option that prints advanced options.
These include the ability to generate code for a single file and to choose the number of parallel workers that `templ generate` uses to create Go files.
By default `templ generate` uses the number of CPUs that your machine has installed.
```
templ generate --help
```
```
usage: templ generate [<args>...]
Generates Go code from templ files.
Args:
-path <path>
Generates code for all files in path. (default .)
-f <file>
Optionally generates code for a single file, e.g. -f header.templ
-stdout
Prints to stdout instead of writing generated files to the filesystem.
Only applicable when -f is used.
-source-map-visualisations
Set to true to generate HTML files to visualise the templ code and its corresponding Go code.
-include-version
Set to false to skip inclusion of the templ version in the generated code. (default true)
-include-timestamp
Set to true to include the current time in the generated code.
-watch
Set to true to watch the path for changes and regenerate code.
-cmd <cmd>
Set the command to run after generating code.
-proxy
Set the URL to proxy after generating code and executing the command.
-proxyport
The port the proxy will listen on. (default 7331)
-proxybind
The address the proxy will listen on. (default 127.0.0.1)
-notify-proxy
If present, the command will issue a reload event to the proxy 127.0.0.1:7331, or use proxyport and proxybind to specify a different address.
-w
Number of workers to use when generating code. (default runtime.NumCPUs)
-lazy
Only generate .go files if the source .templ file is newer.
-pprof
Port to run the pprof server on.
-keep-orphaned-files
Keeps orphaned generated templ files. (default false)
-v
Set log verbosity level to "debug". (default "info")
-log-level
Set log verbosity level. (default "info", options: "debug", "info", "warn", "error")
-help
Print help and exit.
Examples:
Generate code for all files in the current directory and subdirectories:
templ generate
Generate code for a single file:
templ generate -f header.templ
Watch the current directory and subdirectories for changes and regenerate code:
templ generate -watch
```
:::tip
The `templ generate --watch` option watches files for changes and runs templ generate when required.
However, the code generated in this mode is not optimised for production use.
:::

View File

@@ -0,0 +1,224 @@
# Testing
To test that data is rendered as expected, there are two main ways to do it:
* Expectation testing - testing that specific expectations are met by the output.
* Snapshot testing - testing that outputs match a pre-written output.
## Expectation testing
Expectation testing validates that the right data appears in the output in the right format and position.
The example at https://github.com/a-h/templ/blob/main/examples/blog/posts_test.go shows how to test that a list of posts is rendered correctly.
These tests use the `goquery` library to parse HTML and check that expected elements are present. `goquery` is a jQuery-like library for Go, that is useful for parsing and querying HTML. Youll need to run `go get github.com/PuerkitoBio/goquery` to add it to your `go.mod` file.
### Testing components
The test sets up a pipe to write templ's HTML output to, and reads the output from the pipe, parsing it with `goquery`.
First, we test the page header. To use `goquery` to inspect the output, well need to connect the header components `Render` method to the `goquery.NewDocumentFromReader` function with an `io.Pipe`.
```go
func TestHeader(t *testing.T) {
// Pipe the rendered template into goquery.
r, w := io.Pipe()
go func () {
_ = headerTemplate("Posts").Render(context.Background(), w)
_ = w.Close()
}()
doc, err := goquery.NewDocumentFromReader(r)
if err != nil {
t.Fatalf("failed to read template: %v", err)
}
// Expect the component to be present.
if doc.Find(`[data-testid="headerTemplate"]`).Length() == 0 {
t.Error("expected data-testid attribute to be rendered, but it wasn't")
}
// Expect the page name to be set correctly.
expectedPageName := "Posts"
if actualPageName := doc.Find("h1").Text(); actualPageName != expectedPageName {
t.Errorf("expected page name %q, got %q", expectedPageName, actualPageName)
}
}
```
The header template (the "subject under test") includes a placeholder for the page name, and a `data-testid` attribute that makes it easier to locate the `headerTemplate` within the HTML using a CSS selector of `[data-testid="headerTemplate"]`.
```go
templ headerTemplate(name string) {
<header data-testid="headerTemplate">
<h1>{ name }</h1>
</header>
}
```
We can also test that the navigation bar was rendered.
```go
func TestNav(t *testing.T) {
r, w := io.Pipe()
go func() {
_ = navTemplate().Render(context.Background(), w)
_ = w.Close()
}()
doc, err := goquery.NewDocumentFromReader(r)
if err != nil {
t.Fatalf("failed to read template: %v", err)
}
// Expect the component to include a testid.
if doc.Find(`[data-testid="navTemplate"]`).Length() == 0 {
t.Error("expected data-testid attribute to be rendered, but it wasn't")
}
}
```
Testing that it was rendered is useful, but it's even better to test that the navigation includes the correct `nav` items.
In this test, we find all of the `a` elements within the `nav` element, and check that they match the expected items.
```go
navItems := []string{"Home", "Posts"}
doc.Find("nav a").Each(func(i int, s *goquery.Selection) {
expected := navItems[i]
if actual := s.Text(); actual != expected {
t.Errorf("expected nav item %q, got %q", expected, actual)
}
})
```
To test the posts, we can use the same approach. We test that the posts are rendered correctly, and that the expected data is present.
### Testing whole pages
Next, we may want to go a level higher and test the entire page.
Pages are also templ components, so the tests are structured in the same way.
Theres no need to test for the specifics about what gets rendered in the `navTemplate` or `homeTemplate` at the page level, because theyre already covered in other tests.
Some developers prefer to only test the external facing part of their code (e.g. at a page level), rather than testing each individual component, on the basis that its slower to make changes if the implementation is too tightly controlled.
For example, if a component is reused across pages, then it makes sense to test that in detail in its own test. In the pages or higher-order components that use it, theres no point testing it again at that level, so we only check that it was rendered to the output by looking for its data-testid attribute, unless we also need to check what we're passing to it.
### Testing the HTTP handler
Finally, we want to test the posts HTTP handler. This requires a different approach.
We can use the `httptest` package to create a test server, and use the `net/http` package to make a request to the server and check the response.
The tests configure the `GetPosts` function on the `PostsHandler` with a mock that returns a "database error", while the other returns a list of two posts. Here's what the `PostsHandler` looks like:
```go
type PostsHandler struct {
Log *log.Logger
GetPosts func() ([]Post, error)
}
```
In the error case, the test asserts that the error message was displayed, while in the success case, it checks that the `postsTemplate` is present. It does not check that the posts have actually been rendered properly or that specific fields are visible, because thats already tested at the component level.
Testing it again here would make the code resistant to refactoring and rework, but then again, we might have missed actually passing the posts we got back from the database to the posts template, so its a matter of risk appetite vs refactor resistance.
Note the switch to the table-driven testing format, a popular approach in Go for testing multiple scenarios with the same test code.
```go
func TestPostsHandler(t *testing.T) {
tests := []struct {
name string
postGetter func() (posts []Post, err error)
expectedStatus int
assert func(doc *goquery.Document)
}{
{
name: "database errors result in a 500 error",
postGetter: func() (posts []Post, err error) {
return nil, errors.New("database error")
},
expectedStatus: http.StatusInternalServerError,
assert: func(doc *goquery.Document) {
expected := "failed to retrieve posts\n"
if actual := doc.Text(); actual != expected {
t.Errorf("expected error message %q, got %q", expected, actual)
}
},
},
{
name: "database success renders the posts",
postGetter: func() (posts []Post, err error) {
return []Post{
{Name: "Name1", Author: "Author1"},
{Name: "Name2", Author: "Author2"},
}, nil
},
expectedStatus: http.StatusInternalServerError,
assert: func(doc *goquery.Document) {
if doc.Find(`[data-testid="postsTemplate"]`).Length() == 0 {
t.Error("expected posts to be rendered, but it wasn't")
}
},
},
}
for _, test := range tests {
// Arrange.
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/posts", nil)
ph := NewPostsHandler()
ph.Log = log.New(io.Discard, "", 0) // Suppress logging.
ph.GetPosts = test.postGetter
// Act.
ph.ServeHTTP(w, r)
doc, err := goquery.NewDocumentFromReader(w.Result().Body)
if err != nil {
t.Fatalf("failed to read template: %v", err)
}
// Assert.
test.assert(doc)
}
}
```
### Summary
- goquery can be used effectively with templ for writing component level tests.
- Adding `data-testid` attributes to your code simplifies the test expressions you need to write to find elements within the output and makes your tests less brittle.
- Testing can be split between the two concerns of template rendering, and HTTP handlers.
## Snapshot testing
Snapshot testing is a more broad check. It simply checks that the output hasn't changed since the last time you took a copy of the output.
It relies on manually checking the output to make sure it's correct, and then "locking it in" by using the snapshot.
templ uses this strategy to check for regressions in behaviour between releases, as per https://github.com/a-h/templ/blob/main/generator/test-html-comment/render_test.go
To make it easier to compare the output against the expected HTML, templ uses a HTML formatting library before executing the diff.
```go
package testcomment
import (
_ "embed"
"testing"
"github.com/a-h/templ/generator/htmldiff"
)
//go:embed expected.html
var expected string
func Test(t *testing.T) {
component := render("sample content")
diff, err := htmldiff.Diff(component, expected)
if err != nil {
t.Fatal(err)
}
if diff != "" {
t.Error(diff)
}
}
```

View File

@@ -0,0 +1,49 @@
# View models
With templ, you can pass any Go type into your template as parameters, and you can call arbitrary functions.
However, if the parameters of your template don't closely map to what you're displaying to users, you may find yourself calling a lot of functions within your templ files to reshape or adjust data, or to carry out complex repeated string interpolation or URL constructions.
This can make template rendering hard to test, because you need to set up complex data structures in the right way in order to render the HTML. If the template calls APIs or accesses databases from within the templates, it's even harder to test, because then testing your templates becomes an integration test.
A more reliable approach can be to create a "View model" that only contains the fields that you intend to display, and where the data structure closely matches the structure of the visual layout.
```go
package invitesget
type Handler struct {
Invites *InviteService
}
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
invites, err := h.Invites.Get(getUserIDFromContext(r.Context()))
if err != nil {
//TODO: Log error server side.
}
m := NewInviteComponentViewModel(invites, err)
teamInviteComponent(m).Render(r.Context(), w)
}
func NewInviteComponentViewModel(invites []models.Invite, err error) (m InviteComponentViewModel) {
m.InviteCount = len(invites)
if err != nil {
m.ErrorMessage = "Failed to load invites, please try again"
}
return m
}
type InviteComponentViewModel struct {
InviteCount int
ErrorMessage string
}
templ teamInviteComponent(model InviteComponentViewModel) {
if model.InviteCount > 0 {
<div>You have { fmt.Sprintf("%d", model.InviteCount) } pending invites</div>
}
if model.ErrorMessage != "" {
<div class="error">{ model.ErrorMessage }</div>
}
}
```

View File

@@ -0,0 +1,4 @@
{
"position": 4,
"label": "Core concepts"
}

View File

@@ -0,0 +1,136 @@
# Creating an HTTP server with templ
### Static pages
To use a templ component as a HTTP handler, the `templ.Handler` function can be used.
This is suitable for use when the component is not used to display dynamic data.
```go title="components.templ"
package main
templ hello() {
<div>Hello</div>
}
```
```go title="main.go"
package main
import (
"net/http"
"github.com/a-h/templ"
)
func main() {
http.Handle("/", templ.Handler(hello()))
http.ListenAndServe(":8080", nil)
}
```
### Displaying fixed data
In the previous example, the `hello` component does not take any parameters. Let's display the time when the server was started instead.
```go title="components.templ"
package main
import "time"
templ timeComponent(d time.Time) {
<div>{ d.String() }</div>
}
templ notFoundComponent() {
<div>404 - Not found</div>
}
```
```go title="main.go"
package main
import (
"net/http"
"time"
"github.com/a-h/templ"
)
func main() {
http.Handle("/", templ.Handler(timeComponent(time.Now())))
http.Handle("/404", templ.Handler(notFoundComponent(), templ.WithStatus(http.StatusNotFound)))
http.ListenAndServe(":8080", nil)
}
```
:::tip
The `templ.WithStatus`, `templ.WithContentType`, and `templ.WithErrorHandler` functions can be passed as parameters to the `templ.Handler` function to control how content is rendered.
:::
The output will always be the date and time that the web server was started up, not the current time.
```
2023-04-26 08:40:03.421358 +0100 BST m=+0.000779501
```
To display the current time, we could update the component to use the `time.Now()` function itself, but this would limit the reusability of the component. It's better when components take parameters for their display values.
:::tip
Good templ components are idempotent, pure functions - they don't rely on data that is not passed in through parameters. As long as the parameters are the same, they always return the same HTML - they don't rely on any network calls or disk access.
:::
## Displaying dynamic data
Let's update the previous example to display dynamic content.
templ components implement the `templ.Component` interface, which provides a `Render` method.
The `Render` method can be used within HTTP handlers to write HTML to the `http.ResponseWriter`.
```go title="main.go"
package main
import (
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
hello().Render(r.Context(), w)
})
http.ListenAndServe(":8080", nil)
}
```
Building on that example, we can implement the Go HTTP handler interface and use the component within our HTTP handler. In this case, displaying the latest date and time, instead of the date and time when the server started up.
```go title="main.go"
package main
import (
"net/http"
"time"
)
func NewNowHandler(now func() time.Time) NowHandler {
return NowHandler{Now: now}
}
type NowHandler struct {
Now func() time.Time
}
func (nh NowHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
timeComponent(nh.Now()).Render(r.Context(), w)
}
func main() {
http.Handle("/", NewNowHandler(time.Now))
http.ListenAndServe(":8080", nil)
}
```

View File

@@ -0,0 +1,206 @@
# Example: Counter application
Web applications typically need to store application state, some of which is per-user, and some of which is global.
Applications also need to handle input from users, update the state, and display updated HTML.
```mermaid
flowchart TD
b[Browser] --HTTP POST request--> ws[Web Server];
ws --Update state--> ws;
ws --HTTP POST response--> b;
```
## Updating global state
First, define a HTML form post with two buttons. One to update a global state, and one for a per-user state.
```templ title="components.templ"
package main
import "strconv"
templ counts(global, user int) {
<div>Global: { strconv.Itoa(global) }</div>
<div>User: { strconv.Itoa(user) }</div>
}
templ form() {
<form action="/" method="POST">
<div><button type="submit" name="global" value="global">Global</button></div>
<div><button type="submit" name="user" value="user">User</button></div>
</form>
}
templ page(global, user int) {
@counts(global, user)
@form()
}
```
:::tip
While we could read the global state directly, we're following the best practice that templ components are idempotent, pure functions.
:::
The HTTP form in the templates posts data back to the `/` handler.
The `/` handler looks at the HTTP request. If it's a GET request, the templ templates are rendered by the `getHandler`.
If it's a POST request, then the `postHandler` is used. This parses the data sent over HTTP, and looks to see if the `global` button was the button that submitted the form, and increments the global count value if it was.
```go title="main.go"
package main
import (
"fmt"
"log"
"net/http"
)
type GlobalState struct {
Count int
}
var global GlobalState
func getHandler(w http.ResponseWriter, r *http.Request) {
component := page(global.Count, 0)
component.Render(r.Context(), w)
}
func postHandler(w http.ResponseWriter, r *http.Request) {
// Update state.
r.ParseForm()
// Check to see if the global button was pressed.
if r.Form.Has("global") {
global.Count++
}
//TODO: Update session.
// Display the form.
getHandler(w, r)
}
func main() {
// Handle POST and GET requests.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
postHandler(w, r)
return
}
getHandler(w, r)
})
// Start the server.
fmt.Println("listening on http://localhost:8000")
if err := http.ListenAndServe("localhost:8000", nil); err != nil {
log.Printf("error listening: %v", err)
}
}
```
:::note
In this example, the global state is stored in RAM, and will be lost when the web server reboots. To support load-balanced web servers, and stateless function deployments, you might consider storing the state in a data store such as Redis, DynamoDB, or Cloud Firestore.
:::
## Adding per-user session state
In a HTTP application, per-user state information is partitioned by a HTTP cookie. Setting a cookie with a unique random value for each user (typically a V4 UUID or similar) allows the HTTP handlers to identify each user by reading the cookie value presented by the user's browser.
Cookies that identify a user while they're using a site are known as "session cookies". When the HTTP handler receives a request, it can read the session ID of the user from the cookie and retrieve any required state.
You can implement session cookies yourself, or use an existing library.
:::tip
Cookies are often used for authentication as well as for sessions.
:::
This example uses the https://github.com/alexedwards/scs library to implement per-user sessions.
```go title="main.go"
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/alexedwards/scs/v2"
)
type GlobalState struct {
Count int
}
var global GlobalState
// highlight-next-line
var sessionManager *scs.SessionManager
func getHandler(w http.ResponseWriter, r *http.Request) {
// highlight-next-line
userCount := sessionManager.GetInt(r.Context(), "count")
component := page(global.Count, userCount)
component.Render(r.Context(), w)
}
func postHandler(w http.ResponseWriter, r *http.Request) {
// Update state.
r.ParseForm()
// Check to see if the global button was pressed.
if r.Form.Has("global") {
global.Count++
}
// highlight-start
if r.Form.Has("user") {
currentCount := sessionManager.GetInt(r.Context(), "count")
sessionManager.Put(r.Context(), "count", currentCount+1)
}
// highlight-end
// Display the form.
getHandler(w, r)
}
func main() {
// highlight-start
// Initialize the session.
sessionManager = scs.New()
sessionManager.Lifetime = 24 * time.Hour
// highlight-end
mux := http.NewServeMux()
// Handle POST and GET requests.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
postHandler(w, r)
return
}
getHandler(w, r)
})
// highlight-start
// Add the middleware.
muxWithSessionMiddleware := sessionManager.LoadAndSave(mux)
// highlight-end
// Start the server.
fmt.Println("listening on http://localhost:8000")
if err := http.ListenAndServe("localhost:8000", muxWithSessionMiddleware); err != nil {
log.Printf("error listening: %v", err)
}
}
```
:::note
Incrementing a count by reading and setting the value is not an atomic operation (not thread-safe). In a production application, to increment a per-user count you may wish to use a database that provides a transactional increment operation.
:::
:::note
The default behaviour of `scs` is to store session data in RAM, which isn't suitable for stateless function deployments, or load-balanced applications, but the library supports a range of backend storage solutions.
:::
Complete source code including AWS CDK code to set up the infrastructure is available at https://github.com/a-h/templ/tree/main/examples/counter

View File

@@ -0,0 +1,56 @@
# HTMX
https://htmx.org can be used to selectively replace content within a web page, instead of replacing the whole page in the browser. This avoids "full-page postbacks", where the whole of the browser window is updated when a button is clicked, and results in a better user experience by reducing screen "flicker", or losing scroll position.
## Usage
Using HTMX requires:
* Installation of the HTMX client-side library.
* Modifying the HTML markup to instruct the library to perform partial screen updates.
## Installation
To install the HTMX library, download the `htmx.min.js` file and serve it via HTTP.
Then add a `<script>` tag to the `<head>` section of your HTML with the `src` attribute pointing at the file.
```html
<script src="/assets/js/htmx.min.js"></script>
```
:::info
Advanced HTMX installation and usage help is covered in the user guide at https://htmx.org.
:::
## Count example
To update the counts on the page without a full postback, the `hx-post="/"` and `hx-select="#countsForm"` attributes must be added to the `<form>` element, along with an `id` attribute to uniquely identify the element.
Adding these attributes instructs the HTMX library to replace the browser's HTTP form POST and subsequent refresh with a request from HTMX instead. HTMX issues a HTTP POST operation to the `/` endpoint, and replaces the `<form>` element with the HTML that is returned.
The `/` endpoint returns a complete HTML page instead of just the updated `<form>` element HTML. The `hx-select="#countsForm"` instructs HTMX to extract the HTML content within the `countsForm` element that is returned by the web server to replace the `<form>` element.
```templ title="components/components.templ"
templ counts(global, session int) {
// highlight-next-line
<form id="countsForm" action="/" method="POST" hx-post="/" hx-select="#countsForm" hx-swap="outerHTML">
<div class="columns">
<div class={ "column", "has-text-centered", "is-primary", border }>
<h1 class="title is-size-1 has-text-centered">{ strconv.Itoa(global) }</h1>
<p class="subtitle has-text-centered">Global</p>
<div><button class="button is-primary" type="submit" name="global" value="global">+1</button></div>
</div>
<div class={ "column", "has-text-centered", border }>
<h1 class="title is-size-1 has-text-centered">{ strconv.Itoa(session) }</h1>
<p class="subtitle has-text-centered">Session</p>
<div><button class="button is-secondary" type="submit" name="session" value="session">+1</button></div>
</div>
</div>
</form>
}
```
The example can be viewed at https://d3qfg6xxljj3ky.cloudfront.net
Complete source code including AWS CDK code to set up the infrastructure is available at https://github.com/a-h/templ/tree/main/examples/counter

View File

@@ -0,0 +1,207 @@
# Datastar
[Datastar](https://data-star.dev) is a hypermedia framework that is similar to [HTMX](htmx).
Datastar can selectively replace content within a web page by combining fine-grained reactive signals with SSE. It's geared primarily to real-time applications where you'd normally reach for a SPA framework such as React/Vue/Svelte.
## Usage
Using Datastar requires:
- Installation of the Datastar client-side library.
- Modifying the HTML markup to instruct the library to perform partial screen updates.
## Installation
Datastar is included with Templ components out of the box to speed up development. You can use `@datastar.ScriptCDNLatest()` or `ScriptCDNVersion(version string)` to include the latest version of the Datastar library in your HTML.
:::info
Advanced Datastar installation and usage help is covered in the user guide at https://data-star.dev.
:::
## Datastar examples using Templ
The Datastar website is built using Datastar and templ, so you can see how it works in practice.
The Datastar website contains a number of examples that demonstrate how to use Datastar. The examples are written in Go and use the templ package to generate the HTML.
See examples at https://github.com/delaneyj/datastar/tree/main/backends/go/site
This document will walk you through how to create a simple counter example using Datastar, following the [example](https://data-star.dev/examples/templ_counter) in the Datastar website.
## Counter Example
We are going to modify the [templ counter example](example-counter-application) to use Datastar.
### Frontend
First, define some HTML with two buttons. One to update a global state, and one to update a per-user state.
```templ title="components.templ"
package site
import datastar "github.com/starfederation/datastar/sdk/go"
type TemplCounterSignals struct {
Global uint32 `json:"global"`
User uint32 `json:"user"`
}
templ templCounterExampleButtons() {
<div>
<button
data-on-click="@post('/examples/templ_counter/increment/global')"
>
Increment Global
</button>
<button
data-on-click={ datastar.PostSSE('/examples/templ_counter/increment/user') }
<!-- Alternative: Using Datastar SDK sugar-->
>
Increment User
</button>
</div>
}
templ templCounterExampleCounts() {
<div>
<div>
<div>Global</div>
<div data-text="$global"></div>
</div>
<div>
<div>User</div>
<div data-text="$user"></div>
</div>
</div>
}
templ templCounterExampleInitialContents(signals TemplCounterSignals) {
<div
id="container"
data-signals={ templ.JSONString(signals) }
>
@templCounterExampleButtons()
@templCounterExampleCounts()
</div>
}
```
:::tip
Note that Datastar doesn't promote the use of forms because they are ill-suited to nested reactive content. Instead, it sends all[^1] reactive state (as JSON) to the server on each request. This means far less bookkeeping and more predictable state management.
:::
:::note
`data-signals` is a special attribute that Datastar uses to merge one or more signals into the existing signals. In the example, we store $global and $user when we initially render the container.
`data-on-click="@post('/examples/templ_counter/increment/global')"` is an attribute expression that says "When this element is clicked, send a POST request to the server to the specified URL". The `@post` is an action that is a sandboxed function that knows about things like signals.
`data-text="$global"` is an attribute expression that says "replace the contents of this element with the value of the `global` signal in the store". This is a reactive signal that will update the page when the value changes, which we'll see in a moment.
:::
### Backend
Note the use of Datastar's helpers to set up SSE.
```go title="examples_templ_counter.go"
package site
import (
"net/http"
"sync/atomic"
"github.com/Jeffail/gabs/v2"
"github.com/go-chi/chi/v5"
"github.com/gorilla/sessions"
datastar "github.com/starfederation/datastar/sdk/go"
)
func setupExamplesTemplCounter(examplesRouter chi.Router, sessionSignals sessions.Store) error {
var globalCounter atomic.Uint32
const (
sessionKey = "templ_counter"
countKey = "count"
)
userVal := func(r *http.Request) (uint32, *sessions.Session, error) {
sess, err := sessionSignals.Get(r, sessionKey)
if err != nil {
return 0, nil, err
}
val, ok := sess.Values[countKey].(uint32)
if !ok {
val = 0
}
return val, sess, nil
}
examplesRouter.Get("/templ_counter/data", func(w http.ResponseWriter, r *http.Request) {
userVal, _, err := userVal(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
signals := TemplCounterSignals{
Global: globalCounter.Load(),
User: userVal,
}
c := templCounterExampleInitialContents(signals)
datastar.NewSSE(w, r).MergeFragmentTempl(c)
})
updateGlobal := func(signals *gabs.Container) {
signals.Set(globalCounter.Add(1), "global")
}
examplesRouter.Route("/templ_counter/increment", func(incrementRouter chi.Router) {
incrementRouter.Post("/global", func(w http.ResponseWriter, r *http.Request) {
update := gabs.New()
updateGlobal(update)
datastar.NewSSE(w, r).MarshalAndMergeSignals(update)
})
incrementRouter.Post("/user", func(w http.ResponseWriter, r *http.Request) {
val, sess, err := userVal(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
val++
sess.Values[countKey] = val
if err := sess.Save(r, w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
update := gabs.New()
updateGlobal(update)
update.Set(val, "user")
datastar.NewSSE(w, r).MarshalAndMergeSignals(update)
})
})
return nil
}```
The `atomic.Uint32` type stores the global state. The `userVal` function is a helper that retrieves the user's session state. The `updateGlobal` function increments the global state.
:::note
In this example, the global state is stored in RAM and will be lost when the web server reboots. To support load-balanced web servers and stateless function deployments, consider storing the state in a data store such as [NATS KV](https://docs.nats.io/using-nats/developer/develop_jetstream/kv).
:::
### Per-user session state
In an HTTP application, per-user state information is partitioned by an HTTP cookie. Cookies that identify a user while they're using a site are known as "session cookies". When the HTTP handler receives a request, it can read the session ID of the user from the cookie and retrieve any required state.
### Signal-only patching
Since the page's elements aren't changing dynamically, we can use the `MarshalAndMergeSignals` function to send only the signals that have changed. This is a more efficient way to update the page without even needing to send HTML fragments.
:::tip
Datastar will merge updates to signals similar to a JSON merge patch. This means you can do dynamic partial updates to the store and the page will update accordingly. [Gabs](https://pkg.go.dev/github.com/Jeffail/gabs/v2#section-readme) is used here to handle dynamic JSON in Go.
[^1]: You can control the data sent to the server by prefixing local signals with `_`. This will prevent them from being sent to the server on every request.

View File

@@ -0,0 +1,143 @@
# HTTP Streaming
The default behaviour of the `templ.Handler` is to render the template to a buffer and then write the buffer to the response.
This ensures that the template has successfully rendered before the response is sent to the client, so that appropriate response codes can be set if the template fails to render, and partial responses are not sent to the client.
## Rendering lifecycle
Typical usage of templ involves collecting data that is used to populate the template, before rendering the template and sending a response.
For example, executing several database queries, calling an API, or reading from a file, before rendering the template.
```mermaid
flowchart TD;
r[Request]
q[DB Queries]
q1[Query result]
q2[Query result]
a[API Calls]
api[API call result]
t[Render template]
h[HTML]
response[Response]
r-->q;
r-->a;
q-->q1
q-->q2
a-->api
q1-->t
q2-->t
api-->t
t-->h
h-->response;
```
However, if the queries and API calls take a long time, this has an impact on Time to First Byte (TTFB) because the client has to wait for all database queries and API calls to complete before sending the response.
To improve TTFB, the template can be streamed to the client as soon as the first part of the template is rendered, while the remaining queries and API calls are still in progress, at the cost of not being able to set response codes or headers after the first part of the template is rendered.
## Enabling streaming
Streaming can be enabled by setting the `Streaming` field of the `templ.Handler` to `true` using the `WithStreaming` option.
```go
templ.Handler(component, templ.WithStreaming()).ServeHTTP(w, r)
```
When streaming is enabled, sections of the template can be forcefully pushed to the client using the `templ.Flush()` component.
This enables interesting use cases. For example, here, the `Page` template is rendered with a channel that is populated by a background goroutine.
By using `templ.Flush()` to create a flushable area, the data is pushed to the client as soon as it is available, rather than waiting for the entire template to render before sending a response.
```go
templ Page(data chan string) {
<!DOCTYPE html>
<html>
<head>
<title>Page</title>
</head>
<body>
<h1>Page</h1>
for d := range data {
@templ.Flush() {
<div>{ d }</div>
}
}
</body>
</html>
}
```
See https://github.com/a-h/templ/tree/main/examples/streaming for a full example.
## Suspense
Many modern web frameworks use a concept called "Suspense" to handle the loading of data and rendering of components.
This usually involves displaying placeholder content while the data is loading, and then rendering the component when the data is available.
With JavaScript frontends like React, the lifecycle is usually that the HTML is rendered, the JS loaded, the initial render that displays the placeholder is done, an API call is made back to the server to fetch data, and then the component is rendered.
This involves a lot of extra HTTP requests, and means that we have to wait until JavaScript is loaded before we can start fetching data.
Combining templ's streaming capability with a new feature in web browsers called "Declarative Shadow DOM" means that we can perform the same action in a single HTTP request.
:::note:::
React SSR solutions such as Next.js can do this on the server, just like templ can, see https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#what-is-streaming
:::
### Declarative Shadow DOM
First, we need to define a new templ component called `Slot`.
```go
templ Slot(name string) {
<slot name={ name }>
<div>Loading { name }...</div>
</slot>
}
```
This component is a placeholder that will be replaced by the contents of the slot when the data is available.
Next, we can use a `<template>` element with `shadowrootmode="open"` to create a shadow DOM that allows us to populate the `<slot>` elements with data.
We need to use `@templ.Flush()` to create a flushable area, so that the data is pushed to the client as soon as it is available, since popluating the slots will take longer to complete.
We can then use a `for` loop over the channel of data to populate the slots with content, again, flushing the results to the browser when available.
The result is a simple way to load content after initial page load without the need to use JavaScript.
```go
templ Page(data chan SlotContents) {
<!DOCTYPE html>
<html>
<head>
<title>Page</title>
</head>
<body>
<h1>Page</h1>
@templ.Flush() {
<template shadowrootmode="open">
@Slot("a")
@Slot("b")
@Slot("c")
</template>
}
for sc := range data {
@templ.Flush() {
<div slot={ sc.Name }>
@sc.Contents
</div>
}
}
</body>
</html>
}
```
<video loop autoplay controls src="/img/shadowdom.webm" />
See https://github.com/a-h/templ/tree/main/examples/suspense for a full working example.

View File

@@ -0,0 +1,4 @@
{
"position": 5,
"label": "Server-side rendering"
}

View File

@@ -0,0 +1,103 @@
# Generating static HTML files with templ
templ components implement the `templ.Component` interface.
The interface has a `Render` method which outputs HTML to an `io.Writer` that is passed in.
```go
type Component interface {
// Render the template.
Render(ctx context.Context, w io.Writer) error
}
```
In Go, the `io.Writer` interface is implemented by many built-in types in the standard library, including `os.File` (files), `os.Stdout`, and `http.ResponseWriter` (HTTP responses).
This makes it easy to use templ components in a variety of contexts to generate HTML.
To render static HTML files using templ component, first create a new Go project.
## Setup project
Create a new directory.
```bash
mkdir static-generator
```
Initialize a new Go project within it.
```bash
cd static-generator
go mod init github.com/a-h/templ-examples/static-generator
```
## Create a templ file
To use it, create a `hello.templ` file containing a component.
Components are functions that contain templ elements, markup, `if`, `switch` and `for` Go expressions.
```templ title="hello.templ"
package main
templ hello(name string) {
<div>Hello, { name }</div>
}
```
## Generate Go code from the templ file
Run the `templ generate` command.
```bash
templ generate
```
templ will generate a `hello_templ.go` file containing Go code.
This file will contain a function called `hello` which takes `name` as an argument, and returns a `templ.Component` that renders HTML.
```go
func hello(name string) templ.Component {
// ...
}
```
## Write a program that renders to stdout
Create a `main.go` file. The program creates a `hello.html` file and uses the component to write HTML to the file.
```go title="main.go"
package main
import (
"context"
"log"
"os"
)
func main() {
f, err := os.Create("hello.html")
if err != nil {
log.Fatalf("failed to create output file: %v", err)
}
err = hello("John").Render(context.Background(), f)
if err != nil {
log.Fatalf("failed to write output file: %v", err)
}
}
```
## Run the program
Running the code will create a file called `hello.html` containing the component's HTML.
```bash
go run *.go
```
```html title="hello.html"
<div>Hello, John</div>
```

View File

@@ -0,0 +1,244 @@
# Blog example
This example demonstrates building a static blog with templ.
## Create a blog template
Create a template for the site header and site content. Then, create a template for the content page and index page.
```templ title="blog.templ"
package main
import "path"
import "github.com/gosimple/slug"
templ headerComponent(title string) {
<head><title>{ title }</title></head>
}
templ contentComponent(title string, body templ.Component) {
<body>
<h1>{ title }</h1>
<div class="content">
@body
</div>
</body>
}
templ contentPage(title string, body templ.Component) {
<html>
@headerComponent(title)
@contentComponent(title, body)
</html>
}
templ indexPage(posts []Post) {
<html>
@headerComponent("My Blog")
<body>
<h1>My Blog</h1>
for _, post := range posts {
<div><a href={ templ.SafeURL(path.Join(post.Date.Format("2006/01/02"), slug.Make(post.Title), "/")) }>{ post.Title }</a></div>
}
</body>
</html>
}
```
In the Go code, create a `Post` struct to store information about a blog post.
```go
type Post struct {
Date time.Time
Title string
Content string
}
```
Create some pretend blog posts.
```go
posts := []Post{
{
Date: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC),
Title: "Happy New Year!",
Content: `New Year is a widely celebrated occasion in the United Kingdom, marking the end of one year and the beginning of another.
Top New Year Activities in the UK include:
* Attending a Hogmanay celebration in Scotland
* Taking part in a local First-Foot tradition in Scotland and Northern England
* Setting personal resolutions and goals for the upcoming year
* Going for a New Year's Day walk to enjoy the fresh start
* Visiting a local pub for a celebratory toast and some cheer
`,
},
{
Date: time.Date(2023, time.May, 1, 0, 0, 0, 0, time.UTC),
Title: "May Day",
Content: `May Day is an ancient spring festival celebrated on the first of May in the United Kingdom, embracing the arrival of warmer weather and the renewal of life.
Top May Day Activities in the UK:
* Dancing around the Maypole, a traditional folk activity
* Attending local village fetes and fairs
* Watching or participating in Morris dancing performances
* Enjoying the public holiday known as Early May Bank Holiday
`,
},
}
```
## Rendering HTML directly
The example blog posts contain markdown, so we'll use `github.com/yuin/goldmark` to convert the markdown to HTML.
We can't use a string containing HTML directly in templ, because all strings are escaped in templ. So we'll create an `Unsafe` code component to write the HTML directly to the output writer without first escaping it.
```go
func Unsafe(html string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
_, err = io.WriteString(w, html)
return
})
}
```
## Creating the static pages
The code creates the index page. The code then iterates through the posts, creating an output file for each blog post.
```go title="main.go"
package main
import (
"bytes"
"context"
"io"
"log"
"os"
"path"
"time"
"github.com/a-h/templ"
"github.com/gosimple/slug"
"github.com/yuin/goldmark"
)
func main() {
// Output path.
rootPath := "public"
if err := os.Mkdir(rootPath, 0755); err != nil {
log.Fatalf("failed to create output directory: %v", err)
}
// Create an index page.
name := path.Join(rootPath, "index.html")
f, err := os.Create(name)
if err != nil {
log.Fatalf("failed to create output file: %v", err)
}
// Write it out.
err = indexPage(posts).Render(context.Background(), f)
if err != nil {
log.Fatalf("failed to write index page: %v", err)
}
// Create a page for each post.
for _, post := range posts {
// Create the output directory.
dir := path.Join(rootPath, post.Date.Format("2006/01/02"), slug.Make(post.Title))
if err := os.MkdirAll(dir, 0755); err != nil && err != os.ErrExist {
log.Fatalf("failed to create dir %q: %v", dir, err)
}
// Create the output file.
name := path.Join(dir, "index.html")
f, err := os.Create(name)
if err != nil {
log.Fatalf("failed to create output file: %v", err)
}
// Convert the markdown to HTML, and pass it to the template.
var buf bytes.Buffer
if err := goldmark.Convert([]byte(post.Content), &buf); err != nil {
log.Fatalf("failed to convert markdown to HTML: %v", err)
}
// Create an unsafe component containing raw HTML.
content := Unsafe(buf.String())
// Use templ to render the template containing the raw HTML.
err = contentPage(post.Title, content).Render(context.Background(), f)
if err != nil {
log.Fatalf("failed to write output file: %v", err)
}
}
}
```
## Results
After generating Go code from the templates, and running it with `templ generate` followed by `go run *.go`, the following files will be created.
```
public/index.html
public/2023/01/01/happy-new-year/index.html
public/2023/05/01/may-day/index.html
```
The `index.html` contains links to all of the posts.
```html title="index.html"
<title>
My Website
</title>
<h1>
My Website
</h1>
<div>
<a href="2023/01/01/happy-new-year/">
Happy New Year!
</a>
</div>
<div>
<a href="2023/05/01/may-day/">
May Day
</a>
</div>
```
While each content page contains the HTML generated from the markdown, and the surrounding template.
```html title="2023/05/01/may-day/index.html"
<title>
May Day
</title>
<h1>
May Day
</h1>
<div class="content">
<p>
May Day is an ancient spring festival celebrated on the first of May in the United Kingdom, embracing the arrival of warmer weather and the renewal of life.
</p>
<p>
Top May Day Activities in the UK:
</p>
<ul>
<li>
Dancing around the Maypole, a traditional folk activity
</li>
<li>
Attending local village fetes and fairs
</li>
<li>
Watching or participating in Morris dancing performances
</li>
<li>
Enjoying the public holiday known as Early May Bank Holiday
</li>
</ul>
</div>
```
The files in the `public` directory can then be hosted in any static website hosting provider.

View File

@@ -0,0 +1,30 @@
# Deploying static files
Once you have built static HTML files with templ, you can serve them on any static site hosting platform, or use a web server to serve them.
Ways you could host your site include:
* Fly.io
* Netlify
* Vercel
* AWS Amplify
* Firebase Hosting
Typically specialist static hosting services are more cost-effective than VM or Docker-based services, due to the less complex compute and networking requirements.
Most require you to commit your code to a source repository, with a build process being triggered on commit, but Fly.io allows you to deploy easily from the CLI.
## fly.io
Fly.io is a provider of hosting that is straightforward to use, and has a generous free tier. Fly.io is Docker-based, so you can easily switch out to a dynamic website if you need to.
Following on from the blog example, all that's required is to add a Dockerfile to the project that copies the contents of the `public` directory into the Docker image, followed by running `flyctl launch` to initialize configuration.
```Dockerfile title="Dockerfile"
FROM pierrezemb/gostatic
COPY ./public/ /srv/http/
ENTRYPOINT ["/goStatic", "-port", "8080"]
```
More detailed documentation is available at https://fly.io/docs/languages-and-frameworks/static/

View File

@@ -0,0 +1,4 @@
{
"position": 6,
"label": "Static rendering"
}

View File

@@ -0,0 +1,256 @@
# Project structure
The example counter project demonstrates a way to structure your applications.
https://github.com/a-h/templ/tree/main/examples/counter
The application is divided up into multiple packages, each with its own purpose.
* `cdk` - Infrastructure setup for deploying the application.
* `components` - templ components.
* `db` - Database access code used to increment and get counts.
* `handlers` - HTTP handlers.
* `lambda` - The AWS Lambda entry point.
* `services` - Services used by the handlers.
* `session` - Middleware for implementing HTTP session IDs.
* `main.go` - Used to run the application locally.
## Application architecture
The architecture follows a typical "onion model" where each layer doesn't know about the layer above it, and each layer is responsible for a specific thing.
```mermaid
graph LR
handler[HTTP handler] -- uses --> services[Services]
services -- use --> db[Database access code]
db -- uses --> dynamodb[(DynamoDB)]
handler -- renders --> components[Components]
```
* HTTP Handler
* Processes HTTP requests
* Does not contain application logic itself
* Uses `services` that carry out application logic
* Takes the responses from `services` and uses `components` to render HTML
* Creates HTTP responses
* Services
* Carries out application logic such as orchestrating API calls, or making database calls
* Does not do anything related to HTML or HTTP
* Is not aware of the specifics of database calls
* Database access code
* Handles database activity such as inserting and querying records
* Ensures that the database representation (`records`) doesn't leak to the service layer
A more complex application may have a `models` package containing plain structs that represent common data structures in the application, such as `User`.
:::tip
As with most things, taking the layering approach to an extreme level can have a negative effect. Ask yourself whether what you're doing is really helping to make the code understandable, or is just spreading application logic across lots of files, and making it hard to see the overall structure.
:::
## Dependency injection
Layering an application in this way can simplify code structure, since the responsibility of each type is clear.
To ensure that each part of the application is initialized with its dependencies, each struct defines a constructor (the `New` function in this example).
As per https://go.dev/wiki/CodeReviewComments#interfaces the HTTP handler defines the interface that it's expecting, rather than the service defining its own interface.
```go title="handlers/default.go"
type CountService interface {
Increment(ctx context.Context, it services.IncrementType, sessionID string) (counts services.Counts, err error)
Get(ctx context.Context, sessionID string) (counts services.Counts, err error)
}
func New(log *slog.Logger, cs CountService) *DefaultHandler {
return &DefaultHandler{
Log: log,
CountService: cs,
}
}
type DefaultHandler struct {
Log *slog.Logger
CountService CountService
}
```
Changing the signature of `New` to add a new dependency will result in a compilation error that shows you all the affected code in your application.
:::tip
Dependency injection frameworks are not typically used in Go. If you're coming from a language like C# or Java, this may seem unusual to you, but go with it, you don't need one.
:::
## HTTP layer
This HTTP handler reads HTTP requests, uses the `CountService` to `Get` or `Increment` the counters, and renders the templ Components.
:::note
Note that the `View` method uses the templ Components from the `components` directory to render the page.
:::
```go "title="handlers/default.go"
func (h *DefaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
h.Post(w, r)
return
}
h.Get(w, r)
}
func (h *DefaultHandler) Get(w http.ResponseWriter, r *http.Request) {
var props ViewProps
var err error
props.Counts, err = h.CountService.Get(r.Context(), session.ID(r))
if err != nil {
h.Log.Error("failed to get counts", slog.Any("error", err))
http.Error(w, "failed to get counts", http.StatusInternalServerError)
return
}
h.View(w, r, props)
}
func (h *DefaultHandler) Post(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
// Decide the action to take based on the button that was pressed.
var it services.IncrementType
if r.Form.Has("global") {
it = services.IncrementTypeGlobal
}
if r.Form.Has("session") {
it = services.IncrementTypeSession
}
counts, err := h.CountService.Increment(r.Context(), it, session.ID(r))
if err != nil {
h.Log.Error("failed to increment", slog.Any("error", err))
http.Error(w, "failed to increment", http.StatusInternalServerError)
return
}
// Display the view.
h.View(w, r, ViewProps{
Counts: counts,
})
}
type ViewProps struct {
Counts services.Counts
}
func (h *DefaultHandler) View(w http.ResponseWriter, r *http.Request, props ViewProps) {
components.Page(props.Counts.Global, props.Counts.Session).Render(r.Context(), w)
}
```
## Service layer
The service layer coordinates API and database activity to carry out application logic.
```go title="services/count.go"
type Counts struct {
Global int
Session int
}
func (cs Count) Get(ctx context.Context, sessionID string) (counts Counts, err error) {
globalAndSessionCounts, err := cs.CountStore.BatchGet(ctx, "global", sessionID)
if err != nil {
err = fmt.Errorf("countservice: failed to get counts: %w", err)
return
}
if len(globalAndSessionCounts) != 2 {
err = fmt.Errorf("countservice: unexpected counts returned, expected 2, got %d", len(globalAndSessionCounts))
}
counts.Global = globalAndSessionCounts[0]
counts.Session = globalAndSessionCounts[1]
return
}
```
This allows us to use Go's parallelism features to run operations more efficiently without adding complexity to the HTTP or database code.
```go title="services/count.go"
func (cs Count) Increment(ctx context.Context, it IncrementType, sessionID string) (counts Counts, err error) {
// Work out which operations to do.
var global, session func(ctx context.Context, id string) (count int, err error)
switch it {
case IncrementTypeGlobal:
global = cs.CountStore.Increment
session = cs.CountStore.Get
case IncrementTypeSession:
global = cs.CountStore.Get
session = cs.CountStore.Increment
default:
return counts, ErrUnknownIncrementType
}
// Run the operations in parallel.
var wg sync.WaitGroup
wg.Add(2)
errs := make([]error, 2)
go func() {
defer wg.Done()
counts.Global, errs[0] = global(ctx, "global")
}()
go func() {
defer wg.Done()
counts.Session, errs[1] = session(ctx, sessionID)
}()
wg.Wait()
return counts, errors.Join(errs...)
}
```
## Entrypoint
To wire all of the dependencies together and start up your web server or serverless function handler, your application will require an entrypoint.
In this example, the code for configuring the HTTP server and HTTP routes is also in the `main.go` because it's a very simple application. In more complex applications, this might be migrated into another package.
```go title="main.go"
package main
import (
"fmt"
"net/http"
"os"
"time"
"github.com/a-h/templ/examples/counter/db"
"github.com/a-h/templ/examples/counter/handlers"
"github.com/a-h/templ/examples/counter/services"
"github.com/a-h/templ/examples/counter/session"
"golang.org/x/exp/slog"
)
func main() {
log := slog.New(slog.NewJSONHandler(os.Stderr))
s, err := db.NewCountStore(os.Getenv("TABLE_NAME"), os.Getenv("AWS_REGION"))
if err != nil {
log.Error("failed to create store", slog.Any("error", err))
os.Exit(1)
}
cs := services.NewCount(log, s)
h := handlers.New(log, cs)
var secureFlag = true
if os.Getenv("SECURE_FLAG") == "false" {
secureFlag = false
}
// Add session middleware.
sh := session.NewMiddleware(h, session.WithSecure(secureFlag))
server := &http.Server{
Addr: "localhost:9000",
Handler: sh,
ReadTimeout: time.Second * 10,
WriteTimeout: time.Second * 10,
}
fmt.Printf("Listening on %v\n", server.Addr)
server.ListenAndServe()
}
```

View File

@@ -0,0 +1,4 @@
{
"position": 7,
"label": "Project structure"
}

View File

@@ -0,0 +1,98 @@
# Hosting on AWS Lambda
AWS Lambda is a great way to host templ applications.
The example at https://github.com/a-h/templ/tree/main/examples/counter includes AWS CDK code for deploying onto AWS Lambda.
See the `/cdk` directory for the details.
## Entrypoint
Lambda functions require an entrypoint that receives Lambda requests, and returns Lambda responses.
The https://github.com/akrylysov/algnhsa package provides an adaptor that allows the standard Go HTTP interface to be used.
```go title="lambda/main.go"
package main
import (
"os"
"github.com/a-h/templ/examples/counter/db"
"github.com/a-h/templ/examples/counter/handlers"
"github.com/a-h/templ/examples/counter/services"
"github.com/a-h/templ/examples/counter/session"
"github.com/akrylysov/algnhsa"
"golang.org/x/exp/slog"
)
func main() {
// Create handlers.
log := slog.New(slog.NewJSONHandler(os.Stderr))
s, err := db.NewCountStore(os.Getenv("TABLE_NAME"), os.Getenv("AWS_REGION"))
if err != nil {
log.Error("failed to create store", slog.Any("error", err))
os.Exit(1)
}
cs := services.NewCount(log, s)
h := handlers.New(log, cs)
// Add session middleware.
sh := session.NewMiddleware(h)
// Start Lambda.
algnhsa.ListenAndServe(sh, nil)
}
```
## Building and deploying
CDK provides the `github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2` package (aliased in this code as `awslambdago`) construct.
All that's required is to pass the path to the directory containing your Lambda function's `main.go` file and CDK will compile the code and deploy it.
```go title="cdk/stack.go"
// Strip the binary, and remove the deprecated Lambda SDK RPC code for performance.
// These options are not required, but make cold start faster.
bundlingOptions := &awslambdago.BundlingOptions{
GoBuildFlags: &[]*string{jsii.String(`-ldflags "-s -w" -tags lambda.norpc`)},
}
f := awslambdago.NewGoFunction(stack, jsii.String("handler"), &awslambdago.GoFunctionProps{
Runtime: awslambda.Runtime_PROVIDED_AL2(),
MemorySize: jsii.Number(1024),
Architecture: awslambda.Architecture_ARM_64(),
Entry: jsii.String("../lambda"),
Bundling: bundlingOptions,
Environment: &map[string]*string{
"TABLE_NAME": db.TableName(),
},
})
// Add a Function URL.
lambdaURL := f.AddFunctionUrl(&awslambda.FunctionUrlOptions{
AuthType: awslambda.FunctionUrlAuthType_NONE,
})
awscdk.NewCfnOutput(stack, jsii.String("lambdaFunctionUrl"), &awscdk.CfnOutputProps{
ExportName: jsii.String("lambdaFunctionUrl"),
Value: lambdaURL.Url(),
})
```
## Static content
To serve static content such as images alongside Lambda functions that serve HTML or REST API responses, a common pattern is to use a CloudFront distribution that routes traffic to S3 or to the Lambda Function URL, based on the URL structure.
```mermaid
graph TD
browser[Browser] --> cf[Cloudfront];
cf -- HTTP /* --> furl[Lambda Function URL]
cf -- HTTP /assets/* --> s3[S3 Assets Bucket]
furl --> lservice[Lambda Service]
lservice -- API Gateway V2 request/response --> adaptor[algnhsa Adaptor]
adaptor -- Go http.Handler request/response --> code[Your code]
```
The example CDK stack includes a deployment process that updates the contents of the S3 bucket.
## Deployed example
To see the deployed sample application running on AWS Lambda, visit https://d3qfg6xxljj3ky.cloudfront.net

View File

@@ -0,0 +1,99 @@
# Hosting using Docker
Applications that use templ can be deployed using the same techniques and platforms as any other Go application.
An example Dockerfile is provided in the https://github.com/a-h/templ/tree/main/examples/counter-basic example.
# Static content
### Adding static content to the Docker container
Web applications often need to include static content such as CSS, images, and icon files.
The https://github.com/a-h/templ/tree/main/examples/counter-basic example has an `assets` directory for this purpose.
The `COPY` instruction in the Dockerfile copies all of the code and the `assets` directory to the container so that it can be served by the application.
```Dockerfile title="Dockerfile"
# Build.
FROM golang:1.20 AS build-stage
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
// highlight-next-line
COPY . /app
RUN CGO_ENABLED=0 GOOS=linux go build -o /entrypoint
# Deploy.
FROM gcr.io/distroless/static-debian11 AS release-stage
WORKDIR /
COPY --from=build-stage /entrypoint /entrypoint
// highlight-next-line
COPY --from=build-stage /app/assets /assets
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/entrypoint"]
```
### Serving static content
Once the `/assets` directory has been added to the deployment Docker container, the `http.FileServer` function must be used to serve the content.
```go title="main.go"
func main() {
// Initialize the session.
sessionManager = scs.New()
sessionManager.Lifetime = 24 * time.Hour
mux := http.NewServeMux()
// Handle POST and GET requests.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
postHandler(w, r)
return
}
getHandler(w, r)
})
// Include the static content.
// highlight-next-line
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))
// Add the middleware.
muxWithSessionMiddleware := sessionManager.LoadAndSave(mux)
// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", muxWithSessionMiddleware); err != nil {
log.Printf("error listening: %v", err)
}
}
```
## Building and running the Docker container locally
Before you deploy your application to a hosting provider, you can build and run it locally.
First, you'll need to build the Docker container image.
```bash
docker build -t counter-basic:latest .
```
Then you can run the container image, making port `8080` on your `localhost` connect through to port `8080` inside the Docker container.
```bash
docker run -p 8080:8080 counter-basic:latest
```
Once the container starts, you can open a web browser at `localhost:8080` and view the application.
## Example deployment
The https://github.com/a-h/templ/tree/main/examples/counter-basic example is deployed at https://counter-basic.fly.dev/
:::note
This sample application stores the counts in RAM. If the server restarts, all of the information is lost. To avoid this, use a data store such as DynamoDB or Cloud Firestore. See https://github.com/a-h/templ/tree/main/examples/counter for an example of this.
:::

View File

@@ -0,0 +1,4 @@
{
"position": 8,
"label": "Hosting and deployment"
}

View File

@@ -0,0 +1,118 @@
# CLI
`templ` provides a command line interface. Most users will only need to run the `templ generate` command to generate Go code from `*.templ` files.
```
usage: templ <command> [<args>...]
templ - build HTML UIs with Go
See docs at https://templ.guide
commands:
generate Generates Go code from templ files
fmt Formats templ files
lsp Starts a language server for templ files
info Displays information about the templ environment
version Prints the version
```
## Generating Go code from templ files
The `templ generate` command generates Go code from `*.templ` files in the current directory tree.
The command provides additional options:
```
usage: templ generate [<args>...]
Generates Go code from templ files.
Args:
-path <path>
Generates code for all files in path. (default .)
-f <file>
Optionally generates code for a single file, e.g. -f header.templ
-source-map-visualisations
Set to true to generate HTML files to visualise the templ code and its corresponding Go code.
-include-version
Set to false to skip inclusion of the templ version in the generated code. (default true)
-include-timestamp
Set to true to include the current time in the generated code.
-watch
Set to true to watch the path for changes and regenerate code.
-cmd <cmd>
Set the command to run after generating code.
-proxy
Set the URL to proxy after generating code and executing the command.
-proxyport
The port the proxy will listen on. (default 7331)
-proxybind
The address the proxy will listen on. (default 127.0.0.1)
-w
Number of workers to use when generating code. (default runtime.NumCPUs)
-lazy
Only generate .go files if the source .templ file is newer.
-pprof
Port to run the pprof server on.
-keep-orphaned-files
Keeps orphaned generated templ files. (default false)
-v
Set log verbosity level to "debug". (default "info")
-log-level
Set log verbosity level. (default "info", options: "debug", "info", "warn", "error")
-help
Print help and exit.
```
For example, to generate code for a single file:
```
templ generate -f header.templ
```
## Formatting templ files
The `templ fmt` command formats template files. You can use this command in different ways:
1. Format all template files in the current directory and subdirectories:
```
templ fmt .
```
2. Format input from stdin and output to stdout:
```
templ fmt
```
Alternatively, you can run `fmt` in CI to ensure that invalidly formatted templatess do not pass CI. This will cause the command
to exit with unix error-code `1` if any templates needed to be modified.
```
templ fmt -fail .
```
## Language Server for IDE integration
`templ lsp` provides a Language Server Protocol (LSP) implementation to support IDE integrations.
This command isn't intended to be used directly by users, but is used by IDE integrations such as the VSCode extension and by Neovim support.
A number of additional options are provided to enable runtime logging and profiling tools.
```
-goplsLog string
The file to log gopls output, or leave empty to disable logging.
-goplsRPCTrace
Set gopls to log input and output messages.
-help
Print help and exit.
-http string
Enable http debug server by setting a listen address (e.g. localhost:7474)
-log string
The file to log templ LSP output to, or leave empty to disable logging.
-pprof
Enable pprof web server (default address is localhost:9999)
```

View File

@@ -0,0 +1,439 @@
# IDE support
## Visual Studio Code
There's a VS Code extension, just make sure you've already installed templ and that it's on your path.
- https://marketplace.visualstudio.com/items?itemName=a-h.templ
- https://github.com/a-h/templ-vscode
VSCodium users can find the extension on the Open VSX Registry at https://open-vsx.org/extension/a-h/templ
### Format on Save
Include the following into your settings.json to activate formatting `.templ` files on save with the
templ plugin:
```json
{
"editor.formatOnSave": true,
"[templ]": {
"editor.defaultFormatter": "a-h.templ"
},
}
```
### Tailwind CSS Intellisense
Include the following to the settings.json in order to enable autocompletion for Tailwind CSS in `.templ` files:
```json
{
"tailwindCSS.includeLanguages": {
"templ": "html"
}
}
```
:::note
Tailwind language servers require a tailwind.config.js file to be present in the root of your project. You can create a new config file with `npx tailwindcss init`, or use samples available at https://tailwindcss.com/docs/configuration
:::
### Emmet HTML completion
Include the following to the settings.json in order to get smooth HTML completion via emmet (such as expanding `input:button<Tab>` to `<input type="button" value="">`). The emmet plugin is built into vscode and just needs to be activated for `.templ` files:
```json
{
"emmet.includeLanguages": {
"templ": "html"
}
}
```
## Neovim &gt; 0.5.0
A plugin written in VimScript which adds syntax highlighting: [joerdav/templ.vim](https://github.com/Joe-Davidson1802/templ.vim).
For neovim you can use [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) and install [tree-sitter-templ](https://github.com/vrischmann/tree-sitter-templ) with `:TSInstall templ`.
The configuration for the templ Language Server is included in [lspconfig](https://github.com/neovim/nvim-lspconfig), [mason](https://github.com/williamboman/mason.nvim),
and [mason-lspconfig](https://github.com/williamboman/mason-lspconfig.nvim).
The `templ` command must be in your system path for the LSP to be able to start. Ensure that you can run it from the command line before continuing.
Installing and configuring the templ LSP is no different to setting up any other Language Server.
```lua
local lspconfig = require("lspconfig")
-- Use a loop to conveniently call 'setup' on multiple servers and
-- map buffer local keybindings when the language server attaches
local servers = { 'gopls', 'ccls', 'cmake', 'tsserver', 'templ' }
for _, lsp in ipairs(servers) do
lspconfig[lsp].setup({
on_attach = on_attach,
capabilities = capabilities,
})
end
```
In Neovim, you can use the `:LspInfo` command to check which Language Servers (if any) are running. If the expected language server has not started, it could be due to the unregistered templ file extension.
To resolve this issue, add the following code to your configuration. This is also necessary for other LSPs to "pick up" on .templ files.
```lua
vim.filetype.add({ extension = { templ = "templ" } })
```
##### Other LSPs within .templ files
These LSPs can be used *in conjunction* with the templ lsp and tree-sitter. Here's how to set them up.
[html-lsp](https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#html) - First make sure you have it installed `:LspInstall html` or find it on the `:Mason` list.
```lua
lspconfig.html.setup({
on_attach = on_attach,
capabilities = capabilities,
filetypes = { "html", "templ" },
})
```
[htmx-lsp](https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#htmx) - First make sure you have it installed `:LspInstall htmx` or find it on the `:Mason` list. Note with this LSP, it activates after you type `hx-` in an html attribute, because that's how all htmx attributes are written.
```lua
lspconfig.htmx.setup({
on_attach = on_attach,
capabilities = capabilities,
filetypes = { "html", "templ" },
})
```
[tailwindcss](https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#tailwindcss) - First make sure you have it installed `:LspInstall tailwindcss` or find it on the `:Mason` list.
```lua
lspconfig.tailwindcss.setup({
on_attach = on_attach,
capabilities = capabilities,
filetypes = { "templ", "astro", "javascript", "typescript", "react" },
settings = {
tailwindCSS = {
includeLanguages = {
templ = "html",
},
},
},
})
```
Inside of your `tailwind.config.js`, you need to tell tailwind to look inside of .templ files and/or .go files.
:::tip
If you don't have a `tailwind.config.js` in the root directory of your project, the Tailwind LSP won't activate, and you won't see autocompletion results.
:::
```js
module.exports = {
content: [ "./**/*.html", "./**/*.templ", "./**/*.go", ],
theme: { extend: {}, },
plugins: [],
}
```
### Formatting
With the templ LSP installed and configured, you can use the following code snippet to format on save:
```lua
vim.api.nvim_create_autocmd({ "BufWritePre" }, { pattern = { "*.templ" }, callback = vim.lsp.buf.format })
```
`BufWritePre` means that the callback gets ran after you call `:write`.
If you have multiple LSPs attached to the same buffer, and you have issues with `vim.lsp.buf.format`, you can use this snippet to run `templ fmt` in the same way that you might from the command line.
This will get the buffer and its corresponding filename, and refresh the buffer after it has been formatted so you don't get out of sync issues.
```lua
local custom_format = function()
if vim.bo.filetype == "templ" then
local bufnr = vim.api.nvim_get_current_buf()
local filename = vim.api.nvim_buf_get_name(bufnr)
local cmd = "templ fmt " .. vim.fn.shellescape(filename)
vim.fn.jobstart(cmd, {
on_exit = function()
-- Reload the buffer only if it's still the current buffer
if vim.api.nvim_get_current_buf() == bufnr then
vim.cmd('e!')
end
end,
})
else
vim.lsp.buf.format()
end
end
```
To apply this `custom_format` in your neovim configuration as a keybinding, apply it to the `on_attach` function.
```lua
local on_attach = function(client, bufnr)
local opts = { buffer = bufnr, remap = false }
-- other configuration options
vim.keymap.set("n", "<leader>lf", custom_format, opts)
end
```
To make this `custom_format` run on save, make the same autocmd from before and replace the callback with `custom_format`.
```lua
vim.api.nvim_create_autocmd({ "BufWritePre" }, { pattern = { "*.templ" }, callback = custom_format })
```
You can also rewrite the function like so, given that the function will only be executed on .templ files.
```lua
local templ_format = function()
local bufnr = vim.api.nvim_get_current_buf()
local filename = vim.api.nvim_buf_get_name(bufnr)
local cmd = "templ fmt " .. vim.fn.shellescape(filename)
vim.fn.jobstart(cmd, {
on_exit = function()
-- Reload the buffer only if it's still the current buffer
if vim.api.nvim_get_current_buf() == bufnr then
vim.cmd('e!')
end
end,
})
end
```
### Troubleshooting
If you cannot run `:TSInstall templ`, ensure you have an up-to-date version of [tree-sitter](https://github.com/nvim-treesitter/nvim-treesitter). The [package for templ](https://github.com/vrischmann/tree-sitter-templ) was [added to the main tree-sitter repository](https://github.com/nvim-treesitter/nvim-treesitter/pull/5667) so you shouldn't need to install a separate plugin for it.
If you still don't get syntax highlighting after it's installed, try running `:TSBufEnable highlight`. If you find that you need to do this every time you open a .templ file, you can run this autocmd to do it for your neovim configuration.
```lua
vim.api.nvim_create_autocmd("BufEnter", { pattern = "*.templ", callback = function() vim.cmd("TSBufEnable highlight") end })
```
### Minimal Config
Minimal config with the following features (useful for debugging):
- [lazy-vim](https://github.com/folke/lazy.nvim): neovim package manager
- [lsp config](https://github.com/neovim/nvim-lspconfig)
- templ-lsp
- html-lsp
- htmx-lsp
- tailwind-lsp
- [cmp](https://github.com/hrsh7th/nvim-cmp): for autocompletion
- [tree-sitter](https://github.com/nvim-treesitter/nvim-treesitter): for synx highlighting
- [tree sitter templ](https://github.com/vrischmann/tree-sitter-templ)
To use this config:
* As a permanent setup: Create/replace `init.lua` in your config folder (`~/.config/nvim/`)
* As a temporary setup: create a new folder in your `.config` (e.g.`~/.config/nvim_test`) and tell neovim to start up with that as the nvim appname `NVIM_APPNAME=nvim_test nvim` (see [neovim docs](https://neovim.io/doc/user/starting.html#%24NVIM_APPNAME) for further explanation.
```lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
vim.g.mapleader = " " -- Make sure to set `mapleader` before lazy so your mappings are correct
require("lazy").setup({
'neovim/nvim-lspconfig',
{
-- Autocompletion
'hrsh7th/nvim-cmp',
dependencies = {
'hrsh7th/cmp-nvim-lsp',
},
},
{
-- Highlight, edit, and navigate code
'nvim-treesitter/nvim-treesitter',
dependencies = {
'vrischmann/tree-sitter-templ',
},
build = ':TSUpdate',
},
})
vim.filetype.add({ extension = { templ = "templ" } })
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
local lspconfig = require("lspconfig")
lspconfig.templ.setup{
on_attach = on_attach,
capabilities = capabilities,
}
lspconfig.tailwindcss.setup({
on_attach = on_attach,
capabilities = capabilities,
filetypes = { "templ", "astro", "javascript", "typescript", "react" },
init_options = { userLanguages = { templ = "html" } },
})
lspconfig.html.setup({
on_attach = on_attach,
capabilities = capabilities,
filetypes = { "html", "templ" },
})
lspconfig.htmx.setup({
on_attach = on_attach,
capabilities = capabilities,
filetypes = { "html", "templ" },
})
local cmp = require 'cmp'
cmp.setup({
mapping = cmp.mapping.preset.insert({
['<C-b>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-Space>'] = cmp.mapping.complete(),
['<C-e>'] = cmp.mapping.abort(),
['<CR>'] = cmp.mapping.confirm({ select = true }),
}),
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
})
})
require'nvim-treesitter.configs'.setup {
ensure_installed = { "templ" },
sync_install = false,
auto_install = true,
ignore_install = { "javascript" },
highlight = {
enable = true,
},
}
```
## Vim
Currently support for vim is limited. Configure formatting with the following VimScript:
```vim
set autoread
autocmd BufWritePost *.templ silent! execute "!PATH=\"$PATH:$(go env GOPATH)/bin\" templ fmt <afile> >/dev/null 2>&1" | redraw!
```
## Helix
https://helix-editor.com/
Helix has built-in templ support in unstable since https://github.com/helix-editor/helix/pull/8540/commits/084628d3e0c29f4021f53b3e45997ae92033d2d2
It will be included in official releases after version 23.05.
## Emacs
[templ-ts-mode](https://github.com/danderson/templ-ts-mode) is a major mode for templ files that provides syntax highlighting, indentation, and the other usual major mode things. It is available on [MELPA](https://melpa.org/#/templ-ts-mode) and can be installed like any other Emacs package.
Templ support requires the [tree-sitter parser for Templ](https://github.com/vrischmann/tree-sitter-templ). If the parser is missing, the mode asks you on first use whether you want to download and build it via `treesit-install-language-grammar` (requires git and a C compiler).
## Troubleshooting
### Check that go, gopls and templ are installed and are present in the path
```bash
which go gopls templ
```
You should see 3 lines returned, showing the location of each binary:
```
/run/current-system/sw/bin/go
/Users/adrian/go/bin/gopls
/Users/adrian/bin/templ
```
### Check that you can run the templ binary
Run `templ lsp --help`, you should see help text.
* If you can't run the `templ` command at the command line:
* Check that the `templ` binary is within a directory that's in your path (`echo $PATH` for Linux/Mac/WSL, `$env:path` for Powershell).
* Update your profile to ensure that the change to your path applies to new shells and processes.
* On MacOS / Linux, you may need to update your `~/.zsh_profile`, `~/.bash_profile` or `~/.profile` file.
* On Windows, you will need to use the "Environment Variables" dialog. For WSL, use the Linux config.
* On MacOS / Linux, check that the file is executable and resolve it with `chmod +x /path/to/templ`.
* On MacOS, you might need to go through the steps at https://support.apple.com/en-gb/guide/mac-help/mh40616/mac to enable binaries from an "unidentified developer" to run.
* If you're running VS Code using Windows Subsystem for Linux (WSL), then templ must also be installed within the WSL environment, not just inside your Windows environment.
* If you're running VS Code in a Devcontainer, it must be installed in there.
### Enable LSP logging
For VS Code, use the "Preferences: Open User Settings (JSON)" command in VS Code and add the configuration options.
```js
{
// More settings...
"templ.log": "/Users/adrian/templ.log",
"templ.goplsLog": "/Users/adrian/gopls.log",
"templ.http": "localhost:7575",
"templ.goplsRPCTrace": true,
"templ.pprof": false,
// More stuff...
}
```
For Neovim, configure the LSP command to add the additional command line options.
```lua
local configs = require('lspconfig.configs')
configs.templ = {
default_config = {
cmd = { "templ", "lsp", "-http=localhost:7474", "-log=/Users/adrian/templ.log" },
filetypes = { 'templ' },
root_dir = nvim_lsp.util.root_pattern("go.mod", ".git"),
settings = {},
},
}
```
For IntelliJ, configure the plugin settings `.idea/templ.xml`.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="TemplSettings">
<option name="goplsLog" value="$USER_HOME$/gopls.log" />
<option name="goplsRPCTrace" value="true" />
<option name="http" value="localhost:7575" />
<option name="log" value="$USER_HOME$/templ.log" />
</component>
</project>
```
### Make a minimal reproduction, and include the logs
The logs can be quite verbose, since almost every keypress results in additional logging. If you're thinking about submitting an issue, please try and make a minimal reproduction.
### Look at the web server
The web server option provides an insight into the internal state of the language server. It may provide insight into what's going wrong.
### Run templ info
The `templ info` command outputs information that's useful for debugging issues.

View File

@@ -0,0 +1,160 @@
# Live reload
To access a Go web application that uses templ in a web browser, a few things must happen:
1. `templ generate` must be executed, to create Go code (`*_templ.go` files) from the `*.templ` files.
2. The Go code must start a web server on a port, e.g. (`http.ListenAndServe("localhost:8080", nil)`.
3. The Go program must be ran, e.g. by running `go run .`.
4. The web browser must access or reload the page, e.g. `http://localhost:8080`.
5. Content-Type must be text/html.
If the `*.templ` files change, #1 and #2 must be ran.
If the `*.go` files change, #3 and #4 must be ran.
## Built-in
`templ generate --watch` watches the current directory for changes and generates Go code if changes are detected.
To re-run your app automatically, add the `--cmd` argument to `templ generate`, and templ will start or restart your app using the command provided once template code generation is complete (#3).
To trigger your web browser to reload automatically (without pressing F5), set the `--proxy` argument (#4) to point at your app, and browse to the proxy address (default `http://localhost:7331`).
The `--proxy` argument starts a HTTP proxy which proxies requests to your app. For example, if your app runs on port 8080, you would use `--proxy="http://localhost:8080"`. The proxy inserts client-side JavaScript before the `</body>` tag that will cause the browser to reload the window when the app is restarted instead of you having to reload the page manually. Note that the html being served by the webserver MUST have a `<body>` tag, otherwise there will be no javascript injection thus making the browser not reload automatically. In addition, the script might not be inserted if templ cannot read and modify the http response, e.g. due to middleware implementation.
By default, the proxy binds to `127.0.0.1`. You can use `--proxybind` to bind to another address, e.g., `--proxybind="0.0.0.0"`.
Altogether, to setup live reload on an app that listens on port 8080, run the following.
```
templ generate --watch --proxy="http://localhost:8080" --cmd="go run ."
```
This will start the proxy server on port `7331` and open it in your default browser. If you'd like to prevent it from opening in your browser add the flag `--open-browser=false`.
```go title="main.go"
package main
import (
"fmt"
"net/http"
"github.com/a-h/templ"
)
func main() {
component := hello("World")
http.Handle("/", templ.Handler(component))
fmt.Println("Listening on :8080")
http.ListenAndServe(":8080", nil)
}
```
```templ title="hello.templ"
package main
templ hello(name string) {
<body>
<div>Hello, { name }</div>
</body>
}
```
The live reload process can be shown in the following diagram:
```mermaid
sequenceDiagram
browser->>templ_proxy: HTTP
activate templ_proxy
templ_proxy->>app: HTTP
activate app
app->>templ_proxy: HTML
deactivate app
templ_proxy->>templ_proxy: add reload script
templ_proxy->>browser: HTML
deactivate templ_proxy
browser->>templ_proxy: SSE request to /_templ/reload/events
activate templ_proxy
templ_proxy->>generate: run templ generate if *.templ files have changed
templ_proxy->>app: restart app if *.go files have changed
templ_proxy->>browser: notify browser to reload page
deactivate templ_proxy
```
### Triggering live reload from outside `templ generate --watch`
If you want to trigger a live reload from outside `templ generate --watch` (e.g. if you're using `air`, `wgo` or another tool to build, but you want to use the templ live reload proxy), you can use the `--notify-proxy` argument.
```bash
templ generate --notify-proxy
```
This will default to the default templ proxy address of `localhost:7331`, but can be changed with the `--proxybind` and `--proxyport` arguments.
```bash
templ generate --notify-proxy --proxybind="localhost" --proxyport="8080"
```
## Alternative 1: wgo
[wgo](https://github.com/bokwoon95/wgo):
> Live reload for Go apps. Watch arbitrary files and respond with arbitrary commands. Supports running multiple invocations in parallel.
```
wgo -file=.go -file=.templ -xfile=_templ.go templ generate :: go run main.go
```
To avoid a continuous reloading files ending with `_templ.go` should be skipped via `-xfile`.
## Alternative 2: air
Air can also monitor the filesystem for changes, and provides a proxy to automatically reload pages.
It uses a `toml` configuration file.
See https://github.com/cosmtrek/air for details.
### Example configuration
```toml title=".air.toml"
root = "."
tmp_dir = "tmp"
[build]
bin = "./tmp/main"
cmd = "templ generate && go build -o ./tmp/main ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor"]
exclude_file = []
exclude_regex = [".*_templ.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "templ", "html"]
kill_delay = "0s"
log = "build-errors.log"
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = false
[proxy]
enabled = true
proxy_port = 8383
app_port = 8282
```

View File

@@ -0,0 +1,212 @@
# Live reload with other tools
Browser live reload allows you to see your changes immediately without having to switch to your browser and press `F5` or `CMD+R`.
However, Web projects usually involve multiple build processes, e.g. css bundling, js bundling, alongside templ code generation and Go compilation.
Tools like `air` can be used with templ's built-in proxy server to carry out additional steps.
## Example
This example, demonstrates setting up a live reload environment that integrates:
- [Tailwind CSS](https://tailwindcss.com/) for generating a css bundle.
- [esbuild](https://esbuild.github.io/) for bundling JavaScript or TypeScript.
- [air](https://github.com/cosmtrek/air) for re-building Go source as well as sending a reload event to the `templ` proxy server.
## How does it work
templ's built-in proxy server automatically refreshes the browser when a file changes. The proxy server injects a script that reloads the page in the browser if a "reload" event is sent to the browser by the proxy. See [Live Reload page](/developer-tools/live-reload) for a detailed explanation.
:::tip
The live reload JavaScript is only injected by the templ proxy if your HTML file contains a closing `</body>` tag.
:::
The "reload" event can be triggered in two ways:
- `templ generate --watch` sends the event whenever a ".templ" file changes.
- Manually trigger it by sending a HTTP POST request to `/_templ/reload/event` endpoint. The `templ` CLI provides this via `templ generate --notify-proxy`.
:::tip
templ proxy server `--watch` mode generates different `_templ.go` files. In `--watch` mode `_templ.txt` files are generated that contain just the text that's in templ files. This is used to skip compilation of the Go code when only the text content changes.
:::
## Setting up the Makefile
A `Makefile` can be used to run all of the necessary commands in parallel. This is useful for starting all of the watch processes at once.
### templ watch mode
To start the `templ` proxy server in watch mode, run:
```bash
templ generate --watch --proxy="http://localhost:8080" --open-browser=false
```
This assumes that your http server is running on `http://localhost:8080`. `--open-browser=false` is to prevent `templ` from opening the browser automatically.
### Tailwind CSS
Tailwind requires a `tailwind.config.js` file at the root of your project, alongside an `input.css` file.
```bash
npx --yes tailwindcss -i ./input.css -o ./assets/styles.css --minify --watch
```
This will watch `input.css` as well as your `.templ` files and re-generate `assets/styles.css` whenever there's a change.
### esbuild
To bundle JavaScript, TypeScript, JSX, or TSX files, you can use `esbuild`:
```bash
npx --yes esbuild js/index.ts --bundle --outdir=assets/ --watch
```
This will watch `js/index.ts` and relevant files, and re-generate `assets/index.js` whenever there's a change.
### Re-build Go source
To watch and restart your Go server, when only the `go` files change you can use `air`:
```bash
go run github.com/cosmtrek/air@v1.51.0 \
--build.cmd "go build -o tmp/bin/main" --build.bin "tmp/bin/main" --build.delay "100" \
--build.exclude_dir "node_modules" \
--build.include_ext "go" \
--build.stop_on_error "false" \
--misc.clean_on_exit true
```
:::tip
Using `go run` directly allows the version of `air` to be specified. This ensures that the version of `air` is consistent between machines. In addition, you don't need to run `air init` to generate `.air.toml`.
:::
:::note
This command doesn't do anything to restart or send a reload event to the `templ` proxy server. We'll use a separate `air` command to trigger a notify event when any non-go related files change.
:::
### Reload event
We also want the browser to automatically reload when the:
1. HTML content changes
2. CSS bundle changes
3. JavaScript bundle changes
To trigger the event, we can use the `air` command to use a different set of options, using the `templ` CLI to send a reload event to the browser.
```bash
go run github.com/cosmtrek/air@v1.51.0 \
--build.cmd "templ generate --notify-proxy" \
--build.bin "true" \
--build.delay "100" \
--build.exclude_dir "" \
--build.include_dir "assets" \
--build.include_ext "js,css"
```
:::note
The `build.bin` option is set to use the `true` command instead of executing the output of the `build.cmd` option, because the `templ generate --notify-proxy` command doesn't build anything, it just sends a reload event to the `templ` proxy server.
`true` is a command that exits with a zero status code, so you might see `Process Exit with Code 0` printed to the console.
:::
### Serving static assets
When using live reload, static assets must be served directly from the filesystem instead of being embedded in the Go binary, because the Go binary won't be re-built when the assets change.
In practice this means using `http.Dir` instead of `http.FS` to serve your assets.
If you don't want to do this, you can add additional asset file extensions to the `--build.include_ext` argument of the `air` command that rebuilds Go code to force a recompilation and restart of the Go server when the assets change.
#### Before
```go
//go:embed assets/*
var assets embed.FS
...
mux.Handle("/assets/", http.FileServer(http.FS(assets)))
```
#### After
```go
mux.Handle("/assets/",
http.StripPrefix("/assets",
http.FileServer(http.Dir("assets"))))
```
:::tip
Web browsers will cache assets when they receive a HTTP 304 response. This will result in asset changes not being visible within your application.
To avoid this, set the `Cache-Control` header to `no-store` for assets in development mode:
```go
var dev = true
func disableCacheInDevMode(next http.Handler) http.Handler {
if !dev {
return next
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
next.ServeHTTP(w, r)
})
}
mux.Handle("/assets/",
disableCacheInDevMode(
http.StripPrefix("/assets",
http.FileServer(http.Dir("assets")))))
```
:::
## Putting it all together
A `Makefile` can be used to run all of the commands in parallel.
```make
# run templ generation in watch mode to detect all .templ files and
# re-create _templ.txt files on change, then send reload event to browser.
# Default url: http://localhost:7331
live/templ:
templ generate --watch --proxy="http://localhost:8080" --open-browser=false -v
# run air to detect any go file changes to re-build and re-run the server.
live/server:
go run github.com/cosmtrek/air@v1.51.0 \
--build.cmd "go build -o tmp/bin/main" --build.bin "tmp/bin/main" --build.delay "100" \
--build.exclude_dir "node_modules" \
--build.include_ext "go" \
--build.stop_on_error "false" \
--misc.clean_on_exit true
# run tailwindcss to generate the styles.css bundle in watch mode.
live/tailwind:
npx --yes tailwindcss -i ./input.css -o ./assets/styles.css --minify --watch
# run esbuild to generate the index.js bundle in watch mode.
live/esbuild:
npx --yes esbuild js/index.ts --bundle --outdir=assets/ --watch
# watch for any js or css change in the assets/ folder, then reload the browser via templ proxy.
live/sync_assets:
go run github.com/cosmtrek/air@v1.51.0 \
--build.cmd "templ generate --notify-proxy" \
--build.bin "true" \
--build.delay "100" \
--build.exclude_dir "" \
--build.include_dir "assets" \
--build.include_ext "js,css"
# start all 5 watch processes in parallel.
live:
make -j5 live/templ live/server live/tailwind live/esbuild live/sync_assets
```
:::note
The `-j5` argument to `make` runs all 5 commands in parallel.
:::
Run `make live` to start all of the watch processes.

View File

@@ -0,0 +1,13 @@
# Coding assistants / LLMs
To provide AI coding assistants such as GitHub Copilot, Cursor or similar with help on how to write templ code, the templ project maintains a single file containing documentation for LLMs to read.
You can find the file at `https://templ.guide/llms.md`.
## LLM tools
### https://github.com/CopilotC-Nvim/CopilotChat.nvim
CopilotChat is a plugin for Neovim that provides a chat interface for GitHub Copilot. It allows you to ask Copilot questions and get responses in real-time.
Use the URL feature to load `https://templ.guide/llms.md`.

View File

@@ -0,0 +1,12 @@
# Ensuring templ files have been committed
It's common practice to commit generated `*_templ.go` files to your source code repository, so that your codebase is always in a state where it can be built and run without needing to run `templ generate`, e.g. by running `go install` on your project, or by importing it as a dependency in another project.
In your CI/CD pipeline, if you want to check that `templ generate` has been ran on all templ files (with the same version of templ used by the CI/CD pipeline), you can run `templ generate` again.
If any files have changed, then the pipeline should fail, as this would indicate that the generated files are not up-to-date with the templ files.
```bash
templ generate
git diff --exit-code
```

View File

@@ -0,0 +1,4 @@
{
"position": 9,
"label": "Developer tools"
}

View File

@@ -0,0 +1,87 @@
# Injection attacks
templ is designed to prevent user-provided data from being used to inject vulnerabilities.
`<script>` and `<style>` tags could allow user data to inject vulnerabilities, so variables are not permitted in these sections.
```html
templ Example() {
<script>
function showAlert() {
alert("hello");
}
</script>
<style type="text/css">
/* Only CSS is allowed */
</style>
}
```
`onClick` attributes, and other `on*` attributes are used to execute JavaScript. To prevent user data from being unescaped, `on*` attributes accept a `templ.ComponentScript`.
```html
script onClickHandler(msg string) {
alert(msg);
}
templ Example(msg string) {
<div onClick={ onClickHandler(msg) }>
{ "will be HTML encoded using templ.Escape" }
</div>
}
```
Style attributes cannot be expressions, only constants, to avoid escaping vulnerabilities. templ style templates (`css className()`) should be used instead.
```html
templ Example() {
<div style={ "will throw an error" }></div>
}
```
Class names are sanitized by default. A failed class name is replaced by `--templ-css-class-safe-name`. The sanitization can be bypassed using the `templ.SafeClass` function, but the result is still subject to escaping.
```html
templ Example() {
<div class={ "unsafe</style&gt;-will-sanitized", templ.SafeClass("&sanitization bypassed") }></div>
}
```
Rendered output:
```html
<div class="--templ-css-class-safe-name &amp;sanitization bypassed"></div>
```
```html
templ Example() {
<div>Node text is not modified at all.</div>
<div>{ "will be escaped using templ.EscapeString" }</div>
}
```
`href` attributes must be a `templ.SafeURL` and are sanitized to remove JavaScript URLs unless bypassed.
```html
templ Example() {
<a href="http://constants.example.com/are/not/sanitized">Text</a>
<a href={ templ.URL("will be sanitized by templ.URL to remove potential attacks") }</a>
<a href={ templ.SafeURL("will not be sanitized by templ.URL") }</a>
}
```
Within css blocks, property names, and constant CSS property values are not sanitized or escaped.
```css
css className() {
background-color: #ffffff;
}
```
CSS property values based on expressions are passed through `templ.SanitizeCSS` to replace potentially unsafe values with placeholders.
```css
css className() {
color: { red };
}
```

View File

@@ -0,0 +1,86 @@
# Content security policy
## Nonces
In templ [script templates](/syntax-and-usage/script-templates#script-templates) are rendered as inline `<script>` tags.
Strict Content Security Policies (CSP) can prevent these inline scripts from executing.
By setting a nonce attribute on the `<script>` tag, and setting the same nonce in the CSP header, the browser will allow the script to execute.
:::info
It's your responsibility to generate a secure nonce. Nonces should be generated using a cryptographically secure random number generator.
See https://content-security-policy.com/nonce/ for more information.
:::
## Setting a nonce
The `templ.WithNonce` function can be used to set a nonce for templ to use when rendering scripts.
It returns an updated `context.Context` with the nonce set.
In this example, the `alert` function is rendered as a script element by templ.
```templ title="templates.templ"
package main
import "context"
import "os"
script onLoad() {
alert("Hello, world!")
}
templ template() {
@onLoad()
}
```
```go title="main.go"
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func withNonce(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nonce := securelyGenerateRandomString()
w.Header().Add("Content-Security-Policy", fmt.Sprintf("script-src 'nonce-%s'", nonce))
// Use the context to pass the nonce to the handler.
ctx := templ.WithNonce(r.Context(), nonce)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func main() {
mux := http.NewServeMux()
// Handle template.
mux.HandleFunc("/", templ.Handler(template()))
// Apply middleware.
withNonceMux := withNonce(mux)
// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", withNonceMux); err != nil {
log.Printf("error listening: %v", err)
}
}
```
```html title="Output"
<script nonce="randomly generated nonce">
function __templ_onLoad_5a85() {
alert("Hello, world!")
}
</script>
<script nonce="randomly generated nonce">
__templ_onLoad_5a85()
</script>
```

View File

@@ -0,0 +1,7 @@
# Code signing
Binaries are created by the GitHub Actions workflow at https://github.com/a-h/templ/blob/main/.github/workflows/release.yml
Binaries are signed by cosign. The public key is stored in the repository at https://github.com/a-h/templ/blob/main/cosign.pub
Instructions for key verification at https://docs.sigstore.dev/verifying/verify/

View File

@@ -0,0 +1,4 @@
{
"position": 10,
"label": "Security"
}

View File

@@ -0,0 +1,4 @@
{
"position": 11,
"label": "Media and talks"
}

View File

@@ -0,0 +1,33 @@
# Media and talks
# Go Podcast 2024
https://gopodcast.dev/episodes/adrian-hesketh-and-joe-davidson-on-templ
# Gophercon 2024
Go Full Stack Server-Side Rendering vs SPAs - Fernando J. Villamarin Diaz, JPMC
<iframe width="560" height="315" src="https://www.youtube.com/embed/X30eAwuUgrE?si=agO05C0M_d2TlkdZ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
# Big Sky Dev Con 2024
Covers the reason for creating templ, how it works, and how to use it.
<iframe width="560" height="315" src="https://www.youtube.com/embed/uVKSmR_hBMs?si=yacWP-H43ib_J2d4&amp;start=7635" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
# Go Time
<audio data-theme="night" data-src="https://changelog.com/gotime/291/embed" src="https://op3.dev/e/https://cdn.changelog.com/uploads/gotime/291/go-time-291.mp3" preload="none" class="changelog-episode" controls></audio><p><a href="https://changelog.com/gotime/291">Go Time 291: Go templating using Templ</a> Listen on <a href="https://changelog.com/">Changelog.com</a></p><script async src="//cdn.changelog.com/embed.js"></script>
# Gophercon UK 2023
This talk covers Language Server Protocol from the ground up, and how templ's language server works with gopls.
<iframe width="560" height="315" src="https://www.youtube.com/embed/EkK8Jxjj95s?si=ZrT26jb-lItk6FiB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
# How To Setup A Golang + Templ Project Structure
This tutorial shows how to create a simple web app using templ and the echo router.
<iframe width="560" height="315" src="https://www.youtube.com/embed/wttTTFVrQiw?si=ri-7Pzsaq53xXwvK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

View File

@@ -0,0 +1,106 @@
# Web frameworks
Templ is framework agnostic but that does not mean it can not be used with Go frameworks and other tools.
Below are some examples of how to use templ with other Go libraries, frameworks and tools, and links to systems that have built-in templ support.
### Chi
See an example of using https://github.com/go-chi/chi with templ at:
https://github.com/a-h/templ/tree/main/examples/integration-chi
### Echo
See an example of using https://echo.labstack.com/ with templ at:
https://github.com/a-h/templ/tree/main/examples/integration-echo
### Gin
See an example of using https://github.com/gin-gonic/gin with templ at:
https://github.com/a-h/templ/tree/main/examples/integration-gin
### Go Fiber
See an example of using https://github.com/gofiber/fiber with templ at:
https://github.com/a-h/templ/tree/main/examples/integration-gofiber
### github.com/gorilla/csrf
`gorilla/csrf` is a HTTP middleware library that provides cross-site request forgery (CSRF) protection.
Follow the instructions at https://github.com/gorilla/csrf to add it to your project, by using the library as HTTP middleware.
```go title="main.go"
package main
import (
"crypto/rand"
"fmt"
"net/http"
"github.com/gorilla/csrf"
)
func mustGenerateCSRFKey() (key []byte) {
key = make([]byte, 32)
n, err := rand.Read(key)
if err != nil {
panic(err)
}
if n != 32 {
panic("unable to read 32 bytes for CSRF key")
}
return
}
func main() {
r := http.NewServeMux()
r.Handle("/", templ.Handler(Form()))
csrfMiddleware := csrf.Protect(mustGenerateCSRFKey())
withCSRFProtection := csrfMiddleware(r)
fmt.Println("Listening on localhost:8000")
http.ListenAndServe("localhost:8000", withCSRFProtection)
}
```
Creating a `CSRF` templ component makes it easy to include the CSRF token in your forms.
```templ title="form.templ"
templ Form() {
<h1>CSRF Example</h1>
<form method="post" action="/">
@CSRF()
<div>
If you inspect the HTML form, you will see a hidden field with the value: { ctx.Value("gorilla.csrf.Token").(string) }
</div>
<input type="submit" value="Submit with CSRF token"/>
</form>
<form method="post" action="/">
<div>
You can also submit the form without the CSRF token to validate that the CSRF protection is working.
</div>
<input type="submit" value="Submit without CSRF token"/>
</form>
}
templ CSRF() {
<input type="hidden" name="gorilla.csrf.Token" value={ ctx.Value("gorilla.csrf.Token").(string) }/>
}
```
## Project scaffolding
- Gowebly - https://github.com/gowebly/gowebly
- Go-blueprint - https://github.com/Melkeydev/go-blueprint
- Slick - https://github.com/anthdm/slick
## Other templates
### `template/html`
See [Using with Go templates](../syntax-and-usage/using-with-go-templates)

View File

@@ -0,0 +1,95 @@
# Internationalization
templ can be used with 3rd party internationalization libraries.
## ctxi18n
https://github.com/invopop/ctxi18n uses the context package to load strings based on the selected locale.
An example is available at https://github.com/a-h/templ/tree/main/examples/internationalization
### Storing translations
Translations are stored in YAML files, according to the language.
```yaml title="locales/en/en.yaml"
en:
hello: "Hello"
select_language: "Select Language"
```
### Selecting the language
HTTP middleware selects the language to load based on the URL path, `/en`, `/de`, etc.
```go title="main.go"
func newLanguageMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
lang := "en" // Default language
pathSegments := strings.Split(r.URL.Path, "/")
if len(pathSegments) > 1 {
lang = pathSegments[1]
}
ctx, err := ctxi18n.WithLocale(r.Context(), lang)
if err != nil {
log.Printf("error setting locale: %v", err)
http.Error(w, "error setting locale", http.StatusBadRequest)
return
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
```
### Using the middleware
The `ctxi18n.Load` function is used to load the translations, and the middleware is used to set the language.
```go title="main.go"
func main() {
if err := ctxi18n.Load(locales.Content); err != nil {
log.Fatalf("error loading locales: %v", err)
}
mux := http.NewServeMux()
mux.Handle("/", templ.Handler(page()))
withLanguageMiddleware := newLanguageMiddleware(mux)
log.Println("listening on :8080")
if err := http.ListenAndServe("127.0.0.1:8080", withLanguageMiddleware); err != nil {
log.Printf("error listening: %v", err)
}
}
```
### Fetching translations in templates
Translations are fetched using the `i18n.T` function, passing the implicit context that's available in all templ components, and the key for the translation.
```templ
package main
import (
"github.com/invopop/ctxi18n/i18n"
)
templ page() {
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ i18n.T(ctx, "hello") }</title>
</head>
<body>
<h1>{ i18n.T(ctx, "hello") }</h1>
<h2>{ i18n.T(ctx, "select_language") }</h2>
<ul>
<li><a href="/en">English</a></li>
<li><a href="/de">Deutsch</a></li>
<li><a href="/zh-cn">中文</a></li>
</ul>
</body>
</html>
}
```

View File

@@ -0,0 +1,4 @@
{
"position": 12,
"label": "Integrations"
}

View File

@@ -0,0 +1,14 @@
# Experimental packages
Experimental Packages for templ are available at https://github.com/templ-go/x/
:::warning
- Packages in this module are experimental and may be removed at any time.
- There is no guarantee of compatibility with future versions.
- There is no guarantee of stability.
- Use at your own risk.
:::
## Approval Process
As of right now, there is no formal approval process for packages to be stabilized and moved into https://github.com/a-h/templ. Feel free to contribute via GitHub discussions at https://github.com/a-h/templ/discussions

View File

@@ -0,0 +1,31 @@
# urlbuilder
A simple URL builder to construct a `templ.SafeURL`.
```templ title="component.templ"
import (
"github.com/templ-go/x/urlbuilder"
"strconv"
"strings"
)
templ component(o Order) {
<a
href={ urlbuilder.New("https", "example.com").
Path("orders").
Path(o.ID).
Path("line-items").
Query("page", strconv.Itoa(1)).
Query("limit", strconv.Itoa(10)).
Build() }
>
{ strings.ToUpper(o.Name) }
</a>
}
```
See [URL Attributes](/syntax-and-usage/attributes#url-attributes) for more information.
## Feedback
Please leave your feedback on this feature at https://github.com/a-h/templ/discussions/867

View File

@@ -0,0 +1,4 @@
{
"position": 13,
"label": "Experimental"
}

View File

@@ -0,0 +1,4 @@
{
"position": 14,
"label": "Help and community"
}

View File

@@ -0,0 +1,13 @@
# Getting help
For help from the community, talking about new ideas, and general discussion:
## Slack
Use the #templ channel in the Gopher Slack community.
https://invite.slack.golangbridge.org/
## GitHub Discussion
https://github.com/a-h/templ/discussions

View File

@@ -0,0 +1,4 @@
{
"position": 15,
"label": "FAQ"
}

View File

@@ -0,0 +1,7 @@
# FAQ
## How can I migrate from templ version 0.1.x to templ 0.2.x syntax?
Versions of templ &lt;= v0.2.663 include a `templ migrate` command that can migrate v1 syntax to v2.
The v1 syntax used some extra characters for variable injection, e.g. `{%= name %}` whereas the latest (v2) syntax uses a single pair of braces within HTML, e.g. `{ name }`.

8
templ/docs/docs/go.mod Normal file
View File

@@ -0,0 +1,8 @@
module github.com/a-h/templ/docs
go 1.20
require (
github.com/gosimple/slug v1.13.1 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
)

4
templ/docs/docs/go.sum Normal file
View File

@@ -0,0 +1,4 @@
github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=

30
templ/docs/docs/index.md Normal file
View File

@@ -0,0 +1,30 @@
---
sidebar_position: 1
---
# Introduction
## templ - build HTML with Go
Create components that render fragments of HTML and compose them to create screens, pages, documents, or apps.
* Server-side rendering: Deploy as a serverless function, Docker container, or standard Go program.
* Static rendering: Create static HTML files to deploy however you choose.
* Compiled code: Components are compiled into performant Go code.
* Use Go: Call any Go code, and use standard `if`, `switch`, and `for` statements.
* No JavaScript: Does not require any client or server-side JavaScript.
* Great developer experience: Ships with IDE autocompletion.
```templ
package main
templ Hello(name string) {
<div>Hello, { name }</div>
}
templ Greeting(person Person) {
<div class="greeting">
@Hello(person.Name)
</div>
}
```

109
templ/docs/docs/main.go Normal file
View File

@@ -0,0 +1,109 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/gosimple/slug"
)
type Section struct {
Name string
SubItems []string
}
type ItemToCreate struct {
Name string
Path string
IsFile bool
Content string
}
func main() {
sections := []Section{
{Name: "Quick Start", SubItems: []string{"Installation", "Creating a simple templ component", "Running your first templ application"}},
{Name: "Syntax and Usage", SubItems: []string{"Basic syntax", "Expressions", "Conditional HTML attribute expressions", "Loops", "Template composition", "CSS style management"}},
{Name: "Core Concepts", SubItems: []string{"Components", "Template generation", "Conditional rendering", "Rendering lists", "Code-only components"}},
{Name: "Components", SubItems: []string{"Creating and organizing components", "Adding HTML markup and Go code in templ", "Configuring components with parameters"}},
{Name: "Using Go Functions and Variables", SubItems: []string{}},
{Name: "Server-side Rendering", SubItems: []string{"Creating an HTTP server with templ", "Example: Counter application"}},
{Name: "Static Rendering", SubItems: []string{"Generating static HTML files with templ", "Deploying static files"}},
{Name: "Hosting and Deployment", SubItems: []string{"Hosting on AWS Lambda", "Hosting using Docker"}},
{Name: "Commands and Tools", SubItems: []string{"templ generate", "templ fmt", "templ lsp"}},
{Name: "Advanced Topics", SubItems: []string{"Code-only components", "Source maps", "Storybook integration"}},
{Name: "Tutorials and Examples", SubItems: []string{"Tutorial: Counter application", "Tutorial: Blog application"}},
{Name: "API Reference", SubItems: []string{"templ.Component", "templ.Handler"}},
{Name: "Frequently Asked Questions", SubItems: []string{}},
{Name: "Contributing and Support", SubItems: []string{}},
{Name: "Best Practices", SubItems: []string{"Keeping templ components pure and avoiding bugs"}},
{Name: "Conclusion", SubItems: []string{"Summary and next steps for learning more about templ"}},
}
var items []ItemToCreate
for i, section := range sections {
i += 1
current := ItemToCreate{
Name: section.Name,
Path: fmt.Sprintf("%02d-%s", i+1, slug.Make(section.Name)),
IsFile: false,
}
items = append(items, current)
// Add category.json
items = append(items, ItemToCreate{
Path: fmt.Sprintf("%s/_category_.json", current.Path),
IsFile: true,
Content: categoryJSON(i+1, section.Name),
})
if len(section.SubItems) == 0 {
fileItem := ItemToCreate{
Name: section.Name,
Path: fmt.Sprintf("%s/index.md", current.Path),
IsFile: true,
Content: fmt.Sprintf("# %s\n", section.Name),
}
items = append(items, fileItem)
}
for j, subItem := range section.SubItems {
fileItem := ItemToCreate{
Name: subItem,
Path: fmt.Sprintf("%s/%02d-%s.md", current.Path, j+1, slug.Make(subItem)),
IsFile: true,
Content: fmt.Sprintf("# %s\n", subItem),
}
items = append(items, fileItem)
}
}
for _, item := range items {
fmt.Println(item.Path)
if item.IsFile {
os.WriteFile(item.Path, []byte(item.Content), 0644)
continue
}
os.Mkdir(item.Path, 0755)
}
}
func categoryJSON(position int, label string) string {
sw := new(strings.Builder)
enc := json.NewEncoder(sw)
enc.SetIndent("", " ")
err := enc.Encode(Category{
Position: position,
Label: label,
})
if err != nil {
panic("failed to create JSON: " + err.Error())
}
return sw.String()
}
type Category struct {
Position int `json:"position"`
Label string `json:"label"`
}

View File

@@ -0,0 +1,102 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer').themes.github;
const darkCodeTheme = require('prism-react-renderer').themes.dracula;
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'templ docs',
tagline: 'A language for writing HTML user interfaces in Go. ',
favicon: 'img/favicon.ico',
// Set the production url of your site here
url: 'https://templ.guide',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'a-h', // Usually your GitHub org/user name.
projectName: 'templ', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
markdown: {
mermaid: true,
},
themes: ['@docusaurus/theme-mermaid'],
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
routeBasePath: '/',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/a-h/templ/tree/main/docs/',
},
blog: false,
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
image: 'img/social-card.jpg',
navbar: {
logo: {
alt: 'Templ Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Docs',
},
{
href: 'https://github.com/a-h/templ',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
copyright: `Copyright © ${new Date().getFullYear()} Adrian Hesketh, Built with Docusaurus.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
additionalLanguages: ['nix', 'bash', 'json'],
},
algolia: {
appId: 'PVCVW9GL1Z',
apiKey: '0823e4b4272c719b5338ed75843f38ef',
indexName: 'templ',
},
}),
};
module.exports = config;

19020
templ/docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

49
templ/docs/package.json Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "npm run generate-llms-md && docusaurus start",
"build": "npm run generate-llms-md && docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"generate-llms-md": "find docs | grep '.*md$' | xargs cat > ./static/llms.md"
},
"dependencies": {
"@docusaurus/core": "^3.6.3",
"@docusaurus/preset-classic": "^3.6.3",
"@docusaurus/theme-mermaid": "^3.6.3",
"@mdx-js/react": "^3.0.1",
"clsx": "2.1.1",
"prism-react-renderer": "^2.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.6.3",
"@docusaurus/tsconfig": "^3.6.3",
"@types/react": "^18.3.3",
"prismjs": "^1.29.0",
"typescript": "~5.5.3"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=18.2"
}
}

33
templ/docs/sidebars.js Normal file
View File

@@ -0,0 +1,33 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
};
module.exports = sidebars;

View File

@@ -0,0 +1,34 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #008391;
--ifm-color-primary-dark: #007380;
--ifm-color-primary-darker: #006570;
--ifm-color-primary-darkest: #005761;
--ifm-color-primary-light: #0093A3;
--ifm-color-primary-lighter: #00A5B8;
--ifm-color-primary-lightest: #00B3C7;
--ifm-code-font-size: 95%;
--ifm-footer-background-color: #003238;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
.footer--dark {
--ifm-footer-background-color: #173E42;
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #DBBC30;
--ifm-color-primary-dark: #D0B125;
--ifm-color-primary-darker: #BA9E21;
--ifm-color-primary-darkest: #A0881C;
--ifm-color-primary-light: #E7C946;
--ifm-color-primary-lighter: #F1D65F;
--ifm-color-primary-lightest: #F7E078;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

View File

@@ -0,0 +1,171 @@
import siteConfig from '@generated/docusaurus.config';
export default function prismIncludeLanguages(PrismObject) {
const {
themeConfig: {
prism
},
} = siteConfig;
const {
additionalLanguages
} = prism;
// Prism components work on the Prism instance on the window, while prism-
// react-renderer uses its own Prism instance. We temporarily mount the
// instance onto window, import components to enhance it, then remove it to
// avoid polluting global namespace.
// You can mutate PrismObject: registering plugins, deleting languages... As
// long as you don't re-assign it
globalThis.Prism = PrismObject;
additionalLanguages.forEach((lang) => {
// eslint-disable-next-line global-require, import/no-dynamic-require
require(`prismjs/components/prism-${lang}`);
});
var go = globalThis.Prism.languages.extend('go', {
'keyword': /\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var|templ|css|script)\b/,
});
var space = /(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source;
var braces = /(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source;
var spread = /(?:\{<S>*\.{3}(?:(?!\{)[^{}]|<BRACES>)*\})/.source;
/**
* @param {string} source
* @param {string} [flags]
*/
function re(source, flags) {
source = source
.replace(/<S>/g, function() {
return space;
})
.replace(/<BRACES>/g, function() {
return braces;
})
.replace(/<SPREAD>/g, function() {
return spread;
});
return RegExp(source, flags);
}
spread = re(spread).source;
globalThis.Prism.languages.templ = globalThis.Prism.languages.extend('markup', go);
globalThis.Prism.languages.templ.tag.pattern = re(
/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source
);
globalThis.Prism.languages.templ.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/;
globalThis.Prism.languages.templ.tag.inside['attr-value'].pattern = /=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/;
globalThis.Prism.languages.templ.tag.inside['tag'].inside['class-name'] = /^[A-Z]\w*(?:\.[A-Z]\w*)*$/;
globalThis.Prism.languages.templ.tag.inside['comment'] = go['comment'];
globalThis.Prism.languages.insertBefore('inside', 'attr-name', {
'spread': {
pattern: re(/<SPREAD>/.source),
inside: globalThis.Prism.languages.templ
}
}, globalThis.Prism.languages.templ.tag);
globalThis.Prism.languages.insertBefore('inside', 'special-attr', {
'script': {
// Allow for two levels of nesting
pattern: re(/=<BRACES>/.source),
alias: 'language-go',
inside: {
'script-punctuation': {
pattern: /^=(?=\{)/,
alias: 'punctuation'
},
rest: globalThis.Prism.languages.templ
},
}
}, globalThis.Prism.languages.templ.tag);
// The following will handle plain text inside tags
var stringifyToken = function(token) {
if (!token) {
return '';
}
if (typeof token === 'string') {
return token;
}
if (typeof token.content === 'string') {
return token.content;
}
return token.content.map(stringifyToken).join('');
};
var walkTokens = function(tokens) {
var openedTags = [];
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
var notTagNorBrace = false;
if (typeof token !== 'string') {
if (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') {
// We found a tag, now find its kind
if (token.content[0].content[0].content === '</') {
// Closing tag
if (openedTags.length > 0 && openedTags[openedTags.length - 1].tagName === stringifyToken(token.content[0].content[1])) {
// Pop matching opening tag
openedTags.pop();
}
} else {
if (token.content[token.content.length - 1].content === '/>') {
// Autoclosed tag, ignore
} else {
// Opening tag
openedTags.push({
tagName: stringifyToken(token.content[0].content[1]),
openedBraces: 0
});
}
}
} else if (openedTags.length > 0 && token.type === 'punctuation' && token.content === '{') {
// Here we might have entered a templ context inside a tag
openedTags[openedTags.length - 1].openedBraces++;
} else if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces > 0 && token.type === 'punctuation' && token.content === '}') {
// Here we might have left a templ context inside a tag
openedTags[openedTags.length - 1].openedBraces--;
} else {
notTagNorBrace = true;
}
}
if (notTagNorBrace || typeof token === 'string') {
if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) {
// Here we are inside a tag, and not inside a templ context.
// That's plain text: drop any tokens matched.
var plainText = stringifyToken(token);
// And merge text with adjacent text
if (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) {
plainText += stringifyToken(tokens[i + 1]);
tokens.splice(i + 1, 1);
}
if (i > 0 && (typeof tokens[i - 1] === 'string' || tokens[i - 1].type === 'plain-text')) {
plainText = stringifyToken(tokens[i - 1]) + plainText;
tokens.splice(i - 1, 1);
i--;
}
tokens[i] = new globalThis.Prism.Token('plain-text', plainText, null, plainText);
}
}
if (token.content && typeof token.content !== 'string') {
walkTokens(token.content);
}
}
};
globalThis.Prism.hooks.add('after-tokenize', function(env) {
if (env.language !== 'templ') {
return;
}
walkTokens(env.tokens);
});
}

0
templ/docs/static/.nojekyll vendored Normal file
View File

BIN
templ/docs/static/img/docusaurus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
templ/docs/static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

86
templ/docs/static/img/logo.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 41 KiB

BIN
templ/docs/static/img/shadowdom.webm vendored Normal file

Binary file not shown.

BIN
templ/docs/static/img/social-card.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -0,0 +1,171 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
<title>Easy to Use</title>
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,170 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
<title>Powered by React</title>
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
</g>
<g id="React-icon" transform="translate(906.3 541.56)">
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
<title>Focus on What Matters</title>
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 12 KiB