diff --git a/flow-state/.claude/settings.json b/flow-state/.claude/settings.json
new file mode 100644
index 0000000..f65aa61
--- /dev/null
+++ b/flow-state/.claude/settings.json
@@ -0,0 +1,7 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(npm run lint:scenes *)"
+ ]
+ }
+}
diff --git a/flow-state/filmstrip.html b/flow-state/filmstrip.html
index 3f11ee3..8718e24 100644
--- a/flow-state/filmstrip.html
+++ b/flow-state/filmstrip.html
@@ -39,6 +39,15 @@
.row.churn { border-color: #eab308; }
.row.arc { border-color: #22c55e; }
.row.broken { border-color: #a855f7; background: #150e1c; }
+ .row.agitated { border-color: #f97316; background: #1a1210; }
+ .agit { color: #9aa3b5; font-size: 11px; }
+ .agit .meter { display: inline-block; width: 96px; height: 7px; background: #1a1e28; border-radius: 4px; vertical-align: middle; margin-right: 6px; }
+ .agit .meter i { display: block; height: 100%; border-radius: 4px 0 0 4px; }
+ .agit .meter i.low { background: #22c55e; }
+ .agit .meter i.mid { background: #eab308; }
+ .agit .meter i.high { background: #f97316; }
+ .agit b.agitated { color: #f97316; }
+ .agit b.calm { color: #22c55e; }
.head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 6px; flex-wrap: wrap; }
.name { font-size: 14px; color: #e6eaf2; }
.meta { color: #6b7280; font-size: 11px; }
@@ -82,6 +91,7 @@
@@ -118,9 +128,13 @@ let rows = [];
// Read against the arcless reference the variety report prints: around zero is
// a video that is as unlike itself after ten seconds as after four minutes.
-const grade = (row) => (row.error ? 'broken'
- : row.direction < 0.15 ? 'static'
- : row.direction < 0.45 ? 'churn' : 'arc');
+const AGIT_CEILING = 0.70;
+const agitClass = (v) => (v == null ? 'low' : v >= AGIT_CEILING ? 'high' : v >= 0.50 ? 'mid' : 'low');
+const grade = (row) => {
+ if (row.error) return 'broken';
+ if ((row.agitation ?? 0) >= AGIT_CEILING) return 'agitated';
+ return row.direction < 0.15 ? 'static' : row.direction < 0.45 ? 'churn' : 'arc';
+};
// Nine stills are an argument about a video, and the only reply to them is the
// video. The app takes the bank's wav straight from `?song=`, and the seed is
@@ -140,13 +154,19 @@ const watchLink = (name) =>
function renderRow(row) {
const el = document.createElement('div');
el.className = `row ${grade(row)}`;
+ const agit = row.agitation ?? 0;
+ const agitLabel = row.error ? '' :
+ ` ` +
+ ` ` +
+ `agit ${agit.toFixed(2)} · flash ${(row.agitationFlash ?? 0).toFixed(1)}/s · motion ${((row.agitationMotion ?? 0)*100).toFixed(0)}% · struct ${(row.agitationStructPeak ?? row.agitationStruct ?? row.peakStructure ?? 0).toFixed(2)}` +
+ ``;
el.innerHTML = `
${row.name}
watch ▸
${row.bpm ?? '?'} bpm · ${row.sections ?? '?'} sections · ${row.director || ''}
-
direction ${(row.direction ?? 0).toFixed(2)} · drift ${(row.drift ?? 0).toFixed(3)}
+
direction ${(row.direction ?? 0).toFixed(2)} · drift ${(row.drift ?? 0).toFixed(3)}${agitLabel}
${row.error ? `${row.error}` : (row.storyLine || '')}
`;
@@ -183,7 +203,8 @@ function draw() {
const mode = sortSel.value;
const order = songNames();
const sorted = rows.slice().sort((a, b) => (
- mode === 'name' ? order.indexOf(a.name) - order.indexOf(b.name)
+ mode === 'agitation' ? (b.agitation ?? 0) - (a.agitation ?? 0)
+ : mode === 'name' ? order.indexOf(a.name) - order.indexOf(b.name)
: mode === 'drift' ? a.drift - b.drift
: mode === 'plot' ? ((a.story?.plot || '').localeCompare(b.story?.plot || '') || a.direction - b.direction)
: a.direction - b.direction));
@@ -196,10 +217,15 @@ sortSel.addEventListener('change', draw);
function summarise(built) {
const flat = rows.filter((r) => !r.error && r.direction < 0.15).length;
+ const validatedNote = rows.some((r) => r.validatedNote) ? ' · ' + rows.filter((r) => r.validatedNote).length + ' re-tuned' : '';
+ const agited = rows.filter((r) => (r.agitation ?? 0) >= AGIT_CEILING).length;
const meanDir = rows.length
? rows.reduce((a, r) => a + (r.direction || 0), 0) / rows.length : 0;
+ const meanAgit = rows.length
+ ? rows.reduce((a, r) => a + (r.agitation ?? 0), 0) / rows.length : 0;
return `${rows.length} songs · every ${every}s of ${duration}s · ` +
`mean direction ${meanDir.toFixed(2)} · ${flat} with no direction` +
+ ` · mean agit ${meanAgit.toFixed(2)} · ${agited} agitated (≥${AGIT_CEILING.toFixed(2)})${validatedNote}` +
(built ? ` · built ${new Date(built).toLocaleString()}` : '');
}
diff --git a/flow-state/gallery.html b/flow-state/gallery.html
index 0912bc8..6d3d241 100644
--- a/flow-state/gallery.html
+++ b/flow-state/gallery.html
@@ -44,6 +44,12 @@
.row.thin { border-color: #eab308; }
.row.good { border-color: #22c55e; }
.row.broken { border-color: #a855f7; background: #150e1c; }
+ .row.agitated { border-color: #f97316; }
+ .comfort { color: #9aa3b5; font-size: 11px; }
+ .comfort .meter i.low { background: #22c55e; }
+ .comfort .meter i.mid { background: #eab308; }
+ .comfort .meter i.high { background: #ef4444; }
+ .comfort .meter i.hot { background: #f97316; }
.head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 6px; flex-wrap: wrap; }
.name { font-size: 14px; color: #e6eaf2; }
.fam { color: #6b7280; }
@@ -132,6 +138,7 @@
+
@@ -155,8 +162,11 @@