diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go
index bf3d2f1..abfdeaf 100644
--- a/cmd/web/handlers.go
+++ b/cmd/web/handlers.go
@@ -5,57 +5,49 @@ import (
"fmt"
"net/http"
+ "git.32bit.cafe/32bitcafe/guestbook/internal/forms"
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
"git.32bit.cafe/32bitcafe/guestbook/internal/validator"
+ "git.32bit.cafe/32bitcafe/guestbook/ui/views"
)
func (app *application) home(w http.ResponseWriter, r *http.Request) {
- data := app.newTemplateData(r)
- app.render(w, r, http.StatusOK, "home.tmpl.html", data)
-}
-
-type userRegistrationForm struct {
- Name string `schema:"username"`
- Email string `schema:"email"`
- Password string `schema:"password"`
- validator.Validator `schema:"-"`
+ data := app.newCommonData(r)
+ views.Home("Home", data).Render(r.Context(), w)
}
func (app *application) getUserRegister(w http.ResponseWriter, r *http.Request) {
- data := app.newTemplateData(r)
- data.Form = userRegistrationForm{}
- app.render(w, r, http.StatusOK, "usercreate.view.tmpl.html", data)
+ form := forms.UserRegistrationForm{}
+ data := app.newCommonData(r)
+ views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
}
func (app *application) postUserRegister(w http.ResponseWriter, r *http.Request) {
- var form userRegistrationForm
+ var form forms.UserRegistrationForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
-
form.CheckField(validator.NotBlank(form.Name), "name", "This field cannot be blank")
form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
form.CheckField(validator.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long")
-
if !form.Valid() {
- data := app.newTemplateData(r)
- data.Form = form
- app.render(w, r, http.StatusUnprocessableEntity, "usercreate.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ w.WriteHeader(http.StatusUnprocessableEntity)
+ views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
return
}
-
shortId := app.createShortId()
err = app.users.Insert(shortId, form.Name, form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrDuplicateEmail) {
form.AddFieldError("email", "Email address is already in use")
- data := app.newTemplateData(r)
- data.Form = form
- app.render(w ,r, http.StatusUnprocessableEntity, "usercreate.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ w.WriteHeader(http.StatusUnprocessableEntity)
+ views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
} else {
app.serverError(w, r, err)
}
@@ -65,58 +57,42 @@ func (app *application) postUserRegister(w http.ResponseWriter, r *http.Request)
http.Redirect(w, r, "/users/login", http.StatusSeeOther)
}
-type userLoginForm struct {
- Email string `schema:"email"`
- Password string `schema:"password"`
- validator.Validator `schema:"-"`
-}
-
func (app *application) getUserLogin(w http.ResponseWriter, r *http.Request) {
- data := app.newTemplateData(r)
- data.Form = userLoginForm{}
- app.render(w, r, http.StatusOK, "login.view.tmpl.html", data)
+ views.UserLogin("Login", app.newCommonData(r), forms.UserLoginForm{}).Render(r.Context(), w)
}
func (app *application) postUserLogin(w http.ResponseWriter, r *http.Request) {
- var form userLoginForm
-
+ var form forms.UserLoginForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
}
-
form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
-
if !form.Valid() {
- data := app.newTemplateData(r)
- data.Form = userLoginForm{}
- app.render(w, r, http.StatusUnprocessableEntity, "login.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ w.WriteHeader(http.StatusUnprocessableEntity)
+ views.UserLogin("Login", data, form).Render(r.Context(), w)
return
}
-
id, err := app.users.Authenticate(form.Email, form.Password)
if err != nil {
if errors.Is(err, models.ErrInvalidCredentials) {
form.AddNonFieldError("Email or password is incorrect")
- data := app.newTemplateData(r)
- data.Form = form
- app.render(w, r, http.StatusUnprocessableEntity, "login.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ views.UserLogin("Login", data, form).Render(r.Context(), w)
} else {
app.serverError(w, r, err)
}
return
}
-
err = app.sessionManager.RenewToken(r.Context())
if err != nil {
app.serverError(w, r, err)
return
}
-
app.sessionManager.Put(r.Context(), "authenticatedUserId", id)
-
http.Redirect(w, r, "/", http.StatusSeeOther)
}
@@ -126,13 +102,14 @@ func (app *application) postUserLogout(w http.ResponseWriter, r *http.Request) {
app.serverError(w, r, err)
return
}
-
app.sessionManager.Remove(r.Context(), "authenticatedUserId")
app.sessionManager.Put(r.Context(), "flash", "You've been logged out successfully!")
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func (app *application) getUsersList(w http.ResponseWriter, r *http.Request) {
+ // skip templ conversion for this view, which will not be available in the final app
+ // something similar will be available in the admin panel
users, err := app.users.GetAll()
if err != nil {
app.serverError(w, r, err)
@@ -154,18 +131,13 @@ func (app *application) getUser(w http.ResponseWriter, r *http.Request) {
}
return
}
- data := app.newTemplateData(r)
- data.User = user
- app.render(w, r, http.StatusOK, "user.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ views.UserProfile(user.Username, data, user).Render(r.Context(), w)
}
func (app *application) getGuestbookCreate(w http.ResponseWriter, r* http.Request) {
- data := app.newTemplateData(r)
- if r.Header.Get("HX-Request") == "true" {
- app.renderHTMX(w, r, http.StatusOK, "guestbookcreate.part.html", data)
- return
- }
- app.render(w, r, http.StatusOK, "guestbookcreate.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ views.GuestbookCreate("New Guestbook", data).Render(r.Context(), w)
}
func (app *application) postGuestbookCreate(w http.ResponseWriter, r* http.Request) {
@@ -194,24 +166,13 @@ func (app *application) postGuestbookCreate(w http.ResponseWriter, r* http.Reque
func (app *application) getGuestbookList(w http.ResponseWriter, r *http.Request) {
userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId")
- user, err := app.users.GetById(userId)
- if err != nil {
- app.serverError(w, r, err)
- return
- }
guestbooks, err := app.guestbooks.GetAll(userId)
if err != nil {
app.serverError(w, r, err)
return
}
- data := app.newTemplateData(r)
- data.Guestbooks = guestbooks
- data.User = user
- if r.Header.Get("HX-Request") == "true" {
- app.renderHTMX(w, r, http.StatusCreated, "guestbooklist.part.html", data)
- return
- }
- app.render(w, r, http.StatusOK, "guestbooklist.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ views.GuestbookList("Guestbooks", data, guestbooks).Render(r.Context(), w)
}
func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) {
@@ -230,10 +191,8 @@ func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) {
app.serverError(w, r, err)
return
}
- data := app.newTemplateData(r)
- data.Guestbook = guestbook
- data.Comments = comments
- app.render(w, r, http.StatusOK, "guestbook.view.tmpl.html", data)
+ data := app.newCommonData(r)
+ views.GuestbookView("Guestbook", data, guestbook, comments).Render(r.Context(), w)
}
func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Request) {
@@ -267,6 +226,7 @@ type commentCreateForm struct {
}
func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) {
+ // TODO: This will be the embeddable form
slug := r.PathValue("id")
guestbook, err := app.guestbooks.Get(slugToShortId(slug))
if err != nil {
@@ -324,7 +284,7 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt
app.serverError(w, r, err)
return
}
- app.sessionManager.Put(r.Context(), "flash", "Comment successfully posted!")
+ // app.sessionManager.Put(r.Context(), "flash", "Comment successfully posted!")
http.Redirect(w, r, fmt.Sprintf("/guestbooks/%s", guestbookSlug), http.StatusSeeOther)
}
diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go
index ee17b06..e19001c 100644
--- a/cmd/web/helpers.go
+++ b/cmd/web/helpers.go
@@ -41,6 +41,21 @@ func (app *application) renderHTMX(w http.ResponseWriter, r *http.Request, statu
}
}
+func (app *application) renderFullPage(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
+ ts, ok := app.templateCache[page]
+ if !ok {
+ err := fmt.Errorf("the template %s does not exist", page)
+ app.serverError(w, r, err)
+ return
+ }
+
+ w.WriteHeader(status)
+ err := ts.ExecuteTemplate(w, "base", data)
+ if err != nil {
+ app.serverError(w, r, err)
+ }
+}
+
func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) {
ts, ok := app.templateCache[page]
if !ok {
diff --git a/cmd/web/routes.go b/cmd/web/routes.go
index aa21e1c..9276e78 100644
--- a/cmd/web/routes.go
+++ b/cmd/web/routes.go
@@ -12,9 +12,10 @@ func (app *application) routes() http.Handler {
mux.Handle("GET /static/", http.StripPrefix("/static", fileServer))
dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate)
+ standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders)
mux.Handle("/{$}", dynamic.ThenFunc(app.home))
- mux.Handle("POST /guestbooks/{id}/comments/create", dynamic.ThenFunc(app.postGuestbookCommentCreate))
+ mux.Handle("POST /guestbooks/{id}/comments/create", standard.ThenFunc(app.postGuestbookCommentCreate))
mux.Handle("GET /guestbooks/{id}", dynamic.ThenFunc(app.getGuestbook))
mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister))
mux.Handle("POST /users/register", dynamic.ThenFunc(app.postUserRegister))
@@ -31,7 +32,6 @@ func (app *application) routes() http.Handler {
mux.Handle("POST /guestbooks/create", protected.ThenFunc(app.postGuestbookCreate))
mux.Handle("GET /guestbooks/{id}/comments/create", protected.ThenFunc(app.getGuestbookCommentCreate))
- standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders)
return standard.Then(mux)
}
diff --git a/cmd/web/templates.go b/cmd/web/templates.go
index 0eb75bb..7d2908b 100644
--- a/cmd/web/templates.go
+++ b/cmd/web/templates.go
@@ -7,6 +7,7 @@ import (
"time"
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
+ "git.32bit.cafe/32bitcafe/guestbook/ui/views"
"github.com/justinas/nosurf"
)
@@ -77,6 +78,17 @@ func newTemplateCache() (map[string]*template.Template, error) {
return cache, nil
}
+func (app *application) newCommonData(r *http.Request) views.CommonData {
+ return views.CommonData {
+ CurrentYear: time.Now().Year(),
+ Flash: app.sessionManager.PopString(r.Context(), "flash"),
+ IsAuthenticated: app.isAuthenticated(r),
+ CSRFToken: nosurf.Token(r),
+ CurrentUser: app.getCurrentUser(r),
+ IsHtmx: r.Header.Get("Hx-Request") == "true",
+ }
+}
+
func (app *application) newTemplateData(r *http.Request) templateData {
return templateData {
CurrentYear: time.Now().Year(),
diff --git a/go.mod b/go.mod
index 56e429d..f710d94 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
)
require (
+ github.com/a-h/templ v0.3.833 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/justinas/alice v1.2.0 // indirect
github.com/justinas/nosurf v1.1.1 // indirect
diff --git a/go.sum b/go.sum
index 179b0b7..89cc066 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
+github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
github.com/alexedwards/scs/sqlite3store v0.0.0-20240316134038-7e11d57e8885 h1:+DCxWg/ojncqS+TGAuRUoV7OfG/S4doh0pcpAwEcow0=
github.com/alexedwards/scs/sqlite3store v0.0.0-20240316134038-7e11d57e8885/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
diff --git a/internal/forms/forms.go b/internal/forms/forms.go
new file mode 100644
index 0000000..36ebe28
--- /dev/null
+++ b/internal/forms/forms.go
@@ -0,0 +1,17 @@
+package forms
+
+import "git.32bit.cafe/32bitcafe/guestbook/internal/validator"
+
+type UserRegistrationForm struct {
+ Name string `schema:"username"`
+ Email string `schema:"email"`
+ Password string `schema:"password"`
+ validator.Validator `schema:"-"`
+}
+
+type UserLoginForm struct {
+ Email string `schema:"email"`
+ Password string `schema:"password"`
+ validator.Validator `schema:"-"`
+}
+
diff --git a/internal/models/guestbook.go b/internal/models/guestbook.go
index 9e55936..618bdd8 100644
--- a/internal/models/guestbook.go
+++ b/internal/models/guestbook.go
@@ -2,6 +2,7 @@ package models
import (
"database/sql"
+ "strconv"
"time"
)
@@ -15,6 +16,10 @@ type Guestbook struct {
IsActive bool
}
+func (gb Guestbook) Slug() string {
+ return strconv.FormatUint(gb.ShortId, 36)
+}
+
type GuestbookModel struct {
DB *sql.DB
}
diff --git a/ui/static/css/style.css b/ui/static/css/style.css
index b0f123e..140e874 100644
--- a/ui/static/css/style.css
+++ b/ui/static/css/style.css
@@ -1,511 +1,78 @@
-/* Set the global variables for everything. Change these to use your own fonts and colours. */
-:root {
- /* Set sans-serif & mono fonts */
- --sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir,
- "Nimbus Sans L", Roboto, Noto, "Segoe UI", Arial, Helvetica,
- "Helvetica Neue", sans-serif;
- --mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
-
- /* Body font size. By default, effectively 18.4px, based on 16px as 'root em' */
- --base-fontsize: 1.15rem;
-
- /* Major third scale progression - see https://type-scale.com/ */
- --header-scale: 1.25;
-
- /* Line height is set to the "Golden ratio" for optimal legibility */
- --line-height: 1.618;
-
- /* Default (light) theme */
- --bg: #fff;
- --accent-bg: #f5f7ff;
- --text: #212121;
- --text-light: #585858;
- --border: #d8dae1;
- --accent: #0d47a1;
- --accent-light: #90caf9;
- --code: #d81b60;
- --preformatted: #444;
- --marked: #ffdd33;
- --disabled: #efefef;
-}
-
-/* Dark theme */
-@media (prefers-color-scheme: dark) {
- :root {
- --bg: #212121;
- --accent-bg: #2b2b2b;
- --text: #dcdcdc;
- --text-light: #ababab;
- --border: #666;
- --accent: #ffb300;
- --accent-light: #ffecb3;
- --code: #f06292;
- --preformatted: #ccc;
- --disabled: #111;
- }
-
- img,
- video {
- opacity: 0.6;
- }
-}
-
html {
- /* Set the font globally */
- font-family: var(--sans-font);
+ background: lightgray;
}
-/* Make the body a nice central block */
body {
- color: var(--text);
- background: var(--bg);
- font-size: var(--base-fontsize);
- line-height: var(--line-height);
- display: flex;
- min-height: 100vh;
- flex-direction: column;
- flex: 1;
- margin: 0 auto;
- max-width: 45rem;
- padding: 0 0.5rem;
- overflow-x: hidden;
- word-break: break-word;
- overflow-wrap: break-word;
+ max-width: 1024px;
+ margin: 1rem auto;
+ padding: 1rem;
+ background: white;
+ font-size: 1.2rem;
+ line-height: 1.5;
+ font-family: Arial, Helvetica, sans-serif;
}
-/* Make the header bg full width, but the content inline with body */
header {
- background: var(--accent-bg);
- border-bottom: 1px solid var(--border);
text-align: center;
- padding: 2rem 0.5rem;
- width: 100vw;
- position: relative;
- box-sizing: border-box;
- left: 50%;
- right: 50%;
- margin-left: -50vw;
- margin-right: -50vw;
}
-/* Remove margins for header text */
-header h1,
-header p {
- margin: 0;
+body > nav {
+ display: flex;
+ justify-content: space-between;
}
-/* Add a little padding to ensure spacing is correct between content and nav */
-main {
- padding-top: 1.5rem;
+body > nav ul {
+ list-style: none;
+ margin: 0 1rem;
+ padding: 0;
}
-/* Fix line height when title wraps */
-h1,
-h2,
-h3 {
- line-height: 1.1;
-}
-
-/* Format navigation */
-nav {
- font-size: 1rem;
- line-height: 2;
- padding: 1rem 0;
-}
-
-nav a {
- margin: 1rem 1rem 0 0;
- border: 1px solid var(--border);
- border-radius: 5px;
- color: var(--text) !important;
+body > nav li {
display: inline-block;
- padding: 0.1rem 1rem;
- text-decoration: none;
- transition: 0.4s;
+ padding: 0 0.5rem;
}
-nav a:hover {
- color: var(--accent) !important;
- border-color: var(--accent);
+nav form {
+ display: inline-block;
}
-nav a.current:hover {
- text-decoration: none;
+nav button {
+ border: none;
+ background: none;
+ font-family: unset;
+ font-size: unset;
+ color: blue;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+main {
+ padding: 1rem;
+}
+
+div#main {
+ display: flex;
+ flex-flow: row wrap;
+}
+
+div#main nav {
+ flex: 1 1 25%;
+}
+
+div#main > div {
+ flex: 10 1 40%;
+}
+
+main nav ul {
+ list-style: none;
+ margin: 1rem;
+ padding: 0;
}
footer {
- margin-top: 4rem;
- padding: 2rem 1rem 1.5rem 1rem;
- color: var(--text-light);
- font-size: 0.9rem;
text-align: center;
- border-top: 1px solid var(--border);
}
-/* Format headers */
-h1 {
- font-size: calc(
- var(--base-fontsize) * var(--header-scale) * var(--header-scale) *
- var(--header-scale) * var(--header-scale)
- );
- margin-top: calc(var(--line-height) * 1.5rem);
-}
-
-h2 {
- font-size: calc(
- var(--base-fontsize) * var(--header-scale) * var(--header-scale) *
- var(--header-scale)
- );
- margin-top: calc(var(--line-height) * 1.5rem);
-}
-
-h3 {
- font-size: calc(
- var(--base-fontsize) * var(--header-scale) * var(--header-scale)
- );
- margin-top: calc(var(--line-height) * 1.5rem);
-}
-
-h4 {
- font-size: calc(var(--base-fontsize) * var(--header-scale));
- margin-top: calc(var(--line-height) * 1.5rem);
-}
-
-h5 {
- font-size: var(--base-fontsize);
- margin-top: calc(var(--line-height) * 1.5rem);
-}
-
-h6 {
- font-size: calc(var(--base-fontsize) / var(--header-scale));
- margin-top: calc(var(--line-height) * 1.5rem);
-}
-
-/* Format links & buttons */
-a,
-a:visited {
- color: var(--accent);
-}
-
-a:hover {
- text-decoration: none;
-}
-
-a button,
-button,
-[role="button"],
-input[type="submit"],
-input[type="reset"],
-input[type="button"] {
- border: none;
- border-radius: 5px;
- background: var(--accent);
- font-size: 1rem;
- color: var(--bg);
- padding: 0.7rem 0.9rem;
- margin: 0.5rem 0;
- transition: 0.4s;
-}
-
-a button[disabled],
-button[disabled],
-[role="button"][aria-disabled="true"],
-input[type="submit"][disabled],
-input[type="reset"][disabled],
-input[type="button"][disabled],
-input[type="checkbox"][disabled],
-input[type="radio"][disabled],
-select[disabled] {
- cursor: default;
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-input:disabled,
-textarea:disabled,
-select:disabled {
- cursor: not-allowed;
- background-color: var(--disabled);
-}
-
-input[type="range"] {
- padding: 0;
-}
-
-/* Set the cursor to '?' while hovering over an abbreviation */
-abbr {
- cursor: help;
-}
-
-button:focus,
-button:enabled:hover,
-[role="button"]:focus,
-[role="button"]:not([aria-disabled="true"]):hover,
-input[type="submit"]:focus,
-input[type="submit"]:enabled:hover,
-input[type="reset"]:focus,
-input[type="reset"]:enabled:hover,
-input[type="button"]:focus,
-input[type="button"]:enabled:hover,
-input[type="checkbox"]:focus,
-input[type="checkbox"]:enabled:hover,
-input[type="radio"]:focus,
-input[type="radio"]:enabled:hover {
- filter: brightness(1.4);
- cursor: pointer;
-}
-
-/* Format the expanding box */
-details {
- background: var(--accent-bg);
- border: 1px solid var(--border);
- border-radius: 5px;
- margin-bottom: 1rem;
-}
-
-summary {
- cursor: pointer;
- font-weight: bold;
- padding: 0.6rem 1rem;
-}
-
-details[open] {
- padding: 0.6rem 1rem 0.75rem 1rem;
-}
-
-details[open] summary {
- margin-bottom: 0.5rem;
- padding: 0;
-}
-
-details[open] > *:last-child {
- margin-bottom: 0;
-}
-
-/* Format tables */
-table {
- border-collapse: collapse;
- width: 100%;
- margin: 1.5rem 0;
-}
-
-td,
-th {
- border: 1px solid var(--border);
- text-align: left;
- padding: 0.5rem;
-}
-
-th {
- background: var(--accent-bg);
- font-weight: bold;
-}
-
-tr:nth-child(even) {
- /* Set every other cell slightly darker. Improves readability. */
- background: var(--accent-bg);
-}
-
-table caption {
- font-weight: bold;
- margin-bottom: 0.5rem;
-}
-
-/* Lists */
-ol,
-ul {
- padding-left: 3rem;
-}
-
-/* Format forms */
-textarea,
-select,
-input {
- font-size: inherit;
- font-family: inherit;
- padding: 0.5rem;
- margin-bottom: 0.5rem;
- color: var(--text);
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 5px;
- box-shadow: none;
- box-sizing: border-box;
- width: 60%;
- -moz-appearance: none;
- -webkit-appearance: none;
- appearance: none;
-}
-
-/* Add arrow to */
-select {
- background-image: linear-gradient(45deg, transparent 49%, var(--text) 51%),
- linear-gradient(135deg, var(--text) 51%, transparent 49%);
- background-position: calc(100% - 20px), calc(100% - 15px);
- background-size: 5px 5px, 5px 5px;
- background-repeat: no-repeat;
-}
-
-select[multiple] {
- background-image: none !important;
-}
-
-/* checkbox and radio button style */
-input[type="checkbox"],
-input[type="radio"] {
- vertical-align: bottom;
- position: relative;
-}
-
-input[type="radio"] {
- border-radius: 100%;
-}
-
-input[type="checkbox"]:checked,
-input[type="radio"]:checked {
- background: var(--accent);
-}
-
-input[type="checkbox"]:checked::after {
- /* Creates a rectangle with colored right and bottom borders which is rotated to look like a check mark */
- content: " ";
- width: 0.1em;
- height: 0.25em;
- border-radius: 0;
- position: absolute;
- top: 0.05em;
- left: 0.18em;
- background: transparent;
- border-right: solid var(--bg) 0.08em;
- border-bottom: solid var(--bg) 0.08em;
- font-size: 1.8em;
- transform: rotate(45deg);
-}
-input[type="radio"]:checked::after {
- /* creates a colored circle for the checked radio button */
- content: " ";
- width: 0.25em;
- height: 0.25em;
- border-radius: 100%;
- position: absolute;
- top: 0.125em;
- background: var(--bg);
- left: 0.125em;
- font-size: 32px;
-}
-
-/* Make the textarea wider than other inputs */
-textarea {
- width: 80%;
-}
-
-/* Makes input fields wider on smaller screens */
-@media only screen and (max-width: 720px) {
- textarea,
- select,
- input {
- width: 100%;
- }
-}
-
-/* Ensures the checkbox and radio inputs do not have a set width like other input fields */
-input[type="checkbox"],
-input[type="radio"] {
- width: auto;
-}
-
-/* do not show border around file selector button */
-input[type="file"] {
- border: 0;
-}
-
-/* Without this any HTML using