Skip to content

Blog

Christ the Lord Is Risen Today

The last post walked through ASCII art, CSS grids, sparse position markup, and the Unicode fretboard glyph. For a full seasonal song, this version uses the glyph-and-position approach with build-time templating, so concise source expands into a somewhat verbose HTML page.

In G:

Try one of these 4/4 strumming patterns:

  • ↓ ↓↑ ↑↓↑
  • ↓ ↓ ↓↑ ↓↑

Christ the Lord isrisen to day-ay, a-al-le-lu-u-ia!

Sons of men andangelssay-ay, a-al-le-lu-u-ia!

Raise your joys andtry-umphs,high,a-al-le-lu-u-ia!

Sing, ye heavens, andearth re-ply-y, a-al-le-lu-u-ia!

Lives again ourglorious Ki-ing, a-al-le-lu-u-ia!

Where, O death, isnow thysti-ing?A-al-le-lu-u-ia!

Once He died oursoulstosave,a-al-le-lu-u-ia!

Where thy victory,Ograve? a-al-le-lu-u-ia!

Love's redeemingwork isdone, a-al-le-lu-u-ia!

Fought the fight, thebattlewon-on, a-al-le-lu-u-ia!

Death in vain forbidsHimrise,a-al-le-lu-u-ia!

Christ hath openedparadise, a-al-le-lu-u-ia!

For hymn background, this United Methodist Church history note is a good companion to the lead sheet.

The first line in the source looks like this:

{{
    lead_line(
        Christ_the_Lord_is='G',
        risen_to_day_2d='C',
        ay_2c_a_2d='G',
        al_2d='C',
        le_2d='C',
        lu_2d='G',
        u_2d='D7',
        ia_21='G',
    )
}}

That expands at build time into HTML like this:

<p class="lead-line">
    <span class="cell">
        <span class="chord-box" aria-label="G chord diagram" data-chord="G">
            <span data-pos="E3"></span>
            <span data-pos="A2"></span>
            <span data-pos="D0"></span>
            <span data-pos="G0"></span>
            <span data-pos="B0"></span>
            <span data-pos="e3"></span>
        </span>
        <span class="lyric">Christ the Lord is</span>
    </span>
    <span class="cell">
        <span class="chord-box" aria-label="C chord diagram" data-chord="C">
            <span data-pos="E0"></span>
            <span data-pos="A3"></span>
            <span data-pos="D2"></span>
            <span data-pos="G0"></span>
            <span data-pos="B1"></span>
            <span data-pos="e0"></span>
        </span>
        <span class="lyric">risen to day-</span>
    </span>
    <span class="cell">
        <span class="chord-box" aria-label="G chord diagram" data-chord="G">
            <span data-pos="E3"></span>
            <span data-pos="A2"></span>
            <span data-pos="D0"></span>
            <span data-pos="G0"></span>
            <span data-pos="B0"></span>
            <span data-pos="e3"></span>
        </span>
        <span class="lyric">ay, a-</span>
    </span>
    <span class="cell">
        <span class="chord-box" aria-label="C chord diagram" data-chord="C">
            <span data-pos="E0"></span>
            <span data-pos="A3"></span>
            <span data-pos="D2"></span>
            <span data-pos="G0"></span>
            <span data-pos="B1"></span>
            <span data-pos="e0"></span>
        </span>
        <span class="lyric">al-</span>
    </span>
    <span class="cell">
        <span class="chord-box" aria-label="C chord diagram" data-chord="C">
            <span data-pos="E0"></span>
            <span data-pos="A3"></span>
            <span data-pos="D2"></span>
            <span data-pos="G0"></span>
            <span data-pos="B1"></span>
            <span data-pos="e0"></span>
        </span>
        <span class="lyric">le-</span>
    </span>
    <span class="cell">
        <span class="chord-box" aria-label="G chord diagram" data-chord="G">
            <span data-pos="E3"></span>
            <span data-pos="A2"></span>
            <span data-pos="D0"></span>
            <span data-pos="G0"></span>
            <span data-pos="B0"></span>
            <span data-pos="e3"></span>
        </span>
        <span class="lyric">lu-</span>
    </span>
    <span class="cell">
        <span class="chord-box" aria-label="D7 chord diagram" data-chord="D7">
            <span data-pos="E0"></span>
            <span data-pos="A0"></span>
            <span data-pos="D0"></span>
            <span data-pos="G2"></span>
            <span data-pos="B1"></span>
            <span data-pos="e2"></span>
        </span>
        <span class="lyric">u-</span>
    </span>
    <span class="cell">
        <span class="chord-box" aria-label="G chord diagram" data-chord="G">
            <span data-pos="E3"></span>
            <span data-pos="A2"></span>
            <span data-pos="D0"></span>
            <span data-pos="G0"></span>
            <span data-pos="B0"></span>
            <span data-pos="e3"></span>
        </span>
        <span class="lyric">ia!</span>
    </span>
