git-svn-id: svn://losandesgames.com/alfheim-website@20 15359d88-9307-4e75-a9c1-e5686e5897df
This commit is contained in:
parent
c3e894ffb5
commit
90eec7a300
6
go.mod
6
go.mod
@ -3,10 +3,14 @@ module alfheimgame
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/stripe/stripe-go/v78 v78.7.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
)
|
||||
|
||||
require github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
require (
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
)
|
||||
|
||||
39
go.sum
39
go.sum
@ -1,3 +1,5 @@
|
||||
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
||||
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
@ -15,18 +17,55 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stripe/stripe-go/v78 v78.7.0 h1:TdTkzBn0wB0ntgOI74YHpvsNyHPBijX83n4ljsjXh6o=
|
||||
github.com/stripe/stripe-go/v78 v78.7.0/go.mod h1:GjncxVLUc1xoIOidFqVwq+y3pYiG7JLVWiVQxTsLrvQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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=
|
||||
|
||||
12
handlers.go
12
handlers.go
@ -161,7 +161,7 @@ func login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if id > 0 {
|
||||
session.Values["id"] = id
|
||||
fmt.Println("Logged in with id:", id)
|
||||
//fmt.Println("Logged in with id:", id)
|
||||
session.Save(r, w)
|
||||
http.Redirect(w, r, "/account", http.StatusSeeOther)
|
||||
}
|
||||
@ -268,7 +268,7 @@ func account(w http.ResponseWriter, r *http.Request) {
|
||||
id := authenticated_user(w, r)
|
||||
account, err := users.GetAccount(id)
|
||||
|
||||
fmt.Println(id, account)
|
||||
//fmt.Println(id, account)
|
||||
|
||||
text, err := template.ParseFiles("ui/base.html", "ui/account.html")
|
||||
if err != nil {
|
||||
@ -323,7 +323,7 @@ func subscribe(w http.ResponseWriter, r *http.Request) {
|
||||
prices := make([]stripe.Price, 0)
|
||||
|
||||
for results.Next() {
|
||||
fmt.Println(results.Current())
|
||||
//fmt.Println(results.Current())
|
||||
prices = append(prices, *results.Price())
|
||||
}
|
||||
|
||||
@ -486,7 +486,7 @@ func handle_checkout_session_completed(checkoutsession stripe.CheckoutSession) e
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(subscription.Customer.ID)
|
||||
//fmt.Println(subscription.Customer.ID)
|
||||
|
||||
//var status SubscriptionStatus
|
||||
//switch subscription.Status {
|
||||
@ -520,8 +520,8 @@ func handle_payment_method_attached(paymentmethod stripe.PaymentMethod) error {
|
||||
// make this the new customer's default payment method
|
||||
params := &stripe.CustomerParams{}
|
||||
params.DefaultSource = &paymentmethod.ID
|
||||
result, err := customer.Update(paymentmethod.Customer.ID, params)
|
||||
fmt.Println(result)
|
||||
/*result*/_, err := customer.Update(paymentmethod.Customer.ID, params)
|
||||
//fmt.Println(result)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
|
||||
16
main.go
16
main.go
@ -74,14 +74,14 @@ func main() {
|
||||
mux.HandleFunc("/favicon.ico", favicon)
|
||||
|
||||
mux.HandleFunc("/", home)
|
||||
mux.HandleFunc("/login", login)
|
||||
mux.HandleFunc("/logout", logout)
|
||||
mux.HandleFunc("/register", register)
|
||||
mux.HandleFunc("/account", require_authenticated_user(account))
|
||||
mux.HandleFunc("/deleteaccount", require_authenticated_user(deleteaccount))
|
||||
mux.HandleFunc("/subscribe", require_authenticated_user(subscribe_stripe))
|
||||
mux.HandleFunc("/managebilling", require_authenticated_user(managebilling))
|
||||
mux.HandleFunc("/webhook", webhooks)
|
||||
//mux.HandleFunc("/login", login)
|
||||
//mux.HandleFunc("/logout", logout)
|
||||
//mux.HandleFunc("/register", register)
|
||||
//mux.HandleFunc("/account", require_authenticated_user(account))
|
||||
//mux.HandleFunc("/deleteaccount", require_authenticated_user(deleteaccount))
|
||||
//mux.HandleFunc("/subscribe", require_authenticated_user(subscribe_stripe))
|
||||
//mux.HandleFunc("/managebilling", require_authenticated_user(managebilling))
|
||||
//mux.HandleFunc("/webhook", webhooks)
|
||||
|
||||
|
||||
log.Fatal(http.ListenAndServe(*addr, secure_headers(mux)))
|
||||
|
||||
23
models.go
23
models.go
@ -6,10 +6,11 @@ package main
|
||||
|
||||
import "errors"
|
||||
import "time"
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
//import "golang.org/x/crypto/bcrypt"
|
||||
import "database/sql"
|
||||
import _ "github.com/lib/pq"
|
||||
import "fmt"
|
||||
import "github.com/alexedwards/argon2id"
|
||||
|
||||
import "github.com/stripe/stripe-go/v78"
|
||||
import "github.com/stripe/stripe-go/v78/customer"
|
||||
@ -63,7 +64,9 @@ type SubscriptionModel struct {
|
||||
func (m *Usermodel) Insert(username string, password string, firstname string, lastname string, email string) (int32, error) {
|
||||
|
||||
|
||||
hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
//hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
hashedpassword, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||
//fmt.Println(hashedpassword)
|
||||
stmt := `INSERT INTO accounts (username, password, firstname, lastname, email, created) VALUES ($1, $2, $3, $4, $5, NOW()) RETURNING id`
|
||||
|
||||
var insertid int32
|
||||
@ -88,7 +91,7 @@ func (m *Usermodel) Insert(username string, password string, firstname string, l
|
||||
|
||||
stmt = `UPDATE accounts SET stripe_id = $1 WHERE id = $2`
|
||||
|
||||
fmt.Println(customer.ID, insertid)
|
||||
//fmt.Println(customer.ID, insertid)
|
||||
|
||||
_, err = m.DB.Exec(stmt, customer.ID, insertid)
|
||||
if err != nil {
|
||||
@ -103,11 +106,11 @@ func (m *Usermodel) Delete(id int32) error {
|
||||
account, err := users.GetAccount(id)
|
||||
|
||||
if account.StripeID != "" {
|
||||
result, err := customer.Del(account.StripeID, nil)
|
||||
/*result*/_, err := customer.Del(account.StripeID, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(result)
|
||||
//fmt.Println(result)
|
||||
}
|
||||
|
||||
stmt := `DELETE FROM accounts WHERE id = $1`
|
||||
@ -142,15 +145,15 @@ func (m *Usermodel) GetAccount(id int32) (Account, error) {
|
||||
|
||||
func (m *Usermodel) Authenticate(username string, password string) (int32, error) {
|
||||
var id int32
|
||||
var hashedpassword []byte
|
||||
var hashedpassword string
|
||||
row := m.DB.QueryRow("SELECT id, password FROM accounts WHERE username = $1", username)
|
||||
err := row.Scan(&id, &hashedpassword)
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(hashedpassword, []byte(password))
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
match, err := argon2id.ComparePasswordAndHash(password, hashedpassword)
|
||||
if !match {
|
||||
return 0, ErrInvalidCredentials
|
||||
} else if err != nil {
|
||||
return 0, err
|
||||
@ -168,7 +171,7 @@ func (m *Usermodel) ExistsAccount(id int32) bool {
|
||||
}
|
||||
row.Scan(&exists)
|
||||
|
||||
fmt.Println(exists)
|
||||
//fmt.Println(exists)
|
||||
|
||||
return exists
|
||||
}
|
||||
@ -237,7 +240,7 @@ func (m *SubscriptionModel) GetSubscription(id int32) (Subscription, error) {
|
||||
return Subscription{}, err
|
||||
}
|
||||
|
||||
fmt.Println(subscription.Status)
|
||||
//fmt.Println(subscription.Status)
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
@ -1,16 +1,27 @@
|
||||
html {
|
||||
height: 100%;
|
||||
padding: 8px;
|
||||
/*background: no-repeat url(/static/image.png);
|
||||
background-size: cover;
|
||||
background-position: center;*/
|
||||
background-color: black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #3475CB;
|
||||
font-family: "Vollkorn";
|
||||
color: white;
|
||||
margin: 0px;
|
||||
background: no-repeat url(/static/image.png);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100%;
|
||||
margin-top: 0px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0px;
|
||||
min-height: 100%;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
header {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -19,10 +30,11 @@ h1 {
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
background-color: white;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbuttons a {
|
||||
@ -30,13 +42,13 @@ nav {
|
||||
}
|
||||
|
||||
.maintitle {
|
||||
color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.loginbutton {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
background-color: #3475CB;
|
||||
background-color: ;
|
||||
padding-top: 15px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 15px;
|
||||
@ -47,15 +59,21 @@ nav {
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 8px;
|
||||
margin: 0px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-content: center;
|
||||
max-width: 900px;
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.account-wrapper {
|
||||
background: transparent;
|
||||
border: 2px solid white;
|
||||
|
||||
17
ui/base.html
17
ui/base.html
@ -1,8 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<!--
|
||||
/\ | |--- | | |--- || |\ /|
|
||||
/__\ | |-- |---| |-- || | \ / |
|
||||
/ \ |___ | | | |___ || | \/ |
|
||||
-->
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Alfheim</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="A handcrafted economy and politics MMO." />
|
||||
<meta name="author" content="Vicente Ferrari Smith" />
|
||||
<meta name="keywords" content="Alfheim, indie, video game, mmo, colony, colony simulator, vicente ferrari smith, game, economy, politics, alfheim" />
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Vollkorn";
|
||||
@ -17,7 +27,7 @@
|
||||
<nav>
|
||||
<a href="/"><h1 class="maintitle">Alfheim</h1></a>
|
||||
<div class="navbuttons">
|
||||
{{if not .ActiveSubscription}}
|
||||
{{/*{{if not .ActiveSubscription}}
|
||||
<a href="/subscribe"><div class="loginbutton"><strong>Subscribe</strong></div></a>
|
||||
{{end}}
|
||||
|
||||
@ -28,6 +38,7 @@
|
||||
<a href="/login"><div class="loginbutton"><strong>Log in</strong><img src="/static/login_24dp_FILL0_wght400_GRAD0_opsz24.svg" alt=""/></div></a>
|
||||
<a href="/register"><div class="loginbutton"><strong>Register</strong></div></a>
|
||||
{{end}}
|
||||
*/}}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@ -37,7 +48,7 @@
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Alfheim © 2024, Vicente Ferrari Smith, Los Andes Studios, Vienna, Austria.
|
||||
Alfheim © 2024, Vicente Ferrari Smith, Los Andes Studios, Vienna, Austria. All rights reserved.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
{{define "body"}}
|
||||
<div>This is a great game coming soon.</div>
|
||||
<p>Alfheim is a game about humanity: our own will, and our place in the world at large.</p>
|
||||
<p>Alfheim is an MMO where you—the player—administers a medieval settlement of elves.
|
||||
In an infinite world, and with thousands of other players, you will have to design an efficient command economy, if you wish to be able to compete with your enemies... Or your friends?</p>
|
||||
<br\>
|
||||
<img src="/static/image.png" alt="Alfheim" style="height: 100%; width: 100%; object-fit: contain">
|
||||
{{end}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user