From f4ebafdcfcdf772fab47cf39601f682cf5549c85 Mon Sep 17 00:00:00 2001 From: soaos Date: Wed, 24 Sep 2025 00:55:08 -0400 Subject: Added rockbox stats page --- blog/blacklight_shader/blacklight.png | Bin 846587 -> 0 bytes blog/blacklight_shader/index.html | 227 ------ blog/blog.css | 99 +++ blog/rockbox_stats/index.html | 560 +++++++++++++ blog/rockbox_stats/log-setting.bmp | Bin 0 -> 153666 bytes blog/rockbox_stats/playback-settings.bmp | Bin 0 -> 153666 bytes blog/rockbox_stats/player.bmp | Bin 0 -> 153666 bytes index.css | 10 +- index.html | 23 +- rockstats/index.html | 1301 ++++++++++++++++++++++++++++++ style.css | 17 + 11 files changed, 1991 insertions(+), 246 deletions(-) delete mode 100644 blog/blacklight_shader/blacklight.png delete mode 100644 blog/blacklight_shader/index.html create mode 100644 blog/blog.css create mode 100644 blog/rockbox_stats/index.html create mode 100644 blog/rockbox_stats/log-setting.bmp create mode 100644 blog/rockbox_stats/playback-settings.bmp create mode 100644 blog/rockbox_stats/player.bmp create mode 100644 rockstats/index.html diff --git a/blog/blacklight_shader/blacklight.png b/blog/blacklight_shader/blacklight.png deleted file mode 100644 index 2c5caf1..0000000 Binary files a/blog/blacklight_shader/blacklight.png and /dev/null differ diff --git a/blog/blacklight_shader/index.html b/blog/blacklight_shader/index.html deleted file mode 100644 index db4b54e..0000000 --- a/blog/blacklight_shader/index.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - Creating a Blacklight Shader - soaos - - - - Go Home - Go Back -

Creating a Blacklight Shader

- - NOTE: THIS POST WAS TRANSFERRED FROM MARKDOWN BY HAND SO I MIGHT HAVE MISSED SOME STUFF SORRY - -

today i wanted to take a bit of time to write about a shader i implemented for my in-progress game project (more - on that soonβ„’)

-

i wanted to create a "blacklight" effect, where specific lights could reveal part of the base texture. this - shader works with spot lights only, but could be extended to work with point lights

-
- Example of shader running, showing hidden writing on a wall -
Example of shader running, showing hidden writing on a wall.
-
- -

i wrote this shader in wgsl for a bevy engine project, but - it should translate easily to other shading languages

- -

the finished shader can be found as part of this repo

- -

shader inputs

- -

- for this shader, i wanted the following features: -

- - for this to work i need the following information about each light: - - - i also need some info from the vertex shader: - -

-

bevy's default pbr vertex shader provides this information, but as long as you can get this info into your - fragment - shader you should be good to go

- -

lastly i'll take a base color texture and a sampler

- -

- with all of that, i can start off the shader by setting up the inputs and fragment entry point: - -

-	#import bevy_pbr::forward_io::VertexOutput;
-
-	struct BlackLight {
-		position: vec3<f32>,
-		direction: vec3<f32>,
-		range: f32,
-		inner_angle: f32,
-		outer_angle: f32,
-	}
-
-	@group(2) @binding(0) var<storage> lights: array<BlackLight>;
-	@group(2) @binding(1) var base_texture: texture_2d<f32>;
-	@group(2) @binding(2) var base_sampler: sampler;
-
-	@fragment
-	fn fragment(
-		in: VertexOutput,
-	) -> @location(0) vec4<f32> {
-	}
-		
- (bevy uses group 2 for custom shader bindings) -

- -

- since the number of lights is dynamic, i use a storage buffer to store - that information -

- -

shader calculations

- -

the first thing we'll need to know is how close to looking at the fragment the light source - is

- -

- we can get this information using some interesting math: - -

-	let light = lights[0];
-	let light_to_fragment_direction = normalize(in.world_position.xyz - light.position);
-	let light_to_fragment_angle = acos(dot(light.direction, light_to_fragment_direction));
-		
- - the first step of this is taking the dot product of light direction and the direction from - the light to the fragment -

- -

since both direction vectors are normalized, the dot product will be between -1.0 and 1.0

- -

