Author: Pedro Lucas Porcellis <porcellis@eletrotupi.com>
design: rework listing of posts and overall post layout
assets/style.scss | 341 +++++++++++++++++++++----- i18n/en.toml | 12 i18n/pt-br.toml | 12 layouts/_markup/render-codeblock.html | 17 + layouts/_partials/dispatch-entry.html | 16 + layouts/_partials/dispatch-numbers.html | 36 ++ layouts/_partials/head.html | 5 layouts/_partials/masthead.html | 4 layouts/_partials/time.html | 4 layouts/home.html | 21 - layouts/list.html | 25 - layouts/single.html | 57 +++-
diff --git a/assets/style.scss b/assets/style.scss index 9ea13c3cb1c28684d7fc4b77ec1cd89c08c49441..1dd092ba8f26da748128c91531702191533b4333 100644 --- a/assets/style.scss +++ b/assets/style.scss @@ -76,6 +76,11 @@ font-size: 1.125rem; line-height: 1.8; } +::selection { + background-color: var(--accent-signal); + color: var(--bg-canvas); +} + .page-content { max-width: var(--measure); margin: 0 auto; @@ -87,10 +92,6 @@ text-decoration-thickness: 1px; text-underline-offset: 0.15em; } -a:hover { - color: var(--text-primary); -} - a:focus-visible, button:focus-visible { outline: 2px solid var(--accent-signal); @@ -103,26 +104,21 @@ /* ------------------------------------------------------------------------- */ .site-header { position: relative; - text-align: center; } .masthead__link { display: inline-flex; - flex-direction: column; align-items: center; - gap: 0.75rem; + gap: 0.875rem; color: inherit; text-decoration: none; -} - -.masthead__link:hover { - color: inherit; } .avatar { - width: 2.625rem; - height: 2.625rem; + width: 2.25rem; + height: 2.25rem; border-radius: 100%; + flex-shrink: 0; } .masthead__title { @@ -135,9 +131,10 @@ letter-spacing: -0.02em; color: var(--text-primary); } +.byline, .bio { - max-width: 34rem; - margin: 1.25rem auto 0; + max-width: 40rem; + margin: 1rem 0 0; font-size: 1rem; line-height: 1.7; color: var(--text-muted); @@ -177,7 +174,7 @@ } } /* ------------------------------------------------------------------------- */ -/* Dividers */ +/* Dividers, languages, translations */ /* ------------------------------------------------------------------------- */ .divisor { @@ -185,10 +182,6 @@ height: 0; margin: 2rem 0; border-top: 1px dashed var(--border-dashed); } - -/* ------------------------------------------------------------------------- */ -/* Index */ -/* ------------------------------------------------------------------------- */ .languages, .translations { @@ -198,7 +191,6 @@ font-size: 0.75rem; line-height: 1.4; text-transform: uppercase; letter-spacing: 0.08em; - text-align: center; color: var(--text-muted); } @@ -213,7 +205,6 @@ .languages ul, .translations ul { display: flex; flex-wrap: wrap; - justify-content: center; gap: 1rem; margin: 0; padding: 0; @@ -225,62 +216,98 @@ .translations a { text-decoration: none; } -.article-list { +/* ------------------------------------------------------------------------- */ +/* Listing */ +/* ------------------------------------------------------------------------- */ + +.list-title { + margin: 0 0 3.5rem; + font-family: var(--font-mono); + font-size: clamp(1.5rem, 2vw, 1.75rem); + font-weight: 600; + line-height: 1.2; + letter-spacing: -0.02em; + color: var(--text-primary); +} + +.dispatch-list { margin-top: 3.5rem; } -.article + .article { +.dispatch + .dispatch { margin-top: 3.5rem; + padding-top: 3.5rem; + border-top: 1px dashed var(--border-dashed); } -.article-title { - text-align: center; +.dispatch__meta, +.post-meta { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + font-family: var(--font-mono); + font-size: 0.75rem; + font-weight: 500; + line-height: 1.4; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); } -.article-title a { - text-decoration: none; +.dispatch__no, +.post-meta__no { + color: var(--accent-signal); + white-space: nowrap; } -.article-title h2 { - margin: 0; +.dispatch__title { + margin: 0.75rem 0 0; font-family: var(--font-mono); font-size: clamp(1.25rem, 1.6vw, 1.375rem); font-weight: 600; line-height: 1.3; +} + +.dispatch__title a { color: var(--text-primary); + text-decoration: none; } -.article-date, -.inner-article-date { - font-family: var(--font-mono); - font-size: 0.75rem; - font-weight: 500; - line-height: 1.4; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--text-muted); +.dispatch__title a:hover { + color: var(--accent-signal); } -.article-snippet { +.dispatch__blurb { margin-top: 0.875rem; font-size: 1.0625rem; line-height: 1.75; } -.article-snippet p { +.dispatch__blurb p { margin: 0; } +.dispatch__link { + display: inline-block; + margin-top: 1rem; + font-family: var(--font-mono); + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.08em; + text-decoration: none; +} + /* ------------------------------------------------------------------------- */ -/* Article */ +/* Post */ /* ------------------------------------------------------------------------- */ -.inner-article-title { - margin-top: 5rem; - text-align: center; +.post-header { + margin-top: 3.5rem; } -.inner-article-title h1 { +.post-title { margin: 0.75rem 0 0; font-family: var(--font-mono); font-size: clamp(2.25rem, 3vw, 2.75rem); @@ -290,24 +317,41 @@ letter-spacing: -0.02em; color: var(--text-primary); } -.lede { - margin: 1rem 0 0; - font-size: clamp(1.25rem, 1.6vw, 1.375rem); - font-style: italic; +.filed-under { + margin-top: 1.25rem; + font-family: var(--font-mono); + font-size: 0.75rem; line-height: 1.6; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); } -article { +.filed-under a { + color: var(--text-primary); + text-decoration: underline; + text-decoration-color: var(--accent-signal); + text-decoration-thickness: 2px; + text-underline-offset: 3px; +} + +.filed-under a:hover { + color: var(--accent-signal); +} + +.post-body { margin-top: 3.5rem; + counter-reset: section; font-size: 1.125rem; line-height: 1.8; + color: var(--text-secondary); } -article p { +.post-body p { margin: 0 0 1.5rem; } -article h2 { +.post-body h2 { margin: 3rem 0 1rem; font-family: var(--font-mono); font-size: 0.875rem; @@ -318,7 +362,13 @@ letter-spacing: 0.1em; color: var(--text-primary); } -article h3 { +.post-body h2::before { + counter-increment: section; + content: counter(section, decimal-leading-zero) " // "; + color: var(--accent-signal); +} + +.post-body h3 { margin: 2rem 0 0.75rem; font-family: var(--font-mono); font-size: 0.9375rem; @@ -327,43 +377,45 @@ line-height: 1.4; color: var(--text-primary); } -article ul, -article ol { +.post-body ul, +.post-body ol { margin: 0 0 1.5rem; padding-left: 1.5rem; } -article li { +.post-body li { margin-bottom: 0.5rem; } -article strong { +.post-body strong { font-weight: 600; color: var(--text-primary); } -article blockquote { - margin: 2rem 0; - padding-left: 1.5rem; - border-left: 2px solid var(--accent-signal); +.post-body blockquote { + margin: 2.5rem 0; + padding: 0.25rem 0 0.25rem 1.5rem; + border-left: 4px solid var(--accent-signal); + font-size: 1.25rem; font-style: italic; - color: var(--text-muted); + line-height: 1.5; + color: var(--text-primary); } -article img { +.post-body img { max-width: 100%; height: auto; } -article hr { +.post-body hr { margin: 3rem 0; border: 0; border-top: 1px dashed var(--border-dashed); } -article code, -article kbd, -article samp { +.post-body code, +.post-body kbd, +.post-body samp { padding: 0.1em 0.35em; border-radius: 0.2rem; background-color: var(--bg-surface); @@ -371,13 +423,17 @@ font-family: var(--font-mono); font-size: 0.875em; } -article pre code { +.post-body pre code { padding: 0; background: none; font-size: inherit; } -.highlight { +/* ------------------------------------------------------------------------- */ +/* Code blocks */ +/* ------------------------------------------------------------------------- */ + +.codeblock { margin: 2rem 0; border: 1px solid var(--border-subtle); border-radius: 0.375rem; @@ -385,6 +441,44 @@ background-color: var(--bg-surface); overflow: hidden; } +.codeblock__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.625rem 1rem; + border-bottom: 1px solid var(--border-subtle); + background-color: var(--bg-code-header); + font-family: var(--font-mono); + font-size: 0.75rem; + line-height: 1.4; + color: var(--text-muted); +} + +.codeblock__label { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 600; + color: var(--text-primary); +} + +.codeblock__dot { + display: inline-block; + width: 0.625rem; + height: 0.625rem; + border-radius: 100%; + background-color: var(--accent-signal); + flex-shrink: 0; +} + +.codeblock .highlight { + margin: 0; + border: 0; + border-radius: 0; + background: none; +} + .chroma { margin: 0; padding: 1.25rem; @@ -407,23 +501,120 @@ counter-increment: line; content: counter(line); } -.footnotes { - margin-top: 3rem; +/* ------------------------------------------------------------------------- */ +/* Footnotes */ +/* ------------------------------------------------------------------------- */ + +.post-body .footnotes { + margin-top: 4rem; font-size: 0.875rem; line-height: 1.6; color: var(--text-muted); } -.footnotes hr { +.post-body .footnotes hr { width: 4rem; - margin: 0 0 1.5rem; + margin: 0 0 1.25rem; border: 0; border-top: 2px solid var(--text-primary); } +.post-body .footnotes ol { + margin: 0; + padding-left: 1.25rem; + list-style: decimal; +} + +.post-body .footnotes ol::before { + display: block; + margin-bottom: 1rem; + content: "── " var(--footnote-label, "Footnotes") " ──"; + font-family: var(--font-mono); + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-muted); +} + +.post-body .footnotes li { + margin-bottom: 0.75rem; +} + +.post-body .footnotes li::marker { + color: var(--accent-signal); + font-family: var(--font-mono); + font-weight: 700; +} + +.post-body .footnotes p { + margin: 0; + font-size: inherit; + line-height: inherit; +} + +.footnote-ref { + padding: 0 0.1em; + font-family: var(--font-mono); + font-size: 0.8em; + font-weight: 700; + text-decoration: none; +} + +.footnote-ref::before { + content: "["; +} + +.footnote-ref::after { + content: "]"; +} + +.footnote-backref { + margin-left: 0.35rem; + font-family: var(--font-mono); + text-decoration: none; +} + +/* ------------------------------------------------------------------------- */ +/* Dispatch navigation (previous / next) */ +/* ------------------------------------------------------------------------- */ + +.dispatch-nav { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1.5rem; + margin-top: 4rem; + padding-top: 2rem; + border-top: 1px dashed var(--border-dashed); + font-family: var(--font-mono); + font-size: 0.75rem; + line-height: 1.5; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-muted); +} + +.dispatch-nav a { + color: inherit; + text-decoration: none; +} + +.dispatch-nav a:hover { + color: var(--accent-signal); +} + +.dispatch-nav__no { + color: var(--accent-signal); + white-space: nowrap; +} + +/* ------------------------------------------------------------------------- */ +/* Footer */ +/* ------------------------------------------------------------------------- */ + .article-footer { margin-top: 4rem; - text-align: center; } .article-footer__label { diff --git a/i18n/en.toml b/i18n/en.toml index 5ee0474166c0783d9001d8aab15eb076459c4834..340c6a7b03ddbf8c2ecebb3e61385e30a6d50c2f 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -20,6 +20,18 @@ [aboutMe] other = "About me" [toggleTheme] other = "Toggle color scheme" +[logTitle] +other = "Field Notes & Observations" +[byline] +other = "By Pedro Lucas Porcellis -- a brazilian programmer. When I'm not working on FOSS, gardening, baking bread, I'll jot down something here." +[readDispatch] +other = "Read dispatch" +[filedUnder] +other = "Filed under" +[footnotes] +other = "Footnotes & Citations" +[dispatchNav] +other = "Dispatch navigation" [footer] other = """ Comments, questions? Send an email to \ diff --git a/i18n/pt-br.toml b/i18n/pt-br.toml index 60fd87b22929f8f38f13ebc56f20281bbed52260..27c8680da54d8127c534be6fe2abcc58e75551f9 100644 --- a/i18n/pt-br.toml +++ b/i18n/pt-br.toml @@ -20,6 +20,18 @@ [aboutMe] other = "Sobre mim" [toggleTheme] other = "Alternar esquema de cores" +[logTitle] +other = "Notas de Campo & Observações" +[byline] +other = "Por Pedro Lucas Porcellis -- um programador brasileiro. Quando não estou trabalhando em software livre, cuidando do jardim ou fazendo pão, anoto alguma coisa por aqui." +[readDispatch] +other = "Ler o registro" +[filedUnder] +other = "Arquivado em" +[footnotes] +other = "Notas & Referências" +[dispatchNav] +other = "Navegação de registros" [footer] other = """ Comentários, dúvidas? Envie um email para \ diff --git a/layouts/_markup/render-codeblock.html b/layouts/_markup/render-codeblock.html new file mode 100644 index 0000000000000000000000000000000000000000..7dfc598124bdc017adc5ecabc1f022626e5ded50 --- /dev/null +++ b/layouts/_markup/render-codeblock.html @@ -0,0 +1,17 @@ +{{- /* + Code block frame: a header bar identifying the snippet, then the highlighted + code. The left label is the fence's filename attribute when present, falling + back to the language; neither is worth repeating twice, so the header carries + a single label. +*/ -}} +{{- $lang := .Type | default "text" -}} +{{- $highlighted := transform.HighlightCodeBlock . -}} +<div class="codeblock"> + <div class="codeblock__header"> + <span class="codeblock__label"> + <span class="codeblock__dot" aria-hidden="true"></span> + {{ with .Attributes.filename }}{{ . }}{{ else }}{{ $lang | upper }}{{ end }} + </span> + </div> + {{ $highlighted.Wrapped }} +</div> diff --git a/layouts/_partials/dispatch-entry.html b/layouts/_partials/dispatch-entry.html new file mode 100644 index 0000000000000000000000000000000000000000..de21558f1ca4a227ea63b9f3148b2a6f4ac25000 --- /dev/null +++ b/layouts/_partials/dispatch-entry.html @@ -0,0 +1,16 @@ +{{ $p := .page }} +{{ $no := index .nums $p.TranslationKey | default 0 }} +<article class="dispatch"> + <div class="dispatch__meta"> + <span>{{ partial "time.html" $p }} // {{ $p.WordCount }} {{ i18n "words" }}</span> + <span class="dispatch__no">#{{ printf "%03d" $no }}</span> + </div> + + <h2 class="dispatch__title"> + <a href="{{ $p.Permalink }}">{{ $p.Title }}</a> + </h2> + + <div class="dispatch__blurb">{{ $p.Summary }}</div> + + <a class="dispatch__link" href="{{ $p.Permalink }}">{{ i18n "readDispatch" }}</a> +</article> diff --git a/layouts/_partials/dispatch-numbers.html b/layouts/_partials/dispatch-numbers.html new file mode 100644 index 0000000000000000000000000000000000000000..5d8c03d378e8e0dc0f9b4915fa42ce6e09385053 --- /dev/null +++ b/layouts/_partials/dispatch-numbers.html @@ -0,0 +1,36 @@ +{{- /* + Returns a map of TranslationKey -> dispatch number. + + Numbers are derived from date order (oldest first) so no front matter is + required, and translations of the same post share a number because pages are + grouped by TranslationKey across every language. + + The result is language-independent, so callers should use partialCached with + a constant key to compute it once per build: + + {{ $nums := partialCached "dispatch-numbers.html" . "dispatch-numbers" }} + {{ $no := index $nums .TranslationKey }} +*/ -}} +{{- $pages := slice -}} +{{- range .Site.Home.AllTranslations -}} + {{- range where .Site.RegularPages "Section" "blog" -}} + {{- $pages = $pages | append . -}} + {{- end -}} +{{- end -}} + +{{- $byKey := dict -}} +{{- range $pages -}} + {{- $byKey = merge $byKey (dict .TranslationKey .) -}} +{{- end -}} + +{{- $unique := slice -}} +{{- range $key, $page := $byKey -}} + {{- $unique = $unique | append $page -}} +{{- end -}} + +{{- $nums := dict -}} +{{- range $i, $page := sort $unique "Date" -}} + {{- $nums = merge $nums (dict $page.TranslationKey (add $i 1)) -}} +{{- end -}} + +{{- return $nums -}} diff --git a/layouts/_partials/head.html b/layouts/_partials/head.html index c98f727757a715242af034915a973508dc794778..7d2985e1f0cbfac3f0080e89020a8b2fe286699b 100644 --- a/layouts/_partials/head.html +++ b/layouts/_partials/head.html @@ -18,7 +18,12 @@ {{ $style := resources.Get "style.scss" | css.Sass | resources.Minify | resources.Fingerprint }} <link rel="stylesheet" href="{{ $style.RelPermalink }}" /> <style type="text/css" media="screen"> + :root { + --footnote-label: "{{ i18n "footnotes" }}"; + } + {{ partial "css/syntax-light.css" . | safeCSS }} + .dark { {{ partial "css/syntax-dark.css" . | safeCSS }} } diff --git a/layouts/_partials/masthead.html b/layouts/_partials/masthead.html index 36f287a5b9f52b7920c87abe61850b2aa004adeb..a4ad9fac5854c7a55d72588be1e72d8650dca2c0 100644 --- a/layouts/_partials/masthead.html +++ b/layouts/_partials/masthead.html @@ -1,9 +1,9 @@ <a class="masthead__link" href="{{ .Site.BaseURL }}"> <img class="avatar" src="/avatar.jpg" alt="{{ .Site.Title }}"> {{ if .IsHome }} - <h1 class="masthead__title">{{ .Site.Title }}</h1> + <h1 class="masthead__title">{{ i18n "logTitle" }}</h1> {{ else }} - <p class="masthead__title">{{ .Site.Title }}</p> + <p class="masthead__title">{{ i18n "logTitle" }}</p> {{ end }} </a> {{ partial "theme-toggle.html" . }} diff --git a/layouts/_partials/time.html b/layouts/_partials/time.html index 86612c1b44bb1f0c7de178513bae3e9a4b347109..798b7473c2b57361a45719ce574ef588b3101f70 100644 --- a/layouts/_partials/time.html +++ b/layouts/_partials/time.html @@ -1,3 +1 @@ -<time datetime="{{ .Date.Format "2006-01-02T15:04:05Z07:00" | safeHTMLAttr }}"> - {{ .Date | time.Format ":date_long" }} -</time> +<time datetime="{{ .Date.Format "2006-01-02T15:04:05Z07:00" | safeHTMLAttr }}">{{ .Date | time.Format "2006-01-02" }}</time> diff --git a/layouts/home.html b/layouts/home.html index 79a39b6ff256bad0fee0a6a1a6db6a477781ad2b..a529f0c8ad09eb1730fc3fbf5de8f1bf6109f138 100644 --- a/layouts/home.html +++ b/layouts/home.html @@ -1,9 +1,10 @@ {{ define "main" }} +{{ $nums := partialCached "dispatch-numbers.html" . "dispatch-numbers" }} <main> <div class="page-content"> <header class="site-header"> {{ partial "masthead.html" . }} - {{ partial "bio.html" . }} + <p class="byline">{{ i18n "byline" }}</p> <div class="divisor"></div> </header> @@ -11,23 +12,9 @@{{ partial "available-lang.html" . }} </div> - <section class="article-list"> + <section class="dispatch-list"> {{ range (where .Site.RegularPages "Section" "blog") }} - <article class="article"> - <div class="article-title"> - <div class="article-date"> - {{ partial "time.html" . }} - </div> - - <a href="{{ .Permalink }}"> - <h2>{{ .Title }}</h2> - </a> - </div> - - <div class="article-snippet"> - {{ .Summary }} - </div> - </article> + {{ partial "dispatch-entry.html" (dict "page" . "nums" $nums) }} {{ end }} </section> diff --git a/layouts/list.html b/layouts/list.html index 01585717f944269b5145458f5ce565e0da549342..fb1e537db6efb1d1a366a80577cb8fe673d77226 100644 --- a/layouts/list.html +++ b/layouts/list.html @@ -1,4 +1,5 @@ {{ define "main" }} +{{ $nums := partialCached "dispatch-numbers.html" . "dispatch-numbers" }} <main> <div class="page-content"> <header class="site-header"> @@ -6,29 +7,11 @@ {{ partial "masthead.html" . }} <div class="divisor"></div> </header> - <div class="inner-article-title"> - <h1>{{ .Title }}</h1> - </div> + <h1 class="list-title">{{ .Title }}</h1> - <section class="article-list"> + <section class="dispatch-list"> {{ range .Pages }} - <article class="article"> - <div class="article-title"> - {{ if not .Date.IsZero }} - <div class="article-date"> - {{ partial "time.html" . }} - </div> - {{ end }} - - <a href="{{ .Permalink }}"> - <h2>{{ .Title }}</h2> - </a> - </div> - - {{ with .Summary }} - <div class="article-snippet">{{ . }}</div> - {{ end }} - </article> + {{ partial "dispatch-entry.html" (dict "page" . "nums" $nums) }} {{ end }} </section> diff --git a/layouts/single.html b/layouts/single.html index afebcc2c3dbeb8b6e83b0344133cf02829564f2b..6d4eff340ac9924a91e126e6c3a287b25273b685 100644 --- a/layouts/single.html +++ b/layouts/single.html @@ -1,4 +1,6 @@ {{ define "main" }} +{{ $nums := partialCached "dispatch-numbers.html" . "dispatch-numbers" }} +{{ $no := index $nums .TranslationKey | default 0 }} <main> <div class="page-content"> <header class="site-header"> @@ -6,22 +8,51 @@ {{ partial "masthead.html" . }} <div class="divisor"></div> </header> - <div class="inner-article-title"> - <div class="inner-article-date"> - {{ partial "time.html" . }} - · {{ .FuzzyWordCount }} {{ i18n "words" }} + <article> + <header class="post-header"> + <div class="post-meta"> + <span>{{ partial "time.html" . }} // {{ .WordCount }} {{ i18n "words" }}</span> + <span class="post-meta__no">#{{ printf "%03d" $no }}</span> + </div> + + <h1 class="post-title">{{ .Title }}</h1> + + {{ with .GetTerms "tags" }} + <div class="filed-under"> + {{ i18n "filedUnder" }}: + {{ range $i, $term := . }}{{ if $i }}, {{ end }}<a href="{{ $term.Permalink }}">#{{ $term.LinkTitle }}</a>{{ end }} + </div> + {{ end }} + </header> + + {{ partial "translations.html" . }} + + <div class="post-body"> + {{ .Content }} </div> - <h1>{{ .Title }}</h1> - {{ with .Params.description }} - <p class="lede">{{ . }}</p> + </article> + + {{ if or .NextInSection .PrevInSection }} + <nav class="dispatch-nav" aria-label="{{ i18n "dispatchNav" }}"> + {{ with .PrevInSection }} + <a href="{{ .Permalink }}"> + <span class="dispatch-nav__no">← #{{ printf "%03d" (index $nums .TranslationKey | default 0) }}</span> + <span>{{ .Title }}</span> + </a> + {{ else }} + <span></span> {{ end }} - </div> - {{ partial "translations.html" . }} - - <article> - {{ .Content }} - </article> + {{ with .NextInSection }} + <a href="{{ .Permalink }}"> + <span>{{ .Title }}</span> + <span class="dispatch-nav__no">#{{ printf "%03d" (index $nums .TranslationKey | default 0) }} →</span> + </a> + {{ else }} + <span></span> + {{ end }} + </nav> + {{ end }} <div class="article-footer"> <div class="divisor"></div>