Three sites, one directory each

flux.vision, tobias.raayoni-last.com and randogoth.com. They were one site until
October 2026, serving four audiences from a single dated list, which is the only
format that can hold unrelated things at once.

flux.vision is recovered from the Wayback Machine, where it had sat since being
deleted in 2021: 90 articles, closed and maintained rather than updated.
tobias.raayoni-last.com is the professional record, 97 articles back to 1999.
randogoth.com is the technical work, 41 articles across six areas, served as an
ANSI bulletin board.

History starts here deliberately. The development history, the 266-row inventory
that records why each article went where it did, the Wayback pipelines, the
pruned articles, the source archives and the full-size images are kept beside
the repo rather than in it, so a clone is the web presence and not the argument
that produced it. REFACTOR.md is gitignored for the same reason. See README.md
for where that material lives, and keep it: the sort is not reproducible from
these three directories alone.

Each site documents itself in its own README, including the traps that cost time
and look like nothing when you get them wrong.
This commit is contained in:
randogoth 2026-10-05 20:54:17 +03:00
commit 8505b7e9e0
1003 changed files with 13289 additions and 0 deletions

View file

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block title %}Not found · {{ SITENAME }}{% endblock %}
{% block content %}
<div class="pages"><div class="frame">
<h1 class="tight">404</h1>
<div class="text"><p>That page is not here. Try the <a href="{{ SITEURL }}/">index</a> or the <a href="{{ SITEURL }}/timeline.html">timeline</a>.</p></div>
</div></div>
{% endblock %}

View file

