A Static Art Gallery with Hugo

This is not the first time, and won’t be the last time I post about JS-free, bloat-free web. In this post I’d like to share how I build a art collection site for my gf.

the site: https://gi.nseng.art

I’m not a web designer and I’m not a frontend engineer. Yet I’ve wrote a lot about static blogging. I do this to make a point for the bloat-free web.

A simple grid layout

I adopted the example from here. Later I’ll show how to plug this into the hugo system.

Content Organization

Now, this is the tricky part. Simply put, hugo is opt for text, not blob objects like images or videos.

  • For a formal presentation, artworks needs to documented with their meta data e.g. time of making, author, material, techniques, etc..
  • The need for meta data management is similar to the need for bibliography management in acdemia.
  • It’s far more complicated than the usual approach : putting a piece of artwork in a post with markdown image via [alt text]("url" "desc"). For example, each media could appear in different places, with a consistent meta data display. Copying around the hyperlinks is a dead end.

are you talking about wordpress? - Yes I am. Wordpress makes it pretty easy to manage media files and I find it the optimal CMS for artists. But I won’t use it, again, to make a point.

Now, what about content organization?

A typical hugo content structure would look like this:

content
|- category1
    |- post1.md
    |- post2.md
    |- post3
        |- _index.md
        |- sub1.md
        |- sub2.md
        |- img1.jpg
        |- ...
        ...

The problem here is obvious: the assets like media are bounded to posts and there is not a central place for meta data management.

So instead, I do this:

content
|- category1
|   |- post1.md
|   |- post2.md
|   ...
|- static
|   |- media files
|   |- ...
|
|- data
    |- meta.json

the meta.json would be our central place for metadata management. It will look like this:

[

    {
        "id"            : "aq2",
        "name"          : "",
        "desc"          : "A question 2, 2024, Oil on canvas, 70x55cm",
        "image_url"     : "/A_Question_2/A_Question_2.jpg",
        "series"        : "A Question 2",
        "series_url"    : "/arts/a_question_2/",
        "tags"          : ["painting"]
    },
    {
        "id"            : "aq1"
        "name"          : "",
        "desc"          : "A question 1, 2024, Oil on canvas, 160x114cm",
        "image_url"     : "/A_Question_1/A_Question_1.jpg",
        "series"        : "A Question 1",
        "series_url"    : "/arts/a_question_1/",
        "tags"          : ["painting"]
    },
    ...
]
  • each media may belong to a serie
  • each piece of a serie may have its own name
  • each media is identified by its file path (its relative url, same as its relative path to static/)
  • each media is also identified by the id: this is handy to include media via shortcodes
  • each media may be linked to a post (series_url)
  • each media may have one or multiple tags (as in “categories”).

hugo layouts and partials

Now let’s plug the metadata into hugo partials:

a gallery of ALL artworks:

layout/gallery/list.html (only showing relevant parts)

1
2
3
{{ $gl := site.Data.gl_all }}
<!-- filter the list here -->
{{ partial "gallery_grid.html" $gl}}

layout/partials/

<div class="gallery-grid" style="max-width: 80%; margin:auto">
    {{ range . }}
      <figure class="gallery-frame">
          {{with .video_url}}
          <video class="gallery-img" playsinline="" controls="">
              <source src="{{.}}" type="video/mp4">
          </video>
          {{else}}
          <a href="{{.image_url}}" target="_blank">
          <img class="gallery-img"
                src="{{.image_url}}.tn.jpg"
                alt="{{.desc}}"
                title="{{.desc}}">
          </a>
          {{end}}
          <figcaption> <a href ="{{.series_url}}">{{.series}}</a>
              // <i>{{.desc}}</i>

              <div class="tags">
                  {{range .tags}} <a href="/tags/{{.}}"> #{{.}} </a>
                  {{end}}
              </div>
          </figcaption>
     </figure>
    {{end}}
</div> <!--gallery-grid-->

Sub-gallery : filter the json data:

e.g. layouts/paintint/list.html

<center><h1>PAINTINGS</h1></center>
{{ $gl := where site.Data.gl_all "tags" "intersect" (slice "painting")}}
{{ partial "gallery_grid.html" $gl}}

Thumbnails

It’s obvious that we shouldn’t use hi-res pictures for a gallery grid view. I have a simple script to generate thumbnails. The thumbnails are store to the same path as the original, with a suffix .tn.jpg. This makes it easier for templating.

#!/usr/bin/bash
DIR=$1
target_dirs=( "$@" )
echo "making thumbnails : NO RECURSION!"
gen_thumb_for_dir() {
    local dir=$1
    if [ ! -d "$dir" ]; then
        echo "[ERROR] $DIR does not exist."
        return
    fi
    rm $dir/*.tn.jpg
    targets=($(find $dir \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" \)))
    for i in "${targets[@]}"
    do
        echo "PROCESSING $i to $i.tn.jpg"
        convert $i -resize 1280x1280 -strip $i.tn.jpg
    done
}
for i in "${target_dirs[@]}"
do
    gen_thumb_for_dir $i
done

Add media to post via shortcode and ID

layouts/shortocodes/media.html

{{ $gl := site.Data.gl_all }}
{{ $id := .Get "id" }}
{{ range $gl }}
    {{ if eq .id $id }}
        {{ $t := ""}}
        {{ if .name}}
            {{$t = printf "%s - %s" .name .desc}}
        {{else}}
            {{$t = .desc}}
        {{ end }}
        <p class="md__image">
        <img src="{{.image_url}}" alt="{{$t}}"  title="{{$t}}" />
        <div class="img-cap">{{$t}}</div>
        </p>
        {{ else }}
    {{ end }}
{{ end}}

to use in a post:

{{<media id="aq2">}}

TODOs : a CMS

[+] click to leave a comment [+]
the comment system on this blog works via email. The button
below will generate a mailto: link based on this page's url 
and invoke your email client - please edit the comment there!

[optional] even better, encrypt the email with my public key

- don't modify the subject field
- specify a nickname, otherwise your comment will be shown as   
  anonymous
- your email address will not be disclosed
- you agree that the comment is to be made public.
- to take down a comment, send the request via email.

        
>> SEND COMMENT <<