- the dot product of two unit vectors is the cosine of the angle between them (proof - here) -

- -

- therefore, we take the arccosine of that dot product to get the angle between the light and - the fragment -

- -

- once we have this angle we can plug it in to a falloff based on the angle properties of the - light: - -

-	let angle_inner_factor = light.inner_angle/light.outer_angle;
-	let angle_factor = linear_falloff_radius(light_to_fragment_angle / light.outer_angle, angle_inner_factor);
-		
-
-	fn linear_falloff_radius(factor: f32, radius: f32) -> f32 {
-		if factor < radius { return 1.0; } else { 
-			return 1.0 - (factor - radius) / (1.0 - radius); 
-		} 
-	}
-		
-

-

- next, we need to make sure the effect falls off properly over distance we can do this by getting the distance - from the light to - the fragment and normalizing it with the range of the light before plugging that into an inverse square falloff - we'll use squared distance to avoid expensive and unnecessary square root operations: -

-	let light_distance_squared=distance_squared(in.world_position.xyz, light.position);
-	let distance_factor=inverse_falloff_radius(saturate(light_distance_squared / (light.range * light.range)), 0.5);
-		
-
-	fn distance_squared(a: vec3f, b: vec3f) -> f32 {
-		let vec = a - b;
-		return dot(vec, vec);
-	}
-
-	fn inverse_falloff(factor: f32) -> f32 {
-		return pow(1.0 - factor, 2.0);
-	}
-
-	fn inverse_falloff_radius(factor: f32, radius: f32) -> f32 {
-		if factor < radius { return 1.0; } else { 
-			return inverse_falloff((factor - radius) / (1.0 - radius)); 
-		}
-	}
-		
-

-

- now we'll have a float multiplier between 0.0 and 1.0 for our angle and distance to the light we can get the - resulting color by multiplying these with the base color texture: -

-	let base_color = textureSample(base_texture, base_sampler, in.uv);
-	let final_color=base_color * angle_factor * distance_factor;
-		
- this works for one light, but we need to refactor it to loop over all the provided blacklights: -
-
-	@fragment fn fragment( in: VertexOutput ) -> @location(0) vec4<f32> {
-		let base_color = textureSample(base_texture, base_sampler, in.uv);
-		var final_color = vec4f(0.0, 0.0, 0.0, 0.0);
-		for (var i = u32(0); i < arrayLength(&lights); i = i+1) { 
-			let light=lights[i];
-			let light_to_fragment_direction = normalize(in.world_position.xyz - light.position);
-			let light_to_fragment_angle = acos(dot(light.direction, light_to_fragment_direction));
-			let angle_inner_factor = light.inner_angle / light.outer_angle;
-			let angle_factor = linear_falloff_radius(light_to_fragment_angle / light.outer_angle, angle_inner_factor);
-			let light_distance_squared = distance_squared(in.world_position.xyz, light.position);
-			let distance_factor = inverse_falloff_radius(saturate(light_distance_squared / (light.range * light.range)), 0.5);
-			final_color = saturate(final_color + base_color * angle_factor * distance_factor);
-		} 
-		return final_color; 
-	}
-		
- and with that, the shader is pretty much complete you can view the full completed shader code here -

-

have fun!

- - - \ No newline at end of file diff --git a/blog/blog.css b/blog/blog.css new file mode 100644 index 0000000..372964d --- /dev/null +++ b/blog/blog.css @@ -0,0 +1,99 @@ +html { + background: url("/assets/bg.jpg"); + background-attachment: fixed; + background-size: cover; + image-rendering: pixelated; +} + +figure { + background-color: var(--bg0); + margin: 0; + margin-bottom: 1em; +} + +figure > img, figure > * > img, figure > svg, figure > * svg { + background-color: var(--bg-dim); +} + +.cover-image > img { + width: 100%; + height: auto; + padding: 0 50px; + display: block; + box-sizing: border-box; +} + +figcaption { + padding: 0.5em; + font-style: italic; +} + +body { + max-width: 800px; + margin: 1em auto; + background-color: var(--bg1); +} + +.text-section { + padding: 1em; +} + +.fig { + margin: 0.5em; +} + +.fig-horizontal { + width: min-content; + margin: 0.5em auto; +} + +.fig-right { + float: right; + width: min-content; +} + +.fig-left { + float: left; + width: min-content; +} + + +.fig > img, .fig > * > img { + padding: 0.5em; +} + +pre { + padding: 1em 0.5em; + margin: 0; + overflow: scroll; + background-color: var(--bg-dim); +} + +code { + background-color: var(--bg-dim); +} + +table.schema-table { + border-collapse: collapse; + display: inline-table; + margin-bottom: 0.5em; + background-color: var(--bg-dim); +} + +.schema-table th { + background-color: var(--bg0); +} + +.schema-table th,td { + border: 2px solid var(--bg2); + padding: 0.25em; +} + + + +@media (max-width: 700px) { + .fig-left, .fig-right { + float: unset; + margin: 0.5em auto; + } +} \ No newline at end of file diff --git a/blog/rockbox_stats/index.html b/blog/rockbox_stats/index.html new file mode 100644 index 0000000..4aca821 --- /dev/null +++ b/blog/rockbox_stats/index.html @@ -0,0 +1,560 @@ + + + + + + + + + + + +
+ β†° Back + βŒ‚ Home +
+
+
+
+ +