@ -0,0 +1,5 @@
{# The swift mark, as used on invoices. Two nested transforms from the
Inkscape source are both required: the group translate and the path's
own matrix (a 7.5x scale with a Y flip). fill="currentColor" so it takes
var(--title) and inverts with the theme. #}
<svg class="mark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 161.36533 268.29031" aria-hidden="true" focusable="false"><g transform="translate(-150.26933,-390.24689)"><path fill="currentColor" transform="matrix(0.13333333,0,0,-0.13333333,0,816)" d="m 1127.08,2003.92 -0.06,-0.3 c 10.57,-3.95 107.03,-39.95 161.26,-55.57 53.65,-15.46 85.22,-23.27 89.12,-24.24 l 0.01,-0.25 c -7.26,-2.64 -111.38,-40.61 -161.15,-64.09 l -1.53,-0.72 c -12.12,-5.72 -6.89,-23.99 6.42,-22.46 75.33,8.66 191.92,21.61 236.48,24.16 67.75,3.87 248.11,-12.47 250.1,-12.65 l 0.03,-0.02 c 0.27,-0.96 11.45,-41.19 -6.82,-68.11 -18.49,-27.25 -180.05,-218.01 -216.07,-265.71 -36,-47.68 -71.04,-112.89 -98.29,-146.96 -27.25,-34.06 -95.38,-115.81 -124.58,-138.2 -29.2,-22.38 -60.34,-41.85 -53.53,-46.71 6.81,-4.87 76.89,6.81 131.39,22.38 54.51,15.58 241.37,109.98 327.99,175.19 86.62,65.2 189.79,154.09 247.21,212.81 57.42,58.73 120.68,135.62 156.7,184.28 35.97,48.62 38.92,62.26 39.89,91.41 l 0.02,0.08 c 1.65,0.34 126.77,26.55 165.44,69.09 l 37.44,41.2 c 0.99,1.08 2.16,1.97 3.47,2.62 l 19.22,9.6 0.02,0.27 -30.93,23.2 -0.02,0.01 c -0.19,0.22 -29.36,36.08 -78.84,59.36 -46.86,22.06 -62.49,25.89 -64.09,26.25 l -0.12,0.1 c 1.18,3.26 42.8,118.27 42.8,158.58 0,40.87 -44.77,157.66 -81.75,229.68 -36.99,72.02 -114.85,222.88 -140.15,264.73 -25.3,41.85 -115.82,175.19 -185.89,241.37 -70.08,66.18 -199.52,190.76 -238.46,198.54 -38.92,7.79 70.08,-134.3 70.08,-162.53 0,-28.23 35.04,-79.81 36.99,-109 1.95,-29.2 26.27,-90.52 30.17,-114.85 3.89,-24.33 41.48,-131.74 41.48,-131.74 0,0 7.18,-40.53 35.41,-127.15 28.09,-86.21 81.25,-292.94 81.74,-294.88 l -0.03,-0.07 -119.93,-206.33 -0.06,-0.07 c -1.77,-0.23 -111.23,-14.59 -177.75,-32.72 -66.63,-18.18 -97.16,-29.58 -97.56,-29.73 h -0.04 l -333.23,20.15"/></g></svg>

View file

@ -0,0 +1,57 @@
{# Provenance fields arrive as "Name <url>", several separated by "; ".
Rendered as a mono credit line — the one slot no off-the-shelf theme has. #}
{% macro names(raw) %}
{%- for part in raw.split(';') %}
{%- set p = part.strip() %}
{%- if '<' in p %}
{%- set nm = p.split('<')[0].strip() %}
{%- set url = p.split('<')[1]|replace('>','')|trim %}
<a href="{{ url }}" rel="noopener">{{ nm }}</a>
{%- else %}{{ p }}{% endif %}
{%- if not loop.last %}, {% endif %}
{%- endfor %}
{%- endmacro %}
{% macro credits(a) %}
{%- set rows = [('for', 'for'), ('hiredby', 'hired by'), ('with', 'with'), ('photosby', 'photos by')] %}
{%- set any = rows | selectattr(0, 'in', a.metadata) | list %}
{%- if any %}
<div class="credits">
{%- for key, label in rows %}{% if a.metadata.get(key) %}
<div><span class="rel">{{ label }}</span> {{ names(a.metadata.get(key)) }}</div>
{%- endif %}{% endfor %}
</div>
{%- endif %}
{%- endmacro %}
{# A tile. `empty` when no thumbnail survived — about 35 entries are in that
position, so it degrades to a typographic card rather than a hole. #}
{% macro tile(a, SITEURL) %}
{# assetcheck flags references whose file did not survive #}
{% set thumb = a.metadata.get('thumb') if not a.metadata.get('thumb_missing') else none %}
<div class="item"
data-category="{{ a.category }}"
data-project="{{ a.metadata.get('project', '') }}"
data-year="{{ a.date.year }}"
data-title="{{ a.title|striptags }}">
<div class="item-content">
{% if thumb %}
{# One image, not two: siblings dim on hover rather than the target
colourising, so there is no need for a greyscale copy. #}
<div class="hoverthumb">
<img src="{{ SITEURL }}{{ thumb }}" alt="{{ a.title|striptags }}" loading="lazy" decoding="async">
</div>
{% else %}
<div class="hoverthumb empty"><span>{{ a.category }}</span></div>
{% endif %}
<div class="content">
<div class="title">{{ a.title }}</div>
<div class="year">{{ a.date.year }}{% if a.metadata.get('project') %} · {{ a.metadata.get('project') }}{% endif %}</div>
{% set d = a.summary|striptags|trim %}
{% if d %}<div class="description">{{ d|truncate(104, true, '…') }}</div>{% endif %}
{% if a.tags %}<div class="tags">{% for t in a.tags %}<span>{{ t }}</span>{% endfor %}</div>{% endif %}
</div>
<a class="cover" href="{{ SITEURL }}/{{ a.url }}" aria-label="{{ a.title|striptags }}"></a>
</div>
</div>
{% endmacro %}

View file

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block title %}Timeline · {{ SITENAME }}{% endblock %}
{% block content %}
<div class="pages"><div class="frame">
<h1 class="tight">Timeline</h1>
<p class="lede">Everything by date, {{ dates|map(attribute='date.year')|min }}–{{ dates|map(attribute='date.year')|max }}. This is the CV proper.</p>
<ul class="rows">
{% for a in dates %}
<li><a href="{{ SITEURL }}/{{ a.url }}" style="display:flex;gap:1.2rem;align-items:baseline;width:100%">
<span class="yr">{{ a.date.year }}</span>
<span class="ti">{{ a.title }}</span>
<span class="ct">{{ a.category }}{% if a.metadata.get('project') %} · {{ a.metadata.get('project') }}{% endif %}</span>
</a></li>
{% endfor %}
</ul>
</div></div>
{% endblock %}

View file

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% import "_macros.html" as m %}
{% block title %}{{ article.title|striptags }} · {{ SITENAME }}{% endblock %}
{% block meta %}
<meta name="description" content="{{ article.summary|striptags|truncate(180)|e }}">
{% endblock %}
{% block content %}
{% set bg_ok = article.metadata.get('background') and not article.metadata.get('background_missing') %}
{% set th_ok = article.metadata.get('thumb') and not article.metadata.get('thumb_missing') %}
{% set bg = article.metadata.get('background') if bg_ok else (article.metadata.get('thumb') if th_ok else none) %}
{# No hero at all when no image survived: an empty placeholder band is just
dead space above the title. The entry starts under the bar instead. #}
{% if bg %}
<div class="hero" style="background-image:url('{{ SITEURL }}{{ bg }}')">
<div class="scrollcue">scroll</div>
</div>
{% endif %}
<div class="pages">
<article class="frame">
<h1 class="tight">{{ article.title }}</h1>
<div class="entry-meta">
{{ article.date.year }}
· <a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a>
{%- if article.metadata.get('project') %} · {{ article.metadata.get('project') }}{% endif %}
{%- if article.tags %} · {% for t in article.tags %}<a href="{{ SITEURL }}/{{ t.url }}">{{ t }}</a>{% if not loop.last %}, {% endif %}{% endfor %}{% endif %}
</div>
{{ m.credits(article) }}
<div class="text body">
{{ article.content }}
</div>
</article>
</div>
{% endblock %}

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="{{ DEFAULT_LANG }}"{% if THEME_DEFAULT %} data-theme="{{ THEME_DEFAULT }}"{% endif %}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{{ SITENAME }}{% endblock %}</title>
{% block meta %}{% endblock %}
{# Applied before first paint: without this the wrong ground colour flashes. #}
<script>
(function(){try{var t=localStorage.getItem("theme");
if(t){document.documentElement.setAttribute("data-theme",t);}}catch(e){}})();
</script>
<link rel="stylesheet" href="{{ SITEURL }}/theme/css/style.css">
{% if FEED_ALL_ATOM %}
<link rel="alternate" type="application/atom+xml" href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}" title="{{ SITENAME }}">
{% endif %}
</head>
<body>
<nav class="bar">
<a class="me" href="{{ SITEURL }}/">{% include "_logo.html" %}<span>{{ SITENAME }}</span></a>
{% for title, link in MENUITEMS %}<a href="{{ SITEURL }}{{ link }}">{{ title }}</a>
{% endfor %}
<span class="sep">·</span>
<button type="button" data-theme-toggle>dark</button>
</nav>
{% block content %}{% endblock %}
<footer>
{{ SITENAME }}
{% for title, link in MENUITEMS %} · <a href="{{ SITEURL }}{{ link }}">{{ title }}</a>{% endfor %}
</footer>
<script src="{{ SITEURL }}/theme/js/muuri.min.js"></script>
<script src="{{ SITEURL }}/theme/js/site.js"></script>
</body>
</html>

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Disciplines · {{ SITENAME }}{% endblock %}
{% block content %}
<div class="pages"><div class="frame">
<h1 class="tight">Disciplines</h1>
<div class="cloud">
{% for cat, arts in categories %}
<a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }} <span style="color:var(--sub)">{{ arts|length }}</span></a>
{% endfor %}
</div>
</div></div>
{% endblock %}

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% import "_macros.html" as m %}
{% block title %}{{ category }} · {{ SITENAME }}{% endblock %}
{% block content %}
<div class="pages"><div class="frame">
<h1 class="tight">{{ category }}</h1>
<p class="lede">{{ articles|length }} {{ 'entry' if articles|length == 1 else 'entries' }}</p>
</div></div>
<div class="grid">
{% for a in articles %}{{ m.tile(a, SITEURL) }}{% endfor %}
</div>
{% endblock %}

View file

@ -0,0 +1,71 @@
{% extends "base.html" %}
{% import "_macros.html" as m %}
{# `cv` is the three non-media jobs: a record, not portfolio tiles. #}
{% set shown = articles | rejectattr('category.name', 'equalto', 'cv') | list %}
{% set disciplines = shown | map(attribute='category.name') | unique | sort | list %}
{# Engagements, biggest first. A four-year relationship like ClipAward — nine
entries spanning design, projection, animation and organisation — is the
thing this portfolio has that a flat grid cannot show. Grouping is the
default view; the sort buttons fall back to a flat grid. #}
{% set names = [] %}
{% for a in shown if a.metadata.get('project') and a.metadata.get('project') not in names %}
{% if names.append(a.metadata.get('project')) %}{% endif %}
{% endfor %}
{% set groups = [] %}
{% for n in names %}
{% set members = shown | selectattr('metadata.project', 'equalto', n) | list %}
{% if groups.append({'name': n, 'members': members, 'n': members|length,
'first': members | map(attribute='date.year') | min}) %}{% endif %}
{% endfor %}
{# biggest engagement first, then earliest — Articles are not orderable, so
sort on the counts rather than on the member lists #}
{% set groups = groups | sort(attribute='first') | sort(attribute='n', reverse=true) %}
{% set loose = shown | rejectattr('metadata.project') | list %}
{% block content %}
<section class="controls" aria-label="Filter">
<span class="label">discipline</span>
{% for d in disciplines %}
<button type="button" data-axis="category" data-filter="{{ d }}" aria-pressed="false">{{ d }}</button>
{% endfor %}
<span class="count" data-count>{{ shown|length }} entries</span>
</section>
<section class="controls" aria-label="Arrangement">
<span class="label">arrange</span>
<button type="button" data-sort="year-desc" aria-pressed="true">newest</button>
<button type="button" data-sort="group" aria-pressed="false">by engagement</button>
<button type="button" data-sort="year-asc" aria-pressed="false">oldest</button>
<button type="button" data-sort="title" aria-pressed="false">a–z</button>
</section>
<div class="grid">
{% for g in groups %}
{% set yrs = g.members | map(attribute='date.year') | list %}
{% set kinds = g.members | map(attribute='category.name') | unique | sort | list %}
<div class="item head" data-head="{{ g.name }}">
<div class="item-content">
<div class="grouphead">
<span class="gname">{{ g.name }}</span>
<span class="gmeta">{{ g.members|length }} entries · {{ yrs|min }}{% if yrs|min != yrs|max %}–{{ yrs|max }}{% endif %} · {{ kinds|join(', ') }}</span>
</div>
</div>
</div>
{% for a in g.members|sort(attribute='date') %}{{ m.tile(a, SITEURL) }}{% endfor %}
{% endfor %}
{% if loose %}
<div class="item head" data-head="">
<div class="item-content">
<div class="grouphead">
<span class="gname">Single commissions</span>
<span class="gmeta">{{ loose|length }} entries</span>
</div>
</div>
</div>
{% for a in loose|sort(attribute='date', reverse=true) %}{{ m.tile(a, SITEURL) }}{% endfor %}
{% endif %}
</div>
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}{{ page.title|striptags }} · {{ SITENAME }}{% endblock %}
{% block content %}
<div class="pages">
<article class="frame">
{% if page.slug == 'about' %}<div class="markplate">{% include "_logo.html" %}</div>{% endif %}
<h1 class="tight">{{ page.title }}</h1>
<div class="text body">{{ page.content }}</div>
</article>
</div>
{% endblock %}

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% import "_macros.html" as m %}
{% block title %}{{ tag }} · {{ SITENAME }}{% endblock %}
{% block content %}
<div class="pages"><div class="frame">
<h1 class="tight">{{ tag }}</h1>
<p class="lede">Entries that also involved {{ tag }}.</p>
</div></div>
<div class="grid">
{% for a in articles %}{{ m.tile(a, SITEURL) }}{% endfor %}
</div>
{% endblock %}

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Tags · {{ SITENAME }}{% endblock %}
{% block content %}
<div class="pages"><div class="frame">
<h1 class="tight">Tags</h1>
<div class="cloud">
{% for tag, arts in tags|sort %}
<a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }} <span style="color:var(--sub)">{{ arts|length }}</span></a>
{% endfor %}
</div>
</div></div>
{% endblock %}