Kenan Schaefkofer
is a software engineer with passions for games, music, math, and open-source software. This page highlights several personal projects built by Kenan over the years, most of which can still be enjoyed today. You can find his professional experience in his
resume.
{% assign sorted = site.data.projects | sort %} {% comment %} Only visible projects, so rows pair up correctly. {% endcomment %} {% assign visible = "" | split: "" %} {% for project_hash in sorted %} {% assign project = project_hash[1] %} {% unless project.id == "hide" %}{% assign visible = visible | push: project %}{% endunless %} {% endfor %}
{% for project in visible %}
{% assign col = forloop.index0 | modulo: 2 %}
{% if col == 0 %}
<script>
var cards = document.querySelectorAll(".project-card");
cards.forEach(function(card) {
card.addEventListener("mouseenter", function() {
data_id = event.target.getAttribute('data-id');
if (!data_id) return;
vid = document.querySelector('.project-vid[data-id="'+data_id+'"]');
if (!vid) return;
vid.play();
});
card.addEventListener("mouseleave", function() {
data_id = event.target.getAttribute('data-id');
if (!data_id) return;
vid = document.querySelector('.project-vid[data-id="'+data_id+'"]');
if (!vid) return;
vid.pause();
});
});
</script>
<script>
// In a two-card row, let the wordier card claim more horizontal space from
// its partner. Outer edges stay grid-aligned; the center seam goes ragged.
(function () {
var WIDE = window.matchMedia("(min-width: 1000px)");
function balanceRow(row) {
var cards = row.querySelectorAll(".project-card");
if (cards.length !== 2) return; // lone last card: leave as-is
// Measure each card's content demand at an equal 50/50 split.
row.style.gridTemplateColumns = "1fr 1fr";
var a = cards[0].querySelector(".card-right");
var b = cards[1].querySelector(".card-right");
// scrollHeight of the text column ~ how much room the words want.
var da = a ? a.scrollHeight : cards[0].scrollHeight;
var db = b ? b.scrollHeight : cards[1].scrollHeight;
if (!da || !db) return;
// Fully proportional split, clamped so neither card collapses.
var fa = da / (da + db);
fa = Math.max(0.3, Math.min(0.7, fa));
row.style.gridTemplateColumns = fa.toFixed(4) + "fr " + (1 - fa).toFixed(4) + "fr";
}
function balanceAll() {
var rows = document.querySelectorAll(".project-row");
rows.forEach(function (row) {
if (WIDE.matches) {
balanceRow(row);
} else {
row.style.gridTemplateColumns = ""; // single column when narrow
}
});
}
window.addEventListener("resize", balanceAll);
window.addEventListener("load", balanceAll); // after images settle layout
(WIDE.addEventListener ? WIDE.addEventListener("change", balanceAll)
: WIDE.addListener(balanceAll));
balanceAll();
})();
</script>
{% endif %}
{% if project.mp4 != "" %}
{% endif %}
{% if col == 1 or forloop.last %}
{% endif %}
{% endfor %}
{{ project.date }}
{{ project.title }}
{{ project.description }} {{ project.github-text }}