add release credits update UI
Signed-off-by: ari melody <ari@arimelody.me>
This commit is contained in:
parent
7914fba52a
commit
34cddcfdb2
48
admin/components/credits/addcredit.html
Normal file
48
admin/components/credits/addcredit.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<dialog id="addcredit">
|
||||||
|
<header>
|
||||||
|
<h2>Add artist credit</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{{range $Artist := .Artists}}
|
||||||
|
<li class="new-artist"
|
||||||
|
data-id="{{$Artist.ID}}"
|
||||||
|
hx-get="/admin/release/{{$.ReleaseID}}/newcredit/{{$Artist.ID}}"
|
||||||
|
hx-target="#editcredits ul"
|
||||||
|
hx-swap="beforeend"
|
||||||
|
>
|
||||||
|
<img src="{{$Artist.GetAvatar}}" alt="" width="16" loading="lazy" class="artist-avatar">
|
||||||
|
<span class="artist-name">{{$Artist.Name}}</span>
|
||||||
|
<span class="artist-id">({{$Artist.ID}})</span>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{if not .Artists}}
|
||||||
|
<p class="empty">There are no more artists to add.</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<button id="cancel" type="button">Cancel</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
(() => {
|
||||||
|
const newCreditModal = document.getElementById("addcredit")
|
||||||
|
const editCreditsModal = document.getElementById("editcredits")
|
||||||
|
const cancelBtn = newCreditModal.querySelector("#cancel");
|
||||||
|
|
||||||
|
editCreditsModal.addEventListener("htmx:afterSwap", () => {
|
||||||
|
newCreditModal.close();
|
||||||
|
newCreditModal.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelBtn.addEventListener("click", () => {
|
||||||
|
newCreditModal.close();
|
||||||
|
newCreditModal.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
newCreditModal.showModal();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</dialog>
|
119
admin/components/credits/editcredits.html
Normal file
119
admin/components/credits/editcredits.html
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<dialog id="editcredits">
|
||||||
|
<header>
|
||||||
|
<h2>Editing: Credits</h2>
|
||||||
|
<a id="add-credit"
|
||||||
|
class="button new"
|
||||||
|
href="/admin/release/{{.ID}}/addcredit"
|
||||||
|
hx-get="/admin/release/{{.ID}}/addcredit"
|
||||||
|
hx-target="body"
|
||||||
|
hx-swap="beforeend"
|
||||||
|
>Add</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form action="/api/v1/music/{{.ID}}/credits">
|
||||||
|
<ul>
|
||||||
|
{{range .Credits}}
|
||||||
|
<li class="credit" data-artist="{{.Artist.ID}}">
|
||||||
|
<div>
|
||||||
|
<img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||||
|
<div class="credit-info">
|
||||||
|
<p class="artist-name">{{.Artist.Name}}</p>
|
||||||
|
<div class="credit-attribute">
|
||||||
|
<label for="role">Role:</label>
|
||||||
|
<input type="text" name="role" value="{{.Role}}">
|
||||||
|
</div>
|
||||||
|
<div class="credit-attribute">
|
||||||
|
<label for="primary">Primary:</label>
|
||||||
|
<input type="checkbox" name="primary" {{if .Primary}}checked{{end}}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="delete">Delete</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<button id="discard" type="button">Discard</button>
|
||||||
|
<button id="save" type="submit" class="save">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
(() => {
|
||||||
|
const container = document.getElementById("editcredits");
|
||||||
|
const form = document.querySelector("#editcredits form");
|
||||||
|
const creditList = form.querySelector("ul");
|
||||||
|
const addCreditBtn = document.getElementById("add-credit");
|
||||||
|
const discardBtn = form.querySelector("button#discard");
|
||||||
|
|
||||||
|
function creditFromElement(el) {
|
||||||
|
const artistID = el.dataset.artist;
|
||||||
|
const roleInput = el.querySelector(`input[name="role"]`)
|
||||||
|
const primaryInput = el.querySelector(`input[name="primary"]`)
|
||||||
|
const deleteBtn = el.querySelector("button.delete");
|
||||||
|
|
||||||
|
let credit = {
|
||||||
|
"artist": artistID,
|
||||||
|
"role": roleInput.value,
|
||||||
|
"primary": primaryInput.checked,
|
||||||
|
};
|
||||||
|
|
||||||
|
roleInput.addEventListener("change", () => {
|
||||||
|
credit.role = roleInput.value;
|
||||||
|
});
|
||||||
|
primaryInput.addEventListener("change", () => {
|
||||||
|
credit.primary = primaryInput.checked;
|
||||||
|
});
|
||||||
|
deleteBtn.addEventListener("click", e => {
|
||||||
|
if (!confirm("Are you sure you want to delete " + artistID + "'s credit?")) return;
|
||||||
|
el.remove();
|
||||||
|
credits = credits.filter(credit => credit.artist != artistID);
|
||||||
|
});
|
||||||
|
|
||||||
|
return credit;
|
||||||
|
}
|
||||||
|
|
||||||
|
let credits = [...form.querySelectorAll(".credit")].map(el => creditFromElement(el));
|
||||||
|
|
||||||
|
creditList.addEventListener("htmx:afterSwap", e => {
|
||||||
|
const el = creditList.children[creditList.children.length - 1];
|
||||||
|
const credit = creditFromElement(el);
|
||||||
|
credits.push(credit);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.showModal();
|
||||||
|
|
||||||
|
container.addEventListener("close", () => {
|
||||||
|
container.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
form.addEventListener("submit", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
fetch(form.action, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(credits)
|
||||||
|
}).then(res => {
|
||||||
|
if (res.ok) location = location;
|
||||||
|
else {
|
||||||
|
res.text().then(err => {
|
||||||
|
alert(err);
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
alert("Failed to update credits. Check the console for details");
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
discardBtn.addEventListener("click", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
container.close();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</dialog>
|
17
admin/components/credits/newcredit.html
Normal file
17
admin/components/credits/newcredit.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<li class="credit" data-artist="{{.ID}}">
|
||||||
|
<div>
|
||||||
|
<img src="{{.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||||
|
<div class="credit-info">
|
||||||
|
<p class="artist-name">{{.Name}}</p>
|
||||||
|
<div class="credit-attribute">
|
||||||
|
<label for="role">Role:</label>
|
||||||
|
<input type="text" name="role" value="">
|
||||||
|
</div>
|
||||||
|
<div class="credit-attribute">
|
||||||
|
<label for="primary">Primary:</label>
|
||||||
|
<input type="checkbox" name="primary">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="delete">Delete</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
0
admin/components/links/addlink.html
Normal file
0
admin/components/links/addlink.html
Normal file
0
admin/components/links/editlinks.html
Normal file
0
admin/components/links/editlinks.html
Normal file
0
admin/components/links/newlink.html
Normal file
0
admin/components/links/newlink.html
Normal file
0
admin/components/tracks/addtrack.html
Normal file
0
admin/components/tracks/addtrack.html
Normal file
0
admin/components/tracks/edittracks.html
Normal file
0
admin/components/tracks/edittracks.html
Normal file
0
admin/components/tracks/newtrack.html
Normal file
0
admin/components/tracks/newtrack.html
Normal file
|
@ -85,9 +85,9 @@ func MustAuthorise(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(r *http.Request) *Session {
|
func GetSession(r *http.Request) *Session {
|
||||||
// if ADMIN_BYPASS {
|
if ADMIN_BYPASS {
|
||||||
// return &Session{}
|
return &Session{}
|
||||||
// }
|
}
|
||||||
|
|
||||||
var token = ""
|
var token = ""
|
||||||
// is the session token in context?
|
// is the session token in context?
|
||||||
|
@ -177,22 +177,13 @@ func LoginHandler() http.Handler {
|
||||||
cookie.Name = "token"
|
cookie.Name = "token"
|
||||||
cookie.Value = session.Token
|
cookie.Value = session.Token
|
||||||
cookie.Expires = time.Now().Add(24 * time.Hour)
|
cookie.Expires = time.Now().Add(24 * time.Hour)
|
||||||
|
// TODO: uncomment this probably that might be nice i think
|
||||||
// cookie.Secure = true
|
// cookie.Secure = true
|
||||||
cookie.HttpOnly = true
|
cookie.HttpOnly = true
|
||||||
cookie.Path = "/"
|
cookie.Path = "/"
|
||||||
http.SetCookie(w, &cookie)
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
serveTemplate("login.html", loginData{Token: session.Token}).ServeHTTP(w, r)
|
serveTemplate("login.html", loginData{Token: session.Token}).ServeHTTP(w, r)
|
||||||
// w.WriteHeader(http.StatusOK)
|
|
||||||
// w.Header().Add("Content-Type", "text/html")
|
|
||||||
// w.Write([]byte(
|
|
||||||
// "<!DOCTYPE html><html><head>"+
|
|
||||||
// "<meta http-equiv=\"refresh\" content=\"5;url=/admin/\" />"+
|
|
||||||
// "</head><body>"+
|
|
||||||
// "Logged in successfully. "+
|
|
||||||
// "You should be redirected to <a href=\"/admin/\">/admin/</a> in 5 seconds."+
|
|
||||||
// "</body></html>"),
|
|
||||||
// )
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +246,39 @@ func serveTemplate(page string, data any) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveComponent(page string, data any) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fp := filepath.Join("admin", "components", filepath.Clean(page))
|
||||||
|
|
||||||
|
info, err := os.Stat(fp)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := template.ParseFiles(fp)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error parsing template files: %s\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = template.Execute(w, data);
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error executing template: %s\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func staticHandler() http.Handler {
|
func staticHandler() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
info, err := os.Stat(filepath.Join("admin", "static", filepath.Clean(r.URL.Path)))
|
info, err := os.Stat(filepath.Join("admin", "static", filepath.Clean(r.URL.Path)))
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
|
@ -25,14 +26,26 @@ type (
|
||||||
|
|
||||||
func serveRelease() http.Handler {
|
func serveRelease() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
|
id := slices[0]
|
||||||
|
release := global.GetRelease(id)
|
||||||
|
if release == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id := r.URL.Path[1:]
|
if len(slices) > 1 {
|
||||||
release := global.GetRelease(id)
|
switch slices[1] {
|
||||||
if release == nil {
|
case "editcredits":
|
||||||
|
serveEditCredits(release).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
case "addcredit":
|
||||||
|
serveAddCredit(release).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
case "newcredit":
|
||||||
|
serveNewCredit().ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -56,3 +69,55 @@ func serveRelease() http.Handler {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveEditCredits(release *model.Release) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
serveComponent(path.Join("credits", "editcredits.html"), release).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveAddCredit(release *model.Release) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var artists = []*model.Artist{}
|
||||||
|
for _, artist := range global.Artists {
|
||||||
|
var exists = false
|
||||||
|
for _, credit := range release.Credits {
|
||||||
|
if credit.Artist == artist {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
artists = append(artists, artist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
ReleaseID string;
|
||||||
|
Artists []*model.Artist
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
serveComponent(path.Join("credits", "addcredit.html"), response{
|
||||||
|
ReleaseID: release.ID,
|
||||||
|
Artists: artists,
|
||||||
|
}).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveNewCredit() http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
artist := global.GetArtist(strings.Split(r.URL.Path, "/")[3])
|
||||||
|
if artist == nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
serveComponent(path.Join("credits", "newcredit.html"), artist).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ body {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
nav {
|
||||||
width: min(720px, calc(100% - 2em));
|
width: min(720px, calc(100% - 2em));
|
||||||
height: 2em;
|
height: 2em;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
|
@ -27,10 +27,10 @@ header {
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
border: 1px solid #808080;
|
border: 1px solid #808080;
|
||||||
}
|
}
|
||||||
header .icon {
|
nav .icon {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
header .title {
|
nav .title {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ header .title {
|
||||||
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
header a {
|
nav a {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
@ -57,11 +57,11 @@ header a {
|
||||||
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
header a:hover {
|
nav a:hover {
|
||||||
background: #00000010;
|
background: #00000010;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
header #logout {
|
nav #logout {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,11 @@ a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a img {
|
||||||
|
height: .9em;
|
||||||
|
transform: translateY(.1em);
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#release {
|
#release {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 1em;
|
padding: 1.5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1.2em;
|
gap: 1.2em;
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.release-info {
|
.release-info {
|
||||||
margin: .5em 0;
|
margin: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -96,29 +96,26 @@ button:active, .button:active {
|
||||||
border-color: #808080;
|
border-color: #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.edit {
|
button {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
}
|
||||||
|
button.new {
|
||||||
background: #c4ff6a;
|
background: #c4ff6a;
|
||||||
border-color: #84b141;
|
border-color: #84b141;
|
||||||
}
|
}
|
||||||
button.edit:hover {
|
|
||||||
background: #fff;
|
|
||||||
border-color: #d0d0d0;
|
|
||||||
}
|
|
||||||
button.edit:active {
|
|
||||||
background: #d0d0d0;
|
|
||||||
border-color: #808080;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.save {
|
button.save {
|
||||||
background: #6fd7ff;
|
background: #6fd7ff;
|
||||||
border-color: #6f9eb0;
|
border-color: #6f9eb0;
|
||||||
}
|
}
|
||||||
button.save:hover {
|
button.delete {
|
||||||
|
background: #ff7171;
|
||||||
|
border-color: #7d3535;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-color: #d0d0d0;
|
border-color: #d0d0d0;
|
||||||
}
|
}
|
||||||
button.save:active {
|
button:active {
|
||||||
background: #d0d0d0;
|
background: #d0d0d0;
|
||||||
border-color: #808080;
|
border-color: #808080;
|
||||||
}
|
}
|
||||||
|
@ -137,7 +134,7 @@ button[disabled] {
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit {
|
.card.credits .credit {
|
||||||
margin-bottom: .5em;
|
margin-bottom: .5em;
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -150,15 +147,15 @@ button[disabled] {
|
||||||
border: 1px solid #808080;
|
border: 1px solid #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit .artist-avatar {
|
.card.credits .credit .artist-avatar {
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit .artist-name {
|
.card.credits .credit .artist-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit .artist-role small {
|
.card.credits .credit .artist-role small {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
opacity: .66;
|
opacity: .66;
|
||||||
}
|
}
|
||||||
|
@ -182,6 +179,10 @@ button[disabled] {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-title a.button {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.track-id {
|
.track-id {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
font-family: "Monaspace Argon", monospace;
|
font-family: "Monaspace Argon", monospace;
|
||||||
|
@ -217,3 +218,88 @@ button[disabled] {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
width: min(720px, calc(100% - 2em));
|
||||||
|
padding: 2em;
|
||||||
|
border: 1px solid #101010;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog header {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
background: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog div.dialog-actions {
|
||||||
|
margin-top: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: end;
|
||||||
|
gap: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit>div {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
padding: .5em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
border-radius: .5em;
|
||||||
|
background: #f8f8f8f8;
|
||||||
|
border: 1px solid #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit .artist-avatar {
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit .credit-info {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit .credit-info .credit-attribute {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit .credit-info .credit-attribute input[type="text"] {
|
||||||
|
margin-left: .25em;
|
||||||
|
padding: .2em .4em;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-family: inherit;
|
||||||
|
border: 1px solid #8888;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit .artist-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit .artist-role small {
|
||||||
|
font-size: inherit;
|
||||||
|
opacity: .66;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog#editcredits .credit button.delete {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
placeholder="No description provided."
|
placeholder="No description provided."
|
||||||
rows="3"
|
rows="3"
|
||||||
id="description"
|
id="description"
|
||||||
>{{.Description}}</textarea>
|
>{{.Description}}</textarea>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<div class="release-actions">
|
<div class="release-actions">
|
||||||
<a href="/music/{{.ID}}" class="button">Gateway</a>
|
<a href="/music/{{.ID}}" class="button" target="_blank">Gateway <img src="/img/external-link.svg"/></a>
|
||||||
<button type="submit" class="save" id="save" disabled>Save</button>
|
<button type="submit" class="save" id="save" disabled>Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,17 +92,22 @@
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>Credits ({{len .Credits}})</h2>
|
<h2>Credits ({{len .Credits}})</h2>
|
||||||
<button id="update-credits" class="edit">Edit</button>
|
<a class="button edit"
|
||||||
|
href="/admin/release/{{.ID}}/editcredits"
|
||||||
|
hx-get="/admin/release/{{.ID}}/editcredits"
|
||||||
|
hx-target="body"
|
||||||
|
hx-swap="beforeend"
|
||||||
|
>Edit</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card credits">
|
<div class="card credits">
|
||||||
{{range $Credit := .Credits}}
|
{{range .Credits}}
|
||||||
<div class="credit">
|
<div class="credit">
|
||||||
<img src="{{$Credit.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
<img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||||
<div class="credit-info">
|
<div class="credit-info">
|
||||||
<p class="artist-name"><a href="/admin/artists/{{$Credit.Artist.ID}}">{{$Credit.Artist.Name}}</a></p>
|
<p class="artist-name"><a href="/admin/artists/{{.Artist.ID}}">{{.Artist.Name}}</a></p>
|
||||||
<p class="artist-role">
|
<p class="artist-role">
|
||||||
{{$Credit.Role}}
|
{{.Role}}
|
||||||
{{if $Credit.Primary}}
|
{{if .Primary}}
|
||||||
<small>(Primary)</small>
|
<small>(Primary)</small>
|
||||||
{{end}}
|
{{end}}
|
||||||
</p>
|
</p>
|
||||||
|
@ -114,22 +119,44 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>Links ({{len .Links}})</h2>
|
||||||
|
<a class="button edit"
|
||||||
|
href="/admin/release/{{.ID}}/editlinks"
|
||||||
|
hx-get="/admin/release/{{.ID}}/editlinks"
|
||||||
|
hx-target="body"
|
||||||
|
hx-swap="beforeend"
|
||||||
|
>Edit</a>
|
||||||
|
</div>
|
||||||
|
<div class="card links">
|
||||||
|
{{range .Links}}
|
||||||
|
<div class="release-link" data-id="{{.Name}}">
|
||||||
|
<a href="{{.URL}}" class="button">{{.Name}} <img src="/img/external-link.svg"/></a></p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>Tracklist ({{len .Tracks}})</h2>
|
<h2>Tracklist ({{len .Tracks}})</h2>
|
||||||
<button id="update-tracks" class="edit">Edit</button>
|
<a class="button edit"
|
||||||
|
href="/admin/release/{{.ID}}/edittracks"
|
||||||
|
hx-get="/admin/release/{{.ID}}/edittracks"
|
||||||
|
hx-target="body"
|
||||||
|
hx-swap="beforeend"
|
||||||
|
>Edit</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card tracks">
|
<div class="card tracks">
|
||||||
{{range $Track := .Tracks}}
|
{{range .Tracks}}
|
||||||
<div class="track" data-id="{{$Track.ID}}">
|
<div class="track" data-id="{{.ID}}">
|
||||||
<h2 class="track-title">{{$Track.Number}}. {{$Track.Title}}</h2>
|
<h2 class="track-title">{{.Number}}. {{.Title}}</h2>
|
||||||
<p class="track-id">{{$Track.ID}}</p>
|
<p class="track-id">{{.ID}}</p>
|
||||||
{{if $Track.Description}}
|
{{if .Description}}
|
||||||
<p class="track-description">{{$Track.Description}}</p>
|
<p class="track-description">{{.Description}}</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p class="track-description empty">No description provided.</p>
|
<p class="track-description empty">No description provided.</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if $Track.Lyrics}}
|
{{if .Lyrics}}
|
||||||
<p class="track-lyrics">{{$Track.Lyrics}}</p>
|
<p class="track-lyrics">{{.Lyrics}}</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
<p class="track-lyrics empty">There are no lyrics.</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -10,14 +10,17 @@
|
||||||
{{block "head" .}}{{end}}
|
{{block "head" .}}{{end}}
|
||||||
|
|
||||||
<link rel="stylesheet" href="/admin/static/admin.css">
|
<link rel="stylesheet" href="/admin/static/admin.css">
|
||||||
|
<script type="module" src="/script/vendor/htmx.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<img src="/img/favicon.png" alt="" class="icon">
|
<nav>
|
||||||
<a href="/">arimelody.me</a>
|
<img src="/img/favicon.png" alt="" class="icon">
|
||||||
<a href="/admin">home</a>
|
<a href="/">arimelody.me</a>
|
||||||
<a href="/admin/logout" id="logout">log out</a>
|
<a href="/admin">home</a>
|
||||||
|
<a href="/admin/logout" id="logout">log out</a>
|
||||||
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{{block "content" .}}
|
{{block "content" .}}
|
||||||
|
|
9
public/img/external-link.svg
Normal file
9
public/img/external-link.svg
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,-8,-8)">
|
||||||
|
<g transform="matrix(0.9375,0,0,0.9375,8,16)">
|
||||||
|
<path d="M110.222,70.621C110.222,68.866 111.298,67.289 112.932,66.649C114.567,66.008 116.427,66.434 117.62,67.723L126.864,77.707C127.594,78.495 128,79.53 128,80.605L128,96C128,113.661 113.661,128 96,128L32,128C14.339,128 0,113.661 0,96L0,32C0,14.339 14.339,0 32,0L47.395,0C48.47,-0 49.505,0.406 50.293,1.136L60.277,10.38C61.566,11.573 61.992,13.433 61.351,15.068C60.711,16.702 59.134,17.778 57.379,17.778L32,17.778C24.151,17.778 17.778,24.151 17.778,32L17.778,96C17.778,103.849 24.151,110.222 32,110.222L96,110.222C103.849,110.222 110.222,103.849 110.222,96L110.222,70.621ZM65.524,82.956C64.724,83.756 63.638,84.206 62.507,84.206C61.375,84.206 60.29,83.757 59.49,82.956L45.044,68.51C44.243,67.71 43.794,66.625 43.794,65.493C43.794,64.362 44.244,63.276 45.044,62.476L92.16,15.36L75.55,-1.25C74.33,-2.47 73.965,-4.305 74.625,-5.899C75.286,-7.494 76.842,-8.533 78.567,-8.533L132.267,-8.533C133.398,-8.533 134.484,-8.084 135.284,-7.284C136.084,-6.483 136.533,-5.398 136.533,-4.267L136.533,49.433C136.533,51.158 135.494,52.714 133.899,53.375C132.305,54.035 130.47,53.67 129.25,52.45L112.64,35.84L65.524,82.956Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -2,84 +2,72 @@ import "./header.js";
|
||||||
import "./config.js";
|
import "./config.js";
|
||||||
|
|
||||||
function type_out(e) {
|
function type_out(e) {
|
||||||
const text = e.innerText;
|
const text = e.innerText;
|
||||||
const original = e.innerHTML;
|
const original = e.innerHTML;
|
||||||
e.innerText = "";
|
const delay = 25;
|
||||||
const delay = 25;
|
let chars = 0;
|
||||||
let chars = 0;
|
|
||||||
|
|
||||||
function insert_char(character, parent) {
|
function insert_char(character, parent) {
|
||||||
const c = document.createElement("span");
|
if (chars == 0) parent.innerHTML = "";
|
||||||
c.innerText = character;
|
const c = document.createElement("span");
|
||||||
parent.appendChild(c);
|
c.innerText = character;
|
||||||
c.classList.add("newchar");
|
parent.appendChild(c);
|
||||||
|
c.classList.add("newchar");
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize() {
|
||||||
|
e.innerHTML = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
function increment_char() {
|
||||||
|
const newchar = text.substring(chars, chars + 1);
|
||||||
|
insert_char(newchar, e);
|
||||||
|
chars++;
|
||||||
|
if (chars <= text.length) {
|
||||||
|
setTimeout(increment_char, delay);
|
||||||
|
} else {
|
||||||
|
setTimeout(normalize, 250);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function normalize() {
|
increment_char();
|
||||||
e.innerHTML = original;
|
|
||||||
}
|
|
||||||
|
|
||||||
function increment_char() {
|
|
||||||
const newchar = text.substring(chars - 1, chars);
|
|
||||||
insert_char(newchar, e);
|
|
||||||
chars++;
|
|
||||||
if (chars <= text.length) {
|
|
||||||
setTimeout(increment_char, delay);
|
|
||||||
} else {
|
|
||||||
setTimeout(normalize, 250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
increment_char();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fill_list(list) {
|
function fill_list(list) {
|
||||||
const items = list.querySelectorAll("li a, li span");
|
const items = list.querySelectorAll("li a, li span");
|
||||||
items.innerText = "";
|
items.innerText = "";
|
||||||
const delay = 100;
|
const delay = 100;
|
||||||
|
|
||||||
items.forEach((item, iter) => {
|
items.forEach((item, iter) => {
|
||||||
item.style.animationDelay = `${iter * delay}ms`;
|
item.style.animationDelay = `${iter * delay}ms`;
|
||||||
item.style.animationPlayState = "playing";
|
item.style.animationPlayState = "playing";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
[...document.querySelectorAll("h1, h2, h3, h4, h5, h6")]
|
[...document.querySelectorAll(".typeout")]
|
||||||
.filter((e) => e.innerText != "")
|
.filter((e) => e.innerText != "")
|
||||||
.forEach((e) => {
|
.forEach((e) => {
|
||||||
type_out(e);
|
type_out(e);
|
||||||
});
|
console.log(e);
|
||||||
[...document.querySelectorAll("ol, ul")]
|
});
|
||||||
.filter((e) => e.innerText != "")
|
[...document.querySelectorAll("ol, ul")]
|
||||||
.forEach((e) => {
|
.filter((e) => e.innerText != "")
|
||||||
fill_list(e);
|
.forEach((e) => {
|
||||||
});
|
fill_list(e);
|
||||||
|
|
||||||
document.addEventListener("htmx:afterSwap", async event => {
|
|
||||||
const res = await event.detail.xhr.response;
|
|
||||||
var new_head = res.substring(res.indexOf("<head>")+1, res.indexOf("</head>"));
|
|
||||||
if (new_head) {
|
|
||||||
document.head.innerHTML = new_head;
|
|
||||||
}
|
|
||||||
window.scrollY = 0;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const top_button = document.getElementById("backtotop");
|
const top_button = document.getElementById("backtotop");
|
||||||
window.onscroll = () => {
|
window.onscroll = () => {
|
||||||
if (!top_button) return;
|
if (!top_button) return;
|
||||||
const btt_threshold = 100;
|
const btt_threshold = 100;
|
||||||
if (
|
if (
|
||||||
document.body.scrollTop > btt_threshold ||
|
document.body.scrollTop > btt_threshold ||
|
||||||
document.documentElement.scrollTop > btt_threshold
|
document.documentElement.scrollTop > btt_threshold
|
||||||
) {
|
) {
|
||||||
top_button.classList.add("active");
|
top_button.classList.add("active");
|
||||||
} else {
|
} else {
|
||||||
top_button.classList.remove("active");
|
top_button.classList.remove("active");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("swap", () => {
|
|
||||||
start();
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
const swap_event = new Event("swap");
|
|
||||||
|
|
||||||
let caches = {};
|
|
||||||
|
|
||||||
async function cached_fetch(url) {
|
|
||||||
let cached = caches[url];
|
|
||||||
|
|
||||||
const res = cached === undefined ? await fetch(url) : await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
"If-Modified-Since": cached.last_modified
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 304 && cached !== undefined) {
|
|
||||||
return cached.content;
|
|
||||||
}
|
|
||||||
if (res.status !== 200) return;
|
|
||||||
if (!res.headers.get("content-type").startsWith("text/html")) return;
|
|
||||||
|
|
||||||
const text = await res.text();
|
|
||||||
if (res.headers.get("last-modified")) {
|
|
||||||
caches[url] = {
|
|
||||||
content: text,
|
|
||||||
last_modified: res.headers.get("last-modified")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function swap(url, stateful) {
|
|
||||||
if (typeof url !== 'string') return;
|
|
||||||
|
|
||||||
const segments = window.location.href.split("/");
|
|
||||||
if (url.startsWith(window.location.origin) && segments[segments.length - 1].includes("#")) {
|
|
||||||
window.location.href = url;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stateful && window.location.href.endsWith(url)) return;
|
|
||||||
|
|
||||||
const text = await cached_fetch(url);
|
|
||||||
const content = new DOMParser().parseFromString(text, "text/html");
|
|
||||||
|
|
||||||
const stylesheets = [...content.querySelectorAll("link[rel='stylesheet']")];
|
|
||||||
|
|
||||||
// swap title
|
|
||||||
document.title = content.title;
|
|
||||||
// swap body html
|
|
||||||
document.body.innerHTML = content.body.innerHTML;
|
|
||||||
// swap stylesheets
|
|
||||||
const old_sheets = document.head.querySelectorAll("link[rel='stylesheet']");
|
|
||||||
stylesheets.forEach(stylesheet => {
|
|
||||||
let exists = false;
|
|
||||||
old_sheets.forEach(old_sheet => {
|
|
||||||
if (old_sheet.href === stylesheet.href) exists = true;
|
|
||||||
});
|
|
||||||
if (!exists) document.head.appendChild(stylesheet);
|
|
||||||
});
|
|
||||||
old_sheets.forEach(old_sheet => {
|
|
||||||
let exists = false;
|
|
||||||
stylesheets.forEach(stylesheet => {
|
|
||||||
if (stylesheet.href === old_sheet.href) exists = true;
|
|
||||||
});
|
|
||||||
if (!exists) old_sheet.remove();
|
|
||||||
});
|
|
||||||
// push history
|
|
||||||
if (stateful) history.pushState(url, "", url);
|
|
||||||
|
|
||||||
bind(document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
function bind(content) {
|
|
||||||
if (typeof content !== 'object' || content.nodeType !== Node.ELEMENT_NODE) return;
|
|
||||||
|
|
||||||
content.querySelectorAll("[swap-url]").forEach(element => {
|
|
||||||
const href = element.attributes.getNamedItem('swap-url').value;
|
|
||||||
|
|
||||||
element.addEventListener("click", event => {
|
|
||||||
event.preventDefault();
|
|
||||||
swap(href, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
[...element.querySelectorAll("a[href], [swap-url]")].forEach(element => {
|
|
||||||
if (element.href) {
|
|
||||||
if (!element.href.endsWith(href)) return;
|
|
||||||
element.attributes.removeNamedItem("href");
|
|
||||||
}
|
|
||||||
const swap_url = element.attributes.getNamedItem("swap-url");
|
|
||||||
if (swap_url) {
|
|
||||||
if (!swap_url.endsWith(href)) return;
|
|
||||||
element.attributes.removeNamedItem("swap-url");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
content.querySelectorAll("a[href]:not([swap-url])").forEach(element => {
|
|
||||||
if (element.href.includes("#")) return;
|
|
||||||
if (!element.href.startsWith(window.location.origin)) return;
|
|
||||||
const href = element.href.substring(window.location.origin.length);
|
|
||||||
if (href.includes(".")) return;
|
|
||||||
|
|
||||||
element.addEventListener("click", event => {
|
|
||||||
event.preventDefault();
|
|
||||||
swap(element.href, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.dispatchEvent(swap_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("popstate", event => {
|
|
||||||
swap(event.state, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
bind(document.body);
|
|
||||||
});
|
|
1
public/script/vendor/htmx.min.js
vendored
Normal file
1
public/script/vendor/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -4,131 +4,136 @@
|
||||||
@import url("/style/prideflag.css");
|
@import url("/style/prideflag.css");
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Monaspace Argon";
|
font-family: "Monaspace Argon";
|
||||||
src: url("/font/monaspace-argon/MonaspaceArgonVarVF[wght,wdth,slnt].woff2") format("woff2-variations");
|
src: url("/font/monaspace-argon/MonaspaceArgonVarVF[wght,wdth,slnt].woff2") format("woff2-variations");
|
||||||
font-weight: 125 950;
|
font-weight: 125 950;
|
||||||
font-stretch: 75% 125%;
|
font-stretch: 75% 125%;
|
||||||
font-style: oblique 0deg 20deg;
|
font-style: oblique 0deg 20deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: #080808;
|
background: #080808;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
font-family: "Monaspace Argon", monospace;
|
font-family: "Monaspace Argon", monospace;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
text-shadow: 0 0 3em;
|
text-shadow: 0 0 3em;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--links);
|
color: var(--links);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link-button {
|
a.link-button {
|
||||||
padding: .3em .5em;
|
padding: .3em .5em;
|
||||||
border: 1px solid var(--links);
|
border: 1px solid var(--links);
|
||||||
color: var(--links);
|
color: var(--links);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
transition-property: color, border-color, background-color;
|
transition-property: color, border-color, background-color;
|
||||||
transition-duration: .2s;
|
transition-duration: .2s;
|
||||||
animation-delay: 0s;
|
animation-delay: 0s;
|
||||||
animation: list-item-fadein .2s forwards;
|
animation: list-item-fadein .2s forwards;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link-button:hover {
|
a.link-button:hover {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
border-color: #eee;
|
border-color: #eee;
|
||||||
background-color: var(--links) !important;
|
background-color: var(--links) !important;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
box-shadow: 0 0 1em var(--links);
|
box-shadow: 0 0 1em var(--links);
|
||||||
|
}
|
||||||
|
|
||||||
|
a img {
|
||||||
|
height: .9em;
|
||||||
|
transform: translateY(.1em);
|
||||||
}
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.newchar {
|
span.newchar {
|
||||||
animation: newchar 0.25s;
|
animation: newchar 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
a#backtotop {
|
a#backtotop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
padding: .5em .8em;
|
padding: .5em .8em;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
transition-property: opacity, transform, border-color, background-color, color;
|
transition-property: opacity, transform, border-color, background-color, color;
|
||||||
transition-duration: .2s;
|
transition-duration: .2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
a#backtotop.active {
|
a#backtotop.active {
|
||||||
top: 4rem;
|
top: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
a#backtotop:hover {
|
a#backtotop:hover {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
border-color: #eee;
|
border-color: #eee;
|
||||||
background-color: var(--links);
|
background-color: var(--links);
|
||||||
box-shadow: 0 0 1em var(--links);
|
box-shadow: 0 0 1em var(--links);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes newchar {
|
@keyframes newchar {
|
||||||
from {
|
from {
|
||||||
background: #fff8;
|
background: #fff8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes list-item-fadein {
|
@keyframes list-item-fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: #fff8;
|
background: #fff8;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#overlay {
|
#overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-image: linear-gradient(180deg, rgba(0,0,0,0) 15%, rgb(0, 0, 0) 40%, rgb(0, 0, 0) 60%, rgba(0,0,0,0) 85%);
|
background-image: linear-gradient(180deg, rgba(0,0,0,0) 15%, rgb(0, 0, 0) 40%, rgb(0, 0, 0) 60%, rgba(0,0,0,0) 85%);
|
||||||
background-size: 100vw .2em;
|
background-size: 100vw .2em;
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
mix-blend-mode: overlay;
|
mix-blend-mode: overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 780px) {
|
@media screen and (max-width: 780px) {
|
||||||
body {
|
body {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin-top: 4rem;
|
margin-top: 4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -459,6 +459,12 @@ div#extras ul li a.active {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.album-track-subheading {
|
||||||
|
width: fit-content;
|
||||||
|
padding: .3em 1em;
|
||||||
|
background: #101010;
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
BIN
res/external-link.afdesign
Normal file
BIN
res/external-link.afdesign
Normal file
Binary file not shown.
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<nav>
|
<nav>
|
||||||
<div id="header-home" hx-get="/" hx-on="click" hx-target="body" hx-swap="outerHTML show:window:top" preload="mouseover" hx-push-url="true">
|
<div id="header-home">
|
||||||
<img src="/img/favicon.png" id="header-icon" width="100" height="100" alt="">
|
<img src="/img/favicon.png" id="header-icon" width="100" height="100" alt="">
|
||||||
<div id="header-text">
|
<div id="header-text">
|
||||||
<h1>ari melody</h1>
|
<h1>ari melody</h1>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
<rect y="40" width="70" height="10" rx="5" fill="#eee" />
|
<rect y="40" width="70" height="10" rx="5" fill="#eee" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<ul id="header-links" hx-boost="true" hx-target="body" hx-swap="outerHTML show:window:top">
|
<ul id="header-links">
|
||||||
<li>
|
<li>
|
||||||
<a href="/" preload="mouseover">home</a>
|
<a href="/" preload="mouseover">home</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<main>
|
<main>
|
||||||
<h1>
|
<h1 class="typeout">
|
||||||
# hello, world!
|
# hello, world!
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>
|
<h2 class="typeout">
|
||||||
## metadata
|
## metadata
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>
|
<h2 class="typeout">
|
||||||
## cool people
|
## cool people
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,7 @@
|
||||||
|
|
||||||
{{block "head" .}}{{end}}
|
{{block "head" .}}{{end}}
|
||||||
|
|
||||||
<!-- <meta name="htmx-config" content='{"htmx.config.scrollIntoViewOnBoost":false}'> -->
|
<script type="module", src="/script/main.js"></script>
|
||||||
<!-- <script type="application/javascript" src="/script/lib/htmx.js"></script> -->
|
|
||||||
<!-- <script type="application/javascript" src="/script/lib/htmx.min.js"></script> -->
|
|
||||||
<!-- <script type="application/javascript" src="/script/lib/htmx-preload.js"></script> -->
|
|
||||||
<!-- <script type="application/javascript" src="/script/swap.js"></script> -->
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
<div id="background" style="background-image: url({{.GetArtwork}})"></div>
|
<div id="background" style="background-image: url({{.GetArtwork}})"></div>
|
||||||
|
|
||||||
<a href="/music" swap-url="/music" id="go-back" title="back to arimelody.me"><</a>
|
<a href="/music" id="go-back" title="back to arimelody.me"><</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<div id="music-container">
|
<div id="music-container">
|
||||||
|
@ -61,6 +61,7 @@
|
||||||
<p id="type" class="{{.ReleaseType}}">{{.ReleaseType}}</p>
|
<p id="type" class="{{.ReleaseType}}">{{.ReleaseType}}</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p id="type" class="upcoming">upcoming</p>
|
<p id="type" class="upcoming">upcoming</p>
|
||||||
|
<p>Releases: {{.PrintReleaseDate}}</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<ul id="links">
|
<ul id="links">
|
||||||
|
@ -118,6 +119,13 @@
|
||||||
{{range $i, $track := .Tracks}}
|
{{range $i, $track := .Tracks}}
|
||||||
<details>
|
<details>
|
||||||
<summary class="album-track-title">{{$track.Number}}. {{$track.Title}}</summary>
|
<summary class="album-track-title">{{$track.Number}}. {{$track.Title}}</summary>
|
||||||
|
|
||||||
|
{{if $track.Description}}
|
||||||
|
<p class="album-track-subheading">DESCRIPTION</p>
|
||||||
|
{{$track.Description}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<p class="album-track-subheading">LYRICS</p>
|
||||||
{{if $track.Lyrics}}
|
{{if $track.Lyrics}}
|
||||||
{{$track.Lyrics}}
|
{{$track.Lyrics}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -20,13 +20,13 @@
|
||||||
<main>
|
<main>
|
||||||
<script type="module" src="/script/music.js"></script>
|
<script type="module" src="/script/music.js"></script>
|
||||||
|
|
||||||
<h1>
|
<h1 class="typeout">
|
||||||
# my music
|
# my music
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div id="music-container">
|
<div id="music-container">
|
||||||
{{range $Release := .}}
|
{{range $Release := .}}
|
||||||
<div class="music" id="{{$Release.ID}}" swap-url="/music/{{$Release.ID}}">
|
<div class="music" id="{{$Release.ID}}">
|
||||||
<div class="music-artwork">
|
<div class="music-artwork">
|
||||||
<img src="{{$Release.GetArtwork}}" alt="{{$Release.Title}} artwork" width="128" loading="lazy">
|
<img src="{{$Release.GetArtwork}}" alt="{{$Release.Title}} artwork" width="128" loading="lazy">
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 id="usage" class="question">
|
<h2 id="usage" class="question typeout">
|
||||||
<a href="#usage">
|
<a href="#usage">
|
||||||
> "can i use your music in my content?"
|
> "can i use your music in my content?"
|
||||||
</a>
|
</a>
|
||||||
|
|
Loading…
Reference in a new issue