🚧 Created first version from the 'home' code snippets

This commit is contained in:
Maxim Lebedev 2023-12-12 18:52:03 +06:00
parent 6e2b2a61a5
commit b5fcd48dbd
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
20 changed files with 1511 additions and 0 deletions

View File

@ -0,0 +1,44 @@
---
on:
push:
branches:
- master
- develop
env:
DOCKER_REGISTRY: source.toby3d.me
jobs:
docker:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: https://gitea.com/actions/checkout@v3
- name: Set up QEMU
uses: https://gitea.com/docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: https://gitea.com/docker/setup-buildx-action@v2
- name: Login to registry
uses: https://gitea.com/docker/login-action@v2
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ gitea.repository_owner }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
uses: https://gitea.com/docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: "" # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./build/Dockerfile
push: true
tags: ${{ env.DOCKER_REGISTRY }}/${{ gitea.repository }}:${{ gitea.ref_name }}

28
build/Dockerfile Normal file
View File

@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1
# docker build --rm -f build/Dockerfile -t source.toby3d.me/toby3d/pay .
# Build
FROM golang:alpine AS builder
WORKDIR /app
ENV CGO_ENABLED=0
ENV GOFLAGS="-mod=vendor -buildvcs=true"
COPY go.mod go.sum *.go ./
COPY internal ./internal/
COPY vendor ./vendor/
COPY web ./web/
RUN go build -o ./pay
# Run
FROM scratch
WORKDIR /
COPY --from=builder /app/pay /pay
EXPOSE 3000
ENTRYPOINT ["/pay"]

12
go.mod Normal file
View File

@ -0,0 +1,12 @@
module source.toby3d.me/toby3d/pay
go 1.21.5
require (
github.com/caarlos0/env/v10 v10.0.0
github.com/google/go-cmp v0.6.0
github.com/valyala/quicktemplate v1.7.0
golang.org/x/text v0.14.0
)
require github.com/valyala/bytebufferpool v1.0.0 // indirect

27
go.sum Normal file
View File

@ -0,0 +1,27 @@
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -0,0 +1,78 @@
package pay
import (
"context"
"fmt"
"io/fs"
"log"
"net/http"
"strconv"
"time"
"source.toby3d.me/toby3d/pay/internal/common"
"source.toby3d.me/toby3d/pay/internal/domain"
paymenthttpdelivery "source.toby3d.me/toby3d/pay/internal/payment/delivery/http"
"source.toby3d.me/toby3d/pay/internal/urlutil"
)
type Service struct {
server *http.Server
}
func New(logger *log.Logger, static fs.FS, config domain.Config) *Service {
handler := paymenthttpdelivery.NewHandler()
fileServer := http.FileServer(http.FS(static))
return &Service{
server: &http.Server{
Addr: config.Bind,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch head, _ := urlutil.ShiftPath(r.URL.Path); head {
default:
parsed, err := strconv.ParseFloat(head, 64)
if err != nil {
fileServer.ServeHTTP(w, r)
return
}
// NOTE(toby3d): Take my money? No way!
if parsed < 0 {
http.Redirect(w, r, fmt.Sprint("/", parsed*-1),
http.StatusMovedPermanently)
return
}
fallthrough
case "":
handler.ServeHTTP(w, r)
case "manifest.webmanifest":
w.Header().Set(common.HeaderContentType,
common.MIMEApplicationManifestJSONCharsetUTF8)
fileServer.ServeHTTP(w, r)
}
}),
DisableGeneralOptionsHandler: false,
TLSConfig: nil,
ReadTimeout: 500 * time.Millisecond,
ReadHeaderTimeout: 500 * time.Millisecond,
WriteTimeout: 500 * time.Millisecond,
IdleTimeout: 0,
MaxHeaderBytes: 0,
TLSNextProto: nil,
ConnState: nil,
ErrorLog: logger,
BaseContext: nil,
ConnContext: nil,
},
}
}
func (s *Service) Run() error {
return s.server.ListenAndServe()
}
func (s *Service) Stop(ctx context.Context) error {
return s.server.Shutdown(ctx)
}

16
internal/common/common.go Normal file
View File

@ -0,0 +1,16 @@
package common
const (
HeaderAcceptLanguage string = "Accept-Language"
HeaderContentLanguage string = "Content-Language"
HeaderContentType string = "Content-Type"
)
const (
MIMETextHTML string = "text/html"
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
MIMEApplicationManifestJSON string = "application/manifest+json"
MIMEApplicationManifestJSONCharsetUTF8 string = MIMEApplicationManifestJSON + "; " + charsetUTF8
)
const charsetUTF8 string = "charset=UTF-8"

10
internal/domain/config.go Normal file
View File

@ -0,0 +1,10 @@
package domain
import (
"net/url"
)
type Config struct {
BaseURL *url.URL `env:"BASE_URL" envDefault:"http://localhost:3000/"`
Bind string `env:"BIND" envDefault:":3000"`
}

View File

@ -0,0 +1,52 @@
package http
import (
"fmt"
"net/http"
"strconv"
"golang.org/x/text/language"
"golang.org/x/text/message"
"source.toby3d.me/toby3d/pay/internal/common"
"source.toby3d.me/toby3d/pay/internal/urlutil"
"source.toby3d.me/toby3d/pay/web/template"
)
type Handler struct {
matcher language.Matcher
}
func NewHandler() *Handler {
return &Handler{
matcher: language.NewMatcher(append([]language.Tag{language.English},
message.DefaultCatalog.Languages()...)),
}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tags, _, _ := language.ParseAcceptLanguage(r.Header.Get(common.HeaderAcceptLanguage))
tag, _, _ := h.matcher.Match(tags...)
var amount uint64
if head, _ := urlutil.ShiftPath(r.URL.Path); head != "" {
parsed, err := strconv.ParseFloat(head, 64)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// NOTE(toby3d): Take my money? No way!
if parsed < 0 {
http.Redirect(w, r, fmt.Sprint("/", parsed*-1), http.StatusMovedPermanently)
return
}
amount = uint64(parsed * 100)
}
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
template.WriteTemplate(w, template.NewContext(tag, amount))
}