Rockbox Stat Tracking

+

September 22, 2025

+

In this post I talk about how I went about setting up a stat visualization page for my rockbox mp3 player.

+
+
+ + + + + + + + + + + + + + + + + + +Progressive Rock +Alternative +Post-Rock +Post-Hardcore +Post-Metal +Rock +Shoegaze +Progressive Metal + + +
A static site generation experiment
+
+
+
+

Preamble: Digital Sovereignity & Rockbox

+

+ I've been building up a pretty sizeable collection of digital music + over the last couple of years. I think there's a lot of value in owning + the music I pay for and being able to choose how I listen to it. + Purchasing music also allows me to support artists in a more direct + and substantial way than the fractions of cents for using streaming services, + but that's more of a happy consequence than some moral obligation I feel. +

+

+ Over the years, I've enjoyed listening to my music in a variety of ways. + For years I kept all of my music files on all of my devices and used + various local music clients depending on the platform, most notably mpd + and ncmpcpp on linux. Eventually, as I charged headlong into the glorious + world of self-hosting, I began using a central Jellyfin media server that + I stream music and video from. It's super convenient, and works on all of + my devices (including my TV!). +

+

+ My media server is great, and it's been the primary way I listen to music + for a while now. But it has limitations. For example, I don't expose my media + server to the internet, so I'm unable to stream from it while I'm out and + about. And even if I could, the bandwidth requirements would be pretty high. + I figured I would need a dedicated music player if I wanted to take my music + library on the go, and settled on the HIFI Walker H2 after reading some + online recommendations. The ability to install Rockbox, an open-source firmware, + was a big factor in my decision. I couldn't tell you how the device works + out of the box, since I flashed the firmware pretty much immediately once I got it, + but I've been super impressed with how the device works while running Rockbox. +

+

+

+ Screenshot of Rockbox player showing cool theme. +
I'm using a modified version of the InfoMatrix-v2 theme, which looks great.
+
+ Rockbox comes with many codecs for common audio formats including FLAC and MP3. The + device boots extremely quickly, and the interface is snappy. Virtually every aspect + of the user experience is tweakable and customizable to a crazy degree. I've even begun + listening to music on my player even at home, since a device specifically for the + purpose provides less distraction while I'm trying to be productive. +

+

+ All this to say I'm pretty much sold on Rockbox. But there's certain things I + still miss from my days of being a user of popular services like Spotify with + fancy APIs and data tracking. Things like Spotify wrapped or third-party apps + for visualizing playback statistics are a fun way to see what my listening history + looks like and could potentially be used to help find more music that I'd enjoy. + This is why when I noticed that Rockbock has a playback logging feature, a little + lightbulb lit up over my head. +

+
+
+

Generating and Parsing Logs

+

+

+ Logging +
The logging feature can be accessed through the settings menu.
+
+ Rockbox has a feature that logs playback information to a text file. This feature can + be enabled by setting Playback Settings > Logging to "On". With this setting enabled, a + new line gets added to the end of the .rockbox/playback.log file every time you play a track, + containing info about what you played and when. +

+

+ The logging feature is actually already used by the LastFM scrobbler plugin that comes preloaded with + Rockbox, which is probably the simplest way to get insights into your playback. However, + I personally want to avoid using third-party services as much as possible, because it's more fun. +