</p>

Happy Easter!

Fretboard Diagrams

Unicode includes an empty four-string fretboard, 𝄝, for ukulele or bass, and an empty six-string fretboard, 𝄜, for guitar. How are people supposed to compose the fretboard background and marks like ●, ◯, and ✕?

Going all the way back to ASCII art and box drawings, one approach is multiple lines: one column per string, one row per fret, top markers for muted and open strings, and filled dots where fingers land. The canonical left-to-right order for guitars is L, E, A, D, G, B, e, R.

Open C major: x32010

  E   A   D   G   B   e
  ✕           ◯       ◯
  ┌───┬───┬───┬───┬───┐
1 │   │   │   │   ●   │
  ├───┼───┼───┼───┼───┤
2 │   │   ●   │   │   │
  ├───┼───┼───┼───┼───┤
3 │   ●   │   │   │   │
  ├───┼───┼───┼───┼───┤
4 │   │   │   │   │   │
  └───┴───┴───┴───┴───┘

This needed a pre tag with a tuned line-height to reduce the vertical gaps.

<pre style="font-family: monospace; line-height: 1.275">
  E   A   D   G   B   e
  ✕           ◯       ◯
  ┌───┬───┬───┬───┬───┐
1 │   │   │   │   ●   │
  ├───┼───┼───┼───┼───┤
2 │   │   ●   │   │   │
  ├───┼───┼───┼───┼───┤
3 │   ●   │   │   │   │
  ├───┼───┼───┼───┼───┤
4 │   │   │   │   │   │
  └───┴───┴───┴───┴───┘
</pre>

A CSS grid diagram looks like this:

EADGBe 1 2 3 4
<style>
.fretboard-grid {
  display: inline-grid;
  grid-template-columns: repeat(7, 1.5rem);
  grid-template-rows: repeat(6, 1.5rem);
  margin: 1rem 0;
  font: 0.95rem/1 monospace;
  background-image:
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor);
  background-position:
    2.25rem 3rem,
    2.25rem 4.5rem,
    2.25rem 6rem,
    2.25rem 7.5rem,
    2.25rem calc(9rem - 1px),
    2.25rem 3rem,
    3.75rem 3rem,
    5.25rem 3rem,
    6.75rem 3rem,
    8.25rem 3rem,
    9.75rem 3rem;
  background-repeat: no-repeat;
  background-size:
    7.5rem 3px,
    7.5rem 1px,
    7.5rem 1px,
    7.5rem 1px,
    7.5rem 1px,
    1px 6rem,
    1px 6rem,
    1px 6rem,
    1px 6rem,
    1px 6rem,
    1px 6rem;
}

.fretboard-grid > span { display: grid; place-items: center; }
</style>

<div class="fretboard-grid" aria-label="Open C major chord diagram">
  <span></span><span>E</span><span>A</span><span>D</span><span>G</span><span>B</span><span>e</span>
  <span></span><span></span><span></span><span></span><span></span><span></span><span></span>
  <span>1</span><span></span><span></span><span></span><span></span><span></span><span></span>
  <span>2</span><span></span><span></span><span></span><span></span><span></span><span></span>
  <span>3</span><span></span><span></span><span></span><span></span><span></span><span></span>
  <span>4</span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>

