Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 81 additions & 9 deletions website/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const noResults = document.querySelector(".no-results");
const rows = document.querySelectorAll(".table tbody tr.row");
const tags = document.querySelectorAll(".tag");
const tbody = document.querySelector(".table tbody");
let activeDetailRow = null;
let detailPanel = null;

function initRevealSections() {
const sections = document.querySelectorAll("[data-reveal]");
Expand Down Expand Up @@ -128,6 +130,77 @@ function collapseAll() {
row.classList.remove("open");
row.setAttribute("aria-expanded", "false");
});
closeDetailPanel();
}

function getDetailPanel() {
if (detailPanel) return detailPanel;

detailPanel = document.createElement("aside");
detailPanel.id = "project-detail-panel";
detailPanel.className = "detail-panel";
detailPanel.setAttribute("role", "complementary");
detailPanel.setAttribute("aria-hidden", "true");
detailPanel.setAttribute("aria-labelledby", "project-detail-panel-title");
detailPanel.innerHTML =
'<div class="detail-panel-shell">' +
'<div class="detail-panel-header">' +
'<div>' +
'<div class="detail-panel-kicker">Project details</div>' +
'<div class="detail-panel-title" id="project-detail-panel-title"></div>' +
"</div>" +
'<button type="button" class="detail-panel-close" aria-label="Close details">&times;</button>' +
"</div>" +
'<div class="detail-panel-body"></div>' +
"</div>";

detailPanel
.querySelector(".detail-panel-close")
.addEventListener("click", closeDetailPanel);
document.body.appendChild(detailPanel);
return detailPanel;
}

function closeDetailPanel() {
const rowToFocus = activeDetailRow;
if (activeDetailRow) {
activeDetailRow.classList.remove("selected");
activeDetailRow.setAttribute("aria-expanded", "false");
activeDetailRow.removeAttribute("aria-controls");
activeDetailRow = null;
}
if (!detailPanel) return;
detailPanel.classList.remove("visible");
detailPanel.setAttribute("aria-hidden", "true");
return rowToFocus;
}

function openDetailPanel(row, options) {
const shouldFocus = options && options.focusPanel;
if (!row._expandRow) return;
if (activeDetailRow === row) {
const closedRow = closeDetailPanel();
if (shouldFocus && closedRow) closedRow.focus();
return;
}

closeDetailPanel();

const panel = getDetailPanel();
const body = panel.querySelector(".detail-panel-body");
const title = panel.querySelector(".detail-panel-title");
const name = row.querySelector(".col-name a");
const content = row._expandRow.querySelector(".expand-content");
title.textContent = name ? name.textContent.trim() : "Project details";
body.innerHTML = content ? content.innerHTML : "";

row.classList.add("selected");
row.setAttribute("aria-expanded", "true");
row.setAttribute("aria-controls", panel.id);
activeDetailRow = row;
panel.classList.add("visible");
panel.setAttribute("aria-hidden", "false");
if (shouldFocus) panel.querySelector(".detail-panel-close").focus();
}

function applyFilters() {
Expand Down Expand Up @@ -247,6 +320,7 @@ function getSortValue(row, col) {

function sortRows() {
if (!tbody) return;
closeDetailPanel();

const arr = Array.prototype.slice.call(rows);
const col = activeSort.col;
Expand Down Expand Up @@ -313,14 +387,7 @@ if (tbody) {
}
if (!row) return;

const isOpen = row.classList.contains("open");
if (isOpen) {
row.classList.remove("open");
row.setAttribute("aria-expanded", "false");
} else {
row.classList.add("open");
row.setAttribute("aria-expanded", "true");
}
openDetailPanel(row);
});

// Keyboard: Enter or Space on focused .row toggles expand
Expand All @@ -329,7 +396,7 @@ if (tbody) {
const row = e.target.closest("tr.row");
if (!row) return;
e.preventDefault();
row.click();
openDetailPanel(row, { focusPanel: true });
});
}

Expand Down Expand Up @@ -401,6 +468,11 @@ if (searchInput) {
});