+

+ If I take a look at a logfile generated after a bit of listening, I'll see that I've wound up with + a series of lines that each look something like this: +

+
1758478258:336689:336718:/<microSD0>/Music/This Is The Glasshouse/This Is The Glasshouse - 867/This Is The Glasshouse - 867 - 01 Streetlight By Streetlight.flac
+
An example of a log entry for "Streetlight by Streetlight" by This is the Glasshouse. +
+
+

+

+ I wasn't really able to find any information online about the format of these logs, but they appear + to be simple enough to figure out. From what I can tell, each event is broken up into 4 pieces: +

    +
  1. Timestamp: The number of milliseconds since the UNIX epoch. +
  2. Playback Duration: The amount of the song that was played, in milliseconds. +
  3. Total Track Length: The length of the played track, in milliseconds. +
  4. File Path: An absolute path to the file containing the track on the filesystem. +
+ All of this is enough to know what I was listening to and when. I can use the file path to check for + audio tags which can help glean even more information about my listening habits. +

+

Now that I have this information and know how to interpret it, I'm ready to start processing it!

+
+
+

Analyzing Playback History

+

+ In order to get some useful information out of my playback history, I think it's a good idea to start by + building + a database. I created a sqlite database with the following tables: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
songs
idi64PK
titleString
artistsJSON
album_idi64?
genreString?
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
albums
idi64PK
titleString
artistString
cover_artBlob?
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
history
idi64PK
timestampDatetime
durationi64
song_idi64
+
+ I can add more columns later, but this is a good place to start. +

+

+ Now, as I read through the logfile line-by-line, I can check if each album exists before + inserting it into the database: +

+
for line in log_file.lines().flatten() {
+    println!("{line}");
+    // Skip comments
+    if line.starts_with("#") {
+        continue;
+    }
+    let chunks = line.split(":").collect::>();
+
+    let timestamp = DateTime::from_timestamp_secs(
+        i64::from_str_radix(chunks[0], 10).context("Failed to parse timestamp")?,
+    )
+    .context("Failed to convert timestamp")?;
+
+    // Load tags from file on device
+    let file_path = chunks[chunks.len() - 1][1..]
+        .split_once("/")
+        .context("Missing file")?
+        .1;
+    let tags = Tag::new()
+        .read_from_path(args.mount_point.join(file_path))
+        .context("Failed to read audio tags")?;
+
+    //...
+}
+
Parsing log entry and loading audio metadata.
+
+
+
if let Some(existing_album) =
+    sqlx::query("SELECT id FROM albums WHERE title=$1 AND artist=$2")
+        .bind(album_title)
+        .bind(album_artist)
+        .fetch_optional(&mut *db)
+        .await
+        .context("Failed to execute query to find existing album")?
+{
+    let album_id: i64 = existing_album.get("id");
+    info!("Album already exists, id {album_id}");
+    //...
+} else {
+    info!("Inserting new album: {album_title} by {album_artist}");
+    //...
+    let result = sqlx::query(
+        "INSERT INTO albums (title, artist, cover_art) VALUES ($1, $2, $3);",
+    )
+    .bind(album_title)
+    .bind(album_artist)
+    .bind(cover)
+    .execute(&mut *db)
+    .await
+    .context("Failed to execute query to insert album into database")?;
+
+    //...
+}
+
Checking for an album with matching artist and title before creating a new row in the + database.
+
+ I did something similar with the songs and history tables, basically building up a cache + of history information and skipping anything that's already in the database on repeat runs. +

+

+ With this database constructed, it's pretty easy to get a bunch of different information + about my listening. For example (forgive me if my SQL skills are kind of ass lol): +

+
SELECT 
+    songs.title AS song_title,
+    songs.artists AS song_artists,
+    songs.genre AS song_genre,
+    albums.title AS album_title,
+    albums.artist AS album_artist,
+    history.timestamp AS timestamp,
+    history.duration AS duration
+FROM history
+CROSS JOIN songs ON songs.id = history.song_id
+CROSS JOIN albums ON albums.id = songs.album_id
+ORDER BY timestamp DESC;
+
Querying for a list of each history entry along with track metadata, sorted from most to + least recent.
+
+
+
SELECT 
+    songs.genre,
+    SUM(history.duration) AS total_duration
+FROM history
+CROSS JOIN songs ON history.song_id = songs.id
+GROUP BY genre
+ORDER BY total_duration DESC
+LIMIT 10; 
+
Querying for the top 10 most listened genres by playtime.
+
+