A sparse data-pos version can skip the empty cells. The CSS ^= selector matches the starting string letter, and $= matches the ending row number, so A3 decodes to the A string and third fret. The string letters are supplemented with L and R for the margins, and the row numbers with H for the header row and 0 for the nut marker row, which holds and .

1 2 3 4 E A D G B e
<style>
.fretboard-pos {
  display: inline-grid;
  grid-template-columns: repeat(7, 1.5rem);
  grid-template-rows: repeat(6, 1.5rem);
  margin: 1rem 0;
  font: 0.95rem/1 monospace;
  background-image:
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor),
    linear-gradient(currentColor, currentColor);
  background-position:
    2.25rem 3rem,
    2.25rem 4.5rem,
    2.25rem 6rem,
    2.25rem 7.5rem,
    2.25rem calc(9rem - 1px),
    2.25rem 3rem,
    3.75rem 3rem,
    5.25rem 3rem,
    6.75rem 3rem,
    8.25rem 3rem,
    9.75rem 3rem;
  background-repeat: no-repeat;
  background-size:
    7.5rem 3px,
    7.5rem 1px,
    7.5rem 1px,
    7.5rem 1px,
    7.5rem 1px,
    1px 6rem,
    1px 6rem,
    1px 6rem,
    1px 6rem,
    1px 6rem,
    1px 6rem;
}

.fretboard-pos > span { display: grid; place-items: center; }
.fretboard-pos > [data-pos^='L'] { grid-column: 1; }
.fretboard-pos > [data-pos^='E'] { grid-column: 2; }
.fretboard-pos > [data-pos^='A'] { grid-column: 3; }
.fretboard-pos > [data-pos^='D'] { grid-column: 4; }
.fretboard-pos > [data-pos^='G'] { grid-column: 5; }
.fretboard-pos > [data-pos^='B'] { grid-column: 6; }
.fretboard-pos > [data-pos^='e'] { grid-column: 7; }
.fretboard-pos > [data-pos$='H'] { grid-row: 1; }
.fretboard-pos > [data-pos$='0'] { grid-row: 2; }
.fretboard-pos > [data-pos$='1'] { grid-row: 3; }
.fretboard-pos > [data-pos$='2'] { grid-row: 4; }
.fretboard-pos > [data-pos$='3'] { grid-row: 5; }
.fretboard-pos > [data-pos$='4'] { grid-row: 6; }
</style>

<div class="fretboard-pos" aria-label="Open C major chord diagram">
  <span data-pos="L1">1</span>
  <span data-pos="L2">2</span>
  <span data-pos="L3">3</span>
  <span data-pos="L4">4</span>
  <span data-pos="EH">E</span>
  <span data-pos="E0"></span>
  <span data-pos="AH">A</span>
  <span data-pos="A3"></span>
  <span data-pos="DH">D</span>
  <span data-pos="D2"></span>
  <span data-pos="GH">G</span>
  <span data-pos="G0"></span>
  <span data-pos="BH">B</span>
  <span data-pos="B1"></span>
  <span data-pos="eH">e</span>
  <span data-pos="e0"></span>
</div>

A Unicode-fretboard variation can keep the sparse data-pos markup and place marks from the Noto Music source geometry. The six-string and four-string glyphs sit on the same integer grid: a 37 unit stroke, a 135 unit gap, and 172 unit center spacing in a 1000 units-per-em font.

1 2 3 4 E A D G B e
<style>
.fretboard-glyph {
  --glyph-scale: 7rem;
  --column-origin: calc(1.2rem + var(--glyph-scale) * 137 / 2000); /* left inset, source units */
  --gap: calc(var(--glyph-scale) * 172 / 1000); /* source units */
  --row-origin: calc(0.9rem + var(--glyph-scale) * 503 / 2000); /* top inset, source units */
  margin: 1rem 0;
  width: 8.7rem; /* fret-label column plus six-string glyph */
  height: 8.8rem; /* header, open row, four frets, bottom line */
  display: inline-block;
  position: relative;
}

