Implement new header / footer (mostly)

This commit is contained in:
Ben Visness 2024-05-20 21:37:28 -05:00 committed by Ben Visness
parent 19a8c6bf0d
commit 1a0be1912e
26 changed files with 693 additions and 136 deletions

View File

@ -9,7 +9,7 @@ function readableByteSize(numBytes) {
"gb" "gb"
]; ];
let scale = 0; let scale = 0;
while (numBytes > 1024 && scale < scales.length-1) { while (numBytes > 1024 && scale < scales.length - 1) {
numBytes /= 1024; numBytes /= 1024;
scale++; scale++;
} }
@ -28,7 +28,7 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
snippetEdit.avatarLink.href = ownerUrl; snippetEdit.avatarLink.href = ownerUrl;
snippetEdit.username.textContent = ownerName; snippetEdit.username.textContent = ownerName;
snippetEdit.username.href = ownerUrl; snippetEdit.username.href = ownerUrl;
snippetEdit.date.textContent = new Intl.DateTimeFormat([], {month: "2-digit", day: "2-digit", year: "numeric"}).format(date); snippetEdit.date.textContent = new Intl.DateTimeFormat([], { month: "2-digit", day: "2-digit", year: "numeric" }).format(date);
snippetEdit.text.value = text; snippetEdit.text.value = text;
if (attachmentElement) { if (attachmentElement) {
originalAttachment = attachmentElement.cloneNode(true); originalAttachment = attachmentElement.cloneNode(true);
@ -56,7 +56,7 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
updateProjectSelector(); updateProjectSelector();
if (originalSnippetEl) { if (originalSnippetEl) {
snippetEdit.cancelLink.addEventListener("click", function() { snippetEdit.cancelLink.addEventListener("click", function () {
cancel(); cancel();
}); });
} else { } else {
@ -78,7 +78,7 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
if (proj.id == stickyProjectId) { if (proj.id == stickyProjectId) {
projEl.removeButton.remove(); projEl.removeButton.remove();
} else { } else {
projEl.removeButton.addEventListener("click", function(ev) { projEl.removeButton.addEventListener("click", function (ev) {
projEl.root.remove(); projEl.root.remove();
updateProjectSelector(); updateProjectSelector();
}); });
@ -126,7 +126,7 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
option.textContent = remainingProjects[i].name; option.textContent = remainingProjects[i].name;
projectSelector.appendChild(option); projectSelector.appendChild(option);
} }
projectSelector.addEventListener("change", function(ev) { projectSelector.addEventListener("change", function (ev) {
if (projectSelector.selectedOptions.length > 0) { if (projectSelector.selectedOptions.length > 0) {
let selected = projectSelector.selectedOptions[0]; let selected = projectSelector.selectedOptions[0];
if (selected.value != "") { if (selected.value != "") {
@ -237,33 +237,33 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
} }
} }
snippetEdit.uploadLink.addEventListener("click", function() { snippetEdit.uploadLink.addEventListener("click", function () {
snippetEdit.file.click(); snippetEdit.file.click();
}); });
snippetEdit.removeLink.addEventListener("click", function() { snippetEdit.removeLink.addEventListener("click", function () {
clearAttachment(false); clearAttachment(false);
}); });
snippetEdit.replaceLink.addEventListener("click", function() { snippetEdit.replaceLink.addEventListener("click", function () {
snippetEdit.file.click(); snippetEdit.file.click();
}); });
snippetEdit.resetLink.addEventListener("click", function() { snippetEdit.resetLink.addEventListener("click", function () {
clearAttachment(true); clearAttachment(true);
}); });
snippetEdit.uploadResetLink.addEventListener("click", function() { snippetEdit.uploadResetLink.addEventListener("click", function () {
clearAttachment(true); clearAttachment(true);
}); });
snippetEdit.file.addEventListener("change", function() { snippetEdit.file.addEventListener("change", function () {
if (snippetEdit.file.files.length > 0) { if (snippetEdit.file.files.length > 0) {
setFile(snippetEdit.file.files[0]); setFile(snippetEdit.file.files[0]);
} }
}); });
snippetEdit.root.addEventListener("dragover", function(ev) { snippetEdit.root.addEventListener("dragover", function (ev) {
let effect = "none"; let effect = "none";
for (let i = 0; i < ev.dataTransfer.items.length; ++i) { for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") { if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
@ -277,7 +277,7 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
let enterCounter = 0; let enterCounter = 0;
snippetEdit.root.addEventListener("dragenter", function(ev) { snippetEdit.root.addEventListener("dragenter", function (ev) {
enterCounter++; enterCounter++;
let droppable = Array.from(ev.dataTransfer.items).some( let droppable = Array.from(ev.dataTransfer.items).some(
item => item.kind.toLowerCase() === "file" item => item.kind.toLowerCase() === "file"
@ -287,14 +287,14 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
} }
}); });
snippetEdit.root.addEventListener("dragleave", function(ev) { snippetEdit.root.addEventListener("dragleave", function (ev) {
enterCounter--; enterCounter--;
if (enterCounter == 0) { if (enterCounter == 0) {
snippetEdit.root.classList.remove("drop"); snippetEdit.root.classList.remove("drop");
} }
}); });
snippetEdit.root.addEventListener("drop", function(ev) { snippetEdit.root.addEventListener("drop", function (ev) {
enterCounter = 0; enterCounter = 0;
snippetEdit.root.classList.remove("drop"); snippetEdit.root.classList.remove("drop");
@ -305,18 +305,18 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
ev.preventDefault(); ev.preventDefault();
}); });
snippetEdit.text.addEventListener("paste", function(ev) { snippetEdit.text.addEventListener("paste", function (ev) {
const files = ev.clipboardData?.files ?? []; const files = ev.clipboardData?.files ?? [];
if (files.length > 0) { if (files.length > 0) {
setFile(files[0]); setFile(files[0]);
} }
}); });
snippetEdit.text.addEventListener("input", function(ev) { snippetEdit.text.addEventListener("input", function (ev) {
validate(); validate();
}); });
snippetEdit.saveButton.addEventListener("click", function(ev) { snippetEdit.saveButton.addEventListener("click", function (ev) {
let projectsChanged = false; let projectsChanged = false;
let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]"); let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]");
let assignedIds = []; let assignedIds = [];
@ -350,7 +350,7 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
} }
}); });
snippetEdit.deleteButton.addEventListener("click", function(ev) { snippetEdit.deleteButton.addEventListener("click", function (ev) {
if (!window.confirm("Are you sure you want to delete this snippet?")) { if (!window.confirm("Are you sure you want to delete this snippet?")) {
ev.preventDefault(); ev.preventDefault();
return; return;
@ -367,7 +367,7 @@ function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmen
function editTimelineSnippet(timelineItemEl, stickyProjectId) { function editTimelineSnippet(timelineItemEl, stickyProjectId) {
let ownerName = timelineItemEl.querySelector(".user")?.textContent; let ownerName = timelineItemEl.querySelector(".user")?.textContent;
let ownerUrl = timelineItemEl.querySelector(".user")?.href; let ownerUrl = timelineItemEl.querySelector(".user")?.href;
let ownerAvatar = timelineItemEl.querySelector(".avatar-icon")?.src; let ownerAvatar = timelineItemEl.querySelector(".avatar")?.src;
let creationDate = new Date(timelineItemEl.querySelector("time").dateTime); let creationDate = new Date(timelineItemEl.querySelector("time").dateTime);
let rawDesc = timelineItemEl.querySelector(".rawdesc").textContent; let rawDesc = timelineItemEl.querySelector(".rawdesc").textContent;
let attachment = timelineItemEl.querySelector(".timeline-content-box")?.children?.[0]; let attachment = timelineItemEl.querySelector(".timeline-content-box")?.children?.[0];

View File

@ -7200,7 +7200,7 @@ code {
--theme-color-light: #666; --theme-color-light: #666;
--main-background-color: #202020; --main-background-color: #202020;
--main-background-color-transparent: rgba(#202020, 0); --main-background-color-transparent: rgba(#202020, 0);
--card-background: #282828; --card-background: #494949;
--card-background-hover: #333; --card-background-hover: #333;
--dim-background: #252525; --dim-background: #252525;
--dim-background-transparent: rgba(#252525, 0); --dim-background-transparent: rgba(#252525, 0);
@ -8190,10 +8190,10 @@ pre,
} }
/* src/rawdata/scss/header.css */ /* src/rawdata/scss/header.css */
header { header.old {
--logo-height: 3.75rem; --logo-height: 3.75rem;
} }
header .hmn-logo { header.old .hmn-logo {
height: var(--logo-height); height: var(--logo-height);
width: 100%; width: 100%;
text-transform: uppercase; text-transform: uppercase;
@ -8205,12 +8205,12 @@ header .hmn-logo {
color: white !important; color: white !important;
} }
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
header .hmn-logo.big { header.old .hmn-logo.big {
width: 11.25rem; width: 11.25rem;
} }
} }
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
header .hmn-logo.small { header.old .hmn-logo.small {
width: var(--logo-height); width: var(--logo-height);
padding: 0.8rem; padding: 0.8rem;
text-align: justify; text-align: justify;
@ -8222,36 +8222,36 @@ header .hmn-logo {
align-items: stretch; align-items: stretch;
} }
} }
header .items { header.old .items {
position: relative; position: relative;
} }
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
header .root-item { header.old .root-item {
position: relative; position: relative;
height: var(--logo-height); height: var(--logo-height);
} }
} }
header .root-item:not(:hover):not(.clicked) > .submenu { header.old .root-item:not(:hover):not(.clicked) > .submenu {
display: none; display: none;
} }
header .root-item.clicked .svgicon { header.old .root-item.clicked .svgicon {
transform: rotate(180deg); transform: rotate(180deg);
} }
header .root-item > a { header.old .root-item > a {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
font-weight: bold; font-weight: bold;
} }
header .root-item .svgicon { header.old .root-item .svgicon {
font-size: 0.7em; font-size: 0.7em;
} }
header:not(.clicked) .root-item:not(:hover) > .submenu, header.old:not(.clicked) .root-item:not(:hover) > .submenu,
header.clicked .root-item:not(.clicked) > .submenu { header.old.clicked .root-item:not(.clicked) > .submenu {
display: none; display: none;
} }
header .submenu { header.old .submenu {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
@ -8263,7 +8263,7 @@ header .submenu {
border-bottom-style: solid; border-bottom-style: solid;
} }
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
header .submenu { header.old .submenu {
border-top-style: none; border-top-style: none;
border-left-style: solid; border-left-style: solid;
border-right-style: solid; border-right-style: solid;
@ -8271,7 +8271,7 @@ header .submenu {
right: initial; right: initial;
} }
} }
header .submenu > a { header.old .submenu > a {
display: block; display: block;
white-space: nowrap; white-space: nowrap;
z-index: 1; z-index: 1;
@ -8279,10 +8279,57 @@ header .submenu > a {
text-align: center; text-align: center;
} }
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
header .submenu > a { header.old .submenu > a {
text-align: left; text-align: left;
} }
} }
header {
background-color: var(--card-background);
}
header .hmn-logo {
font-family: "MohaveHMN", sans-serif;
text-transform: uppercase;
font-size: 1.6rem;
padding: 0.6rem 0.8rem;
line-height: 1;
}
header .menu-chevron {
display: inline-block;
margin-left: var(--spacing-extra-small);
font-size: var(--font-size-7);
}
header .avatar {
width: 1.8rem;
}
header .header-nav > a,
header .header-nav > .root-item > a {
display: block;
padding: var(--spacing-medium);
}
header .header-nav .submenu {
display: flex;
flex-direction: column;
position: absolute;
z-index: 100;
min-width: 8rem;
background-color: var(--card-background);
}
header .header-nav .submenu > a {
padding: var(--spacing-small) var(--spacing-medium);
display: block;
white-space: nowrap;
z-index: 1;
}
header .header-nav .root-item:not(:hover):not(.clicked) > .submenu {
display: none;
}
header .header-nav .root-item.clicked .svgicon {
transform: rotate(180deg);
}
header:not(.clicked) .root-item:not(:hover) > .submenu,
header.clicked .root-item:not(.clicked) > .submenu {
display: none;
}
/* src/rawdata/scss/icons.css */ /* src/rawdata/scss/icons.css */
@font-face { @font-face {
@ -8470,28 +8517,13 @@ span.icon-rss::before {
} }
/* src/rawdata/scss/timeline.css */ /* src/rawdata/scss/timeline.css */
.avatar-icon { .avatar {
object-fit: cover; object-fit: cover;
border-radius: 100%; border-radius: 100%;
overflow: hidden; overflow: hidden;
object-fit: cover;
background-color: var(--dimmest-color); background-color: var(--dimmest-color);
width: 2.5rem;
height: 2.5rem;
border: 2px solid;
border-color: var(--theme-color);
flex-shrink: 0; flex-shrink: 0;
} }
.avatar-icon.big {
width: 3rem;
height: 3rem;
}
@media screen and (min-width: 35em) {
.avatar-icon.big {
width: 3.875rem;
height: 3.875rem;
}
}
.timeline-item { .timeline-item {
background-color: var(--card-background); background-color: var(--card-background);
--fade-color: var(--card-background); --fade-color: var(--card-background);

View File

@ -12867,7 +12867,7 @@ input[type=submit].inline-button {
width: 50px; width: 50px;
} }
.avatar-icon { .avatar {
width: 40px; width: 40px;
height: 40px; height: 40px;
flex-shrink: 0; flex-shrink: 0;
@ -13025,7 +13025,7 @@ input[type=submit].inline-button {
background-color: transparent; background-color: transparent;
} }
.featured-post .meta .avatar-icon { .featured-post .meta .avatar {
left: -60px; left: -60px;
bottom: -5px; bottom: -5px;
} }
@ -13943,7 +13943,7 @@ span.icon-rss::before {
margin-right: 10px; margin-right: 10px;
} }
.user_suggestions .user .avatar-icon { .user_suggestions .user .avatar {
left: -50px; left: -50px;
bottom: 0px; bottom: 0px;
border-radius: 50%; border-radius: 50%;
@ -14017,19 +14017,19 @@ span.icon-rss::before {
color: var(--main-color); color: var(--main-color);
} }
.timeline-item .avatar-icon { .timeline-item .avatar {
border: 2px solid; border: 2px solid;
border-color: #666; border-color: #666;
border-color: var(--theme-color); border-color: var(--theme-color);
} }
.timeline-item .avatar-icon.big { .timeline-item .avatar.big {
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
} }
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
.timeline-item .avatar-icon.big { .timeline-item .avatar.big {
width: 3.875rem; width: 3.875rem;
height: 3.875rem; height: 3.875rem;
} }

View File

@ -1,4 +1,4 @@
header { header.old {
--logo-height: 3.75rem; --logo-height: 3.75rem;
.hmn-logo { .hmn-logo {
@ -112,3 +112,68 @@ header {
} }
} }
} }
header {
background-color: var(--card-background);
.hmn-logo {
font-family: 'MohaveHMN', sans-serif;
text-transform: uppercase;
font-size: 1.6rem;
padding: 0.6rem 0.8rem;
line-height: 1;
}
.menu-chevron {
/* ensure that it also has .svgicon */
display: inline-block;
margin-left: var(--spacing-extra-small);
font-size: var(--font-size-7);
}
.avatar {
width: 1.8rem;
}
.header-nav {
>a,
>.root-item>a {
display: block;
padding: var(--spacing-medium);
}
.submenu {
display: flex;
flex-direction: column;
position: absolute;
z-index: 100;
min-width: 8rem;
background-color: var(--card-background);
>a {
padding: var(--spacing-small) var(--spacing-medium);
display: block;
white-space: nowrap;
z-index: 1;
}
}
.root-item {
&:not(:hover):not(.clicked)>.submenu {
display: none;
}
&.clicked .svgicon {
transform: rotate(180deg);
}
}
}
&:not(.clicked) .root-item:not(:hover),
&.clicked .root-item:not(.clicked) {
>.submenu {
display: none;
}
}
}

View File

@ -1,25 +1,9 @@
.avatar-icon { .avatar {
object-fit: cover; object-fit: cover;
border-radius: 100%; border-radius: 100%;
overflow: hidden; overflow: hidden;
object-fit: cover;
background-color: var(--dimmest-color); background-color: var(--dimmest-color);
width: 2.5rem;
height: 2.5rem;
border: 2px solid;
border-color: var(--theme-color);
flex-shrink: 0; flex-shrink: 0;
&.big {
width: 3rem;
height: 3rem;
@media screen and (min-width: 35em) {
width: 3.875rem;
height: 3.875rem;
}
}
} }
.timeline-item { .timeline-item {

View File

@ -70,7 +70,8 @@ $breakpoint-large: screen and (min-width: 60em)
--main-background-color: #202020; --main-background-color: #202020;
--main-background-color-transparent: rgba(#202020, 0); --main-background-color-transparent: rgba(#202020, 0);
--card-background: #282828; /* --card-background: #282828; */
--card-background: #494949;
--card-background-hover: #333; --card-background-hover: #333;
--dim-background: #252525; --dim-background: #252525;

View File

@ -14,7 +14,7 @@
{{ if .Posts }} {{ if .Posts }}
{{ range .Posts }} {{ range .Posts }}
<div class="flex items-start ph3 pv3 background-even"> <div class="flex items-start ph3 pv3 background-even">
<img class="avatar-icon mr2" src="{{ .Author.AvatarUrl }}"> <img class="avatar mr2" src="{{ .Author.AvatarUrl }}">
<div class="flex-grow-1 overflow-hidden"> <div class="flex-grow-1 overflow-hidden">
<div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div> <div class="title mb1"><a href="{{ .Url }}">{{ .Title }}</a></div>
<div class="details"> <div class="details">

View File

@ -6,7 +6,7 @@
{{ with .MainPost }} {{ with .MainPost }}
<div class="flex justify-between items-center mt2 mb3"> <div class="flex justify-between items-center mt2 mb3">
<div class="flex items-center"> <div class="flex items-center">
<div class="avatar-icon contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div> <div class="avatar contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div>
<div class="flex flex-column ml2"> <div class="flex flex-column ml2">
<div> <div>
<a class="username" href="{{ .Author.ProfileUrl }}" target="_blank">{{ .Author.Name }}</a> <a class="username" href="{{ .Author.ProfileUrl }}" target="_blank">{{ .Author.Name }}</a>
@ -54,7 +54,7 @@
{{ range .Comments }} {{ range .Comments }}
<div class="pa3 flex items-start background-even"> <div class="pa3 flex items-start background-even">
<div> <div>
<div class="avatar-icon contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div> <div class="avatar contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div>
</div> </div>
<div class="pl3 flex flex-column w-100"> <div class="pl3 flex flex-column w-100">
<div class="flex justify-between"> <div class="flex justify-between">

View File

@ -18,7 +18,7 @@
</div> </div>
{{ range .Posts }} {{ range .Posts }}
<div class="post-list-item flex items-center ph3 pv2 {{ if .Unread }}unread{{ else }}read{{ end }} {{ .Classes }}"> <div class="post-list-item flex items-center ph3 pv2 {{ if .Unread }}unread{{ else }}read{{ end }} {{ .Classes }}">
<img class="avatar-icon mr2" src="{{ .User.AvatarUrl }}"> <img class="avatar mr2" src="{{ .User.AvatarUrl }}">
<div class="flex-grow-1 overflow-hidden"> <div class="flex-grow-1 overflow-hidden">
{{ template "breadcrumbs.html" .Breadcrumbs }} {{ template "breadcrumbs.html" .Breadcrumbs }}
<div class="title nowrap truncate"><a href="{{ .Url }}" title="{{ .Preview }}">{{ if .PostTypePrefix }}{{ .PostTypePrefix }}: {{ end }}{{ .Title }}</a></div> <div class="title nowrap truncate"><a href="{{ .Url }}" title="{{ .Preview }}">{{ if .PostTypePrefix }}{{ .PostTypePrefix }}: {{ end }}{{ .Title }}</a></div>

View File

@ -0,0 +1,5 @@
<footer>
<div class="mv5 h3 fill-current link--white">
<a href="{{ .Header.HMNHomepageUrl }}">{{ svg "hmn_circuit" }}</a>
</div>
</footer>

View File

@ -0,0 +1,103 @@
<header id="site-header" class="flex flex-row items-center link--white">
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo flex-shrink-0">
Handmade
</a>
<div class="flex-grow-1 flex-shrink-1"></div>
<div class="header-nav flex flex-row items-center lh-solid f6">
<a href="{{ .Header.ProjectIndexUrl }}">Projects</a>
<a href="{{ .Header.JamsUrl }}">Jams</a>
<div class="root-item">
<a aria-expanded="false" aria-controls="events-submenu" class="menu-dropdown-js" href="#">
Events <div class="menu-chevron svgicon">{{ svg "chevron-down-thick" }}</div>
</a>
<div class="submenu" id="events-submenu">
<a href="{{ .Header.JamsUrl }}">Jams</a>
<a href="https://twitch.tv/HandmadeNetwork">Unwind</a>
</div>
</div>
<div class="root-item">
<a aria-expanded="false" aria-controls="resource-submenu" class="menu-dropdown-js" href="#">
Resources <div class="menu-chevron svgicon">{{ svg "chevron-down-thick" }}</div>
</a>
<div class="submenu" id="resource-submenu">
<a href="{{ .Header.ForumsUrl }}">Forums</a>
<a href="{{ .Header.FishbowlUrl }}">Fishbowls</a>
<a href="{{ .Header.PodcastUrl }}">Podcast</a>
<!-- TODO: This is not a "resource", nor do we want to imply that we own Handmade Cities. -->
<a href="https://handmadecities.com/media/">Handmade Cities</a>
<!-- TODO: What about our existing conferences page? How to properly reference HandmadeCon? Does this give enough context? (Make sure to reference Casey's email to me and Abner.) -->
<a href="https://guide.handmadehero.org/hmcon/">HandmadeCon</a>
</div>
</div>
<div class="root-item">
<a aria-expanded="false" aria-controls="about-submenu" class="menu-dropdown-js" href="#">
About <div class="menu-chevron svgicon">{{ svg "chevron-down-thick" }}</div>
</a>
<div class="submenu" id="about-submenu">
<a href="{{ .Header.ManifestoUrl }}">Manifesto</a>
<a href="{{ .Header.AboutUrl }}">About the Team</a>
</div>
</div>
</div>
<a class="db ph3 pv2 flex" href="{{ or .Header.UserProfileUrl .LoginPageUrl }}">
<img class="avatar" src="{{ with .User }}{{ .AvatarUrl }}{{ end }}">
</a>
</header>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
const header = document.querySelector('#site-header');
// set up dropdown stuff for mobile / touch
{
const rootItems = header.querySelectorAll('.root-item');
function clearDropdowns() {
for (const item of rootItems) {
item.classList.remove('clicked');
}
}
function clickDropdown(el) {
header.classList.add('clicked');
if (el.classList.contains('clicked')) {
clearDropdowns();
} else {
clearDropdowns();
el.classList.add('clicked');
}
}
for (const item of rootItems) {
if (item.querySelector('.submenu')) {
item.addEventListener('click', e => {
clickDropdown(item);
e.stopPropagation();
});
}
}
}
// dropdown accessiblity
{
const dropdowns = document.querySelectorAll('.menu-dropdown-js');
for(let i = 0; i < dropdowns.length; i++) {
let dropdown = dropdowns[i];
dropdown.addEventListener('click', e => {
e.preventDefault();
for(let j = 0; j < dropdowns.length; j++){
let each = dropdowns[j];
if(each != dropdown){
each.setAttribute("aria-expanded", false);
}
}
// getAttribute returns a string so we have to do it this way
var toSetTo = dropdown.getAttribute("aria-expanded") == "false" ? "true" : "false";
dropdown.setAttribute("aria-expanded", toSetTo);
console.log(dropdown);
});
}
}
});
</script>

View File

@ -1,4 +1,4 @@
<header id="site-header" class="mb3 bb bw1 b--theme-dark"> <header id="site-header" class="old mb3 bb bw1 b--theme-dark">
<a href="#content-start" class="sr sr-focusable" id="content-jump">Jump to Content</a> <a href="#content-start" class="sr sr-focusable" id="content-jump">Jump to Content</a>
<div class="flex justify-center justify-end-ns relative"> <div class="flex justify-center justify-end-ns relative">
{{ if .User }} {{ if .User }}

View File

@ -6,7 +6,7 @@
</div> </div>
{{ end }} {{ end }}
<div class="details pa3 flex-grow-1"> <div class="details pa3 flex-grow-1">
<h3 class="mb1">{{ .Project.Name }}</h3> <h3 class="mb2 f4">{{ .Project.Name }}</h3>
<div class="blurb">{{ .Project.Blurb }}</div> <div class="blurb">{{ .Project.Blurb }}</div>
<div class="badges mt2"> <div class="badges mt2">
{{ if .Project.LifecycleString }} {{ if .Project.LifecycleString }}

View File

@ -23,7 +23,7 @@
<div data-tmpl="asset_container" class="bg-dark-gray flex justify-center"></div> <div data-tmpl="asset_container" class="bg-dark-gray flex justify-center"></div>
<div class="bg--content pa3 overflow-y-auto"> <div class="bg--content pa3 overflow-y-auto">
<div class="timeline-user-info mb2 flex items-center"> <div class="timeline-user-info mb2 flex items-center">
<img class="avatar-icon lite mr2" data-tmpl="avatar"/> <img class="avatar lite mr2" data-tmpl="avatar"/>
<a class="user" data-tmpl="userLink"></a> <a class="user" data-tmpl="userLink"></a>
<a data-tmpl="date" class="datetime tr" style="flex: 1 1 auto;"></a> <a data-tmpl="date" class="datetime tr" style="flex: 1 1 auto;"></a>
</div> </div>

View File

@ -16,7 +16,7 @@
<input data-tmpl="removeAttachment" type="hidden" name="remove_attachment" value="false" /> <input data-tmpl="removeAttachment" type="hidden" name="remove_attachment" value="false" />
<input data-tmpl="file" type="file" name="file" class="dn" /> <input data-tmpl="file" type="file" name="file" class="dn" />
<div class="flex items-center"> <div class="flex items-center">
<a data-tmpl="avatarLink" class="flex-shrink-0"><img data-tmpl="avatarImg" class="avatar-icon lite mr2" /></a> <a data-tmpl="avatarLink" class="flex-shrink-0"><img data-tmpl="avatarImg" class="avatar lite mr2" /></a>
<a data-tmpl="username" class="flex-shrink-0"></a> <a data-tmpl="username" class="flex-shrink-0"></a>
<div class="spacer flex-grow-1"></div> <div class="spacer flex-grow-1"></div>
<span data-tmpl="date" class="flex-shrink-0">Date</span> <span data-tmpl="date" class="flex-shrink-0">Date</span>

View File

@ -5,7 +5,7 @@ It should be called with ThreadListItem.
*/}} */}}
<div class="thread-list-item flex items-center ph3 pv2 {{ if .Unread }}unread{{ else }}read{{ end }} background-even {{ .Classes }}"> <div class="thread-list-item flex items-center ph3 pv2 {{ if .Unread }}unread{{ else }}read{{ end }} background-even {{ .Classes }}">
<img class="avatar-icon mr2" src="{{ .FirstUser.AvatarUrl }}"> <img class="avatar mr2" src="{{ .FirstUser.AvatarUrl }}">
<div class="flex-grow-1 overflow-hidden"> <div class="flex-grow-1 overflow-hidden">
<div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div> <div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
<div class="details"> <div class="details">
@ -18,7 +18,7 @@ It should be called with ThreadListItem.
{{ end }} {{ end }}
</div> </div>
<div class="latestpost dn flex-ns flex-shrink-0 items-center ml2"> <div class="latestpost dn flex-ns flex-shrink-0 items-center ml2">
<img class="avatar-icon mr2" src="{{ .LastUser.AvatarUrl }}"> <img class="avatar mr2" src="{{ .LastUser.AvatarUrl }}">
<div> <div>
<div>Last post {{ timehtml (relativedate .LastDate) .LastDate }}</div> <div>Last post {{ timehtml (relativedate .LastDate) .LastDate }}</div>
<a class="user" href="{{ .LastUser.ProfileUrl }}">{{ .LastUser.Name }}</a> <a class="user" href="{{ .LastUser.ProfileUrl }}">{{ .LastUser.Name }}</a>

View File

@ -3,7 +3,7 @@
<div class="flex items-center"> <div class="flex items-center">
{{ if .OwnerAvatarUrl }} {{ if .OwnerAvatarUrl }}
<a class="flex flex-shrink-0" href="{{ .OwnerUrl }}"> <a class="flex flex-shrink-0" href="{{ .OwnerUrl }}">
<img class="avatar-icon lite {{ if not .SmallInfo }}big{{ end }} {{ if .SmallInfo }}mr2{{ else }}mr3{{ end }}" src="{{ .OwnerAvatarUrl }}" /> <img class="avatar lite {{ if not .SmallInfo }}big{{ end }} {{ if .SmallInfo }}mr2{{ else }}mr3{{ end }}" src="{{ .OwnerAvatarUrl }}" />
</a> </a>
{{ end }} {{ end }}

View File

@ -22,7 +22,7 @@
<div class="mw8 m--center ph3 ph4-l"> <div class="mw8 m--center ph3 ph4-l">
<h2 class="mb2">Jam recap</h2> <h2 class="mb2">Jam recap</h2>
<div class="mb4 flex items-center"> <div class="mb4 flex items-center">
<div class="avatar-icon contain bg-center" style="background-image:url('{{ .Ben.AvatarUrl }}');"></div> <div class="avatar contain bg-center" style="background-image:url('{{ .Ben.AvatarUrl }}');"></div>
<div class="flex flex-column ml2"> <div class="flex flex-column ml2">
<div> <div>
<a class="username" href="{{ .Ben.ProfileUrl }}" target="_blank">{{ .Ben.Name }}</a> <a class="username" href="{{ .Ben.ProfileUrl }}" target="_blank">{{ .Ben.Name }}</a>

View File

@ -0,0 +1,245 @@
{{/*
This is a copy-paste from base.html because we want to preserve the unique
style of this page no matter what future changes we make to the base.
*/}}
<!DOCTYPE html>
<html lang="en-US" {{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" sizes="16x16" href="{{ static "learningjam2024/favicon-16x16.png" }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "learningjam2024/favicon-32x32.png" }}">
{{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }}
{{ range .OpenGraphItems }}
{{ if .Property }}
<meta property="{{ .Property }}" content="{{ .Value }}" />
{{ else }}
<meta name="{{ .Name }}" content="{{ .Value }}" />
{{ end }}
{{ end }}
{{ if .Title }}
<title>{{ .Title }} | Handmade Network</title>
{{ else }}
<title>Handmade Network</title>
{{ end }}
<meta name="theme-color" content="#003C83">
<script src="{{ static "js/templates.js" }}"></script>
<link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}">
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
<style>
:root {
--theme-gradient-dark: linear-gradient(to bottom right, #003c83, #019AD2);
--theme-gradient-light: linear-gradient(to bottom right, #8BD5FF, #5899FF);
--white: #fff;
--bg-button: rgba(255, 255, 255, 0);
--bg-button-hover: rgba(255, 255, 255, 0.1);
--charcoal: #2F2F2F;
--gray: #CBCBCB;
--rich-gray: #494949;
--spacing-0: 0;
--spacing-1: .25rem;
--spacing-2: .5rem;
--spacing-3: 1rem;
--spacing-4: 2rem;
--spacing-5: 4rem;
--spacing-6: 8rem;
--spacing-7: 16rem;
--border-radius-2: 0.25rem;
--link-color: transparent;
}
body {
font-family: "Inter", "Fira Sans", sans-serif;
font-size: 1rem; /* remove this override when base stylesheet is less dumb */
}
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
}
.w2-5 {
width: 3rem;
}
.link--white {
--link-color: var(--white);
}
.post-content p {
/* stupid override, should be done by .post-content instead of .content */
margin: 0.6rem 0;
}
.bg--theme-gradient-dark {
background: var(--theme-gradient-dark);
}
.bg--theme-gradient-light {
background: var(--theme-gradient-light);
}
.bg--charcoal {
background: var(--charcoal);
}
.bg--rich-gray {
background: var(--rich-gray);
}
.bg--gray {
background: var(--gray);
}
.b--charcoal {
border-color: var(--charcoal);
}
.b--rich-gray {
border-color: var(--rich-gray);
}
.c--theme-gradient-dark {
background: var(--theme-gradient-dark);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
a, .c--theme-gradient-light {
background: var(--theme-gradient-light);
background-clip: text;
-webkit-background-clip: text;
}
.c--theme-gradient-light {
color: transparent;
}
.c--gray {
color: var(--gray);
}
.btn--jam {
border: 1px solid var(--white);
border-radius: var(--border-radius-2);
padding: var(--spacing-2) var(--spacing-3);
background-color: var(--bg-button);
}
.btn--jam.small {
padding: var(--spacing-1) var(--spacing-2);
}
.btn--jam:hover {
background-color: var(--bg-button-hover);
}
.button-simple {
background: var(--bg-button);
border: 1px solid var(--white);
}
.button-simple:hover {
background: var(--bg-button-hover);
}
.c-white {
color: var(--white);
}
.invisible {
visibility: hidden;
}
.svg-mask {
mask: var(--mask-url);
-webkit-mask: var(--mask-url);
}
.fill-current {
fill: currentColor;
}
.square {
aspect-ratio: 1 / 1;
}
.wide-screen {
aspect-ratio: 16 / 9;
}
.iframe-fill iframe {
flex-grow: 1;
}
.jam-logo {
max-width: 18rem;
}
.jam-logo-small {
max-width: 8rem;
}
.jam-title {
/* align with the width of the logo */
font-size: 2.76rem;
font-weight: 700;
/* align with the text's actual bounding box */
line-height: 1.18;
margin-top: -0.18em;
margin-left: -0.03em;
margin-right: -0.03em;
}
.jam-title.small {
font-size: 2.25rem;
/* align with the text's actual bounding box */
line-height: 1.18;
margin-top: -0.18em;
margin-left: -0.03em;
margin-right: -0.03em;
}
/* not small */
@media screen and (min-width: 35em) {
.jam-logo {
max-width: 24rem;
}
.jam-title {
font-size: 3.7rem;
}
}
/* not small */
@media screen and (min-width: 35em) {
.flex-basis-40-ns {
flex-basis: 40%;
}
}
</style>
{{ template "extrahead" . }}
</head>
<body class="bg--charcoal flex flex-column">
{{ template "header-2024.html" . }}
<!-- TODO: Notices -->
<div class="bg--charcoal c-white flex-grow-1">
{{ block "content" . }}{{ end }}
</div>
{{ template "footer-2024.html" . }}
</body>
</html>

View File

@ -337,7 +337,7 @@
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
} }
.avatar-icon { .avatar {
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
} }
</style> </style>

View File

@ -1,39 +1,153 @@
{{ $c := hex2color .Color }} {
{
$c : =hex2color .Color
}
}
:root { :root {
--theme-color: {{ $c | color2css }}; --theme-color: {
{
$c | color2css
}
}
--theme-color-dim: {{ lightness 0.75 $c | color2css }}; ;
--theme-color-dimmer: {{ lightness 0.8 $c | color2css }};
--theme-color-dimmest: {{ lightness 0.85 $c | color2css }};
--theme-color-dark: {{ lightness 0.35 $c | color2css }}; --theme-color-dim: {
--theme-color-light: {{ lightness 0.55 $c | color2css }}; {
lightness 0.75 $c | color2css
}
}
--link-color: {{ lightness 0.35 $c | color2css }}; ;
--link-hover-color: {{ lightness 0.45 $c | color2css }};
--background-even-background: {{ lightness 0.95 $c | color2css }}; --theme-color-dimmer: {
{
lightness 0.8 $c | color2css
}
}
;
--theme-color-dimmest: {
{
lightness 0.85 $c | color2css
}
}
;
--theme-color-dark: {
{
lightness 0.35 $c | color2css
}
}
;
--theme-color-light: {
{
lightness 0.55 $c | color2css
}
}
;
--link-color: {
{
lightness 0.35 $c | color2css
}
}
;
--link-hover-color: {
{
lightness 0.45 $c | color2css
}
}
;
--background-even-background: {
{
lightness 0.95 $c | color2css
}
}
;
--input-lite-border: var(--link-color); --input-lite-border: var(--link-color);
}
@media (prefers-color-scheme: dark) {
:root {
--theme-color-dim: {{ lightness 0.35 $c | color2css }};
--theme-color-dimmer: {{ lightness 0.3 $c | color2css }};
--theme-color-dimmest: {{ lightness 0.2 $c | color2css }};
--theme-color-dark: {{ lightness 0.30 $c | color2css }};
--theme-color-light: {{ lightness 0.55 $c | color2css }};
--link-color: {{ lightness 0.55 $c | color2css }};
--link-hover-color: {{ lightness 0.65 $c | color2css }};
--background-even-background: {{ lightness 0.15 $c | color2css }};
} }
}
.unread .avatar-icon { @media (prefers-color-scheme: dark) {
:root {
--theme-color-dim: {
{
lightness 0.35 $c | color2css
}
}
;
--theme-color-dimmer: {
{
lightness 0.3 $c | color2css
}
}
;
--theme-color-dimmest: {
{
lightness 0.2 $c | color2css
}
}
;
--theme-color-dark: {
{
lightness 0.30 $c | color2css
}
}
;
--theme-color-light: {
{
lightness 0.55 $c | color2css
}
}
;
--link-color: {
{
lightness 0.55 $c | color2css
}
}
;
--link-hover-color: {
{
lightness 0.65 $c | color2css
}
}
;
--background-even-background: {
{
lightness 0.15 $c | color2css
}
}
;
}
}
.unread .avatar {
border: 2px solid var(--link-color); border: 2px solid var(--link-color);
} }

View File

@ -1,4 +1,4 @@
{{ template "base.html" . }} {{ template "base-2024.html" . }}
{{ define "extrahead" }} {{ define "extrahead" }}
{{ range .Screenshots }} {{ range .Screenshots }}
@ -27,7 +27,7 @@
<div class="mb3"> <div class="mb3">
{{ range $i, $owner := .Owners }} {{ range $i, $owner := .Owners }}
<div class="flex mb3 items-center"> <div class="flex mb3 items-center">
<img class="avatar-icon mr2" src="{{ $owner.AvatarUrl }}" /> <img class="avatar mr2" src="{{ $owner.AvatarUrl }}" />
<a class="rel" href="{{ $owner.ProfileUrl }}">{{ $owner.Name }}</a> <a class="rel" href="{{ $owner.ProfileUrl }}">{{ $owner.Name }}</a>
</div> </div>
{{ end }} {{ end }}

View File

@ -13,7 +13,7 @@
<div class="carousel-item flex pv3 pl3 w-100 h-100 bg--dim items-center {{ if eq $index 0 }}active{{ end }}"> <div class="carousel-item flex pv3 pl3 w-100 h-100 bg--dim items-center {{ if eq $index 0 }}active{{ end }}">
<div class="flex-grow-1 pr3 relative flex flex-column h-100 justify-center"> <div class="flex-grow-1 pr3 relative flex flex-column h-100 justify-center">
<a href="{{ $project.Url }}"> <a href="{{ $project.Url }}">
<h3>{{ $project.Name }}</h3> <h3 class="f3">{{ $project.Name }}</h3>
</a> </a>
<div class="carousel-description"> <div class="carousel-description">
{{ $project.ParsedDescription }} {{ $project.ParsedDescription }}
@ -46,7 +46,7 @@
{{ end }} {{ end }}
{{ if .OfficialProjects }} {{ if .OfficialProjects }}
<div class="ph3 pt3 bg--dim br2 flex flex-column"> <div class="ph3 pt3 bg--dim br2 flex flex-column">
<h2>Official Projects</h2> <h2 class="f3 mb2">Official Projects</h2>
<div class="grid grid-1 grid-2-ns g3"> <div class="grid grid-1 grid-2-ns g3">
{{ range .OfficialProjects }} {{ range .OfficialProjects }}
{{ template "project_card.html" projectcarddata . "" }} {{ template "project_card.html" projectcarddata . "" }}
@ -57,9 +57,9 @@
{{ end }} {{ end }}
{{ if .PersonalProjects }} {{ if .PersonalProjects }}
<div class="ph3 pt3 bg--dim br2 flex flex-column"> <div class="ph3 pt3 bg--dim br2 flex flex-column">
<h2>Personal Projects</h2> <h2 class="f3 mb2">Personal Projects</h2>
<p>Many community members have projects of their own. Want to join them? <a href="{{ .CreateProjectLink }}">Create your own.</a></p> <div>Many community members have projects of their own. Want to join them? <a href="{{ .CreateProjectLink }}">Create your own.</a></div>
<div class="grid grid-1 grid-2-ns g3"> <div class="mt3 grid grid-1 grid-2-ns g3">
{{ range .PersonalProjects }} {{ range .PersonalProjects }}
{{ template "project_card.html" projectcarddata . "" }} {{ template "project_card.html" projectcarddata . "" }}
{{ end }} {{ end }}

View File

@ -58,6 +58,8 @@ type Header struct {
JamsUrl string JamsUrl string
EducationUrl string EducationUrl string
CalendarUrl string CalendarUrl string
ManifestoUrl string
AboutUrl string
Project *ProjectHeader Project *ProjectHeader
} }

View File

@ -82,6 +82,8 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
JamsUrl: hmnurl.BuildJamsIndex(), JamsUrl: hmnurl.BuildJamsIndex(),
EducationUrl: hmnurl.BuildEducationIndex(), EducationUrl: hmnurl.BuildEducationIndex(),
CalendarUrl: hmnurl.BuildCalendarIndex(), CalendarUrl: hmnurl.BuildCalendarIndex(),
ManifestoUrl: hmnurl.BuildManifesto(),
AboutUrl: hmnurl.BuildAbout(),
}, },
Footer: templates.Footer{ Footer: templates.Footer{
HomepageUrl: hmnurl.BuildHomepage(), HomepageUrl: hmnurl.BuildHomepage(),

View File

@ -43,3 +43,7 @@ nodemon --exec "esbuild src\rawdata\scss\style.css --bundle --loader:.ttf=file -
- [ ] Remove all uses of .content-block - [ ] Remove all uses of .content-block
- [ ] Figure out what's up with the projects on the jam pages - [ ] Figure out what's up with the projects on the jam pages
- [ ] Determine if we actually need .project-carousel, or if our general carousel styles (?) are sufficient - [ ] Determine if we actually need .project-carousel, or if our general carousel styles (?) are sufficient
- [ ] Rename `-2024` files
- [ ] Validate accessibility of navigation
- [ ] Make navigation work on mobile
- [ ] Clean up code TODOs