Compare commits
11 Commits
old
...
bebb2f08f6
| Author | SHA1 | Date | |
|---|---|---|---|
| bebb2f08f6 | |||
| a92da686c5 | |||
| 8952108bb5 | |||
| 752aeb9082 | |||
| 8e173b9797 | |||
| 03cbe99dad | |||
| 54658e707d | |||
| 1429a5f924 | |||
| 85d6c82ad3 | |||
| 4d1df3b3ea | |||
| 86681f0875 |
4
.gitignore
vendored
@@ -1,3 +1 @@
|
||||
.idea/
|
||||
static/blog/
|
||||
geodb/
|
||||
config.json
|
||||
|
||||
25
Dockerfile
@@ -1,25 +0,0 @@
|
||||
FROM golang AS build
|
||||
COPY . /go/src/github.com/Kugelschieber/marvinblum
|
||||
WORKDIR /go/src/github.com/Kugelschieber/marvinblum
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
|
||||
ENV GOPATH=/go
|
||||
ENV CGO_ENABLED=0
|
||||
RUN go build -ldflags "-s -w" main.go
|
||||
|
||||
FROM alpine
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add --no-cache && \
|
||||
apk add ca-certificates && \
|
||||
rm -rf /var/cache/apk/*
|
||||
COPY --from=build /go/src/github.com/Kugelschieber/marvinblum /app
|
||||
WORKDIR /app
|
||||
|
||||
# default config
|
||||
ENV MB_LOGLEVEL=info
|
||||
ENV MB_ALLOWED_ORIGINS=*
|
||||
ENV MB_HOST=0.0.0.0:8888
|
||||
|
||||
EXPOSE 8888
|
||||
CMD ["/app/main"]
|
||||
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Marvin Blum
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,3 +1,7 @@
|
||||
# marvinblum.de
|
||||
|
||||
My website.
|
||||
My [website](https://marvinblum.de) built with [Shifu](https://github.com/emvi/shifu).
|
||||
|
||||
## Running Locally
|
||||
|
||||
Copy `dev_config.json` to `config.json` and start Shifu using `shifu` inside the project directory.
|
||||
|
||||
1
assets/js/main.js
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Hello from Shifu!");
|
||||
35
assets/scss/_font.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url("../font/OpenSans-Italic.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url("../font/OpenSans-Regular.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url("../font/OpenSans-Bold.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url("../font/OpenSans-BoldItalic.ttf") format("truetype");
|
||||
}
|
||||
119
assets/scss/main.scss
Normal file
@@ -0,0 +1,119 @@
|
||||
@import "_font.scss";
|
||||
|
||||
$borderRadius: 8px;
|
||||
$textColor: #0f0f0f;
|
||||
$lightGray: #e6e6e6;
|
||||
$highlight: #82b3db;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
font-size: 18px;
|
||||
line-height: 1.5;
|
||||
color: $textColor;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 80px 40px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-size: 32px;
|
||||
margin: 0 0 40px 0;
|
||||
font-weight: normal;
|
||||
|
||||
a {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
transition: all 0.3s;
|
||||
color: $highlight;
|
||||
|
||||
&:hover {
|
||||
color: $textColor;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0 0 80px 0;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
a {
|
||||
color: $textColor;
|
||||
|
||||
&:hover {
|
||||
color: $highlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
border-radius: $borderRadius;
|
||||
border: 2px solid $highlight;
|
||||
color: $textColor;
|
||||
transition: all 0.3s;
|
||||
color: $highlight;
|
||||
|
||||
&:hover {
|
||||
background: $highlight;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 0 0 80px 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: 0 0 80px 0;
|
||||
padding: 40px 0;
|
||||
border-style: solid;
|
||||
border-width: 2px 0 0 0;
|
||||
border-color: $lightGray;
|
||||
}
|
||||
|
||||
.intro {
|
||||
article {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 256px;
|
||||
max-height: 256px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: $borderRadius;
|
||||
}
|
||||
}
|
||||
}
|
||||
206
blog/blog.go
@@ -1,206 +0,0 @@
|
||||
package blog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Kugelschieber/marvinblum/tpl"
|
||||
emvi "github.com/emvi/api-go"
|
||||
"github.com/emvi/logbuch"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
blogCacheTime = time.Minute * 15
|
||||
blogFileCache = "static/blog"
|
||||
maxLatestArticles = 3
|
||||
)
|
||||
|
||||
var (
|
||||
linkRegex = regexp.MustCompile(`(?iU)href="/read/([^"]+)"`)
|
||||
attachmentRegex = regexp.MustCompile(`(?iU)(href|src)="([^"]+)/api/v1/content/([^"]+)"`)
|
||||
attachmentURLRegex = regexp.MustCompile(`(?iU)(href|src)="([^"]+/api/v1/content/)([^"]+)"`)
|
||||
)
|
||||
|
||||
type Blog struct {
|
||||
client *emvi.Client
|
||||
articles []emvi.Article // sorted by date published (descending)
|
||||
articleMap map[string]emvi.Article // id -> article
|
||||
articlesYear map[int][]emvi.Article // year -> articleMap
|
||||
nextUpdate time.Time
|
||||
cache *tpl.Cache
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func NewBlog(cache *tpl.Cache) *Blog {
|
||||
logbuch.Info("Initializing blog")
|
||||
b := &Blog{cache: cache}
|
||||
b.client = emvi.NewClient(os.Getenv("MB_EMVI_CLIENT_ID"),
|
||||
os.Getenv("MB_EMVI_CLIENT_SECRET"),
|
||||
os.Getenv("MB_EMVI_ORGA"),
|
||||
nil)
|
||||
b.nextUpdate = time.Now().Add(blogCacheTime)
|
||||
|
||||
if err := os.MkdirAll(blogFileCache, 0755); err != nil {
|
||||
logbuch.Error("Error creating blog file cache directory", logbuch.Fields{"err": err})
|
||||
}
|
||||
|
||||
b.loadArticles()
|
||||
return b
|
||||
}
|
||||
|
||||
func (blog *Blog) GetArticle(id string) emvi.Article {
|
||||
blog.refreshIfRequired()
|
||||
return blog.articleMap[id]
|
||||
}
|
||||
|
||||
func (blog *Blog) GetArticles() map[int][]emvi.Article {
|
||||
blog.refreshIfRequired()
|
||||
return blog.articlesYear
|
||||
}
|
||||
|
||||
func (blog *Blog) GetLatestArticles() []emvi.Article {
|
||||
blog.refreshIfRequired()
|
||||
articles := make([]emvi.Article, 0, 3)
|
||||
i := 1
|
||||
|
||||
for _, v := range blog.articles {
|
||||
articles = append(articles, v)
|
||||
i++
|
||||
|
||||
if i > maxLatestArticles {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return articles
|
||||
}
|
||||
|
||||
func (blog *Blog) loadArticles() {
|
||||
blog.m.Lock()
|
||||
defer blog.m.Unlock()
|
||||
logbuch.Info("Refreshing blog articleMap...")
|
||||
articles, offset, count := make([]emvi.Article, 0), 0, 1
|
||||
var err error
|
||||
|
||||
for count > 0 {
|
||||
var results []emvi.Article
|
||||
results, _, err = blog.client.FindArticles("", &emvi.ArticleFilter{
|
||||
BaseSearch: emvi.BaseSearch{Offset: offset},
|
||||
Tags: "blog",
|
||||
SortPublished: emvi.SortDescending,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logbuch.Error("Error loading blog article", logbuch.Fields{"err": err})
|
||||
break
|
||||
}
|
||||
|
||||
offset += len(results)
|
||||
count = len(results)
|
||||
|
||||
for _, article := range results {
|
||||
articles = append(articles, article)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
for i, article := range articles {
|
||||
article.LatestArticleContent = blog.loadArticle(article)
|
||||
articles[i] = article
|
||||
}
|
||||
|
||||
blog.setArticles(articles)
|
||||
}
|
||||
|
||||
blog.nextUpdate = time.Now().Add(blogCacheTime)
|
||||
logbuch.Info("Done", logbuch.Fields{"count": len(articles)})
|
||||
}
|
||||
|
||||
func (blog *Blog) loadArticle(article emvi.Article) *emvi.ArticleContent {
|
||||
existingArticle := blog.articleMap[article.Id]
|
||||
var content *emvi.ArticleContent
|
||||
|
||||
if len(existingArticle.Id) == 0 || !existingArticle.ModTime.Equal(article.ModTime) {
|
||||
var err error
|
||||
_, content, _, err = blog.client.GetArticle(article.Id, article.LatestArticleContent.LanguageId, 0)
|
||||
|
||||
if err != nil {
|
||||
logbuch.Error("Error loading article", logbuch.Fields{"err": err, "id": article.Id})
|
||||
return nil
|
||||
}
|
||||
|
||||
blog.downloadAttachments(article.Id, content.Content)
|
||||
content.Content = linkRegex.ReplaceAllString(content.Content, `href="/blog/$1"`)
|
||||
content.Content = attachmentRegex.ReplaceAllString(content.Content, fmt.Sprintf(`$1="/static/blog/%s/$3"`, article.Id))
|
||||
logbuch.Debug("Article loaded", logbuch.Fields{"id": article.Id})
|
||||
} else {
|
||||
content = existingArticle.LatestArticleContent
|
||||
logbuch.Debug("Article up to date, skipping refreshing cache", logbuch.Fields{"id": article.Id})
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
func (blog *Blog) downloadAttachments(id, content string) {
|
||||
if _, err := os.Stat(filepath.Join(blogFileCache, id)); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Join(blogFileCache, id), 0755); err != nil {
|
||||
logbuch.Error("Error creating article file cache directory", logbuch.Fields{"err": err, "id": id})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
results := attachmentURLRegex.FindAllStringSubmatch(content, -1)
|
||||
|
||||
for _, attachment := range results {
|
||||
if len(attachment) == 4 {
|
||||
resp, err := http.Get(attachment[2] + attachment[3])
|
||||
|
||||
if err != nil {
|
||||
logbuch.Error("Error downloading blog attachment", logbuch.Fields{"err": err, "id": id})
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
logbuch.Error("Error reading blog attachment body", logbuch.Fields{"err": err, "id": id})
|
||||
continue
|
||||
}
|
||||
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logbuch.Error("Error closing response body on attachment download", logbuch.Fields{"err": err, "id": id})
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(blogFileCache, id, attachment[3]), data, 0755); err != nil {
|
||||
logbuch.Error("Error saving blog attachment on disk", logbuch.Fields{"err": err, "id": id})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (blog *Blog) setArticles(articles []emvi.Article) {
|
||||
blog.articles = articles
|
||||
blog.articleMap = make(map[string]emvi.Article)
|
||||
blog.articlesYear = make(map[int][]emvi.Article)
|
||||
|
||||
for _, article := range articles {
|
||||
if blog.articlesYear[article.Published.Year()] == nil {
|
||||
blog.articlesYear[article.Published.Year()] = make([]emvi.Article, 0)
|
||||
}
|
||||
|
||||
blog.articlesYear[article.Published.Year()] = append(blog.articlesYear[article.Published.Year()], article)
|
||||
blog.articleMap[article.Id] = article
|
||||
}
|
||||
}
|
||||
|
||||
func (blog *Blog) refreshIfRequired() {
|
||||
if blog.nextUpdate.Before(time.Now()) {
|
||||
blog.cache.Clear()
|
||||
blog.loadArticles()
|
||||
}
|
||||
}
|
||||
17
content/home.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"path": {
|
||||
"en": "/"
|
||||
},
|
||||
"sitemap": {
|
||||
"priority": "1.0"
|
||||
},
|
||||
"content": {
|
||||
"content": [
|
||||
{"ref": "head"},
|
||||
{
|
||||
"tpl": "intro"
|
||||
},
|
||||
{"ref": "end"}
|
||||
]
|
||||
}
|
||||
}
|
||||
3
content/refs/end.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"tpl": "end"
|
||||
}
|
||||
11
content/refs/head.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"tpl": "head",
|
||||
"copy": {
|
||||
"en": {
|
||||
"copyright": "Marvin Blum",
|
||||
"author": "Marvin Blum",
|
||||
"title": "Hi, I'm Marvin.",
|
||||
"meta_description": "Hi, I'm Marvin, co-founder of Pirsch Analytics, software engineer, and open-source enthusiast."
|
||||
}
|
||||
}
|
||||
}
|
||||
18
dev.sh
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This file is for local development only!
|
||||
# It configures and starts the website for local development.
|
||||
# The "secret" for the Emvi API can be shared, as it gives access to public content only.
|
||||
|
||||
export MB_LOGLEVEL=debug
|
||||
export MB_ALLOWED_ORIGINS=*
|
||||
export MB_HOST=localhost:8080
|
||||
export MB_HOT_RELOAD=true
|
||||
export MB_EMVI_CLIENT_ID=3fBBn144yvSF9R3dPC8l
|
||||
export MB_EMVI_CLIENT_SECRET=
|
||||
export MB_EMVI_ORGA=marvin
|
||||
export MB_PIRSCH_CLIENT_ID=gEb3pvgxZvZzFRlOTdMgPtyLvNYgeVKe
|
||||
export MB_PIRSCH_CLIENT_SECRET=E7UqJehmxgnVuw81oq6ZhJAx9vCHqMimCUFfil7UFgbGhgQVVINqU7JqHBgaUvHg
|
||||
export MB_PIRSCH_HOSTNAME=marvinblum.de
|
||||
|
||||
go run main.go
|
||||
39
dev_config.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"dev": true,
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": 8080,
|
||||
"shutdown_time": 30,
|
||||
"write_timeout": 5,
|
||||
"read_timeout": 5,
|
||||
"hostname": "marvinblum.de",
|
||||
"secure_cookies": false,
|
||||
"cookie_domain_name": "marvinblum.de"
|
||||
},
|
||||
"content": {
|
||||
"provider": "git",
|
||||
"update_seconds": 30,
|
||||
"repository": "https://github.com/Kugelschieber/marvinblum.git"
|
||||
},
|
||||
"cors": {
|
||||
"origins": "*",
|
||||
"loglevel": "info"
|
||||
},
|
||||
"sass": {
|
||||
"entrypoint": "main.scss",
|
||||
"dir": "assets/scss",
|
||||
"watch": true,
|
||||
"out": "static/web/main.css",
|
||||
"out_source_map": "static/web/main.css.map"
|
||||
},
|
||||
"js": {
|
||||
"entrypoint": "main.js",
|
||||
"dir": "assets/js",
|
||||
"watch": true,
|
||||
"out": "static/web/main.min.js"
|
||||
},
|
||||
"analytics": {
|
||||
"provider": "pirsch",
|
||||
"client_secret": ""
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.3"
|
||||
container_name: traefik
|
||||
restart: always
|
||||
networks:
|
||||
- traefik-internal
|
||||
command:
|
||||
- "--api.dashboard=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--providers.docker.network=marvinblum_traefik-internal"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.tls-resolver.acme.httpchallenge=true"
|
||||
- "--certificatesresolvers.tls-resolver.acme.httpchallenge.entrypoint=web"
|
||||
- "--certificatesresolvers.tls-resolver.acme.email=marvin@marvinblum.de"
|
||||
- "--certificatesresolvers.tls-resolver.acme.storage=/letsencrypt/acme.json"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /root/marvinblum/letsencrypt:/letsencrypt
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.port=9999"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.marvinblum.de`)"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=tls-resolver"
|
||||
- "traefik.http.routers.traefik.middlewares=traefik-auth"
|
||||
- "traefik.http.middlewares.traefik-auth.basicauth.users=marvinblum:$$apr1$$u.IJozER$$DoY0zwzgAciDpPs4vZvxY/"
|
||||
# Global redirection: http to https
|
||||
- 'traefik.http.routers.http-catchall.rule=HostRegexp(`{host:(www\.)?.+}`)'
|
||||
- "traefik.http.routers.http-catchall.entrypoints=web"
|
||||
- "traefik.http.routers.http-catchall.middlewares=wwwtohttps"
|
||||
# Global redirection: https (www.) to https
|
||||
- 'traefik.http.routers.wwwsecure-catchall.rule=HostRegexp(`{host:(www\.).+}`)'
|
||||
- "traefik.http.routers.wwwsecure-catchall.entrypoints=websecure"
|
||||
- "traefik.http.routers.wwwsecure-catchall.tls=true"
|
||||
- "traefik.http.routers.wwwsecure-catchall.middlewares=wwwtohttps"
|
||||
# middleware: http(s)://(www.) to https://
|
||||
- 'traefik.http.middlewares.wwwtohttps.redirectregex.regex=^https?://(?:www\.)?(.+)'
|
||||
- 'traefik.http.middlewares.wwwtohttps.redirectregex.replacement=https://$${1}'
|
||||
- 'traefik.http.middlewares.wwwtohttps.redirectregex.permanent=true'
|
||||
marvinblum:
|
||||
image: kugel/marvinblum
|
||||
container_name: marvinblum
|
||||
restart: always
|
||||
depends_on:
|
||||
- traefik
|
||||
networks:
|
||||
- traefik-internal
|
||||
env_file:
|
||||
- secrets.env
|
||||
environment:
|
||||
MB_EMVI_CLIENT_ID: 3fBBn144yvSF9R3dPC8l
|
||||
MB_EMVI_ORGA: marvin
|
||||
MB_PIRSCH_CLIENT_ID: mkiAzI2ZGjGBv8fpwh1A09fCJ8G1YFgx
|
||||
MB_PIRSCH_HOSTNAME: marvinblum.de
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.port=8888"
|
||||
- "traefik.http.routers.marvinblum.rule=Host(`marvinblum.de`) || Host(`www.marvinblum.de`)"
|
||||
- "traefik.http.routers.marvinblum.entrypoints=websecure"
|
||||
- "traefik.http.routers.marvinblum.tls=true"
|
||||
- "traefik.http.routers.marvinblum.tls.certresolver=tls-resolver"
|
||||
|
||||
networks:
|
||||
traefik-internal:
|
||||
driver: bridge
|
||||
18
go.mod
@@ -1,18 +0,0 @@
|
||||
module github.com/Kugelschieber/marvinblum
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emvi/api-go v0.2.2
|
||||
github.com/emvi/logbuch v1.1.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gosimple/slug v1.9.0
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/lib/pq v1.9.0
|
||||
github.com/pirsch-analytics/pirsch-go-sdk v0.0.0-20201215183417-0e2a519a0dd1
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/stretchr/testify v1.6.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
||||
33
go.sum
@@ -1,33 +0,0 @@
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emvi/api-go v0.2.2 h1:NrZNl0o0xAbgfK1dFsRt/BKLesHLVjC4OKXHKTZsIis=
|
||||
github.com/emvi/api-go v0.2.2/go.mod h1:g9RdDC3s5ebCknAHQQ5PjoM2vRFSyyGoOUX3QkDKU+o=
|
||||
github.com/emvi/logbuch v1.1.1/go.mod h1:J2Wgbr3BuSc1JO+D2MBVh6q3WPVSK5GzktwWz8pvkKw=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
|
||||
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pirsch-analytics/pirsch-go-sdk v0.0.0-20201215183417-0e2a519a0dd1 h1:yn6F902YFyEMfXQIARsTXLW42TQKuH+BrKk0MjQu/ps=
|
||||
github.com/pirsch-analytics/pirsch-go-sdk v0.0.0-20201215183417-0e2a519a0dd1/go.mod h1:PF2vnJw8FYcXQe6OTPQQcGn8l/agkpl7T4YO9d2aPSE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
201
main.go
@@ -1,201 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Kugelschieber/marvinblum/blog"
|
||||
"github.com/Kugelschieber/marvinblum/tpl"
|
||||
"github.com/NYTimes/gziphandler"
|
||||
emvi "github.com/emvi/api-go"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/gorilla/mux"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/pirsch-analytics/pirsch-go-sdk"
|
||||
"github.com/rs/cors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
staticDir = "static"
|
||||
staticDirPrefix = "/static/"
|
||||
logTimeFormat = "2006-01-02_15:04:05"
|
||||
envPrefix = "MB_"
|
||||
shutdownTimeout = time.Second * 30
|
||||
)
|
||||
|
||||
var (
|
||||
client *pirsch.Client
|
||||
tplCache *tpl.Cache
|
||||
blogInstance *blog.Blog
|
||||
)
|
||||
|
||||
func serveAbout() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
go hit(r)
|
||||
tplCache.Render(w, "about.html", struct {
|
||||
Articles []emvi.Article
|
||||
}{
|
||||
blogInstance.GetLatestArticles(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func serveLegal() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
go hit(r)
|
||||
tplCache.Render(w, "legal.html", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func serveBlogPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
go hit(r)
|
||||
tplCache.Render(w, "blog.html", struct {
|
||||
Articles map[int][]emvi.Article
|
||||
}{
|
||||
blogInstance.GetArticles(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func serveBlogArticle() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
slug := strings.Split(vars["slug"], "-")
|
||||
|
||||
if len(slug) == 0 {
|
||||
http.Redirect(w, r, "/notfound", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
article := blogInstance.GetArticle(slug[len(slug)-1])
|
||||
|
||||
if len(article.Id) == 0 {
|
||||
http.Redirect(w, r, "/notfound", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// track the hit if the article was found, otherwise we don't care
|
||||
go hit(r)
|
||||
|
||||
tplCache.RenderWithoutCache(w, "article.html", struct {
|
||||
Title string
|
||||
Content template.HTML
|
||||
Published time.Time
|
||||
}{
|
||||
article.LatestArticleContent.Title,
|
||||
template.HTML(article.LatestArticleContent.Content),
|
||||
article.Published,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func serveTracking() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "https://marvinblum.pirsch.io/", http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func serveNotFound() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tplCache.Render(w, "notfound.html", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func setupRouter() *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
router.PathPrefix(staticDirPrefix).Handler(http.StripPrefix(staticDirPrefix, gziphandler.GzipHandler(http.FileServer(http.Dir(staticDir)))))
|
||||
router.Handle("/blog/{slug}", serveBlogArticle())
|
||||
router.Handle("/blog", serveBlogPage())
|
||||
router.Handle("/legal", serveLegal())
|
||||
router.Handle("/tracking", serveTracking())
|
||||
router.Handle("/", serveAbout())
|
||||
router.NotFoundHandler = serveNotFound()
|
||||
return router
|
||||
}
|
||||
|
||||
func configureLog() {
|
||||
logbuch.SetFormatter(logbuch.NewFieldFormatter(logTimeFormat, "\t\t"))
|
||||
logbuch.Info("Configure logging...")
|
||||
level := strings.ToLower(os.Getenv("MB_LOGLEVEL"))
|
||||
|
||||
if level == "debug" {
|
||||
logbuch.SetLevel(logbuch.LevelDebug)
|
||||
} else if level == "info" {
|
||||
logbuch.SetLevel(logbuch.LevelInfo)
|
||||
} else {
|
||||
logbuch.SetLevel(logbuch.LevelWarning)
|
||||
}
|
||||
}
|
||||
|
||||
func logEnvConfig() {
|
||||
for _, e := range os.Environ() {
|
||||
if strings.HasPrefix(e, envPrefix) {
|
||||
pair := strings.Split(e, "=")
|
||||
logbuch.Info(pair[0] + "=" + pair[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func configureCors(router *mux.Router) http.Handler {
|
||||
logbuch.Info("Configuring CORS...")
|
||||
|
||||
origins := strings.Split(os.Getenv("MB_ALLOWED_ORIGINS"), ",")
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: origins,
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||
AllowedHeaders: []string{"*"},
|
||||
AllowCredentials: true,
|
||||
Debug: strings.ToLower(os.Getenv("MB_CORS_LOGLEVEL")) == "debug",
|
||||
})
|
||||
return c.Handler(router)
|
||||
}
|
||||
|
||||
func start(handler http.Handler) {
|
||||
logbuch.Info("Starting server...")
|
||||
var server http.Server
|
||||
server.Handler = handler
|
||||
server.Addr = os.Getenv("MB_HOST")
|
||||
|
||||
go func() {
|
||||
sigint := make(chan os.Signal)
|
||||
signal.Notify(sigint, os.Interrupt)
|
||||
<-sigint
|
||||
logbuch.Info("Shutting down server...")
|
||||
ctx, _ := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logbuch.Fatal("Error shutting down server gracefully", logbuch.Fields{"err": err})
|
||||
}
|
||||
}()
|
||||
|
||||
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||
logbuch.Fatal("Error starting server", logbuch.Fields{"err": err})
|
||||
}
|
||||
|
||||
logbuch.Info("Server shut down")
|
||||
}
|
||||
|
||||
func hit(r *http.Request) {
|
||||
if err := client.Hit(r); err != nil {
|
||||
logbuch.Warn("Error sending page hit to pirsch", logbuch.Fields{"err": err})
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
configureLog()
|
||||
logEnvConfig()
|
||||
client = pirsch.NewClient(os.Getenv("MB_PIRSCH_CLIENT_ID"),
|
||||
os.Getenv("MB_PIRSCH_CLIENT_SECRET"),
|
||||
os.Getenv("MB_PIRSCH_HOSTNAME"),
|
||||
nil)
|
||||
tplCache = tpl.NewCache()
|
||||
blogInstance = blog.NewBlog(tplCache)
|
||||
router := setupRouter()
|
||||
corsConfig := configureCors(router)
|
||||
start(corsConfig)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
MB_EMVI_CLIENT_SECRET=
|
||||
MB_PIRSCH_CLIENT_SECRET=
|
||||
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,281 +0,0 @@
|
||||
/*! concrete.css v1.3.0 | MIT License | github.com/louismerlin/concrete.css/ */
|
||||
|
||||
/* The Basics */
|
||||
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 2rem;
|
||||
font-weight: 400;
|
||||
background: white;
|
||||
color: #121212;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
*, ::after, ::before {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #121212;
|
||||
}
|
||||
|
||||
blockquote, dl, figure, form, ol, p, pre, table, ul {
|
||||
margin-bottom: 2.2rem;
|
||||
}
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* A Cool Container */
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 66rem;
|
||||
padding: 0 1rem;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* The Button */
|
||||
|
||||
.button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button,
|
||||
.button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
border-radius: 0;
|
||||
background: white;
|
||||
line-height: 2.2rem;
|
||||
font-size: 1.6rem;
|
||||
color: #121212;
|
||||
border: 0.3rem solid #121212;
|
||||
padding: 0.4rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.filled,
|
||||
.button.filled,
|
||||
input[type="button"].filled,
|
||||
input[type="reset"].filled,
|
||||
input[type="submit"].filled {
|
||||
color: white;
|
||||
background: #121212;
|
||||
}
|
||||
|
||||
.button,
|
||||
button,
|
||||
dd,
|
||||
dt,
|
||||
li,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
margin-bottom: 1.0rem;
|
||||
}
|
||||
|
||||
/* The List */
|
||||
|
||||
ul {
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
/* The Form */
|
||||
|
||||
fieldset {
|
||||
border-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
label, legend {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
input[type="search"],
|
||||
input[type="tel"],
|
||||
input[type="text"],
|
||||
input[type="url"],
|
||||
textarea,
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border: 0.1rem solid #121212;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
box-sizing: inherit;
|
||||
padding: .6rem 1.0rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
fieldset, input, select, textarea {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* The Table */
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 0.6rem 0;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 0.1rem solid #121212;
|
||||
}
|
||||
|
||||
th {
|
||||
border-bottom: 0.3rem solid #121212;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* The Blockquote and the Code */
|
||||
|
||||
blockquote, pre {
|
||||
border-left: 0.3rem solid #121212;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding: 1rem 1.5rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
pre {
|
||||
border: 0.1rem dotted #121212;
|
||||
border-left: 0.3rem solid #121212;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
padding: 1rem 1.4rem;
|
||||
font-size: 1.6rem;
|
||||
white-space: pre;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* The Progress Bar */
|
||||
|
||||
progress {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
display: block;
|
||||
height: 1rem;
|
||||
border: 0.1rem solid #121212;
|
||||
background: white;
|
||||
color: #121212;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: white;
|
||||
}
|
||||
progress::-webkit-progress-value {
|
||||
background-color: #121212;
|
||||
}
|
||||
progress::-moz-progress-bar {
|
||||
background-color: #121212;
|
||||
}
|
||||
progress::-ms-fill {
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
/* The Break Line */
|
||||
|
||||
hr {
|
||||
border: 0.2rem solid #121212;
|
||||
border-bottom-width: 0.1rem;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #121212;
|
||||
color: white;
|
||||
}
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
button,
|
||||
.button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
background: #121212;
|
||||
color: white;
|
||||
border-color: white;
|
||||
}
|
||||
button.filled,
|
||||
.button.filled,
|
||||
input[type="button"].filled,
|
||||
input[type="reset"].filled,
|
||||
input[type="submit"].filled {
|
||||
color: #121212;
|
||||
background: white;
|
||||
}
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
input[type="search"],
|
||||
input[type="tel"],
|
||||
input[type="text"],
|
||||
input[type="url"],
|
||||
textarea,
|
||||
select {
|
||||
color: white;
|
||||
border-color: white;
|
||||
}
|
||||
td {
|
||||
border-bottom-color: white;
|
||||
}
|
||||
th {
|
||||
border-bottom-color: white;
|
||||
}
|
||||
blockquote, pre {
|
||||
border-left-color: white;
|
||||
}
|
||||
pre {
|
||||
border-color: white;
|
||||
border-left-color: white;
|
||||
}
|
||||
progress {
|
||||
border-color: white;
|
||||
background: #121212;
|
||||
color: white;
|
||||
}
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: #121212;
|
||||
}
|
||||
progress::-webkit-progress-value {
|
||||
background-color: white;
|
||||
}
|
||||
progress::-moz-progress-bar {
|
||||
background-color: white;
|
||||
}
|
||||
progress::-ms-fill {
|
||||
background-color: white;
|
||||
}
|
||||
hr {
|
||||
border-color: white;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 567 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -1,218 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="460.000000pt" height="460.000000pt" viewBox="0 0 460.000000 460.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,460.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2000 4199 c-80 -8 -163 -31 -185 -50 -5 -5 0 -9 14 -9 36 0 14 -18
|
||||
-32 -25 -23 -4 -59 -18 -79 -31 -21 -13 -42 -24 -47 -24 -6 0 -16 -9 -24 -20
|
||||
-7 -10 -32 -23 -56 -29 -48 -10 -91 -45 -91 -72 0 -10 -9 -21 -20 -24 -11 -3
|
||||
-20 -15 -20 -26 0 -10 -6 -19 -13 -19 -15 0 -65 -40 -51 -40 5 0 -8 -11 -28
|
||||
-23 l-37 -23 19 -29 c13 -20 16 -34 9 -46 -5 -10 -9 -27 -9 -38 0 -12 -4 -21
|
||||
-10 -21 -5 0 -10 -7 -10 -17 0 -15 1 -15 18 0 10 10 24 17 31 17 17 0 -9 -46
|
||||
-53 -93 -20 -21 -37 -48 -37 -60 -1 -12 -2 -33 -3 -47 -1 -14 -7 -25 -13 -25
|
||||
-7 0 -13 -4 -12 -9 0 -5 -3 -12 -8 -15 -5 -3 -19 -23 -32 -43 -29 -49 -17 -70
|
||||
15 -27 25 34 32 33 14 -2 -8 -13 -7 -19 0 -19 7 0 3 -12 -7 -27 -28 -41 -44
|
||||
-83 -33 -83 9 0 4 -145 -5 -161 -13 -20 30 -293 45 -284 3 2 6 -15 7 -38 1
|
||||
-23 4 -82 5 -132 2 -49 7 -97 10 -105 3 -8 14 -44 23 -80 19 -70 30 -90 51
|
||||
-90 11 0 14 17 14 70 0 79 20 210 32 210 4 0 8 -5 8 -11 0 -27 75 -168 107
|
||||
-201 90 -92 268 -126 391 -74 34 15 83 42 108 61 25 19 50 33 55 29 5 -3 9 0
|
||||
9 7 0 7 3 10 6 6 8 -8 42 23 64 58 43 71 91 271 72 302 -4 6 -18 14 -31 18
|
||||
-26 8 -81 59 -81 75 0 6 -5 10 -12 10 -6 0 -9 2 -6 5 7 7 -58 75 -72 75 -6 0
|
||||
-22 7 -35 16 -14 9 -51 20 -82 24 -32 5 -99 14 -148 21 -101 14 -141 4 -160
|
||||
-39 -5 -12 -17 -22 -27 -22 -25 0 -49 -11 -43 -20 3 -5 15 -6 28 -3 19 4 18 2
|
||||
-5 -12 -16 -8 -28 -20 -28 -25 0 -6 -7 -10 -16 -10 -8 0 -13 4 -9 9 3 5 1 12
|
||||
-4 15 -5 4 -8 -2 -7 -11 3 -19 -28 -42 -56 -43 -10 0 -18 6 -18 13 -4 122 -2
|
||||
238 3 244 12 11 8 23 -7 23 -19 0 -29 30 -15 47 7 7 9 16 5 20 -3 3 2 15 11
|
||||
25 14 15 15 21 4 27 -10 7 -10 13 -2 29 9 16 8 24 -4 36 -8 8 -11 17 -6 20 8
|
||||
6 25 64 22 79 -1 5 2 6 7 3 4 -2 13 4 20 15 7 10 8 19 3 19 -6 0 -3 15 6 33
|
||||
77 154 59 139 173 141 74 2 122 20 143 53 5 8 12 11 18 8 5 -4 9 -2 9 3 0 5
|
||||
14 9 32 10 18 0 50 8 73 18 92 38 112 45 163 54 30 5 65 19 78 29 14 11 32 17
|
||||
40 14 8 -3 14 -1 14 4 0 10 57 18 201 29 37 2 77 7 90 10 46 11 285 13 334 3
|
||||
28 -6 61 -12 75 -15 93 -19 219 -123 268 -221 9 -18 19 -33 23 -33 4 0 10 -11
|
||||
13 -24 11 -43 106 -121 106 -86 0 6 -4 10 -10 10 -15 0 -12 40 3 41 6 1 22 0
|
||||
34 0 12 -1 26 5 31 15 12 22 4 54 -14 54 -9 0 -14 11 -14 30 0 17 -6 33 -14
|
||||
36 -8 3 -17 16 -20 29 -3 13 -18 30 -34 37 -16 8 -26 21 -25 32 1 13 -2 15
|
||||
-10 7 -18 -18 -27 -13 -27 15 0 14 -4 23 -9 19 -5 -3 -27 15 -48 40 -20 25
|
||||
-43 43 -50 41 -6 -3 -27 13 -45 34 -18 21 -49 57 -68 79 -19 23 -39 41 -44 41
|
||||
-5 0 -20 10 -33 22 -19 17 -23 19 -17 5 9 -24 -3 -21 -38 8 -43 36 -132 54
|
||||
-371 75 -163 14 -292 32 -297 41 -9 14 -89 17 -180 8z m319 -51 c11 -11 12
|
||||
-10 6 0 -6 11 -3 12 11 4 11 -5 47 -12 82 -16 35 -4 73 -11 85 -16 18 -7 14
|
||||
-9 -20 -9 -30 -1 -43 -5 -43 -15 0 -8 -6 -17 -12 -19 -7 -3 25 -6 72 -7 141
|
||||
-2 186 -10 141 -25 -12 -3 -21 -14 -20 -23 0 -11 3 -12 6 -4 7 17 23 15 23 -2
|
||||
0 -10 -16 -17 -47 -21 -27 -3 -82 -10 -124 -16 -139 -20 -288 3 -264 40 6 11
|
||||
-4 14 -35 12 -10 0 -11 1 -2 5 6 3 10 10 6 15 -4 8 69 15 117 11 12 -1 16 6
|
||||
15 23 -1 23 -16 37 -16 15 0 -5 -7 -10 -15 -10 -8 0 -15 6 -15 14 0 16 -80 33
|
||||
-99 21 -6 -3 -11 -2 -11 2 0 5 -12 8 -27 7 -16 0 -37 2 -48 5 -11 3 25 9 80
|
||||
12 55 3 109 6 121 7 11 1 26 -4 33 -10z m291 -44 c0 -2 -9 -4 -20 -4 -11 0
|
||||
-20 4 -20 9 0 5 9 7 20 4 11 -3 20 -7 20 -9z m-362 -1 c-10 -2 -26 -2 -35 0
|
||||
-10 3 -2 5 17 5 19 0 27 -2 18 -5z m672 -88 c0 -5 -5 -3 -10 5 -5 8 -10 20
|
||||
-10 25 0 6 5 3 10 -5 5 -8 10 -19 10 -25z m-230 5 c0 -5 -7 -10 -15 -10 -8 0
|
||||
-15 5 -15 10 0 6 7 10 15 10 8 0 15 -4 15 -10z m-933 -1134 c-7 -17 22 -25
|
||||
108 -33 76 -6 88 -17 40 -38 -31 -12 -40 -13 -47 -3 -4 8 -8 8 -8 2 0 -6 -36
|
||||
-16 -81 -23 -65 -10 -86 -10 -110 1 -16 7 -29 16 -29 20 0 15 43 46 80 57 22
|
||||
6 40 16 40 21 0 6 3 10 6 10 3 0 4 -6 1 -14z m-273 -72 l-49 -46 -3 -81 c-2
|
||||
-45 1 -93 7 -107 9 -24 10 -24 11 10 5 81 11 106 32 122 22 16 23 15 45 -24
|
||||
13 -22 23 -49 23 -59 0 -11 8 -20 18 -21 9 0 24 -1 32 -2 8 -1 16 -9 18 -19 2
|
||||
-9 8 -17 14 -17 6 0 23 -14 38 -30 24 -26 34 -30 85 -31 45 -1 59 -5 62 -19 6
|
||||
-22 140 -55 190 -45 l35 6 -21 -29 c-59 -84 -148 -126 -266 -126 -104 0 -168
|
||||
25 -235 93 -67 68 -101 145 -108 249 -6 90 14 149 66 192 56 46 60 36 6 -16z
|
||||
m-217 -36 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m620 6 c12 -3
|
||||
24 -12 27 -19 4 -11 -11 -14 -72 -12 -42 0 -88 -3 -102 -7 -22 -8 -23 -7 -11
|
||||
8 23 28 106 44 158 30z m23 -114 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6
|
||||
5 10 10 10 6 0 10 -4 10 -10z"/>
|
||||
<path d="M2448 4193 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||
<path d="M2523 4184 c3 -3 33 -12 67 -19 70 -15 72 -7 2 12 -52 13 -79 16 -69
|
||||
7z"/>
|
||||
<path d="M2674 4146 c23 -18 36 -20 36 -6 0 6 -4 10 -9 10 -5 0 -18 3 -28 6
|
||||
-16 5 -16 4 1 -10z"/>
|
||||
<path d="M2702 2976 c-19 -5 -40 -21 -50 -38 -9 -15 -24 -28 -32 -28 -9 0 -29
|
||||
-13 -45 -30 -16 -16 -33 -30 -38 -30 -5 0 -37 -21 -70 -46 -50 -37 -65 -44
|
||||
-86 -38 -34 10 -101 11 -101 2 0 -5 1 -8 3 -9 1 0 25 -6 52 -13 36 -9 53 -20
|
||||
62 -37 14 -31 23 -109 12 -109 -5 0 -9 5 -9 12 0 6 -3 9 -6 5 -3 -3 -3 -19 1
|
||||
-36 4 -21 13 -31 25 -31 10 0 22 -9 25 -20 9 -28 71 -119 107 -157 17 -17 58
|
||||
-44 92 -59 52 -24 74 -29 146 -29 47 0 105 6 129 13 66 20 43 25 -30 6 -116
|
||||
-30 -270 8 -341 83 -30 32 -103 171 -94 180 3 4 6 0 6 -7 0 -11 3 -11 11 -3
|
||||
15 15 3 47 -16 40 -20 -8 -31 74 -16 124 30 101 200 177 411 184 91 3 112 1
|
||||
159 -18 121 -49 167 -141 143 -284 -6 -37 -10 -68 -8 -70 2 -2 15 41 29 94 17
|
||||
61 34 103 46 113 11 8 21 30 23 50 3 35 3 35 -57 51 -68 18 -151 54 -160 69
|
||||
-3 5 -15 10 -25 10 -10 0 -25 7 -34 15 -8 8 -23 15 -33 16 -10 0 -13 3 -5 6 6
|
||||
2 12 8 12 13 0 11 -49 19 -55 9 -5 -7 -23 -6 -125 4 -14 2 -40 -1 -58 -7z"/>
|
||||
<path d="M3294 2929 c2 -25 9 -56 16 -70 13 -23 12 -24 -7 -6 -20 17 -20 16
|
||||
-22 -40 -1 -36 -7 -60 -15 -65 -11 -6 -11 -17 1 -65 8 -32 14 -69 14 -83 0
|
||||
-21 16 -131 34 -242 2 -15 1 -29 -4 -32 -8 -5 -13 -96 -6 -96 9 0 55 52 55 61
|
||||
0 6 -4 8 -9 5 -4 -3 0 25 11 62 21 73 23 117 7 205 -11 61 -6 82 17 73 8 -3
|
||||
14 1 15 12 1 9 5 4 10 -13 8 -30 8 -26 -4 65 -1 8 -7 24 -14 35 -6 11 -12 25
|
||||
-13 30 -2 6 -4 15 -5 20 -12 40 -12 53 0 49 7 -3 14 5 17 20 3 14 1 23 -3 20
|
||||
-5 -3 -9 1 -9 9 0 8 -12 23 -27 33 -15 9 -35 27 -45 38 -16 20 -17 20 -14 -25z
|
||||
m50 -181 c-12 -19 -44 13 -44 44 l0 32 25 -34 c13 -18 22 -37 19 -42z"/>
|
||||
<path d="M2640 2794 c-19 -7 -43 -13 -53 -13 -10 -1 -20 -8 -23 -16 -4 -8 -13
|
||||
-15 -21 -15 -8 0 -11 -4 -8 -10 3 -5 2 -10 -2 -10 -5 0 -9 -16 -9 -35 0 -32 6
|
||||
-40 28 -36 8 1 -4 -36 -22 -70 -19 -35 13 -52 118 -63 29 -3 32 -1 32 25 0 27
|
||||
1 28 49 24 46 -4 51 -2 80 31 17 19 31 45 31 58 0 12 3 26 6 29 11 10 64 -26
|
||||
64 -43 0 -11 11 -15 40 -16 47 -2 51 9 13 33 -16 10 -36 25 -46 34 -17 16
|
||||
-102 40 -147 43 -27 1 -53 36 -32 43 6 3 12 9 12 14 0 13 -69 9 -110 -7z m13
|
||||
-131 c2 -10 8 -26 12 -35 5 -14 2 -18 -13 -18 -27 0 -35 15 -28 46 7 28 22 32
|
||||
29 7z m151 -4 c-8 -14 -24 -10 -24 6 0 9 6 12 15 9 8 -4 12 -10 9 -15z m-137
|
||||
-101 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z"/>
|
||||
<path d="M3502 2556 c-1 -14 2 -26 7 -26 5 0 8 -11 7 -24 -2 -14 1 -30 6 -38
|
||||
5 -8 8 5 6 35 -2 55 -22 96 -26 53z"/>
|
||||
<path d="M1225 2520 c-8 -14 4 -110 14 -110 12 0 13 108 2 114 -5 4 -13 2 -16
|
||||
-4z"/>
|
||||
<path d="M3115 2471 c-7 -23 -88 -109 -125 -133 -21 -14 -22 -16 -4 -12 42 11
|
||||
158 143 141 161 -3 2 -8 -5 -12 -16z"/>
|
||||
<path d="M1304 2298 c3 -10 1 -18 -4 -18 -6 0 -10 -9 -10 -20 0 -13 7 -20 19
|
||||
-20 23 0 36 -16 21 -25 -12 -7 -4 -25 12 -25 15 0 3 52 -23 92 -11 18 -19 25
|
||||
-15 16z"/>
|
||||
<path d="M1955 2130 c3 -5 11 -10 16 -10 6 0 7 5 4 10 -3 6 -11 10 -16 10 -6
|
||||
0 -7 -4 -4 -10z"/>
|
||||
<path d="M2063 2104 c-7 -3 -13 -14 -13 -25 0 -10 -4 -19 -10 -19 -20 0 -9
|
||||
-21 18 -33 15 -6 31 -17 35 -24 12 -19 30 -16 45 7 7 11 21 20 32 20 26 0 25
|
||||
13 -4 45 -24 25 -77 40 -103 29z"/>
|
||||
<path d="M2416 2081 c-3 -5 13 -11 37 -13 40 -5 55 -3 46 6 -11 9 -78 16 -83
|
||||
7z"/>
|
||||
<path d="M2033 1926 c4 -10 7 -20 7 -22 0 -2 7 -4 15 -4 8 0 15 -4 15 -10 0
|
||||
-5 5 -10 11 -10 16 0 6 45 -11 45 -8 0 -21 5 -29 9 -11 7 -13 5 -8 -8z"/>
|
||||
<path d="M1840 1840 c0 -19 3 -21 12 -12 9 9 9 15 0 24 -9 9 -12 7 -12 -12z"/>
|
||||
<path d="M1790 1835 c-10 -12 -10 -15 4 -15 9 0 16 7 16 15 0 8 -2 15 -4 15
|
||||
-2 0 -9 -7 -16 -15z"/>
|
||||
<path d="M1895 1840 c-3 -5 3 -10 15 -10 12 0 18 5 15 10 -3 6 -10 10 -15 10
|
||||
-5 0 -12 -4 -15 -10z"/>
|
||||
<path d="M2110 1782 c0 -5 -6 -9 -12 -9 -7 0 -37 -5 -67 -12 -29 -6 -72 -9
|
||||
-96 -5 -33 4 -44 2 -49 -11 -3 -10 -11 -13 -18 -9 -12 7 25 -38 65 -79 15 -16
|
||||
21 -15 18 5 0 3 12 0 27 -7 35 -18 131 -35 137 -25 3 5 14 6 25 3 15 -4 20 0
|
||||
20 14 0 13 8 19 25 21 14 1 25 -3 25 -8 0 -7 8 -6 23 1 12 7 51 14 87 15 36 2
|
||||
101 5 145 8 48 2 82 0 86 -6 8 -12 119 0 119 13 0 5 -8 9 -17 10 -16 0 -43 5
|
||||
-70 10 -4 1 -16 -1 -27 -6 -10 -4 -60 -3 -110 4 -51 6 -99 9 -108 6 -21 -8
|
||||
-90 24 -81 38 3 6 1 7 -6 3 -6 -4 -18 0 -27 8 -9 9 -19 14 -24 11 -5 -3 -11
|
||||
-1 -15 5 -7 12 -75 14 -75 2z"/>
|
||||
<path d="M1573 1643 c-21 -8 -15 -30 10 -37 21 -7 21 -7 2 -15 -27 -11 -45 -3
|
||||
-38 17 5 13 3 14 -7 5 -7 -6 -19 -9 -26 -6 -19 7 -18 -5 2 -24 9 -10 13 -25
|
||||
10 -39 -3 -12 -2 -26 4 -29 5 -3 10 -13 10 -21 0 -15 40 -106 50 -114 4 -3 15
|
||||
-21 26 -40 10 -19 21 -37 25 -40 16 -15 77 -125 70 -128 -11 -4 -1 -45 13 -54
|
||||
5 -4 12 -22 14 -40 1 -18 12 -40 23 -50 22 -19 6 -31 -23 -17 -13 7 -15 5 -11
|
||||
-11 6 -22 11 -24 32 -11 9 6 19 4 28 -4 15 -16 17 -45 3 -45 -7 0 -7 -6 0 -20
|
||||
14 -26 34 -26 27 0 -3 11 -1 18 4 14 5 -3 9 -11 9 -18 0 -7 11 -23 25 -36 14
|
||||
-13 25 -20 25 -16 0 4 10 1 23 -6 l22 -12 -25 -6 -25 -6 27 -6 c15 -3 25 -1
|
||||
22 4 -3 5 23 6 62 2 50 -5 65 -4 60 4 -10 16 28 16 57 0 14 -7 72 -11 157 -11
|
||||
164 1 195 5 246 32 21 12 43 21 47 21 12 0 81 32 87 41 3 3 19 15 35 25 17 11
|
||||
51 41 75 68 25 27 53 57 63 67 9 11 17 23 17 27 0 5 12 19 26 33 38 36 65 84
|
||||
58 104 -6 14 -3 16 14 11 17 -6 21 -4 16 8 -4 10 -1 16 7 16 7 0 10 3 7 6 -4
|
||||
3 -18 -2 -32 -12 -25 -16 -28 -16 -46 1 -11 10 -23 15 -27 12 -3 -4 -1 -7 5
|
||||
-7 6 0 16 -9 22 -20 9 -17 8 -19 -7 -13 -10 4 -29 17 -42 29 l-25 23 -11 -22
|
||||
c-8 -17 -21 -23 -53 -25 -23 -2 -42 -8 -42 -13 0 -6 4 -8 9 -4 5 3 12 1 15 -4
|
||||
11 -17 -10 -21 -30 -6 -26 19 -34 19 -28 -2 5 -17 5 -17 -9 0 -17 20 -35 23
|
||||
-33 5 0 -7 -3 -13 -9 -13 -5 0 -10 8 -9 18 1 23 -17 22 -23 -3 -3 -11 -9 -17
|
||||
-14 -14 -5 3 -9 -2 -9 -10 0 -9 9 -21 20 -28 23 -15 27 -40 5 -32 -8 4 -15 2
|
||||
-15 -4 0 -6 -13 -24 -29 -40 -18 -17 -26 -33 -21 -38 5 -5 -4 -9 -23 -9 -24 0
|
||||
-38 -7 -54 -29 -13 -17 -23 -36 -23 -44 0 -8 -5 -10 -12 -5 -7 4 -34 6 -61 5
|
||||
-31 -1 -49 2 -51 10 -2 7 -7 22 -12 33 -6 18 -8 18 -11 3 -6 -24 -25 -23 -53
|
||||
2 -16 14 -26 17 -30 9 -4 -6 -10 4 -14 23 -5 26 -9 30 -16 18 -8 -13 -10 -12
|
||||
-10 3 0 9 -6 17 -12 18 -7 0 -28 20 -46 45 -26 36 -30 49 -22 64 5 10 10 24
|
||||
10 30 0 15 -43 30 -51 18 -3 -5 -9 -3 -12 6 -5 11 0 16 14 16 29 0 16 15 -36
|
||||
39 -45 21 -55 37 -34 58 13 13 -23 53 -63 71 -27 11 -32 20 -29 52 1 11 -9 15
|
||||
-40 14 -25 0 -43 4 -46 13 -3 7 -9 10 -14 7 -6 -3 -17 0 -26 8 -12 9 -15 9 -9
|
||||
1 4 -7 2 -13 -4 -13 -6 0 -8 14 -4 39 6 39 6 40 -40 62 -45 21 -48 21 -60 4
|
||||
-12 -16 -13 -16 -19 -1 -7 16 -13 18 -34 9z m10 -87 c4 -10 1 -13 -8 -9 -8 3
|
||||
-12 9 -9 14 7 12 11 11 17 -5z m37 -22 c0 -14 -4 -23 -9 -20 -5 3 -7 15 -4 26
|
||||
7 28 13 25 13 -6z m1206 -299 c2 -10 5 -29 9 -42 3 -13 1 -23 -4 -23 -13 0
|
||||
-17 8 -25 54 -5 28 -4 37 5 34 7 -2 13 -13 15 -23z m57 -15 c3 6 4 5 3 -2 -4
|
||||
-18 -28 -16 -34 2 -4 13 -3 13 10 2 11 -9 18 -9 21 -2z m-242 -48 c-7 -2 -18
|
||||
1 -23 6 -8 8 -4 9 13 5 13 -4 18 -8 10 -11z"/>
|
||||
<path d="M2985 1340 c3 -6 -1 -13 -10 -16 -12 -5 -13 -9 -4 -14 6 -5 17 -3 24
|
||||
3 13 13 9 37 -7 37 -5 0 -6 -5 -3 -10z"/>
|
||||
<path d="M3117 1283 c-3 -5 -7 -35 -11 -68 -4 -33 -9 -82 -12 -108 l-6 -49 30
|
||||
16 c65 34 201 7 236 -46 8 -13 13 -38 11 -58 -2 -19 -4 -41 -4 -47 -1 -7 -12
|
||||
-13 -25 -13 -15 0 -26 -7 -29 -17 -4 -14 -5 -12 -6 5 -1 14 5 22 15 22 9 0 14
|
||||
6 11 13 -2 6 -11 11 -21 9 -9 -2 -16 4 -16 13 0 18 -16 20 -24 3 -3 -7 -5 -1
|
||||
-3 14 3 17 -4 36 -20 54 -12 15 -23 24 -23 19 0 -4 -6 -3 -12 1 -7 5 -35 9
|
||||
-63 9 l-50 0 -9 -83 c-6 -53 -5 -100 1 -127 11 -47 9 -154 -2 -200 -15 -57
|
||||
-78 -151 -149 -220 -99 -98 -201 -187 -234 -205 -15 -8 -41 -24 -57 -36 -42
|
||||
-28 -150 -79 -212 -99 -38 -12 -70 -14 -125 -10 -83 7 -186 32 -208 50 -8 7
|
||||
-28 13 -45 13 -16 0 -43 6 -60 13 -27 11 -54 17 -96 22 -8 1 -23 10 -33 20
|
||||
-10 9 -21 17 -26 17 -14 0 -148 138 -155 159 -3 11 -15 23 -26 26 -17 6 -26
|
||||
35 -20 68 1 4 -6 10 -14 13 -8 4 -15 12 -15 20 0 8 -5 14 -11 14 -6 0 -9 7 -5
|
||||
15 3 8 1 15 -4 15 -6 0 -10 6 -10 14 0 8 -5 16 -12 18 -6 2 -13 15 -14 29 -2
|
||||
13 -9 53 -15 89 -8 44 -10 129 -5 273 4 141 3 206 -4 202 -5 -3 -10 -12 -10
|
||||
-19 0 -8 -6 -19 -13 -25 -14 -11 -37 -76 -43 -121 -5 -42 -6 -224 0 -240 5
|
||||
-14 15 -90 22 -155 1 -16 3 -54 3 -83 1 -53 2 -54 85 -140 108 -112 178 -198
|
||||
205 -250 12 -23 20 -42 17 -42 -5 0 -72 27 -111 45 -17 7 -51 23 -78 34 -26
|
||||
11 -47 22 -47 24 0 2 26 2 58 1 l57 -3 -65 15 c-63 15 -132 42 -138 55 -1 3
|
||||
-16 21 -32 39 -17 19 -30 40 -30 48 0 8 -12 29 -27 47 -16 18 -40 48 -56 66
|
||||
-15 19 -36 45 -47 58 -11 13 -20 29 -20 36 0 7 -7 25 -15 42 -26 52 -14 220
|
||||
16 239 5 3 9 12 9 20 0 14 7 28 89 193 23 46 40 85 38 88 -9 8 -101 -24 -167
|
||||
-58 -74 -38 -197 -154 -254 -238 -20 -29 -36 -57 -36 -61 0 -4 -25 -24 -55
|
||||
-45 -59 -39 -85 -77 -85 -123 0 -28 -6 -32 -107 -77 -60 -26 -171 -75 -248
|
||||
-110 -77 -35 -180 -78 -230 -95 -50 -17 -117 -45 -150 -63 l-60 -32 -3 -117
|
||||
-3 -118 625 0 624 0 16 35 c24 51 36 52 36 5 0 -29 4 -40 13 -37 6 2 12 16 11
|
||||
30 -2 37 25 35 39 -3 l11 -30 613 0 c653 0 638 -1 696 50 16 14 57 45 91 70
|
||||
34 25 90 65 124 90 34 25 85 61 113 81 27 20 98 81 156 135 124 115 219 179
|
||||
312 209 21 6 20 5 -4 -32 -13 -21 -29 -43 -35 -50 -5 -7 -10 -15 -10 -19 0 -3
|
||||
-9 -18 -20 -34 -11 -15 -16 -31 -12 -36 4 -5 3 -6 -2 -2 -8 7 -46 -32 -46 -47
|
||||
0 -3 -13 -20 -30 -38 -16 -18 -30 -35 -30 -39 0 -4 -9 -10 -20 -13 -11 -3 -20
|
||||
-12 -20 -19 0 -7 -11 -18 -25 -24 -14 -6 -25 -15 -25 -20 0 -6 -54 -51 -119
|
||||
-97 -8 -5 -31 -27 -53 -48 -40 -39 -110 -71 -140 -63 -10 2 -18 0 -18 -6 0 -5
|
||||
9 -12 20 -15 11 -3 18 -9 15 -13 -3 -5 -15 -6 -27 -2 -14 3 -19 2 -14 -6 5 -9
|
||||
228 -12 872 -12 l864 0 0 123 0 122 -61 27 c-55 25 -61 30 -54 50 4 13 10 42
|
||||
12 65 6 49 0 58 -114 191 -68 79 -106 132 -159 218 -39 64 -186 240 -236 282
|
||||
-29 24 -75 55 -102 69 -65 33 -194 56 -271 48 -55 -6 -75 -1 -211 44 -121 41
|
||||
-164 51 -217 51 -36 0 -68 -3 -70 -7z m253 -413 c0 -5 -7 -7 -15 -4 -11 5 -13
|
||||
2 -9 -10 4 -11 1 -16 -10 -16 -14 0 -15 4 -6 20 11 20 40 28 40 10z"/>
|
||||
<path d="M2450 1085 c0 -8 5 -15 10 -15 6 0 10 7 10 15 0 8 -4 15 -10 15 -5 0
|
||||
-10 -7 -10 -15z"/>
|
||||
<path d="M2705 61 c-3 -6 4 -9 15 -8 11 1 20 5 20 9 0 11 -28 10 -35 -1z"/>
|
||||
<path d="M2760 60 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
|
||||
-10 -4 -10 -10z"/>
|
||||
<path d="M2790 60 c0 -5 7 -10 16 -10 8 0 12 5 9 10 -3 6 -10 10 -16 10 -5 0
|
||||
-9 -4 -9 -10z"/>
|
||||
<path d="M2835 50 c3 -5 10 -10 16 -10 5 0 9 5 9 10 0 6 -7 10 -16 10 -8 0
|
||||
-12 -4 -9 -10z"/>
|
||||
<path d="M2835 10 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7
|
||||
-4 -4 -10z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
BIN
static/font/OpenSans-Bold.ttf
Normal file
BIN
static/font/OpenSans-BoldItalic.ttf
Normal file
BIN
static/font/OpenSans-Italic.ttf
Normal file
BIN
static/font/OpenSans-Regular.ttf
Normal file
BIN
static/img/me.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
7
static/js/Chart-v2.9.3.bundle.min.js
vendored
349
static/normalize.css
vendored
@@ -1,349 +0,0 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
138
static/style.css
@@ -1,138 +0,0 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 700px;
|
||||
padding: 100px 20px 400px 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.title div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 48px;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.title h2 {
|
||||
font-weight: normal;
|
||||
margin-top: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.title img {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
border-radius: 100%;
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menu .button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tracking {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.tracking-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tracking-form input {
|
||||
margin: 3px 6px 3px 0;
|
||||
}
|
||||
|
||||
.embed {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
border: 1px dotted #121212;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.embed .image {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.embed .info {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.embed .info .title {
|
||||
font-weight: bold;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.embed .info .url {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
.break-line-anywhere {
|
||||
line-break: anywhere;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 80px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
padding: 40px 20px 80px 20px;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.title h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.title img {
|
||||
max-width: 72px;
|
||||
max-height: 72px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tracking-form {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
1
static/web/main.css
Normal file
@@ -0,0 +1 @@
|
||||
@font-face{font-family:"Open Sans";font-style:italic;font-weight:normal;font-stretch:100%;font-display:swap;src:url("../font/OpenSans-Italic.ttf") format("truetype")}@font-face{font-family:"Open Sans";font-style:normal;font-weight:normal;font-stretch:100%;font-display:swap;src:url("../font/OpenSans-Regular.ttf") format("truetype")}@font-face{font-family:"Open Sans";font-style:normal;font-weight:bold;font-stretch:100%;font-display:swap;src:url("../font/OpenSans-Bold.ttf") format("truetype")}@font-face{font-family:"Open Sans";font-style:italic;font-weight:bold;font-stretch:100%;font-display:swap;src:url("../font/OpenSans-BoldItalic.ttf") format("truetype")}*{box-sizing:border-box;font-family:"Open Sans",sans-serif;font-size:18px;line-height:1.5;color:#0f0f0f}body{padding:80px 40px;max-width:1000px;margin:0 auto}h1,h2{font-size:32px;margin:0 0 40px 0;font-weight:normal}h1 a,h2 a{font-size:32px}h2{font-size:26px}a{text-decoration:none;transition:all .3s;color:#82b3db}a:hover{color:#0f0f0f}nav{display:flex;justify-content:space-between;align-items:center;margin:0 0 80px 0}nav ul{display:flex;gap:20px;margin:0;padding:0}nav ul li{list-style:none;margin:0;padding:0}nav ul li a{color:#0f0f0f}nav ul li a:hover{color:#82b3db}nav .contact{display:inline-block;padding:8px 16px;border-radius:8px;border:2px solid #82b3db;color:#0f0f0f;transition:all .3s;color:#82b3db}nav .contact:hover{background:#82b3db;color:#fff}section{margin:0 0 80px 0}footer{margin:0 0 80px 0;padding:40px 0;border-style:solid;border-width:2px 0 0 0;border-color:#e6e6e6}.intro article{display:flex;gap:40px;justify-content:flex-start;align-items:center}.intro article h1{margin:0}.intro article img{max-width:256px;max-height:256px;aspect-ratio:1;border-radius:8px}/*# sourceMappingURL=main.css.map */
|
||||
1
static/web/main.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../assets/scss/_font.scss","../../assets/scss/main.scss"],"names":[],"mappings":"AAAA,WACI,wBACA,kBACA,mBACA,kBACA,kBACA,0DAGJ,WACI,wBACA,kBACA,mBACA,kBACA,kBACA,2DAGJ,WACI,wBACA,kBACA,iBACA,kBACA,kBACA,wDAGJ,WACI,wBACA,kBACA,iBACA,kBACA,kBACA,8DC1BJ,EACC,sBACA,mCACA,eACA,gBACA,MATW,QAYZ,KACC,kBACA,iBACA,cAGD,MACC,eACA,kBACA,mBAEA,UACC,eAIF,GACC,eAGD,EACC,qBACA,mBACA,MAjCW,QAmCX,QACC,MAtCU,QA0CZ,IACC,aACA,8BACA,mBACA,kBAEA,OACC,aACA,SACA,SACA,UAEA,UACC,gBACA,SACA,UAEA,YACC,MA5DQ,QA8DR,kBACC,MA7DO,QAmEX,aACC,qBACA,iBACA,cAzEa,IA0Eb,yBACA,MA1EU,QA2EV,mBACA,MA1EU,QA4EV,mBACC,WA7ES,QA8ET,WAKH,QACC,kBAGD,OACC,kBACA,eACA,mBACA,uBACA,aA7FW,QAiGX,eACC,aACA,SACA,2BACA,mBAEA,kBACC,SAGD,mBACC,gBACA,iBACA,eACA,cAjHY","file":"main.css"}
|
||||
1
static/web/main.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(()=>{console.log("Hello from Shifu!");})();
|
||||
@@ -1,93 +0,0 @@
|
||||
{{template "head.html"}}
|
||||
|
||||
<div class="title">
|
||||
<div>
|
||||
<h1>marvin blum</h1>
|
||||
<h2>welcome to my website!</h2>
|
||||
</div>
|
||||
<img src="../static/avatar_100.jpg" alt="Marvin Blum" />
|
||||
</div>
|
||||
|
||||
{{template "menu.html"}}
|
||||
|
||||
<section>
|
||||
<h2>Who Am I?</h2>
|
||||
<p>
|
||||
I'm a full stack software engineer from Germany, open source and Linux enthusiast and co-founder of <a href="https://emvi.com/" target="_blank">Emvi</a>.
|
||||
In love with Go, but fluent in a lot of programming languages.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Latest Blog Posts</h2>
|
||||
{{range $article := .Articles}}
|
||||
<p>
|
||||
<small>{{format $article.Published "2. January 2006"}}</small>
|
||||
<br />
|
||||
<a href="/blog/{{slug $article.LatestArticleContent.Title}}-{{$article.Id}}">{{$article.LatestArticleContent.Title}}</a>
|
||||
</p>
|
||||
{{else}}
|
||||
<p>There are no blog posts yet...</p>
|
||||
{{end}}
|
||||
<p>
|
||||
<a href="/blog">View all</a>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Projects</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://emvi.com/" target="_blank">Emvi</a> a note taking, collaboration and knowledge management tool for personal use and teams of any size
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/pirsch-analytics/pirsch" target="_blank">pirsch</a> a server side, no-cookie and privacy focused tracking library for Golang
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/emvi/logbuch" target="_blank">logbuch</a> a simple Golang logging library
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/emvi/null" target="_blank">null</a> a Golang library for nullable types supporting databases and json
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/emvi/hide" target="_blank">hide</a> a Golang library to obscure IDs on the API layer
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/assetto-corsa-web/accweb" target="_blank">accweb</a> a web interface to manage game servers
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Skills</h2>
|
||||
<ul>
|
||||
<li>Go (Golang)</li>
|
||||
<li>JavaScript (Vue, Node)</li>
|
||||
<li>HTML, CSS, Sass and all the web fuzz</li>
|
||||
<li>Java</li>
|
||||
<li>PHP</li>
|
||||
<li>Linux</li>
|
||||
<li>Docker</li>
|
||||
<li>Kubernetes</li>
|
||||
<li>... and more</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Work</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://pirsch.io/" target="_blank">Pirsch</a> co-founder of a privacy-friendly, open-source, web analytics SaaS and library
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://emvi.com/" target="_blank">Emvi</a> co-founder of a note taking, collaboration and knowledge management SaaS startup
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://skalar.marketing/" target="_blank">skalar marketing</a> as a full stack developer and web designer
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.bertelsmann.com/divisions/arvato/#st-1" target="_blank">arvato</a> as a Java developer
|
||||
</li>
|
||||
<li>
|
||||
some freelancing from time to time
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{template "end.html"}}
|
||||
@@ -1,10 +0,0 @@
|
||||
{{template "head.html"}}
|
||||
{{template "menu.html"}}
|
||||
|
||||
<section>
|
||||
<h1>{{.Title}}</h1>
|
||||
<small>Published on {{format .Published "2. January 2006"}}</small>
|
||||
{{.Content}}
|
||||
</section>
|
||||
|
||||
{{template "end.html"}}
|
||||
@@ -1,25 +0,0 @@
|
||||
{{template "head.html"}}
|
||||
{{template "menu.html"}}
|
||||
|
||||
<section>
|
||||
<h1>Blog</h1>
|
||||
</section>
|
||||
{{range $year, $articles := .Articles}}
|
||||
<section>
|
||||
<h2>{{$year}}</h2>
|
||||
|
||||
{{range $article := $articles}}
|
||||
<p>
|
||||
<small>{{format $article.Published "2. January 2006"}}</small>
|
||||
<br />
|
||||
<a href="/blog/{{slug $article.LatestArticleContent.Title}}-{{$article.Id}}">{{$article.LatestArticleContent.Title}}</a>
|
||||
</p>
|
||||
{{end}}
|
||||
</section>
|
||||
{{else}}
|
||||
<section>
|
||||
<p>There are no blog posts yet...</p>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
{{template "end.html"}}
|
||||
@@ -1,14 +0,0 @@
|
||||
<hr />
|
||||
<section>
|
||||
<p>
|
||||
Would you like to see more? Read my blog articles on <a href="https://emvi.com/blog" target="_blank">Emvi</a>, my project page on <a href="https://github.com/Kugelschieber" target="_blank">GitHub</a> or send me a <a href="mailto:marvin@marvinblum.de">mail</a>.
|
||||
</p>
|
||||
<p>
|
||||
This page uses <a href="https://concrete.style/" target="_blank">concrete</a> for styling. Check it out!
|
||||
</p>
|
||||
<p>
|
||||
This page does not use cookies. <a href="/legal">Legal</a>
|
||||
</p>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,34 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/" />
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="copyright" content="Marvin Blum" />
|
||||
<meta name="author" content="Marvin Blum" />
|
||||
<meta name="title" content="Marvin Blum" />
|
||||
<meta name="description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||
<meta name="msapplication-TileColor" content="#000000" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="twitter:card" content="profile" />
|
||||
<meta name="twitter:site" content="@m5blum" />
|
||||
<meta name="twitter:title" content="Marvin Blum" />
|
||||
<meta name="twitter:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||
<meta name="twitter:image" content="https://marvinblum.de/avatar.png" />
|
||||
<meta property="og:url" content="https://marvinblum.de/" />
|
||||
<meta property="og:title" content="Marvin Blum" />
|
||||
<meta property="og:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||
<meta property="og:image" content="https://marvinblum.de/avatar.png" />
|
||||
<title>marvin blum</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="../static/favicon/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="../static/favicon/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="../static/favicon/favicon-16x16.png" />
|
||||
<link rel="manifest" href="../static/favicon/site.webmanifest" />
|
||||
<link rel="mask-icon" href="../static/favicon/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="stylesheet" type="text/css" href="../static/normalize.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../static/concrete.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../static/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
@@ -1,23 +0,0 @@
|
||||
{{template "head.html"}}
|
||||
{{template "menu.html"}}
|
||||
|
||||
<section>
|
||||
<h1>Legal</h1>
|
||||
</section>
|
||||
<section>
|
||||
<h2>According to §5 TMG</h2>
|
||||
<p>
|
||||
Marvin Blum<br />
|
||||
Gerhard-Hauptmannstraße 3<br />
|
||||
33378 Rheda-Wiedenbrück, Germany<br />
|
||||
<a href="mailto:marvin@marvinblum.de">marvin@marvinblum.de</a>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Cookie Policy</h2>
|
||||
<p>
|
||||
This page does not use cookies.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{template "end.html"}}
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="menu">
|
||||
<a href="/" class="button filled">About</a>
|
||||
<a href="/blog" class="button filled">Blog</a>
|
||||
<a href="mailto:marvin@marvinblum.de" class="button">Contact me</a>
|
||||
<a href="https://github.com/Kugelschieber" target="_blank" class="button">GitHub</a>
|
||||
<a href="https://twitter.com/m5blum" target="_blank" class="button">Twitter</a>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
{{template "head.html"}}
|
||||
{{template "menu.html"}}
|
||||
|
||||
<section>
|
||||
<h2>Page not found</h2>
|
||||
<p>
|
||||
Nothing to see here...<br />
|
||||
<a href="/">Return home</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{{template "end.html"}}
|
||||
2
tpl/end.html
Normal file
@@ -0,0 +1,2 @@
|
||||
</body>
|
||||
</html>
|
||||
24
tpl/head.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- $title := copy .Page .Content "title" -}}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{.Page.Language}}">
|
||||
<head>
|
||||
<base href="/" />
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="copyright" content="{{copy .Page .Content "copyright"}}" />
|
||||
<meta name="author" content="{{copy .Page .Content "author"}}" />
|
||||
<meta name="title" content="{{$title}}" />
|
||||
<meta name="description" content="{{copy .Page .Content "meta_description"}}" />
|
||||
<link rel="canonical" href="{{.Page.CanonicalLink}}" />
|
||||
|
||||
{{range $language, $path := .Page.Path}}
|
||||
<link rel="alternate" hreflang="{{if eq $language "en"}}x-default{{else}}{{$language}}{{end}}" href="{{fmt "%s%s" hostname $path}}" />
|
||||
{{end}}
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/static/web/main.css?v={{.CMS.LastUpdate}}" />
|
||||
<script src="/static/web/main.min.js?v={{.CMS.LastUpdate}}"></script>
|
||||
|
||||
<title>{{$title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
46
tpl/intro.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<nav>
|
||||
<div>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/blog">Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/Kugelschieber" target="_blank">GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/m5blum" target="_blank">Twitter/X</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://social.anoxinon.de/@m5blum" target="_blank">Mastodon</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<a href="mailto:marvin.blum@emvi.com" class="contact">Contact me</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="intro">
|
||||
<article>
|
||||
<h1>Hi, I'm Marvin, co-founder of <a href="https://pirsch.io" target="_blank">Pirsch Analytics</a> and <a href="https://emvi.com" target="_blank">Emvi</a>, software engineer, and open-source enthusiast.</h1>
|
||||
<img src="/static/img/me.jpg" alt="Picture" />
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>TODO</h2>
|
||||
<ul>
|
||||
<li>About</li>
|
||||
<li>Skills</li>
|
||||
<li>Projects</li>
|
||||
<li>Work Experience</li>
|
||||
<li>Hire me!</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>TODO Footer</p>
|
||||
</footer>
|
||||
119
tpl/template.go
@@ -1,119 +0,0 @@
|
||||
package tpl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/gosimple/slug"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
templateDir = "template/*"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
tpl *template.Template
|
||||
cache map[string][]byte
|
||||
hotReload bool
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCache() *Cache {
|
||||
cache := &Cache{
|
||||
cache: make(map[string][]byte),
|
||||
hotReload: os.Getenv("MB_HOT_RELOAD") == "true",
|
||||
}
|
||||
logbuch.Debug("Template cache hot reload", logbuch.Fields{"hot_reload": cache.hotReload})
|
||||
cache.load()
|
||||
return cache
|
||||
}
|
||||
|
||||
func (cache *Cache) load() {
|
||||
logbuch.Debug("Loading templates")
|
||||
funcMap := template.FuncMap{
|
||||
"slug": slug.Make,
|
||||
"format": func(t time.Time, layout string) string { return t.Format(layout) },
|
||||
"multiply": func(a, b float64) float64 { return a * b },
|
||||
"divide": func(a, b float64) float64 { return a / b },
|
||||
"round": func(f float64) string { return fmt.Sprintf("%.2f", f) },
|
||||
"intRange": intRange,
|
||||
"float64": func(i int) float64 { return float64(i) },
|
||||
}
|
||||
var err error
|
||||
cache.tpl, err = template.New("").Funcs(funcMap).ParseGlob(templateDir)
|
||||
|
||||
if err != nil {
|
||||
logbuch.Fatal("Error loading template", logbuch.Fields{"err": err})
|
||||
}
|
||||
|
||||
logbuch.Debug("Templates loaded", logbuch.Fields{"hot_reload": cache.hotReload})
|
||||
}
|
||||
|
||||
func (cache *Cache) Render(w http.ResponseWriter, name string, data interface{}) {
|
||||
cache.m.RLock()
|
||||
|
||||
if cache.cache[name] == nil || cache.hotReload {
|
||||
cache.m.RUnlock()
|
||||
cache.m.Lock()
|
||||
defer cache.m.Unlock()
|
||||
logbuch.Debug("Rendering template", logbuch.Fields{"name": name})
|
||||
|
||||
if cache.hotReload {
|
||||
logbuch.Debug("Reloading templates")
|
||||
cache.load()
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
if err := cache.tpl.ExecuteTemplate(&buffer, name, data); err != nil {
|
||||
logbuch.Error("Error executing template", logbuch.Fields{"err": err, "name": name})
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
cache.cache[name] = buffer.Bytes()
|
||||
} else {
|
||||
cache.m.RUnlock()
|
||||
}
|
||||
|
||||
if _, err := w.Write(cache.cache[name]); err != nil {
|
||||
logbuch.Error("Error sending response to client", logbuch.Fields{"err": err, "template": name})
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (cache *Cache) RenderWithoutCache(w http.ResponseWriter, name string, data interface{}) {
|
||||
if cache.hotReload {
|
||||
logbuch.Debug("Reloading templates")
|
||||
cache.load()
|
||||
}
|
||||
|
||||
if err := cache.tpl.ExecuteTemplate(w, name, data); err != nil {
|
||||
logbuch.Error("Error executing template", logbuch.Fields{"err": err, "name": name})
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (cache *Cache) Clear() {
|
||||
cache.m.Lock()
|
||||
defer cache.m.Unlock()
|
||||
cache.cache = make(map[string][]byte)
|
||||
}
|
||||
|
||||
func intRange(start, end int) []int {
|
||||
if end-start < 0 {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
r := make([]int, end-start)
|
||||
|
||||
for i := 0; i < end-start; i++ {
|
||||
r[i] = start + i
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||