document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && detailPanel && activeDetailRow) {
const rowToFocus = closeDetailPanel();
if (rowToFocus) rowToFocus.focus();
return;
}
if (
e.key === "/" &&
!["INPUT", "TEXTAREA", "SELECT"].includes(
Expand Down
148 changes: 148 additions & 0 deletions website/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,10 @@ kbd {
border-bottom-color: transparent;
}

.row.selected td {
background: linear-gradient(180deg, var(--row-open-start), var(--row-open-end));
}

.col-num {
width: 3.5rem;
color: var(--ink-muted);
Expand Down Expand Up @@ -1036,6 +1040,119 @@ th[data-sort].sort-asc::after {
vertical-align: bottom;
}

.detail-panel {
position: fixed;
top: 4.75rem;
right: 0.75rem;
z-index: 40;
width: min(30rem, calc(100vw - 1.5rem));
max-height: calc(100vh - 6rem);
overflow: hidden;
color: var(--ink-soft);
background:
linear-gradient(180deg, oklch(100% 0 0 / 0.9), oklch(97% 0.01 75 / 0.96)),
var(--bg-paper);
border: 1px solid var(--line-strong);
border-radius: 0.9rem;
box-shadow:
0 1.7rem 3.8rem -2.4rem oklch(0% 0 0 / 0.55),
0 0.35rem 1.2rem -0.9rem oklch(0% 0 0 / 0.28);
opacity: 0;
pointer-events: none;
transform: translateX(1rem) scale(0.985);
transition:
opacity 180ms ease,
transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.detail-panel.visible {
opacity: 1;
pointer-events: auto;
transform: translateX(0) scale(1);
}

.detail-panel-shell {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
max-height: inherit;
}

.detail-panel-header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 1rem;
align-items: start;
padding: 1.05rem 1.15rem 0.95rem;
border-bottom: 1px solid var(--line);
background: oklch(98% 0.01 75 / 0.72);
}

.detail-panel-kicker {
margin-bottom: 0.32rem;
color: var(--ink-muted);
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}

.detail-panel-body {
max-height: calc(100vh - 12rem);
padding: 1.05rem 1.15rem 1.15rem;
overflow: auto;
}

.detail-panel-close {
width: 2rem;
height: 2rem;
border: 1px solid var(--line);
border-radius: 999px;
color: var(--ink-muted);
background: var(--bg-paper);
font: inherit;
font-size: 1.15rem;
line-height: 1;
cursor: pointer;
transition:
color 160ms ease,
border-color 160ms ease,
background-color 160ms ease,
transform 160ms ease;
}

.detail-panel-close:hover {
color: var(--ink);
background: var(--accent-soft);
border-color: oklch(68% 0.08 58 / 0.5);
}

.detail-panel-close:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}

.detail-panel-close:active {
transform: translateY(1px);
}

.detail-panel-title {
margin: 0;
color: var(--ink);
font-size: var(--text-lg);
font-weight: 800;
line-height: 1.2;
overflow-wrap: break-word;
}

.detail-panel .expand-content {
animation: none;
}

.detail-panel .expand-meta {
padding-top: 0.8rem;
border-top: 1px solid var(--line);
}

.expand-sep {
margin-inline: 0.25rem;
color: var(--line-strong);
Expand Down Expand Up @@ -1722,6 +1839,33 @@ th[data-sort].sort-asc::after {
padding-right: 0.8rem;
}

.detail-panel {
top: auto;
right: 0;
bottom: 0;
left: 0;
width: auto;
max-height: min(74vh, 34rem);
border-right: 0;
border-bottom: 0;
border-left: 0;
border-radius: 1rem 1rem 0 0;
transform: translateY(1.2rem);
}

.detail-panel.visible {
transform: translateY(0);
}

.detail-panel-header,
.detail-panel-body {
padding-inline: 1rem;
}

.detail-panel-body {
max-height: calc(min(74vh, 34rem) - 5.5rem);
}

.col-stars {
width: 5.4rem;
}
Expand All @@ -1736,6 +1880,10 @@ th[data-sort].sort-asc::after {
scroll-behavior: auto;
}

.detail-panel {
transition: none;
}

*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
Expand Down