.fretboard-glyph::before {
  content: '\1D11C'; /* MUSICAL SYMBOL SIX-STRING FRETBOARD */
  color: currentColor;
  font: var(--glyph-scale)/1 'Noto Music', sans-serif;
  left: 1.2rem; /* left inset */
  opacity: 0.32; /* fade glyph behind the marks */
  pointer-events: none;
  position: absolute;
  top: 0.9rem; /* top inset */
  z-index: 0;
}

.fretboard-glyph > span {
  font: 0.95rem/1 monospace; /* overlay text size and leading */
  position: absolute;
  transform: translate(-50%, -50%);
  z-index: 1;
}
.fretboard-glyph > [data-pos^='L'] { left: 0.3rem; transform: translateY(-50%); /* fret-label column */ }
.fretboard-glyph > [data-pos^='E'] { left: var(--column-origin); }
.fretboard-glyph > [data-pos^='A'] { left: calc(var(--column-origin) + var(--gap)); }
.fretboard-glyph > [data-pos^='D'] { left: calc(var(--column-origin) + var(--gap) * 2); }
.fretboard-glyph > [data-pos^='G'] { left: calc(var(--column-origin) + var(--gap) * 3); }
.fretboard-glyph > [data-pos^='B'] { left: calc(var(--column-origin) + var(--gap) * 4); }
.fretboard-glyph > [data-pos^='e'] { left: calc(var(--column-origin) + var(--gap) * 5); }
.fretboard-glyph > [data-pos$='H'] { top: 0.45rem; /* top inset - header adjustment */ }
.fretboard-glyph > [data-pos$='0'] { top: 1.2rem; /* top inset + nut adjustment */ }
.fretboard-glyph > [data-pos$='1'] { top: var(--row-origin); }
.fretboard-glyph > [data-pos$='2'] { top: calc(var(--row-origin) + var(--gap)); }
.fretboard-glyph > [data-pos$='3'] { top: calc(var(--row-origin) + var(--gap) * 2); }
.fretboard-glyph > [data-pos$='4'] { top: calc(var(--row-origin) + var(--gap) * 3); }
</style>

<div class="fretboard-glyph" aria-label="Open C major chord diagram">
  <span data-pos="L1">1</span>
  <span data-pos="L2">2</span>
  <span data-pos="L3">3</span>
  <span data-pos="L4">4</span>
  <span data-pos="EH">E</span>
  <span data-pos="E0"></span>
  <span data-pos="AH">A</span>
  <span data-pos="A3"></span>
  <span data-pos="DH">D</span>
  <span data-pos="D2"></span>
  <span data-pos="GH">G</span>
  <span data-pos="G0"></span>
  <span data-pos="BH">B</span>
  <span data-pos="B1"></span>
  <span data-pos="eH">e</span>
  <span data-pos="e0"></span>
</div>

These approaches also apply to musical staves.

Trihard

A barn quilt fiddle widget.

Each square carries four triangles in west (W), north (N), south (S), east (E) order. Each triangle cycles through background, foreground, and accent. Positions run A01 through Z99.

Wide Key = Equals Value Logging

Key = Equals Value

Wide key=value logging keeps information explicit, compact, and searchable. It uses named fields for most values; free-form text can still go on the right-hand side of msg=. It also avoids the complexity and visual noise of JavaScript Object Notation (JSON). Labeled Tab Separated Values (LTSV) keeps the same wide, one-line shape while using : colon and \t tab separators instead of = equals and space. Brandur wrote about logfmt in 2013: stable keys, small values, one request or task per line. Another description I've thought is appropriate for this format is "Wide INI (WINI)". In grep, journalctl, or Papertrail, searching for st=5 catches status codes 500..599 inclusive--with no parser in the loop. The Python structlog library exposes KV renderer and logfmt for this style directly, and so does python-logfmter.