View File

@ -0,0 +1,22 @@
package urlutil
import (
"path"
"strings"
)
// ShiftPath splits off the first component of p, which will be cleaned of
// relative components before processing. head will never contain a slash and
// tail will always be a rooted path without trailing slash.
//
// See: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
func ShiftPath(p string) (head, tail string) {
p = path.Clean("/" + p)
i := strings.Index(p[1:], "/") + 1
if i <= 0 {
return p[1:], "/"
}
return p[1:i], p[i:]
}

View File

@ -0,0 +1,36 @@
package urlutil_test
import (
"testing"
"source.toby3d.me/toby3d/pay/internal/urlutil"
)
func TestShiftPath(t *testing.T) {
t.Parallel()
for name, tc := range map[string]struct {
input, expectHead, expectTail string
}{
"root": {"/", "", "/"},
"file": {"/foo", "foo", "/"},
"dir": {"/foo/", "foo", "/"},
"dir-file": {"/foo/bar", "foo", "/bar"},
"subdir": {"/foo/bar/", "foo", "/bar"},
} {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()
head, tail := urlutil.ShiftPath(tc.input)
if head != tc.expectHead {
t.Errorf("ShiftPath(%s) = '%s', want '%s'", tc.input, head, tc.expectHead)
}
if tail != tc.expectTail {
t.Errorf("ShiftPath(%s) = '%s', want '%s'", tc.input, tail, tc.expectTail)
}
})
}
}

View File

@ -0,0 +1,46 @@
{
"language": "en",
"messages": [
{
"id": "Donate ${Amount__100} to {Toby3d}",
"message": "Donate ${Amount__100} to {Toby3d}",
"translation": {
"select": {
"feature": "plural",
"arg": "Amount__100",
"cases": {
"=0": {
"msg": "Donate to {Toby3d}"
},
"=1": {
"msg": "Donate onedollar™ to {Toby3d}"
},
"other": {
"msg": "Donate ${Amount__100} to {Toby3d}"
}
}
}
},
"translatorComment": "Copied from source.",
"placeholders": [
{
"id": "Amount__100",
"string": "%[1].2f",
"type": "float64",
"underlyingType": "float64",
"argNum": 1,
"expr": "ctx.amount / 100"
},
{
"id": "Toby3d",
"string": "%[2]s",
"type": "string",
"underlyingType": "string",
"argNum": 2,
"expr": "\"toby3d\""
}
],
"fuzzy": true
}
]
}

View File

@ -0,0 +1,46 @@
{
"language": "en",
"messages": [
{
"id": "Donate ${Amount__100} to {Toby3d}",
"message": "Donate ${Amount__100} to {Toby3d}",
"translation": {
"select": {
"feature": "plural",
"arg": "Amount__100",
"cases": {
"=0": {
"msg": "Donate to {Toby3d}"
},
"=1": {
"msg": "Donate onedollar™ to {Toby3d}"
},
"other": {
"msg": "Donate ${Amount__100} to {Toby3d}"
}
}
}
},
"translatorComment": "Copied from source.",
"placeholders": [
{
"id": "Amount__100",
"string": "%.2[1]f",
"type": "float64",
"underlyingType": "float64",
"argNum": 1,
"expr": "ctx.amount / 100"
},
{
"id": "Toby3d",
"string": "%[2]s",
"type": "string",
"underlyingType": "string",
"argNum": 2,
"expr": "\"toby3d\""
}
],
"fuzzy": true
}
]
}

View File

@ -0,0 +1,44 @@
{
"language": "ru",
"messages": [
{
"id": "Donate ${Amount__100} to {Toby3d}",
"message": "Donate ${Amount__100} to {Toby3d}",
"translation": {
"select": {
"feature": "plural",
"arg": "Amount__100",
"cases": {
"=0": {
"msg": "Пожертвовать {Toby3d}"
},
"=1": {
"msg": "Пожертвовать долор™ {Toby3d}"
},
"other": {
"msg": "Пожертвовать ${Amount__100} {Toby3d}"
}
}
}
},
"placeholders": [
{
"id": "Amount__100",
"string": "%[1].2f",
"type": "float64",
"underlyingType": "float64",
"argNum": 1,
"expr": "ctx.amount / 100"
},
{
"id": "Toby3d",
"string": "%[2]s",
"type": "string",
"underlyingType": "string",
"argNum": 2,
"expr": "\"toby3d\""
}
]
}
]
}

View File

@ -0,0 +1,44 @@
{
"language": "ru",
"messages": [
{
"id": "Donate ${Amount__100} to {Toby3d}",
"message": "Donate ${Amount__100} to {Toby3d}",
"translation": {
"select": {
"feature": "plural",
"arg": "Amount__100",
"cases": {
"=0": {
"msg": "Пожертвовать {Toby3d}"
},
"=1": {
"msg": "Пожертвовать долор™ {Toby3d}"
},
"other": {
"msg": "Пожертвовать ${Amount__100} {Toby3d}"
}
}
}
},
"placeholders": [
{
"id": "Amount__100",
"string": "%.2[1]f",
"type": "float64",
"underlyingType": "float64",
"argNum": 1,
"expr": "ctx.amount / 100"
},
{
"id": "Toby3d",
"string": "%[2]s",
"type": "string",
"underlyingType": "string",
"argNum": 2,
"expr": "\"toby3d\""
}
]
}
]
}

