New theme, who dis?

This commit is contained in:
Garrett Mills 2025-02-22 20:10:24 -05:00
parent e443623e26
commit da1aa660ca
50 changed files with 1043 additions and 244 deletions

View File

@ -39,7 +39,11 @@ export class Home extends Controller {
@Inject() @Inject()
protected readonly session!: Session protected readonly session!: Session
public async welcome(feedPosts: Collection<FeedPost>) { public async welcome() {
return view('welcome')
}
public async welcome70s(feedPosts: Collection<FeedPost>) {
const workItems = await this.getWorkItems() const workItems = await this.getWorkItems()
const workItemYears = workItems.map(item => item.startDate.getFullYear()).unique() const workItemYears = workItems.map(item => item.startDate.getFullYear()).unique()
@ -47,7 +51,7 @@ export class Home extends Controller {
.map(item => item.startDate.getFullYear()) .map(item => item.startDate.getFullYear())
.unique() .unique()
return view('welcome', { return view('welcome_70s', {
...this.getThemeCSS(), ...this.getThemeCSS(),
feedPosts: feedPosts.toArray(), feedPosts: feedPosts.toArray(),
workItemGroups: workItems.groupBy(item => item.startDate.getFullYear()), workItemGroups: workItems.groupBy(item => item.startDate.getFullYear()),
@ -66,6 +70,13 @@ export class Home extends Controller {
}) })
} }
public style() {
return view('style', {
title: 'Style Test',
...this.getThemeCSS(),
})
}
public optOutPrompt() { public optOutPrompt() {
return view('message', { return view('message', {
title: 'Analytics Opt-Out', title: 'Analytics Opt-Out',

View File

@ -18,9 +18,15 @@ Route.endpoint('options', '**')
Route Route
.group('/', () => { .group('/', () => {
Route.get('/') Route.get('/70s')
.pre(SiteTheme) .pre(SiteTheme)
.parameterMiddleware(LoadFeedPosts) .parameterMiddleware(LoadFeedPosts)
.calls<Home>(Home, home => home.welcome70s)
.alias('home2')
Route.get('/')
// .pre(SiteTheme)
// .parameterMiddleware(LoadFeedPosts)
.calls<Home>(Home, home => home.welcome) .calls<Home>(Home, home => home.welcome)
.alias('home') .alias('home')
@ -121,6 +127,10 @@ Route
.pre(SiteTheme) .pre(SiteTheme)
.calls<Home>(Home, home => home.technical) .calls<Home>(Home, home => home.technical)
Route.get('/style')
.pre(SiteTheme)
.calls<Home>(Home, home => home.style)
Route.get('/analytics/opt-out') Route.get('/analytics/opt-out')
.pre(SiteTheme) .pre(SiteTheme)
.calls<Home>(Home, home => home.optOutPrompt) .calls<Home>(Home, home => home.optOutPrompt)

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

View File

@ -147,6 +147,7 @@ b {
.section-border .section-border-inner-2 { .section-border .section-border-inner-2 {
border-top: 15px solid var(--c-line-2); border-top: 15px solid var(--c-line-2);
} }
body { body {
background: var(--c-background); background: var(--c-background);
color: var(--c-font); color: var(--c-font);

View File

@ -0,0 +1,314 @@
@font-face {
font-family: 'Obsidian';
src: url('font/obsidian/Obsidian-Roman.otf') format('opentype');
font-weight: normal;
font-style: normal;
/*font-display: swap;*/
}
@font-face {
font-family: "JetBrains Mono";
src: url("font/jetbrains-mono/JetBrainsMono-Regular.woff2") format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Reckless";
src: url("font/reckless/WEB/Reckless-Regular.woff2") format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Reckless";
src: url("font/reckless/WEB/Reckless-RegularItalic.woff2") format('woff2');
font-weight: normal;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Reckless";
src: url("font/reckless/WEB/Reckless-Bold.woff2") format('woff2');
font-weight: bold;
font-style: normal;
font-display: swap;
}
body {
--background: #111;
--background-2: #252525;
--background-3: #444;
--color: #fffbe3;
--color-2: #d0c895;
background: var(--background);
color: var(--color);
font-family: "Reckless", serif;
margin: 0;
padding: 0;
overflow-x: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
.wrapper {
max-width: 800px;
width: calc(100% - 40px);
padding-left: 20px;
padding-right: 20px;
margin-bottom: 100px;
min-height: 100vh;
}
code {
font-family: "JetBrains Mono", monospace;
font-size: 0.8em;
color: var(--color-2);
}
p, h1, h2, h3, h4, h5, h6, li {
font-size: 1.3em;
}
h1:before, h2:before, h3:before, h4:before, h5:before, h6:before {
font-family: "Reckless", serif;
color: var(--color-2);
margin-right: 10px;
}
h1 {
font-size: 2em;
margin: 0;
margin-top: 50px;
}
h1:before {
content: '#';
/*margin-left: -27px;*/
}
h2 {
font-size: 1.7em;
margin: 0;
margin-top: 50px;
}
h2:before {
content: '##';
/*margin-left: -27px;*/
}
h3 {
font-size: 1.4em;
margin: 0;
margin-top: 50px;
}
h3:before {
content: '###';
/*margin-left: -27px;*/
}
h4 {
font-size: 1.3em;
color: var(--color-2);
margin: 0;
margin-top: 50px;
}
h4:before {
content: '####';
/*margin-left: -27px;*/
}
h5 {
font-size: 1.3em;
color: var(--color-2);
margin: 0;
margin-top: 50px;
}
h5:before {
content: '#####';
/*margin-left: -27px;*/
}
h6 {
font-size: 1.3em;
color: var(--color-2);
margin: 0;
margin-top: 50px;
}
h6:before {
content: '######';
/*margin-left: -27px;*/
}
a {
color: #dfdbc3;
}
p {
margin: 20px 0 0;
padding: 0;
}
.feed-item {
margin: 30px 0;
background: var(--background-2);
padding: 15px;
border: 1px solid var(--background-3);
}
.feed-item .tag, .secondary {
color: var(--color-2);
font-style: italic;
}
.secondary {
font-size: 0.8em;
}
.button-links a {
margin-right: 10px;
}
.obsidian {
font-family: "Obsidian", serif;
}
.hero {
font-family: "Obsidian", serif;
font-size: 11em;
margin-top: 0;
}
nav {
margin-top: 25px;
padding-bottom: 25px;
margin-bottom: 25px;
border-bottom: 1px solid #7f7b63;
/*margin-bottom: 150px;*/
}
nav ul {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 30px;
}
nav ul a {
color: #fffbe3;
text-decoration: none;
}
nav ul a:hover {
color: #cfcbb3;
}
blockquote {
border-left: 2px solid var(--color-2);
color: var(--color-2);
padding-left: 10px;
margin-left: 10px;
}
img {
max-width: 100%;
}
center {
margin: 0 20px;
color: var(--color-2);
}
pre {
overflow-x: scroll;
}
hr {
border-color: var(--color-2);
margin: 30px 0;
}
button {
border: 1px solid var(--background-3);
color: var(--color-2);
background: var(--background-2);
padding: 3px 15px;
margin: 15px 0;
transition: all 0.1s linear;
}
button:hover {
cursor: pointer;
color: var(--color);
background: var(--background);
border-color: var(--color-2);
}
ul.plain {
list-style-type: none;
margin: 0;
padding: 0;
}
ul.plain li {
margin: 20px 0;
}
.italic {
font-style: italic;
}
#about {
margin-top: 50px;
}
footer {
width: 100%;
min-height: 300px;
background-color: #ffffff08;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
footer .obsidian {
font-size: 4em;
margin-right: 30px;
padding-right: 30px;
border-right: 1px solid #fffbe1;
}
footer p {
margin: 5px 0 0;
}
.inline-markmark-logo {
height: 16pt;
margin-bottom: -3px;
opacity: 0.75;
}
@media screen and (max-width: 500px) {
nav ul {
flex-direction: column;
gap: 20px;
}
footer {
flex-direction: column;
align-items: start;
}
footer .obsidian, footer p {
margin: 0 50px;
}
footer .obsidian {
border-right: unset;
margin-bottom: 20px;
}
}

349
src/app/resources/assets/normalize.css vendored Normal file
View File

@ -0,0 +1,349 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@ -1,7 +1,7 @@
extends template extends template
block blog_content block blog_content
h2(style='font-size: 36pt') Uh, oh! h2 Uh, oh!
p Looks like that post no longer exists, or the link is broken. p Looks like that post no longer exists, or the link is broken.
p p
a.button(href=named('home') + '#contact') Let Me Know a.button(href=named('home') + '#contact') Let me know.

View File

@ -1,12 +1,13 @@
extends template extends template
block blog_content block blog_content
h2(style="font-size: 36pt") Post Archive h2 Post Archive
.recent-posts .recent-posts
each year in postYears each year in postYears
h3(style="background-color: var(--c-font)") #{year} h3 #{year}
ul.plain
each post in postsByYear[year] each post in postsByYear[year]
.post-tile li
.date #{blogDate(post.date)} .secondary #{blogDate(post.date)}
a.title(href=blogUrl(post)) #{post.title} a.title(href=blogUrl(post)) #{post.title}

View File

@ -1,7 +1,7 @@
extends template extends template
block blog_content block blog_content
h2(style="font-size: 36pt") An RSS Manifesto h2 An RSS Manifesto
blockquote More than a convenience, <em>RSS is good for the web</em>. blockquote More than a convenience, <em>RSS is good for the web</em>.

View File

@ -3,10 +3,11 @@ extends template
block blog_content block blog_content
p Write-ups and musings, often technical, sometimes not. p Write-ups and musings, often technical, sometimes not.
h2(style="font-size: 36pt") Recent(ish) Posts h2 Recent(ish) Posts
.recent-posts .recent-posts
ul.plain
each post in posts each post in posts
.post-tile li
.date #{blogDate(post.date)} .secondary #{blogDate(post.date)}
a.title(href=blogUrl(post)) #{post.title} a.title(href=blogUrl(post)) #{post.title}

View File

@ -23,7 +23,7 @@ block meta
// fixme: twitter:image // fixme: twitter:image
block append style block append style
link(rel='stylesheet' href=asset('highlight/styles/' + themeRecord.highlightTheme + '.min.css')) link(rel='stylesheet' href=asset('highlight/styles/dark.min.css'))
block append script block append script
script(src=asset('highlight/highlight.min.js')) script(src=asset('highlight/highlight.min.js'))
@ -38,10 +38,9 @@ block blog_content
h2.post-title #{post.title} h2.post-title #{post.title}
p.post-byline by Garrett Mills on #{blogDate(post.date)} p.post-byline by Garrett Mills on #{blogDate(post.date)}
.post-tags .post-tags
ul p.button-links
each tag in post.tags each tag in post.tags
li a.button-small.secondary(href=named('blog')+'/tag/'+tag) ##{tag}
a.button(href=named('blog')+'/tag/'+tag) ##{tag}
.post-content !{renderedPost} .post-content !{renderedPost}
@ -51,6 +50,7 @@ block blog_content
.section-border-inner-2 .section-border-inner-2
.comments-container .comments-container
hr
h1 Comments h1 Comments
p Thanks for reading! I'd love to hear your thoughts and questions. p Thanks for reading! I'd love to hear your thoughts and questions.
p My blog uses an email-based comments system: <a href="mailto:#{chorusAddress}">submit a comment</a> p My blog uses an email-based comments system: <a href="mailto:#{chorusAddress}">submit a comment</a>

View File

@ -1,10 +1,11 @@
extends template extends template
block blog_content block blog_content
h2(style="font-size: 36pt; font-family: Lora, serif") ##{tag} h2 Posts w/ Tag: #{tag}
.recent-posts .recent-posts
ul.plain
each post in posts each post in posts
.post-tile li
.date #{blogDate(post.date)} .secondary #{blogDate(post.date)}
a.title(href=blogUrl(post)) #{post.title} a.title(href=blogUrl(post)) #{post.title}

View File

@ -1,9 +1,9 @@
extends template extends template
block blog_content block blog_content
h2(style="font-size: 36pt") Tags h2 Tags
.post-tags.listed .post-tags.listed
ul ul
each tag in tags each tag in tags
li <a class="button" href="#{named('blog')}/tag/#{tag}">##{tag}</a> (#{counts[tag]}) li <a class="button" href="#{named('blog')}/tag/#{tag}">##{tag}</a>&nbsp;&nbsp;&nbsp;<span style="color: var(--color-2); font-variant-ligatures: none;">(#{counts[tag]})</a>

View File

@ -1,4 +1,4 @@
extends ../template_70s extends ../template_bam
block append style block append style
link(rel='alternate' href=named('blog:atom') title="Garrett's Blog" type='application/atom+xml') link(rel='alternate' href=named('blog:atom') title="Garrett's Blog" type='application/atom+xml')
@ -8,27 +8,12 @@ block append style
block content block content
.container#top .container#top
.inner .inner
if !post
.hero
// FIXME: make this the default?
.hero-box(style='margin-bottom: 0')
h1 Garrett Mills
section#blog-header section#blog-header
if !post h1 Garrett's Blog
h2 Blog p.button-links
else
h2(style='font-size: 26pt; padding-top: 20px') Garrett's Blog
ul.inline-nav
li
a.button(href=named('blog')) Home a.button(href=named('blog')) Home
li
a.button(href=named('home')) Main Site
li
a.button(href=named('blog:archive')) Archive a.button(href=named('blog:archive')) Archive
li
a.button(href=named('blog:tags')) Tags a.button(href=named('blog:tags')) Tags
li
a.button(href=named('blog:feeds')) Feeds a.button(href=named('blog:feeds')) Feeds
block blog_content block blog_content

View File

@ -1,17 +1,14 @@
extends template_70s extends template_bam
block content block content
.container#top .container#top
.inner .inner
.hero
.hero-box
h1 Garrett Mills
section#recent section#recent
h2 posts & updates h1 Notes
.button-links p.button-links
a.button-small(href='/feed/rss.xml') rss a.button-small(href='/feed/rss.xml') RSS
a.button-small(href='/feed/atom.xml') atom a.button-small(href='/feed/atom.xml') Atom
a.button-small(href='/feed/json.json') json a.button-small(href='/feed/json.json') JSON
each item in feedPosts each item in feedPosts
.feed-item .feed-item
.feed-category(id=item.feedPostId) .feed-category(id=item.feedPostId)
@ -21,4 +18,4 @@ block content
p.text (draft) p.text (draft)
p.text !{item.body} p.text !{item.body}
.bottom .bottom
.stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a>&nbsp;&nbsp;|&nbsp;&nbsp;#{item.postedAt.toLocaleString()} .stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a>&nbsp;&nbsp;|&nbsp;&nbsp;#{item.postedAt.toLocaleDateString()}

View File

@ -1,18 +1,15 @@
extends template_70s extends template_bam
block content block content
.container#top .container#top
.inner .inner
.hero
.hero-box
h1 Garrett Mills
section#header section#header
h1 Bookmarks h1 Bookmarks
p Below is a random collection of links to other sites that I found interesting. p Below is a random collection of links to other sites that I found interesting.
p I think publishing personal bookmark lists is a great way to better connect and explore the smaller side of the Internet. p I think publishing personal bookmark lists is a great way to better connect and explore the smaller side of the Internet.
p This list is also available in the following formats: p This list is also available in the following formats:
ul ul
li <a href="#{route('/links.mark.md')}"><img class="inline-markmark-logo" src="#{asset(textIsLight ? 'markmark-light.svg' : 'markmark-dark.svg')}"/> MarkMark</a> (<a href="#{route('/markmark')}">learn more</a>). li <a href="#{route('/links.mark.md')}"><img class="inline-markmark-logo" src="#{asset('markmark-light.svg')}"/> MarkMark</a> (<a href="#{route('/markmark')}">learn more</a>).
li <a href="#{named('links:rss')}">RSS</a> li <a href="#{named('links:rss')}">RSS</a>
li <a href="#{named('links:atom')}">Atom</a> li <a href="#{named('links:atom')}">Atom</a>
li <a href="#{named('links:json')}">JSON</a> li <a href="#{named('links:json')}">JSON</a>
@ -22,15 +19,22 @@ block append style
style. style.
section { margin-top: 0; padding-bottom: 0; } section { margin-top: 0; padding-bottom: 0; }
h1 { font-size: 40pt; } #links h1:before {
h2 { font-size: 30pt; } content: unset;
h3 { font-size: 24pt; } }
h4 { font-size: 18pt; }
#links h1 { font-size: 25pt; margin-top: 60px; } #links h1 p {
font-size: 0.8em;
}
*:not(li) > ul > li { margin-top: 20px; } #links li p {
font-size: 1em;
}
#links .link-url a {
font-size: 0.7em;
}
.markmark.link-tags { margin-left: 30px; } .markmark.link-tags { margin-left: 30px; }
.markmark.link-tag { color: var(--c-font-muted); font-size: 0.8em; } .markmark.link-tag { color: var(--color-2); font-size: 0.8em; }
.markmark.link-date { margin-left: 30px; color: var(--c-font-muted); font-size: 0.8em; } .markmark.link-date { margin-left: 30px; color: var(--color-2); font-size: 0.8em; }

View File

@ -1,9 +1,9 @@
extends template_70s extends template_bam
block content block content
.container#home .container#home
.inner(style='max-width: 40vw; margin-top: 50px') .inner(style='max-width: 40vw; margin-top: 50px')
h3 !{title} h1 !{title}
p !{message} p !{message}
if buttonAction if buttonAction
form(action=buttonAction method=(buttonMethod || 'get')) form(action=buttonAction method=(buttonMethod || 'get'))

View File

@ -1,4 +1,4 @@
extends template_70s extends template_bam
block append style block append style
link(rel='stylesheet' data-name='vs/editor/editor.main' href=asset('monaco/package/min/vs/editor/editor.main.css')) link(rel='stylesheet' data-name='vs/editor/editor.main' href=asset('monaco/package/min/vs/editor/editor.main.css'))
@ -7,8 +7,8 @@ block content
.container#home .container#home
if needsAccessKey if needsAccessKey
.inner .inner
h2 Snippet h1 Snippet
h3 #{snippet.slug} h2 #{snippet.slug}
p This snippet is protected by an access key. Please enter it to view: p This snippet is protected by an access key. Please enter it to view:
form(method='get') form(method='get')
.form-group .form-group
@ -16,8 +16,8 @@ block content
button View button View
else if needsConfirm else if needsConfirm
.inner .inner
h2 Snippet h1 Snippet
h3 #{snippet.slug} h2 #{snippet.slug}
p This snippet is single-access only. Once you view it, it will be permanently deleted. Continue? p This snippet is single-access only. Once you view it, it will be permanently deleted. Continue?
form(method='get') form(method='get')
if accessKey if accessKey
@ -26,9 +26,9 @@ block content
button Continue button Continue
else else
.inner(style='width: 100%') .inner(style='width: 100%')
h2 Snippet h1 Snippet
h3 #{snippet.slug} h2 #{snippet.slug}
#monaco-container(style="width: 100%; height: 100%") #monaco-container(style="width: 100%; height: 90vh")
block append script block append script
script. script.

View File

@ -0,0 +1,25 @@
extends template_bam
block content
section
h1 Header 1
p Lorem ipsum dolor sit amet. Now is the time for all good men to come to the aid of their party before the quick brown fox jumps over the lazy little puppy dog.
h2 Header 2
p Lorem ipsum dolor sit amet. Now is the time for all good men to come to the aid of their party before the quick brown fox jumps over the lazy little puppy dog.
h3 Header 3
p Lorem ipsum dolor sit amet. Now is the time for all good men to come to the aid of their party before the quick brown fox jumps over the lazy little puppy dog.
h4 Header 4
p Lorem ipsum dolor sit amet. Now is the time for all good men to come to the aid of their party before the quick brown fox jumps over the lazy little puppy dog.
h5 Header 5
p Lorem ipsum dolor sit amet. Now is the time for all good men to come to the aid of their party before the quick brown fox jumps over the lazy little puppy dog.
h6 Header 6
p Lorem ipsum dolor sit amet. Now is the time for all good men to come to the aid of their party before the quick brown fox jumps over the lazy little puppy dog.
ul
li Item 1
li Item 2
li Item 3
ol
li Item 3
li Item 2
li Item 1
p Lorem <a href="#">ipsum dolor sit</a> amet. Now is the time for all good men to come to the aid of their party before the quick brown fox jumps over the lazy little puppy dog.

View File

@ -1,57 +1,30 @@
extends template_70s extends template_bam
block content block content
.container#top
.inner
.hero
.hero-box
h1 Garrett Mills
section#technical section#technical
h2(style="font-size: 48pt") technical details for nerds h1 Technical Details for Nerds
.fira-p
p This page contains a smattering of technical information that I think some people might find interesting, but not enough people for it to be included on the main page. p This page contains a smattering of technical information that I think some people might find interesting, but not enough people for it to be included on the main page.
h3 Website Theme h2 Website Theme
p I was going for a retro/70s aesthetic for this site. The CSS theme is fully-parameterized over a set of color variables. p I had a lot of fun with the <a href="https://static.garrettmills.dev/assets/70s.png" target="_blank">previous 70s-inspired design</a>. I think visual design can do a lot to communicate a <i>vibe</i>.
p The first time you visit the homepage, the server randomly selects a theme for your browser to use across the site. p The current vibe feels like <i>less</i>. Less visual complexity, less scope, less techno-optimism, and perhaps a <i>bit</i> less pretentiousness.
p I'm using a combination of <a href="https://creativemarket.com/storytypefont/6695985-The-Wobliy-Retro-Serif-Font" target="_blank">The Wobliy</a> as a display font and <a href="http://cyreal.org/fonts/lora" target="_blank">Lora</a> as a body font. p This design is my attempt to return to a <a href="https://motherfuckingwebsite.com/" target="_blank">simpler</a> <a href="http://bettermotherfuckingwebsite.com/" target="_blank">design</a> <a href="https://thebestmotherfucking.website/" target="_blank">language</a> that better conveys my current mood, with really (<i>really</i>) good fonts on a solid background.
p For the curious, you can change the theme using the buttons below:
ul.theme-buttons h3 Fonts
each key in themeKeys p For the new logo, I'm using the <a href="https://www.typography.com/fonts/obsidian/overview" target="_blank">Obsidian Roman</a> display font by Hoefler & Co. I don't love to support Monotype, but Obsidian is just <i>so good</i>.
li p The body font is comprised of a few variants of <a href="https://displaay.net/typeface/reckless-collection/reckless/" target="_blank">Reckless</a> by Displaay.
a.button(href=`../technical?theme=${key}`) #{config(`app.colors.${key}.displayName`)}#{key === themeName ? ' (current)' : ''} p For code/monospace text, I'm using my go-to font for code, <a href="https://www.jetbrains.com/lp/mono/" target="_blank">JetBrains Mono</a>. It's probably the single font I spend the most time looking at.
br
br
p You can also change the theme using the little-known <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Alternative_style_sheets" target="_blank">alternate stylesheets</a> feature by going to View > Page Style in your browser. h2#license Source Code & Licensing
a(href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank')
h4 The Stars img(style='margin-top: 15px' src=asset('cc-by-nc-sa.png'))
p The homepage of this site features a constellation of 6 translucent stars which appears at the top of the page.
p The positions of the stars are randomly generated based on the dimensions of the page. To discourage clustered/skewed constellations, the following criteria are used:
ol
li Standard deviation of the x-axis offset
li Standard deviation of the y-axis offset
li Standard deviation of the centroid distance
p Because it's interesting, you can view the # of generation attempts, as well as the aforementioned criteria in the footer of the homepage.
h3#license Source Code & Licensing
a(href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank' style='margin-top: 15px')
img(src=asset('cc-by-nc-sa.png'))
p This website, its source code, and its contents are licensed under the terms of the Creative Commons BY-NC-SA 4.0 license. Learn more <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">here</a>. p This website, its source code, and its contents are licensed under the terms of the Creative Commons BY-NC-SA 4.0 license. Learn more <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">here</a>.
p The source code for this site is available openly under the terms of the aforementioned license. You can view it <a href="https://code.garrettmills.dev/garrettmills/www" target="_blank">here</a>. p The source code for this site is available openly under the terms of the aforementioned license. You can view it <a href="https://code.garrettmills.dev/garrettmills/www" target="_blank">here</a>.
h3 Framework h2 Framework
p This site is built with Extollo, my free & libre application framework. You can learn more about Extollo <a href="https://extollo.garrettmills.dev" target="_blank">here</a>. p This site is built with Extollo, my free & libre application framework. You can learn more about Extollo <a href="https://extollo.garrettmills.dev" target="_blank">here</a>.
if false h2 Analytics
h3 The Background Animation
p The cascading code animation you see on some pages of this site is generated using real code pulled from my personal repositories.
p When you access a page, the backend for this site calls an API endpoint on my <a href="https://code.garrettmills.dev/garrettmills" target="_blank">Gitea server</a> to load a code snippet from a random file.
p Then, that snippet is converted to a double-layer SVG. The animation comes from automatically generating a series of CSS animation steps using <code>nth-child(...)</code> CSS calls on the various text-stroke lines.
p (Try "view page source" on the main page to see an example.)
h3 Analytics
p This site, and some of the others in my <code>*.garrettmills.dev</code> platform record pseudo-anonymous page-view information. In particular, it stores: p This site, and some of the others in my <code>*.garrettmills.dev</code> platform record pseudo-anonymous page-view information. In particular, it stores:
ul ul
li Remote hostname li Remote hostname
@ -63,12 +36,12 @@ block content
p Collecting the above information helps me gauge the rate-of-access of various pages on my platform over time. p Collecting the above information helps me gauge the rate-of-access of various pages on my platform over time.
p My analytics system is home-grown, intentionally, to record <em>just</em> the information I wanted to use. So, I avoid collecting any additional information than is necessary for my purposes. p My analytics system is home-grown, intentionally, to record <em>just</em> the information I wanted to use. So, I avoid collecting any additional information than is necessary for my purposes.
h4 Opting-Out h3 Opting-Out
p <small>Status: !{isOptOut ? 'You have opted-out of page view recording' : 'Will record page views'}</small> p <small>Status: !{isOptOut ? 'You have opted-out of page view recording.' : 'Will record page views.'}</small>
p Even though my analytics-system is geared towards individual privacy and minimal obtrusiveness, you can still opt-out to avoid having your page views recorded for <code>*.garrettmills.dev</code> sites. p Even though my analytics-system is geared towards individual privacy and minimal obtrusiveness, you can still opt-out to avoid having your page views recorded for <code>*.garrettmills.dev</code> sites.
p To do so, click <a href="#{named('opt-out-prompt')}">here</a>. p To do so, click <a href="#{named('opt-out-prompt')}">here</a>.
h4 Collection Methods h3 Collection Methods
p It's not super relevant to the privacy aspect, but for those who are curious, I record page-views two different ways: p It's not super relevant to the privacy aspect, but for those who are curious, I record page-views two different ways:
ul ul
li On first-party <code>garrettmills.dev</code> pages, the page-views are recorded on the back-end when the page is served. li On first-party <code>garrettmills.dev</code> pages, the page-views are recorded on the back-end when the page is served.
@ -76,4 +49,7 @@ block content
p When you opt-out of page-view recording, this site sets a CORS-compatible cookie <code>analytics.optout</code> in your browser. If the analytics endpoints detect this cookie, the page view will not be recorded. p When you opt-out of page-view recording, this site sets a CORS-compatible cookie <code>analytics.optout</code> in your browser. If the analytics endpoints detect this cookie, the page view will not be recorded.
section#login section#login
br
br
br
a(href=named('@auth:coreid:login')) Login a(href=named('@auth:coreid:login')) Login

View File

@ -80,7 +80,7 @@ body
a(href='/#work') what I'm working on a(href='/#work') what I'm working on
li li
a(href='/#contact') get in touch a(href='/#contact') get in touch
li //li
a(href='/#recent') latest updates a(href='/#recent') latest updates
.col .col
ul.links ul.links

View File

@ -0,0 +1,106 @@
doctype html
head
style.
body {
--background: #111;
--background-2: #252525;
--background-3: #444;
--color: #fffbe3;
--color-2: #d0c895;
background: var(--background);
color: var(--color);
font-family: "Reckless", serif;
margin: 0;
padding: 0;
overflow-x: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
.wrapper {
max-width: 800px;
width: calc(100% - 40px);
padding-left: 20px;
padding-right: 20px;
margin-bottom: 100px;
min-height: 100vh;
}
block meta
meta(charset='utf-8')
meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')
meta(http-equiv='x-ua-compatible' content='ie=edge')
meta(name='description' content='Hi, there! My name is Garrett. I am a computer scientist, software engineer, and speaker.')
meta(name='keywords' content='garrett mills glmdev developer speaker flitter extollo student')
meta(name='author' content=config('app.name', 'Garrett Mills'))
meta(name='robots' content='index, follow')
block title
if title
title #{title} | #{config('app.name', 'Garrett Mills')}
else
title #{config('app.name', 'Garrett Mills')}
block style
link(rel='stylesheet' href=asset('normalize.css'))
link(rel='stylesheet' href=asset('main-bam.css'))
//script.
// window.glmdev = window.glmdev || {}
// window.glmdev.themeStats = window.glmdev.themeStats || []
// window.glmdev.themeStats.push('Default Theme: !{themeDisplayName}')
//link(rel='stylesheet' href=asset('main-70s.css'))
//link(rel='stylesheet' href=`/theme/${themeName}.css` title=themeDisplayName)
//if themeStylesheets
// each sheet in themeStylesheets
// link(rel='alternate stylesheet' href=sheet.url title=sheet.displayName)
link(rel='author' href='/humans.txt')
//link(rel="alternate" href="/links.mark.md" title="Garrett Mills - My Bookmarks" type="text/markdown;variant=markmark")
//link(rel="alternate" href="/links" title="Garrett Mills - My Bookmarks" type="text/html")
//link(rel="alternate" href="/links/atom.xml" title="Garrett's Bookmarks (Atom)" type="application/atom+xml")
//link(rel="alternate" href="/links/rss2.xml" title="Garrett's Bookmarks (RSS)" type="application/rss+xml")
//link(rel="alternate" href="/links/json.json" title="Garrett's Bookmarks (JSON)" type="application/feed+json")
//link(rel="alternate" href="/feed/atom.xml" title="Garrett Mills - Posts & Updates (Atom)" type="application/atom+xml")
//link(rel="alternate" href="/feed/rss.xml" title="Garrett Mills - Posts & Updates (RSS)" type="application/rss+xml")
//link(rel="alternate" href="/feed/json.json" title="Garrett Mills - Posts & Updates (JSON)" type="application/feed+json")
//link(rel="alternate" href="/blog/atom.xml" title="Garrett's Blog (Atom)" type="application/atom+xml")
//link(rel="alternate" href="/blog/rss2.xml" title="Garrett's Blog (RSS)" type="application/rss+xml")
//link(rel="alternate" href="/blog/json.json" title="Garrett's Blog (JSON)" type="application/feed+json")
//link(rel='apple-touch-icon' sizes='180x180' href=asset('favicon/apple-touch-icon.png'))
//link(rel='manifest' href=asset('favicon/site.webmanifest'))
//link(rel='icon' type='image/png' sizes='32x32' href=asset('favicon/favicon-32x32.png'))
//link(rel='icon' type='image/png' sizes='16x16' href=asset('favicon/favicon-16x16.png'))
//link(rel='shortcut icon' href=asset('favicon/favicon.ico'))
body
.wrapper
header.hero glm.
nav
ul
li
a(href='/') Home
//li
a(href='/timeline') Timeline
li
a(href='/blog') Blog
li
a(href='/feed') Notes
li
a(href='/links') Bookmarks <img class="inline-markmark-logo" src="#{asset('markmark-light.svg')}"/>
block content
footer
.obsidian glm.
p &copy 2015-#{(new Date).getFullYear()} Garrett Mills — <a href="/technical">More Info</a>
if user()
p &nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;
a(href="/dash") Dashboard
block script

View File

@ -1,95 +1,18 @@
extends template_70s extends template_bam
block content block content
.container#home
.inner
section.hero.full-height
.stars
each val in [1, 2, 3, 4, 5, 6]
svg(id=('star-' + val) width='47.581146mm' height='78.547104mm' viewbox='0 0 47.581146 78.547104' version='1.1' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg')
sodipodi:namedview#namedview7(pagecolor='#ffffff' bordercolor='currentColor' borderopacity='0.25' inkscape:showpageshadow='2' inkscape:pageopacity='0.0' inkscape:pagecheckerboard='0' inkscape:deskcolor='#d1d1d1' inkscape:document-units='mm' showgrid='false')
defs#defs2
g#layer1(inkscape:label='Layer 1' inkscape:groupmode='layer' transform='translate(-81.41733,-109.42827)')
path#path1153(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105,112.92827 c 0,0 1.66434,35.57173 -20.082663,35.57173' inkscape:export-filename='star.svg' inkscape:export-xdpi='96' inkscape:export-ydpi='96')
path#path1153-3(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105.41582,112.92827 c 0,0 -1.66434,35.57173 20.08267,35.57173')
path#path1153-6(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105,184.47538 c 0,0 1.66434,-35.57173 -20.082665,-35.57173')
path#path1153-3-7(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105.41582,184.47538 c 0,0 -1.66434,-35.57173 20.08267,-35.57173')
.hero-box
h1 Garrett Mills
p Software engineer, computer scientist, and nerd.
//p Hi, there. My name is Garrett, and I'm a computer scientist, software engineer, and speaker.
.section-border
.section-border-inner-1
.section-border-inner-2
section#about section#about
h2 about me img(style="height: 250px;" src=asset('img/01-self-portrait-bw.jpg'))
.about-container p.italic Hi, I'm Garrett.
.img p I'm a computer scientist and software engineer. Professionally, I design and build flexible, scalable software systems. Personally, I study programming languages, libre software, visual design, and the small web.
img(src="/assets/img/profile.jpg" width=300 height=300) p Other things I like: self-hosting, RSS, photography, cooking, and really good fonts.
.about p#contact ✉️&nbsp;&nbsp;&nbsp;
p Hi, I'm Garrett. Welcome to my corner of the internet.
p I'm a computer scientist and software engineer specializing in software/systems architectures and programming languages. Some of my more recent projects include <a href="https://github.com/swarmlang/swarm" target="_blank">the Swarm programming language</a>, <a href="https://extollo.garrettmills.dev" target="_blank">the Extollo framework</a>, and <a href="https://code.garrettmills.dev/Starship/CoreID" target="_blank">the CoreID SSO server</a>.
p I sometimes write <a href="#{named('blog')}">blog posts</a> and <a href="https://code.garrettmills.dev/garrettmills" target="_blank">publish code</a> from my projects.
.section-border.odd
.section-border-inner-1
.section-border-inner-2
if workItemYears && workItemYears.length
section#work
h2 what I'm working on
.timeline-group
each year in workItemYears
.work-container(class=(workItemHiddenYears.includes(year) ? 'timeline-item theme-hide' : 'timeline-item'))
.timeline-container
.timeline-year #{year}
each item in workItemGroups[year]
.work-container(class=(item.endDate ? 'timeline-item theme-hide' : 'timeline-item'))
.timeline-container
.timeline-content
.range-small #{item.rangeDisplay()}
h3 !{item.name}
p !{item.description}
.work-container
button#timeline-view-all show past work
.section-border
.section-border-inner-1
.section-border-inner-2
section#contact
h2 get in touch
.contact-container
.message
p I'd love to hear from you if you have questions or inquiries related to me or my projects. You can get in touch by text, e-mail, or using this form. I also occasionally share thoughts on my <a href="/blog">blog</a>.
p <b>E-mail:</b> <a href="mailto:shout@garrettmills.dev">shout@garrettmills.dev</a>
.form
form#contact-form(method='post' action=named('contact'))
.form-group
input#contactEmail.form-control(type='email' name='email' placeholder='E-Mail Address' required)
input#contactEEmail(type='email' name='e-mail')
.form-group
input#contactFirst.form-control(name='name' placeholder='Name' required)
.form-group
textarea.form-control#contactMessage(name='message' placeholder='Message' required rows=6)
.form-group
button Send
.section-border.odd
.section-border-inner-1
.section-border-inner-2
section#recent
h2 latest updates
each item in feedPosts
.feed-item
.feed-category(id='feedPostTag_' + item.feedPostId)
.tag #{item.tag}
span
if !item.visible
p.text (draft)
p.text !{item.body}
.bottom
.stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a>&nbsp;&nbsp;|&nbsp;&nbsp;#{item.postedAt.toLocaleString()}
.row.mt-4
.col-12.text-center
a.button(href="/feed") view all
block append script block append script
script. script.
document.getElementById('contactEEmail').style.display = 'none' // If you're reading this, you're not the problem ;)
const homeContactP = document.querySelector('#contact')
const emailAnchor = document.createElement('a')
emailAnchor.href = 'mailto:shout' + '@' + 'garrettmills.dev'
emailAnchor.innerText = 'shout' + '@' + 'garrettmills.dev'
homeContactP.appendChild(emailAnchor)

View File

@ -0,0 +1,95 @@
extends template_70s
block content
.container#home
.inner
section.hero.full-height
.stars
each val in [1, 2, 3, 4, 5, 6]
svg(id=('star-' + val) width='47.581146mm' height='78.547104mm' viewbox='0 0 47.581146 78.547104' version='1.1' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg')
sodipodi:namedview#namedview7(pagecolor='#ffffff' bordercolor='currentColor' borderopacity='0.25' inkscape:showpageshadow='2' inkscape:pageopacity='0.0' inkscape:pagecheckerboard='0' inkscape:deskcolor='#d1d1d1' inkscape:document-units='mm' showgrid='false')
defs#defs2
g#layer1(inkscape:label='Layer 1' inkscape:groupmode='layer' transform='translate(-81.41733,-109.42827)')
path#path1153(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105,112.92827 c 0,0 1.66434,35.57173 -20.082663,35.57173' inkscape:export-filename='star.svg' inkscape:export-xdpi='96' inkscape:export-ydpi='96')
path#path1153-3(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105.41582,112.92827 c 0,0 -1.66434,35.57173 20.08267,35.57173')
path#path1153-6(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105,184.47538 c 0,0 1.66434,-35.57173 -20.082665,-35.57173')
path#path1153-3-7(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105.41582,184.47538 c 0,0 -1.66434,-35.57173 20.08267,-35.57173')
.hero-box
h1 Garrett Mills
p Software engineer, computer scientist, and nerd.
//p Hi, there. My name is Garrett, and I'm a computer scientist, software engineer, and speaker.
.section-border
.section-border-inner-1
.section-border-inner-2
section#about
h2 about me
.about-container
.img
img(src="/assets/img/profile.jpg" width=300 height=300)
.about
p Hi, I'm Garrett. Welcome to my corner of the internet.
p I'm a computer scientist and software engineer specializing in software/systems architectures and programming languages. Some of my more recent projects include <a href="https://github.com/swarmlang/swarm" target="_blank">the Swarm programming language</a>, <a href="https://extollo.garrettmills.dev" target="_blank">the Extollo framework</a>, and <a href="https://code.garrettmills.dev/Starship/CoreID" target="_blank">the CoreID SSO server</a>.
p I sometimes write <a href="#{named('blog')}">blog posts</a> and <a href="https://code.garrettmills.dev/garrettmills" target="_blank">publish code</a> from my projects.
.section-border.odd
.section-border-inner-1
.section-border-inner-2
if workItemYears && workItemYears.length
section#work
h2 what I'm working on
.timeline-group
each year in workItemYears
.work-container(class=(workItemHiddenYears.includes(year) ? 'timeline-item theme-hide' : 'timeline-item'))
.timeline-container
.timeline-year #{year}
each item in workItemGroups[year]
.work-container(class=(item.endDate ? 'timeline-item theme-hide' : 'timeline-item'))
.timeline-container
.timeline-content
.range-small #{item.rangeDisplay()}
h3 !{item.name}
p !{item.description}
.work-container
button#timeline-view-all show past work
.section-border
.section-border-inner-1
.section-border-inner-2
section#contact
h2 get in touch
.contact-container
.message
p I'd love to hear from you if you have questions or inquiries related to me or my projects. You can get in touch by text, e-mail, or using this form. I also occasionally share thoughts on my <a href="/blog">blog</a>.
p <b>E-mail:</b> <a href="mailto:shout@garrettmills.dev">shout@garrettmills.dev</a>
.form
form#contact-form(method='post' action=named('contact'))
.form-group
input#contactEmail.form-control(type='email' name='email' placeholder='E-Mail Address' required)
input#contactEEmail(type='email' name='e-mail')
.form-group
input#contactFirst.form-control(name='name' placeholder='Name' required)
.form-group
textarea.form-control#contactMessage(name='message' placeholder='Message' required rows=6)
.form-group
button Send
.section-border.odd
.section-border-inner-1
.section-border-inner-2
section#recent
h2 latest updates
each item in feedPosts
.feed-item
.feed-category(id='feedPostTag_' + item.feedPostId)
.tag #{item.tag}
span
if !item.visible
p.text (draft)
p.text !{item.body}
.bottom
.stamp <a href="#{named('feed')}##{item.feedPostId}" class="feed-edit-button">permalink</a>&nbsp;&nbsp;|&nbsp;&nbsp;#{item.postedAt.toLocaleString()}
.row.mt-4
.col-12.text-center
a.button(href="/feed") view all
block append script
script.
document.getElementById('contactEEmail').style.display = 'none'