Format Published Structured Key/Value Element Visually Noisy
Prose No ❌ None ❌ \n newline ❌ No ✅
INInitialization (INI) 1980s Yes ✅ = equals ✅ \n newline ❌ No ✅
Common Log Format (CLF) 1995 No ❌ None ❌ space ✅ 🤷
YAML Ain't Markup Language (YAML) 2001 Yes ✅ : colon ✅ \n newline plus indentation ❌ No ✅
JavaScript Object Notation (JSON) 2001 Yes ✅ : colon ✅ , comma ✅ Yes ❌
Labeled Tab-Separated Values (LTSV) 2012 Yes ✅ : colon ✅ \t tab ✅ No ✅
Tom's Obvious Markup Language (TOML) 2013 Yes ✅ = equals ✅ \n newline ❌ No ✅
logfmt 2013 Yes ✅ = equals ✅ space ✅ No ✅

Emit keys consistently to implement structured key=value logging. Most operational values are simple scalars. Serialize iterables and mappings compactly to remain simple: list=1,2,3 dict=1..2:buckle-my-shoe,3..4:knock-at-the-door. This follows the LTSV guidance of avoiding quotes and escapes to keep tooling requirements minimal. As an escape hatch, complex data can be stuffed into the value as Python literals or JSON. Reducing unnecessary punctuation also reduces LLM token usage.

Wide Events / Canonical Log Lines

Fewer lines are better, with one line per request (or task) as the ideal. Canonical log lines makes that operational case well: coherent context, easier streaming, and cleaner copy-paste to record incident details. Multiple lines per request (or task) make key consistency harder to maintain. LoggingSucks reaches the same practical conclusion from another angle: wide single-line records make debugging and operations efficient, although Boris Tane's unexplained choice of JSON over key=value adds punctuation and tool requirements where clarity should win.

Keys/Labels

An aspiration here is to combine Gunicorn and Django output into one consistent wide key=value log line. In the meantime, here is an example of configuring gunicorn with abbreviated keys:

access_log_format = '%(r)s st=%(s)s lb=%(h)s ip=%({x-forwarded-for}i)s rt=%(L)ss us=%(u)s rf=%(f)s'

Of course, abbreviating has downsides, so use whole words when they are a better choice or when you are unsure.

Abbr LTSV Recommend Description Apache nginx gunicorn
time Time the request was received %t $time_local %(t)s
lb or ip* host Nearest client IP %h $remote_addr %(h)s
ff or ip* forwardedfor Forwarded client IP %{X-Forwarded-For}i $http_x_forwarded_for %({x-forwarded-for}i)s
us user Remote user %u $remote_user %(u)s
em User email
req First line of request (method, uri, rows) %r $request %(r)s
method Request method %m $request_method %(m)s
uri Request URI %U%q $request_uri %(U)s%(q)s
protocol Requested Protocol (usually "HTTP/1.0" or "HTTP/1.1") %H $server_protocol %(H)s
st status Status code %>s $status %(s)s
size Size of response in bytes, excluding HTTP headers; suffix with b for explicit units %Bb (or %bb for compatibility with combined format) ${body_bytes_sent}b %(B)sb
reqsize Bytes received, including request and headers; suffix with b for explicit units %Ib (mod_log_io required) ${request_length}b %({content-length}i)sb
rf referer Referer header %{Referer}i $http_referer %(f)s
ua ua User-Agent header %{User-agent}i $http_user_agent %(a)s
hs vhost Host header %{Host}i $host %({host}i)s
reqtime_microsec The time taken to serve the request, in microseconds; suffix with us for explicit units %Dus %(D)sus
rt reqtime The time taken to serve the request, in seconds; suffix with s for explicit units %Ts ${request_time}s %(L)ss
cache X-Cache header %{X-Cache}o $upstream_http_x_cache %({x-cache}o)s
runtime Execution time for processing some request, e.g. X-Runtime header for application server or processing time of SQL for DB server. %{X-Runtime}o $upstream_http_x_runtime %({x-runtime}o)s
apptime Response time from the upstream server $upstream_response_time

* Replace lb or ff with ip based on presence or absence of load balancer.

Numbers 3

Numbers 3 (BSB)

Tent of Meeting

Numbers 2

The Order of the Camps

Numbers 2 (BSB)