61
locales_gen.go Normal file
View File

@ -0,0 +1,61 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
package main
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/message/catalog"
)
type dictionary struct {
index []uint32
data string
}
func (d *dictionary) Lookup(key string) (data string, ok bool) {
p, ok := messageKeyToIndex[key]
if !ok {
return "", false
}
start, end := d.index[p], d.index[p+1]
if start == end {
return "", false
}
return d.data[start:end], true
}
func init() {
dict := map[string]catalog.Dictionary{
"en": &dictionary{index: enIndex, data: enData},
"ru": &dictionary{index: ruIndex, data: ruData},
}
fallback := language.MustParse("en")
cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
if err != nil {
panic(err)
}
message.DefaultCatalog = cat
}
var messageKeyToIndex = map[string]int{
"Donate $%.2f to %s": 0,
}
var enIndex = []uint32{ // 2 elements
0x00000000, 0x00000053,
} // Size: 32 bytes
const enData string = "" + // Size: 83 bytes
"\x14\x01\x81\x01\x02=\x00\x10\x02Donate to %[2]s=\x01\x1d\x02Donate oned" +
"ollar™ to %[2]s\x00\x19\x02Donate $%.2[1]f to %[2]s"
var ruIndex = []uint32{ // 2 elements
0x00000000, 0x00000081,
} // Size: 32 bytes
const ruData string = "" + // Size: 129 bytes
"\x14\x01\x81\x01\x02=\x00\x1f\x02Пожертвовать %[2]s=\x01-\x02Пожертвоват" +
"ь долор™ %[2]s\x00(\x02Пожертвовать $%.2[1]f %[2]s"
// Total table size 276 bytes (0KiB); checksum: 3BFB37FB

90
main.go Normal file
View File

@ -0,0 +1,90 @@
//go:generate go install github.com/valyala/quicktemplate/qtc@master
//go:generate qtc -dir=web
//go:generate go install golang.org/x/text/cmd/gotext@master
//go:generate gotext -srclang=en update -lang=en,ru -out=locales_gen.go
package main
import (
"context"
"embed"
"flag"
"io/fs"
"log"
"os"
"os/signal"
"path/filepath"
"runtime"
"runtime/pprof"
"syscall"
"github.com/caarlos0/env/v10"
"source.toby3d.me/toby3d/pay/internal/cmd/pay"
"source.toby3d.me/toby3d/pay/internal/domain"
)
//go:embed web/static/*
var embedStatic embed.FS
var static fs.FS = os.DirFS(filepath.Join("web", "static"))
func main() {
logger := log.New(os.Stdout, "pay\t", log.LstdFlags)
// static, err := fs.Sub(static, filepath.Join("web", "static"))
// if err != nil {
// logger.Fatalln(err)
// }
cpuProfilePath := flag.String("cpuprofile", "", "set path to saving CPU memory profile")
memProfilePath := flag.String("memprofile", "", "set path to saving pprof memory profile")
flag.Parse()
config := new(domain.Config)
if err := env.ParseWithOptions(config, env.Options{Prefix: "PAY_"}); err != nil {
logger.Fatalln(err)
}
app := pay.New(logger, static, *config)
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
if cpuProfilePath != nil && *cpuProfilePath != "" {
cpuProfile, err := os.Create(*cpuProfilePath)
if err != nil {
logger.Fatalln("could not create CPU profile:", err)
}
defer cpuProfile.Close()
if err = pprof.StartCPUProfile(cpuProfile); err != nil {
logger.Fatalln("could not start CPU profile:", err)
}
defer pprof.StopCPUProfile()
}
go app.Run()
<-done
if err := app.Stop(context.Background()); err != nil {
logger.Fatalln(err)
}
if memProfilePath == nil && *memProfilePath == "" {
return
}
memProfile, err := os.Create(*memProfilePath)
if err != nil {
logger.Fatalln("could not create memory profile:", err)
}
defer memProfile.Close()
runtime.GC() // NOTE(toby3d): get up-to-date statistics
if err = pprof.WriteHeapProfile(memProfile); err != nil {
logger.Fatalln("could not write memory profile:", err)
}
}

View File

@ -0,0 +1,36 @@
{
"dir": "ltr",
"lang": "en",
"name": "NotDotPay",
"short_name": "not.pay",
"scope": "/",
"icons": [
{
"sizes": "192x192",
"src": "/icon-192.png",
"type": "image/png",
"purpose": "maskable"
},
{
"sizes": "512x512",
"src": "/icon-512.png",
"type": "image/png",
"purpose": "maskable"
}
],
"display": "minimal-ui",
"orientation": "natural",
"start_url": ".",
"id": "/",
"theme_color": "#000",
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.paypal.android.p2pmobile",
"id": "com.paypal.android.p2pmobile"
}
],
"prefer_related_applications": false,
"background_color": "#fff",
"shortcuts": []
}

160
web/static/styles.css Normal file
View File