+

+ It's all well and good to be able to view this information using a database client, + but it would be really cool if I could visualize this data somehow. +

+
+
+

Visualizing this Data Somehow

+

+ I wanted to make this data available on my website for people to view, and for a bunch of mostly trivial + reasons I won't get into here, I have a couple of requirements for pages on this site: +

    +
  1. Pages need to be static. +
  2. Pages need to be JavaScript-free. +
+ This means any chart rendering needs to be done automatically at build time before + deploying. I don't currently use a static site generator for my site (just for fun), + so I'm basically going to need to write one specifically to generate this page. +

+

+ I won't get too deep into the specifics of how I queried the database and generated each visualization + on + the page, but I can explain the visualizations I created using the queries from the previous section. + For the + listening history I wanted to generate a table displaying the information. To accomplish this, I first + used a combination of sqlx's ability to convert a row to a struct and serde to serialize + the rows as JSON values. +

+
#[derive(Serialize, Deserialize, FromRow)]
+struct HistoryEntry {
+    song_title: String,
+    song_artists: Value,
+    timestamp: DateTime<Utc>,
+    duration: i64,
+    album_title: String,
+    album_artist: Option<String>,
+    song_genre: Option<String>,
+}
+
+//...later
+let history = sqlx::query_as::<_, HistoryEntry>(
+    /* SELECT... */
+).fetch_all(&mut *db).await;
+
+//...later still, tera context accepts
+let mut context = tera::Context::new();
+context.insert("history", &history);
+
+
Struct definition for a history entry, allowing conversion from a sqlx row and + de/serialization from/to JSON.
+
+

+

+ In order to keep the generation as painless as possible, I decided to use the Tera template + engine, which allows me to define a template HTML file and substitute in values from + a context which I can define before rendering. In the case of the table, I can just generate a <tr> + matching the data for each item: +

+
{% macro history_table(history) %}
+<h3>Playback History</h3>
+<div class="table-container">
+    <table>
+        <thead>
+            <tr>
+                <th>Timestamp</th>
+                <th>Played Duration</th>
+                <th>Title</th>
+                <th>Artists</th>
+                <th>Album</th>
+                <th>Genre</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for item in history %}<tr>
+                <td>{{ item.timestamp | date(format="%Y-%m-%d %H:%M:%S") }}</td>
+                <td>{{ item.duration | hms }}</td>
+                <td>{{ item.song_title }}</td>
+                <td>{{ item.song_artists }}</td>
+                <td>{{ item.album_title }}</td>
+                <td>{{ item.song_genre }}</td>
+            </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+</div>
+{% endmacro history_table %}
+
+ A Tera macro for generating a table from a list of playback history items. + I used a macro so I can re-use this later if I want to add time range views. + (last month, year, etc.) +
+
+

+

+ I wrote similar macros for each of the visualizations I wanted to create. Most are + easy, but for my top 10 genres I wanted to display a pie chart. I found a pretty decent + data visualization crate called charming that's able to render to html, however + the output contains javascript so it's a no-go for me. Luckily, it can also render to + an SVG which I can embed nicely within the page. +

+ + + + + + + + + + + + + + + + + + +Progressive Rock +Alternative +Post-Rock +Post-Hardcore +Post-Metal +Rock +Shoegaze +Progressive Metal + + +
Here's one I generated just now.
+
+

+

+ And that's pretty much all there is to it! The finished thing can be found here. +

+
+
+ + + \ No newline at end of file diff --git a/blog/rockbox_stats/log-setting.bmp b/blog/rockbox_stats/log-setting.bmp new file mode 100644 index 0000000..29789fa Binary files /dev/null and b/blog/rockbox_stats/log-setting.bmp differ diff --git a/blog/rockbox_stats/playback-settings.bmp b/blog/rockbox_stats/playback-settings.bmp new file mode 100644 index 0000000..cee3cfb Binary files /dev/null and b/blog/rockbox_stats/playback-settings.bmp differ diff --git a/blog/rockbox_stats/player.bmp b/blog/rockbox_stats/player.bmp new file mode 100644 index 0000000..452a057 Binary files /dev/null and b/blog/rockbox_stats/player.bmp differ diff --git a/index.css b/index.css index 62d4571..9ae6d1e 100644 --- a/index.css +++ b/index.css @@ -9,15 +9,6 @@ html { image-rendering: pixelated; } -/* Links */ -a { - color: var(--blue); -} - -a:visited { - color: var(--purple); -} - #weird-fucking-header-container { display: inline-grid; grid-template-columns: 1fr; @@ -33,3 +24,4 @@ a:visited { grid-template-columns: 1fr 1fr 1fr; gap: 4px; } + diff --git a/index.html b/index.html index 2b49255..20341f8 100644 --- a/index.html +++ b/index.html @@ -33,18 +33,21 @@