@ -0,0 +1,160 @@
:root {
--ratio: 1.5;
--s-5: calc(var(--s-4) / var(--ratio));
--s-4: calc(var(--s-3) / var(--ratio));
--s-3: calc(var(--s-2) / var(--ratio));
--s-2: calc(var(--s-1) / var(--ratio));
--s-1: calc(var(--s0) / var(--ratio));
--s0: 1rem;
--s1: calc(var(--s0) * var(--ratio));
--s2: calc(var(--s1) * var(--ratio));
--s3: calc(var(--s2) * var(--ratio));
--s4: calc(var(--s3) * var(--ratio));
--s5: calc(var(--s4) * var(--ratio));
--measure: 60ch;
/* System font stack, see: https://systemfontstack.com/ */
font-family:
-apple-system,
BlinkMacSystemFont,
avenir next,
avenir,
segoe ui,
helvetica neue,
helvetica,
Cantarell,
Ubuntu,
roboto,
noto,
arial,
sans-serif;
font-size: calc(1rem + 0.5vw);
line-height: var(--ratio);
}
* {
box-sizing: border-box;
max-inline-size: var(--measure);
}
html,
body,
div,
header,
nav,
main,
footer {
max-inline-size: none;
}
/* Button */
.button,
button {
--_background: var(--background, #ccc);
--_color: var(--color, #000);
/* NOTE(toby3d): reset everything */
all: unset;
/* NOTE(toby3d): start from scratch */
-moz-appearance: none;
-webkit-appearance: none;
align-items: baseline;
appearance: none;
background: var(--_background);
border-radius: 0.3125em;
border: 0;
color: var(--_color);
cursor: pointer;
display: inline-flex;
font-family: inherit;
font-size: 1rem;
justify-content: center;
min-width: 6.25em;
padding: 0.625em 1em;
text-align: center;
text-decoration: none;
}
.button[href*='liberapay.com'],
button[href*='liberapay.com'] {
--background: #f6c915;
--color: #1a171b;
}
.button[href*='paypal.me'],
button[href*='paypal.me'] {
--background: #ffd140;
--color: #001435;
}
.button .icon,
.button svg,
button .icon,
button svg {
margin-inline-end: 0.25rem;
height: 0.75em;
height: 1cap;
width: 0.75em;
width: 1cap;
}
/* Button behavior, see: https://bitsofco.de/when-do-the-hover-focus-and-active-pseudo-classes-apply/ */
.button:hover,
button:hover {
/* TODO(toby3d): better color mathing with good WCAG grading */
--background: #1d49aa;
}
/* See: https://bitsofco.de/when-is-focus-visible-visible/ */
.button:focus-visible,
.button:focus,
button:focus-visible,
button:focus {
outline: 4px solid #cbd6ee;
}
/* See: https://bitsofco.de/when-is-focus-visible-visible/ */
.button:focus:not(:focus-visible),
button:focus:not(:focus-visible) {
outline: none;
}
.button:active,
button:active {
/* TODO(toby3d): better color mathing with good WCAG grading */
--background: green;
}
.button:not([href]),
button[disabled] {
/* TODO(toby3d): better color mathing with good WCAG grading */
--background: #6c7589;
--color: #d2d5db;
cursor: not-allowed;
}
.button:not([href]) .icon,
.button:not([href]) svg,
button[disabled] .icon,
button[disabled] svg {
filter: grayscale(1);
}
/* Cluster */
.cluster {
--_align: var(--align, flex-start);
--_justify: var(--justify, flex-start);
--_space: var(--space, var(--s1));
align-items: var(--_align);
display: flex;
flex-wrap: wrap;
gap: var(--_space);
justify-content: var(--_justify);
}
/* Utilities */
.list-style-type\:none {
list-style-type: none;
}
.padding-inline-start\:unset {
padding-inline-start: unset;
}

142
web/template/template.qtpl Normal file
View File

@ -0,0 +1,142 @@
{% package template %}
{% import (
"golang.org/x/text/language"
"golang.org/x/text/message"
) %}
{% interface Page {
body()
dir()
lang()
t(format message.Reference, v ...any)
title()
head()
} %}
{% code
type Context struct {
language language.Tag
printer *message.Printer
amount float64
}
func NewContext(lang language.Tag, amount uint64) *Context {
return &Context{
language: lang,
printer: message.NewPrinter(lang),
amount: float64(amount),
}
}
%}
{% stripspace %}
{% func (ctx Context) head() %}
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="preload stylesheet"{% space %}
as="style"{% space %}
href="/styles.css"{% space %}
type="text/css"{% space %}
fetchpriority="high"{% space %}
referrerpolicy="no-referrer"{% space %}
crossorigin="anonymous">
{% endfunc %}
{% func (ctx Context) body() %}
<h1>{%= ctx.title() %}</h1>
<ul class="[ cluster ][ list-style-type:none padding-inline-start:unset ]">
<li>
<a class="button"{% space %}
rel="noopener noreferrer payment"{% space %}
href="https://liberapay.com/toby3d/donate{% if ctx.amount > 0 %}?amount={%f.2 ctx.amount/100 %}&currency=USD&period=monthly{% endif %}">
{%= icon("liberapay") %}
Liberapay
</a>
</li>
<li>
<a class="button"{% space %}
rel="noopener noreferrer payment"{% space %}
href="https://www.paypal.me/toby3dKZ{% if ctx.amount > 0 %}/{%f.2 ctx.amount/100 %}USD{% endif %}">
{%= icon("paypal") %}
PayPal
</a>
</span>
</li>
</ul>
{% endfunc %}
{% func (ctx Context) dir() %}
{% switch ctx.language %}
{% default %}
ltr
{% case language.Arabic, language.Persian, language.Hebrew, language.Urdu %}
rtl
{% endswitch %}
{% endfunc %}
{% func (ctx Context) lang() %}
{% code base, _ := ctx.language.Base() %}
{%s base.String() %}
{% endfunc %}
{% func (ctx Context) t(format message.Reference, v ...any) %}
{%s ctx.printer.Sprintf(format, v...) %}
{% endfunc %}
{% func (ctx Context) title() %}
{%= ctx.t(`Donate $%.2f to %s`, ctx.amount/100, "toby3d") %}
{% endfunc %}
{% func Template(p Page) %}
<!DOCTYPE html>
<html lang="{%= p.lang() %}" dir="{%= p.dir() %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{%= p.title() %}</title>
{%= p.head() %}
</head>
<body>
{%= p.body() %}
</body>
</html>
{% endfunc %}
{% func icon(id string) %}
{% switch id %}
{% case "liberapay" %}
<svg class="icon"{% space %}
width="32"{% space %}
height="32"{% space %}
viewBox="0 0 80 80"{% space %}
aria-hidden="true"{% space %}
focusable="false"{% space %}
xmlns="http://www.w3.org/2000/svg">
<path fill="#1a171b" d="M25.91 63.04c-3.57 0-6.37-.47-8.41-1.4a9.03 9.03 0 0 1-4.38-3.8 11.15 11.15 0 0 1-1.28-5.55c.03-2.08.32-4.31.87-6.67L22.3 5.57l11.69-1.81L23.5 47.2c-.2.9-.32 1.73-.34 2.49-.03.75.11 1.42.41 2 .3.57.82 1.04 1.55 1.39a8.7 8.7 0 0 0 3.05.68l-2.26 9.28m42.24-24.96c0 3.67-.6 7.03-1.81 10.07a23.94 23.94 0 0 1-5.01 7.88 22.43 22.43 0 0 1-7.7 5.17 25.4 25.4 0 0 1-9.76 1.85c-1.71 0-3.42-.16-5.13-.46l-3.4 13.65H24.19L36.7 24.05a67.6 67.6 0 0 1 6.9-1.62c2.6-.48 5.4-.71 8.42-.71 2.81 0 5.24.42 7.27 1.28a13.6 13.6 0 0 1 5.02 3.5 14.32 14.32 0 0 1 2.9 5.21c.63 1.99.95 4.11.95 6.37M40.78 53.54c.85.2 1.91.3 3.17.3 1.96 0 3.74-.36 5.35-1.09a11.8 11.8 0 0 0 4.11-3.05 14.1 14.1 0 0 0 2.64-4.72c.63-1.83.95-3.86.95-6.07 0-2.16-.48-4-1.44-5.5-.95-1.51-2.61-2.27-4.97-2.27-1.61 0-3.12.15-4.53.46l-5.28 21.94" />
</svg>
{% case "paypal" %}
<svg class="icon"{% space %}
width="32"{% space %}
height="32"{% space %}
viewBox="0 0 154.7 190.5"{% space %}
aria-hidden="true"{% space %}
focusable="false"{% space %}
xmlns="http://www.w3.org/2000/svg">
{% comment %}Left blue{% endcomment %}
<path fill="#003087" d="M28 0a5.5 5.5 0 0 0-5.5 4.6L.1 147.2a4.5 4.5 0 0 0 4.4 5.2h33.3l8.3-52.5 9-57.2a5.5 5.5 0 0 1 5.4-4.6h47.8c8.7 0 16.6 2 23.4 5.6C132 19.7 112.4 0 85.3 0z" />
{% comment %}Middle blue{% endcomment %}
<path fill="#001c64" d="M60.5 38.1a5.5 5.5 0 0 0-5.4 4.6l-9 57.2-8.3 52.5 8.3-52.5a5.5 5.5 0 0 1 5.4-4.6H78a54 54 0 0 0 53.8-51.6 50 50 0 0 0-23.4-5.6z" />
{% comment %}Right blue{% endcomment %}
<path fill="#0070e0" d="M131.7 43.7a54 54 0 0 1-53.8 51.6H51.5c-2.7 0-5 2-5.4 4.6l-8.3 52.5-5.2 33a4.5 4.5 0 0 0 4.4 5.1h28.7a5.5 5.5 0 0 0 5.4-4.6l7.6-48a5.5 5.5 0 0 1 5.4-4.5H101a54 54 0 0 0 53.2-45.7c3-18.7-6.5-35.6-22.5-44z" />
</svg>
{% endswitch %}
{% endfunc %}
{% endstripspace %}

View File

@ -0,0 +1,517 @@
// Code generated by qtc from "template.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line web/template/template.qtpl:1
package template
//line web/template/template.qtpl:3
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
)
//line web/template/template.qtpl:8
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line web/template/template.qtpl:8
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line web/template/template.qtpl:8
type Page interface {
//line web/template/template.qtpl:8
body() string
//line web/template/template.qtpl:8
streambody(qw422016 *qt422016.Writer)
//line web/template/template.qtpl:8
writebody(qq422016 qtio422016.Writer)
//line web/template/template.qtpl:8
dir() string
//line web/template/template.qtpl:8
streamdir(qw422016 *qt422016.Writer)
//line web/template/template.qtpl:8
writedir(qq422016 qtio422016.Writer)
//line web/template/template.qtpl:8
lang() string
//line web/template/template.qtpl:8
streamlang(qw422016 *qt422016.Writer)
//line web/template/template.qtpl:8
writelang(qq422016 qtio422016.Writer)
//line web/template/template.qtpl:8
t(format message.Reference, v ...any) string
//line web/template/template.qtpl:8
streamt(qw422016 *qt422016.Writer, format message.Reference, v ...any)
//line web/template/template.qtpl:8
writet(qq422016 qtio422016.Writer, format message.Reference, v ...any)
//line web/template/template.qtpl:8
title() string
//line web/template/template.qtpl:8
streamtitle(qw422016 *qt422016.Writer)
//line web/template/template.qtpl:8
writetitle(qq422016 qtio422016.Writer)
//line web/template/template.qtpl:8
head() string
//line web/template/template.qtpl:8
streamhead(qw422016 *qt422016.Writer)
//line web/template/template.qtpl:8
writehead(qq422016 qtio422016.Writer)
//line web/template/template.qtpl:8
}
//line web/template/template.qtpl:18
type Context struct {
language language.Tag
printer *message.Printer
amount float64
}
func NewContext(lang language.Tag, amount uint64) *Context {
return &Context{
language: lang,
printer: message.NewPrinter(lang),
amount: float64(amount),
}
}
//line web/template/template.qtpl:34
func (ctx Context) streamhead(qw422016 *qt422016.Writer) {
//line web/template/template.qtpl:34
qw422016.N().S(`<link rel="icon" href="/favicon.ico" sizes="32x32"><link rel="icon" href="/icon.svg" type="image/svg+xml"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><link rel="manifest" href="/manifest.webmanifest"><link rel="preload stylesheet"`)
//line web/template/template.qtpl:39
qw422016.N().S(` `)
//line web/template/template.qtpl:39
qw422016.N().S(`as="style"`)
//line web/template/template.qtpl:40
qw422016.N().S(` `)
//line web/template/template.qtpl:40
qw422016.N().S(`href="/styles.css"`)
//line web/template/template.qtpl:41
qw422016.N().S(` `)
//line web/template/template.qtpl:41
qw422016.N().S(`type="text/css"`)
//line web/template/template.qtpl:42
qw422016.N().S(` `)
//line web/template/template.qtpl:42
qw422016.N().S(`fetchpriority="high"`)
//line web/template/template.qtpl:43
qw422016.N().S(` `)
//line web/template/template.qtpl:43
qw422016.N().S(`referrerpolicy="no-referrer"`)
//line web/template/template.qtpl:44
qw422016.N().S(` `)
//line web/template/template.qtpl:44
qw422016.N().S(`crossorigin="anonymous">`)
//line web/template/template.qtpl:46
}
//line web/template/template.qtpl:46
func (ctx Context) writehead(qq422016 qtio422016.Writer) {
//line web/template/template.qtpl:46
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:46
ctx.streamhead(qw422016)
//line web/template/template.qtpl:46
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:46
}
//line web/template/template.qtpl:46
func (ctx Context) head() string {
//line web/template/template.qtpl:46
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:46
ctx.writehead(qb422016)
//line web/template/template.qtpl:46
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:46
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:46
return qs422016
//line web/template/template.qtpl:46
}
//line web/template/template.qtpl:48
func (ctx Context) streambody(qw422016 *qt422016.Writer) {
//line web/template/template.qtpl:48
qw422016.N().S(`<h1>`)
//line web/template/template.qtpl:49
ctx.streamtitle(qw422016)
//line web/template/template.qtpl:49
qw422016.N().S(`</h1><ul class="[ cluster ][ list-style-type:none padding-inline-start:unset ]"><li><a class="button"`)
//line web/template/template.qtpl:53
qw422016.N().S(` `)
//line web/template/template.qtpl:53
qw422016.N().S(`rel="noopener noreferrer payment"`)
//line web/template/template.qtpl:54
qw422016.N().S(` `)
//line web/template/template.qtpl:54
qw422016.N().S(`href="https://liberapay.com/toby3d/donate`)
//line web/template/template.qtpl:55
if ctx.amount > 0 {
//line web/template/template.qtpl:55
qw422016.N().S(`?amount=`)
//line web/template/template.qtpl:55
qw422016.N().FPrec(ctx.amount/100, 2)
//line web/template/template.qtpl:55
qw422016.N().S(`&currency=USD&period=monthly`)
//line web/template/template.qtpl:55
}
//line web/template/template.qtpl:55
qw422016.N().S(`">`)
//line web/template/template.qtpl:57
streamicon(qw422016, "liberapay")
//line web/template/template.qtpl:57
qw422016.N().S(`Liberapay</a></li><li><a class="button"`)
//line web/template/template.qtpl:62
qw422016.N().S(` `)
//line web/template/template.qtpl:62
qw422016.N().S(`rel="noopener noreferrer payment"`)
//line web/template/template.qtpl:63
qw422016.N().S(` `)
//line web/template/template.qtpl:63
qw422016.N().S(`href="https://www.paypal.me/toby3dKZ`)
//line web/template/template.qtpl:64
if ctx.amount > 0 {
//line web/template/template.qtpl:64
qw422016.N().S(`/`)
//line web/template/template.qtpl:64
qw422016.N().FPrec(ctx.amount/100, 2)
//line web/template/template.qtpl:64
qw422016.N().S(`USD`)
//line web/template/template.qtpl:64
}
//line web/template/template.qtpl:64
qw422016.N().S(`">`)
//line web/template/template.qtpl:66
streamicon(qw422016, "paypal")
//line web/template/template.qtpl:66
qw422016.N().S(`PayPal</a></span></li></ul>`)
//line web/template/template.qtpl:72
}
//line web/template/template.qtpl:72
func (ctx Context) writebody(qq422016 qtio422016.Writer) {
//line web/template/template.qtpl:72
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:72
ctx.streambody(qw422016)
//line web/template/template.qtpl:72
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:72
}
//line web/template/template.qtpl:72
func (ctx Context) body() string {
//line web/template/template.qtpl:72
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:72
ctx.writebody(qb422016)
//line web/template/template.qtpl:72
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:72
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:72
return qs422016
//line web/template/template.qtpl:72
}
//line web/template/template.qtpl:74
func (ctx Context) streamdir(qw422016 *qt422016.Writer) {
//line web/template/template.qtpl:75
switch ctx.language {
//line web/template/template.qtpl:76
default:
//line web/template/template.qtpl:76
qw422016.N().S(`ltr`)
//line web/template/template.qtpl:78
case language.Arabic, language.Persian, language.Hebrew, language.Urdu:
//line web/template/template.qtpl:78
qw422016.N().S(`rtl`)
//line web/template/template.qtpl:80
}
//line web/template/template.qtpl:81
}
//line web/template/template.qtpl:81
func (ctx Context) writedir(qq422016 qtio422016.Writer) {
//line web/template/template.qtpl:81
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:81
ctx.streamdir(qw422016)
//line web/template/template.qtpl:81
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:81
}
//line web/template/template.qtpl:81
func (ctx Context) dir() string {
//line web/template/template.qtpl:81
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:81
ctx.writedir(qb422016)
//line web/template/template.qtpl:81
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:81
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:81
return qs422016
//line web/template/template.qtpl:81
}
//line web/template/template.qtpl:83
func (ctx Context) streamlang(qw422016 *qt422016.Writer) {
//line web/template/template.qtpl:84
base, _ := ctx.language.Base()
//line web/template/template.qtpl:85
qw422016.E().S(base.String())
//line web/template/template.qtpl:86
}
//line web/template/template.qtpl:86
func (ctx Context) writelang(qq422016 qtio422016.Writer) {
//line web/template/template.qtpl:86
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:86
ctx.streamlang(qw422016)
//line web/template/template.qtpl:86
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:86
}
//line web/template/template.qtpl:86
func (ctx Context) lang() string {
//line web/template/template.qtpl:86
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:86
ctx.writelang(qb422016)
//line web/template/template.qtpl:86
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:86
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:86
return qs422016
//line web/template/template.qtpl:86
}
//line web/template/template.qtpl:88
func (ctx Context) streamt(qw422016 *qt422016.Writer, format message.Reference, v ...any) {
//line web/template/template.qtpl:89
qw422016.E().S(ctx.printer.Sprintf(format, v...))
//line web/template/template.qtpl:90
}
//line web/template/template.qtpl:90
func (ctx Context) writet(qq422016 qtio422016.Writer, format message.Reference, v ...any) {
//line web/template/template.qtpl:90
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:90
ctx.streamt(qw422016, format, v...)
//line web/template/template.qtpl:90
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:90
}
//line web/template/template.qtpl:90
func (ctx Context) t(format message.Reference, v ...any) string {
//line web/template/template.qtpl:90
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:90
ctx.writet(qb422016, format, v...)
//line web/template/template.qtpl:90
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:90
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:90
return qs422016
//line web/template/template.qtpl:90
}
//line web/template/template.qtpl:92
func (ctx Context) streamtitle(qw422016 *qt422016.Writer) {
//line web/template/template.qtpl:93
ctx.streamt(qw422016, `Donate $%.2f to %s`, ctx.amount/100, "toby3d")
//line web/template/template.qtpl:94
}
//line web/template/template.qtpl:94
func (ctx Context) writetitle(qq422016 qtio422016.Writer) {
//line web/template/template.qtpl:94
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:94
ctx.streamtitle(qw422016)
//line web/template/template.qtpl:94
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:94
}
//line web/template/template.qtpl:94
func (ctx Context) title() string {
//line web/template/template.qtpl:94
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:94
ctx.writetitle(qb422016)
//line web/template/template.qtpl:94
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:94
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:94
return qs422016
//line web/template/template.qtpl:94
}
//line web/template/template.qtpl:96
func StreamTemplate(qw422016 *qt422016.Writer, p Page) {
//line web/template/template.qtpl:96
qw422016.N().S(`<!DOCTYPE html><html lang="`)
//line web/template/template.qtpl:98
p.streamlang(qw422016)
//line web/template/template.qtpl:98
qw422016.N().S(`" dir="`)
//line web/template/template.qtpl:98
p.streamdir(qw422016)
//line web/template/template.qtpl:98
qw422016.N().S(`"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>`)
//line web/template/template.qtpl:102
p.streamtitle(qw422016)
//line web/template/template.qtpl:102
qw422016.N().S(`</title>`)
//line web/template/template.qtpl:103
p.streamhead(qw422016)
//line web/template/template.qtpl:103
qw422016.N().S(`</head><body>`)
//line web/template/template.qtpl:106
p.streambody(qw422016)
//line web/template/template.qtpl:106
qw422016.N().S(`</body></html>`)
//line web/template/template.qtpl:109
}
//line web/template/template.qtpl:109
func WriteTemplate(qq422016 qtio422016.Writer, p Page) {
//line web/template/template.qtpl:109
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:109
StreamTemplate(qw422016, p)
//line web/template/template.qtpl:109
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:109
}
//line web/template/template.qtpl:109
func Template(p Page) string {
//line web/template/template.qtpl:109
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:109
WriteTemplate(qb422016, p)
//line web/template/template.qtpl:109
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:109
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:109
return qs422016
//line web/template/template.qtpl:109
}
//line web/template/template.qtpl:111
func streamicon(qw422016 *qt422016.Writer, id string) {
//line web/template/template.qtpl:112
switch id {
//line web/template/template.qtpl:113
case "liberapay":
//line web/template/template.qtpl:113
qw422016.N().S(`<svg class="icon"`)
//line web/template/template.qtpl:114
qw422016.N().S(` `)
//line web/template/template.qtpl:114
qw422016.N().S(`width="32"`)
//line web/template/template.qtpl:115
qw422016.N().S(` `)
//line web/template/template.qtpl:115
qw422016.N().S(`height="32"`)
//line web/template/template.qtpl:116
qw422016.N().S(` `)
//line web/template/template.qtpl:116
qw422016.N().S(`viewBox="0 0 80 80"`)
//line web/template/template.qtpl:117
qw422016.N().S(` `)
//line web/template/template.qtpl:117
qw422016.N().S(`aria-hidden="true"`)
//line web/template/template.qtpl:118
qw422016.N().S(` `)
//line web/template/template.qtpl:118
qw422016.N().S(`focusable="false"`)
//line web/template/template.qtpl:119
qw422016.N().S(` `)
//line web/template/template.qtpl:119
qw422016.N().S(`xmlns="http://www.w3.org/2000/svg"><path fill="#1a171b" d="M25.91 63.04c-3.57 0-6.37-.47-8.41-1.4a9.03 9.03 0 0 1-4.38-3.8 11.15 11.15 0 0 1-1.28-5.55c.03-2.08.32-4.31.87-6.67L22.3 5.57l11.69-1.81L23.5 47.2c-.2.9-.32 1.73-.34 2.49-.03.75.11 1.42.41 2 .3.57.82 1.04 1.55 1.39a8.7 8.7 0 0 0 3.05.68l-2.26 9.28m42.24-24.96c0 3.67-.6 7.03-1.81 10.07a23.94 23.94 0 0 1-5.01 7.88 22.43 22.43 0 0 1-7.7 5.17 25.4 25.4 0 0 1-9.76 1.85c-1.71 0-3.42-.16-5.13-.46l-3.4 13.65H24.19L36.7 24.05a67.6 67.6 0 0 1 6.9-1.62c2.6-.48 5.4-.71 8.42-.71 2.81 0 5.24.42 7.27 1.28a13.6 13.6 0 0 1 5.02 3.5 14.32 14.32 0 0 1 2.9 5.21c.63 1.99.95 4.11.95 6.37M40.78 53.54c.85.2 1.91.3 3.17.3 1.96 0 3.74-.36 5.35-1.09a11.8 11.8 0 0 0 4.11-3.05 14.1 14.1 0 0 0 2.64-4.72c.63-1.83.95-3.86.95-6.07 0-2.16-.48-4-1.44-5.5-.95-1.51-2.61-2.27-4.97-2.27-1.61 0-3.12.15-4.53.46l-5.28 21.94" /></svg>`)
//line web/template/template.qtpl:124
case "paypal":
//line web/template/template.qtpl:124
qw422016.N().S(`<svg class="icon"`)
//line web/template/template.qtpl:125
qw422016.N().S(` `)
//line web/template/template.qtpl:125
qw422016.N().S(`width="32"`)
//line web/template/template.qtpl:126
qw422016.N().S(` `)
//line web/template/template.qtpl:126
qw422016.N().S(`height="32"`)
//line web/template/template.qtpl:127
qw422016.N().S(` `)
//line web/template/template.qtpl:127
qw422016.N().S(`viewBox="0 0 154.7 190.5"`)
//line web/template/template.qtpl:128
qw422016.N().S(` `)
//line web/template/template.qtpl:128
qw422016.N().S(`aria-hidden="true"`)
//line web/template/template.qtpl:129
qw422016.N().S(` `)
//line web/template/template.qtpl:129
qw422016.N().S(`focusable="false"`)
//line web/template/template.qtpl:130
qw422016.N().S(` `)
//line web/template/template.qtpl:130
qw422016.N().S(`xmlns="http://www.w3.org/2000/svg">`)
//line web/template/template.qtpl:133
qw422016.N().S(`<path fill="#003087" d="M28 0a5.5 5.5 0 0 0-5.5 4.6L.1 147.2a4.5 4.5 0 0 0 4.4 5.2h33.3l8.3-52.5 9-57.2a5.5 5.5 0 0 1 5.4-4.6h47.8c8.7 0 16.6 2 23.4 5.6C132 19.7 112.4 0 85.3 0z" />`)
//line web/template/template.qtpl:135
qw422016.N().S(`<path fill="#001c64" d="M60.5 38.1a5.5 5.5 0 0 0-5.4 4.6l-9 57.2-8.3 52.5 8.3-52.5a5.5 5.5 0 0 1 5.4-4.6H78a54 54 0 0 0 53.8-51.6 50 50 0 0 0-23.4-5.6z" />`)
//line web/template/template.qtpl:137
qw422016.N().S(`<path fill="#0070e0" d="M131.7 43.7a54 54 0 0 1-53.8 51.6H51.5c-2.7 0-5 2-5.4 4.6l-8.3 52.5-5.2 33a4.5 4.5 0 0 0 4.4 5.1h28.7a5.5 5.5 0 0 0 5.4-4.6l7.6-48a5.5 5.5 0 0 1 5.4-4.5H101a54 54 0 0 0 53.2-45.7c3-18.7-6.5-35.6-22.5-44z" /></svg>`)
//line web/template/template.qtpl:140
}
//line web/template/template.qtpl:141
}
//line web/template/template.qtpl:141
func writeicon(qq422016 qtio422016.Writer, id string) {
//line web/template/template.qtpl:141
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/template.qtpl:141
streamicon(qw422016, id)
//line web/template/template.qtpl:141
qt422016.ReleaseWriter(qw422016)
//line web/template/template.qtpl:141
}
//line web/template/template.qtpl:141
func icon(id string) string {
//line web/template/template.qtpl:141
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/template.qtpl:141
writeicon(qb422016, id)
//line web/template/template.qtpl:141
qs422016 := string(qb422016.B)
//line web/template/template.qtpl:141
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/template.qtpl:141
return qs422016
//line web/template/template.qtpl:141
}