Stuff on this Site

@@ -53,9 +56,9 @@
@@ -65,8 +68,8 @@
  • β–Άβƒž @soaosdev
  • 🐘 soaos@furry.engineer
  • πŸ¦€ - @Y1EKP4PU77qby4lI+m5MN6+NcYdjTdRQlV6NmluevuY=.ed25519
  • + href="https://ssb.soaos.dev/~core/ssb/#@Y1EKP4PU77qby4lI+m5MN6+NcYdjTdRQlV6NmluevuY=.ed25519" target="_blank">~😎 + soaos
    diff --git a/rockstats/index.html b/rockstats/index.html new file mode 100644 index 0000000..cfd916c --- /dev/null +++ b/rockstats/index.html @@ -0,0 +1,1301 @@ + + + + + + + + + + + + + + + + + +
    + β†° Back + βŒ‚ Home +
    +

    Rockbox Stats

    +

    This page shows a bunch of information about the music I've been listening to on my mp3 player. Think of it kind of like my own personal Spotify wrapped, minus the antichrist.

    +

    This page is updated wehenever I feel like regenerating it.

    + +

    Playback History

    +

    Total playtime: 7:57:48, 117 tracks

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TimestampPlayed DurationTitleArtistsAlbumGenre
    2025-09-23 19:41:013:37The Misty Veil of May[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:37:242:26An Old Owl Calling[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:34:583:03Night in a Mossy Hut[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:31:552:31Crying Wind[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:29:233:05Dawn[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:26:173:27Hidden Valley[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:22:503:32The Gathering of Deer[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:19:173:49A Wild River to Take You Home[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 19:15:223:16A Wild River to Take You Home[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-23 16:05:052:49Twin Fantasy (Those Boys)[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:52:1116:10Famous Prophets (Stars)[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:36:017:39High to Death[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:28:225:39Cute Thing[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:22:426:46Bodys[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:15:555:25Nervous Young Inhumans[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:10:305:04Sober to Death[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:05:251:29Stop Smoking (We Love You)[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 15:03:5613:18Beach Life‐in‐Death[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 14:50:372:52My Boy (Twin Fantasy)[Car Seat Headrest]Twin FantasyAlternative
    2025-09-23 14:47:441:32The Light & the Glass[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:46:123:54A Favor House Atlantic[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:42:184:15The Camper Velourium III: Al the Killer[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:38:025:22The Camper Velourium II: Backend of Forever[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:32:405:21The Camper Velourium I: Faint of Hearts[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:27:184:05Blood Red Summer[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:23:136:35The Crowing[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:16:375:08Three Evils (Embodied in Love and Shadow)[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:11:295:00Cuts Marked in the March of Men[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 14:06:288:12In Keeping Secrets of Silent Earth: 3[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 13:49:392:07The Ring in Return[Coheed and Cambria]In Keeping Secrets of Silent Earth: 3Progressive Rock
    2025-09-23 13:45:295:45Life and Death[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:39:433:25Father[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:36:172:16Son[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:34:013:15Go Get Your Gun[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:30:454:05This Beautiful Life[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:26:403:39He Said He Had a Story[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:23:014:41Saved[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:18:194:13Mustard Gas[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:14:065:01The Thief[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:09:054:51The Poison Woman[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 13:04:134:39The Tank[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 12:59:344:49What It Means to Be Alone[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 12:54:445:29In Cauda Venenum[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 12:49:151:38Writing on a Wall[The Dear Hunter]Act III: Life and DeathProgressive Rock
    2025-09-23 12:47:367:09Vital Vessels Vindicate[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:40:274:13Black Sandy Beaches[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:36:134:28Dear Ms. Leading[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:31:454:29Where the Road Parts[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:27:156:07Red Hands[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:22:051:13Red Hands[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:15:593:48Blood of the Rose[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:12:103:44Evicted[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:08:264:45Smiling Swine[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 12:03:407:46The Bitter Suite III: Embrace[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:55:546:06The Bitter Suite I & II: Meeting Ms. Leading / Through the Dime[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:49:474:57The Church & the Dime[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:44:494:18The Oracles on the Delphi Express[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:40:319:29The Lake and the River[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:35:124:25The Lake and the River[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:33:312:44The Lake and the River[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:30:474:59The Procession[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:25:570:38The Death and the Berth[The Dear Hunter]Act II: The Meaning of, & All Things Regarding Ms. LeadingProgressive Rock
    2025-09-23 11:24:064:03The River North[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 11:20:036:00His Hands Matched His Tongue[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 11:14:026:00The Pimp and the Priest[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 11:08:027:021878[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 11:01:005:56The Inquiry of Ms. Terri[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 10:55:035:56City Escape[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 10:49:071:43The Lake South[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 10:47:231:55Battesimo del fuoco[The Dear Hunter]Act I: The Lake South, the River NorthProgressive Rock
    2025-09-23 10:44:364:24Chapter X[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:40:123:32Chapter IX[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:36:393:39Chapter VIII[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:33:006:08Chapter VII[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:26:513:45Chapter VI[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:23:063:39Chapter V[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:19:273:24Chapter IV[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:16:024:24Chapter III[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:11:372:28Chapter II[Sufferer]SuffererPost-Hardcore
    2025-09-23 10:09:091:23Chapter I[Sufferer]SuffererPost-Hardcore
    2025-09-21 19:50:5312:26Sleep[Godspeed You Black Emperor!]Lift Your Skinny Fists Like Antennas to HeavenPost-Rock
    2025-09-21 19:50:183:27A Wild River to Take You Home[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-21 19:45:181:28A Wild River to Take You Home[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-21 19:43:490:28A Wild River to Take You Home[Black Hill & Silent Island]Tales of the Night ForestPost-Rock
    2025-09-21 19:40:110:48Night Ela (Mystic Thing)[Candy Claws]Ceres & Calypso in the Deep TimeShoegaze
    2025-09-21 19:28:371:28Night Ela (Mystic Thing)[Candy Claws]Ceres & Calypso in the Deep TimeShoegaze
    2025-09-21 19:26:400:42The Tragedy[The Pax Cecilia]Blessed Are the BondsPost-Metal
    2025-09-21 19:25:585:01A Dance With Death[We Lost the Sea]A Single FlowerPost-Rock
    2025-09-21 19:25:003:26Homecoming: Denied![Harakiri for the Sky]Aokigahara MMXXIIPost-Metal
    2025-09-21 19:21:331:18Keeping the Blade[Coheed and Cambria]Good Apollo I’m Burning Star IV, Volume One: From Fear Through the Eyes of MadnessProgressive Rock
    2025-09-21 19:20:141:47I Existed[Snooze]I Know How You Will DieProgressive Metal
    2025-09-21 19:14:227:40Robinson[This Is The Glasshouse]867Progressive Rock
    2025-09-21 19:06:423:35October[This Is The Glasshouse]867Progressive Rock
    2025-09-21 19:03:0710:21Old George[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:52:459:34867[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:43:113:38Two-Headed Calf[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:39:336:257Bass / Lorne[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:33:079:51January[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:23:153:39Southpaw[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:19:368:37Before Machinery[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:10:585:36Streetlight by Streetlight[This Is The Glasshouse]867Progressive Rock
    2025-09-21 18:05:210:17Streetlight by Streetlight[This Is The Glasshouse]867Progressive Rock
    2025-09-21 15:00:220:02Before Machinery[This Is The Glasshouse]867Progressive Rock
    2025-09-21 15:00:125:36Streetlight by Streetlight[This Is The Glasshouse]867Progressive Rock
    2025-09-21 02:29:430:24The Diary of Jane[Breaking Benjamin]PhobiaRock
    2025-09-21 02:29:201:13Intro[Breaking Benjamin]PhobiaRock
    2025-09-21 01:23:282:00The Diary of Jane[Breaking Benjamin]PhobiaRock
    2025-09-20 19:51:540:05Chapter X[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:410:01Chapter IX[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:390:01Chapter VIII[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:380:01Chapter VII[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:360:01Chapter VI[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:350:01Chapter V[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:330:01Chapter IV[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:320:01Chapter III[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:300:01Chapter II[Sufferer]SuffererPost-Hardcore
    2025-09-20 19:51:290:02Chapter I[Sufferer]SuffererPost-Hardcore
    +
    + + +

    Top Genres

    +
    +
    + + + + + + + + + + + + + + + + + + +Progressive Rock +Alternative +Post-Rock +Post-Hardcore +Post-Metal +Rock +Shoegaze +Progressive Metal + + +
    +
      + +
    1. Progressive Rock: 5:09:49 (64.8%) + +
    2. Alternative: 1:07:16 (14.1%) + +
    3. Post-Rock: 51:41 (10.8%) + +
    4. Post-Hardcore: 37:09 (7.8%) + +
    5. Post-Metal: 4:09 (0.9%) + +
    6. Rock: 3:39 (0.8%) + +
    7. Shoegaze: 2:17 (0.5%) + +
    8. Progressive Metal: 1:47 (0.4%) + +
    +
    + + +

    Top Albums

    +
      +
    1. +
      + + πŸ–Έ Act II: The Meaning of, & All Things Regarding Ms. Leading + πŸ‘€ The Dear Hunter + ⏱ 1:25:26 +
      +
    2. + +
    3. +
      + + πŸ–Έ 867 + 🫏 This Is The Glasshouse + ⏱ 1:14:56 +
      +
    4. + +
    5. +
      + + πŸ–Έ Twin Fantasy + πŸ‘€ Car Seat Headrest + ⏱ 1:07:16 +
      +
    6. + +
    7. +
      + + πŸ–Έ Act III: Life and Death + πŸ‘€ The Dear Hunter + ⏱ 57:51 +
      +
    8. + +
    9. +
      + + πŸ–Έ In Keeping Secrets of Silent Earth: 3 + πŸ‘€ Coheed and Cambria + ⏱ 51:36 +
      +
    10. + +
    11. +
      + + πŸ–Έ Act I: The Lake South, the River North + πŸ‘€ The Dear Hunter + ⏱ 38:38 +
      +
    12. + +
    13. +
      + + πŸ–Έ Sufferer + πŸ‘€ Sufferer + ⏱ 37:09 +
      +
    14. + +
    15. +
      + + πŸ–Έ Tales of the Night Forest + πŸ‘€ Black Hill & Silent Island + ⏱ 34:13 +
      +
    16. + +
    17. +
      + + πŸ–Έ Lift Your Skinny Fists Like Antennas to Heaven + πŸ‘€ Godspeed You! Black Emperor + ⏱ 12:26 +
      +
    18. + +
    19. +
      + + πŸ–Έ A Single Flower + πŸ‘€ We Lost the Sea + ⏱ 5:01 +
      +
    20. + +
    + + +

    Top Artists

    +
      +
    1. + The Dear Hunter: 3:01:57 +
    2. + +
    3. + This Is The Glasshouse: 1:14:56 +
    4. + +
    5. + Car Seat Headrest: 1:07:16 +
    6. + +
    7. + Coheed and Cambria: 52:55 +
    8. + +
    9. + Sufferer: 37:09 +
    10. + +
    11. + Black Hill & Silent Island: 34:13 +
    12. + +
    13. + Godspeed You Black Emperor!: 12:26 +
    14. + +
    15. + We Lost the Sea: 5:01 +
    16. + +
    17. + Breaking Benjamin: 3:39 +
    18. + +
    19. + Harakiri for the Sky: 3:26 +
    20. + +
    + + + + \ No newline at end of file diff --git a/style.css b/style.css index 03f81d9..c45a247 100644 --- a/style.css +++ b/style.css @@ -44,6 +44,14 @@ body { perspective-origin: top left } +pre, code, samp { + font-family: unifont; +} + +samp { + color: var(--gray2) +} + .section { margin: 1rem; padding: 1rem; @@ -188,3 +196,12 @@ a.evil:hover { flex-direction: column; align-items: center; } + +/* Links */ +a { + color: var(--blue); +} + +a:visited { + color: var(--purple); +} \ No newline at end of file -- cgit v1.2.3