Web-demo styling

This commit is contained in:
2026-06-05 06:13:33 -04:00
parent d6304f1ef3
commit fb9674fcdb
4 changed files with 125 additions and 85 deletions
+42 -2
View File
@@ -86,11 +86,18 @@ body {
.layout {
display: grid;
grid-template-columns: 22rem minmax(0, 1fr);
grid-template-rows: auto 1fr;
grid-template-areas:
"col-top col-viewer"
"col-info col-viewer";
gap: 2rem;
padding: 2rem 1rem 1rem;
max-width: 78rem;
margin: 0 auto;
}
#col-top { grid-area: col-top; }
#col-viewer { grid-area: col-viewer; }
#col-info { grid-area: col-info; }
/* Control cards, form fields, and action buttons. */
.controls {
@@ -181,6 +188,32 @@ button.secondary:hover {
gap: 0.55rem;
}
/* Collapsible cards: clicking the h2 toggles visibility of card body.
Only cards with class .collapsible get this behavior. */
.card.collapsible > h2 {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.card.collapsible > h2::after {
content: "▾";
font-size: 2rem;
color: var(--muted);
margin-left: auto;
padding-left: 0.5rem;
flex-shrink: 0;
}
.card.collapsible.collapsed > h2::after {
content: "▸";
}
.card.collapsible > h2:hover::after {
color: var(--off-fg);
}
.card.collapsible.collapsed > *:not(h2) {
display: none;
}
.note p, .small {
color: var(--off-fg);
font-size: 0.82rem;
@@ -284,9 +317,16 @@ button.secondary:hover {
font-size: 0.76rem;
}
/* Stack controls above the board on narrower screens. */
/* On narrower screens, stack controls → viewer → info. */
@media (max-width: 900px) {
.layout { grid-template-columns: 1fr; }
.layout {
grid-template-columns: 1fr;
grid-template-rows: auto auto auto;
grid-template-areas:
"col-top"
"col-viewer"
"col-info";
}
.site-header { flex-direction: column; }
}
+12
View File
@@ -690,6 +690,16 @@ async function predict() {
}
/** Initialize collapsible cards: clicking the h2 toggles the .collapsed class. */
function initCollapsibleCards() {
document.querySelectorAll(".card.collapsible > h2").forEach((heading) => {
const card = heading.parentElement;
heading.addEventListener("click", () => {
card.classList.toggle("collapsed");
});
});
}
/** Attach a listener only when optional markup exists. */
function addListenerIfPresent(id, eventName, handler) {
const element = $(id);
@@ -717,6 +727,8 @@ async function init() {
await setBoardBackground("tb2");
syncAngleSelectors(40);
initCollapsibleCards();
addListenerIfPresent("gen-board", "change", async (e) => {
syncBoardSelectors(e.target.value);
await setBoardBackground(e.target.value);
+61 -80
View File
@@ -4,10 +4,9 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ClimbingBoardGPT</title>
<link rel="stylesheet" href="/static/app.css?v=19" />
<link rel="stylesheet" href="/static/app.css?v=20" />
</head>
<body>
<!-- Top-level status: the app script replaces this with model readiness. -->
<header class="site-header">
<div>
<p class="eyebrow">ClimbingBoardGPT</p>
@@ -18,9 +17,9 @@
</header>
<main class="layout">
<!-- Left column: generation controls, prediction controls, and project notes. -->
<section class="controls">
<div class="card">
<!-- Left column, row 1: interactive controls (collapsible). -->
<section class="controls" id="col-top">
<div class="card collapsible" id="card-gen">
<h2>Generate a climb</h2>
<label>Board
<select id="gen-board">
@@ -41,7 +40,7 @@
<button id="generate-btn">Generate</button>
</div>
<div class="card">
<div class="card collapsible" id="card-predict">
<h2>Predict grade</h2>
<label>Board
<select id="pred-board">
@@ -58,7 +57,7 @@
<button id="predict-btn" disabled>Predict pasted / clicked climb</button>
</div>
<div class="card">
<div class="card collapsible" id="card-builder">
<h2>Click holds to build a climb</h2>
<label>Role for next clicked hold
<select id="click-role">
@@ -78,80 +77,10 @@
</p>
<ul id="builder-list" class="builder-list"></ul>
</div>
<div class="card explain" id="explain-card">
<h2>What the controls mean</h2>
<dl>
<dt>Temperature</dt>
<dd>Sampling randomness. Lower values are more conservative; higher values are more exploratory.</dd>
<dt>Target V-grade</dt>
<dd>The grade token given to the generator. The generated route is also checked by the grade predictor.</dd>
<dt>Known climb</dt>
<dd>An exact match against the tokenized dataset: same board, same angle, and same hold-role set.</dd>
<dt>Validity</dt>
<dd>A structural check: enough holds, no duplicate placements, at least one start, and at least one finish.</dd>
</dl>
</div>
<div class="card note" id="model-disclaimer-card">
<h2>Research demo caveat</h2>
<p>
This is an experimental model demo. Generated climbs and predicted grades may be wrong, especially at rare grades or sparse board/angle combinations.
</p>
</div>
<div class="card explain">
<h2>How this works</h2>
<p>
Routes are converted into tokens such as
<code>&lt;BOARD_TB2&gt;</code>, <code>&lt;ANGLE_40&gt;</code>,
and <code>&lt;TB2_p652_start&gt;</code>.
</p>
<p>
The generator samples a token sequence. The grade predictor removes the grade token and estimates difficulty from the board, angle, and hold-role tokens.
</p>
</div>
<div class="card note">
<h2>Links</h2>
<ul class="link-list">
<li><a href="https://pawelsarkowicz.xyz" target="_blank" rel="noreferrer">pawelsarkowicz.xyz</a></li>
<li><a href="https://github.com/psark007/ClimbingBoardGPT" target="_blank" rel="noreferrer">ClimbingBoardGPT repo</a></li>
<li><a href="https://github.com/psark007/ClimbingBoardGPT/blob/main/LICENSE" target="_blank" rel="noreferrer">License</a></li>
<li><a href="https://github.com/psark007/Tension-Board-2-Analysis" target="_blank" rel="noreferrer">Tension Board 2 Analysis repo</a></li>
<li><a href="https://github.com/psark007/Kilter-Board-Analysis" target="_blank" rel="noreferrer">Kilter Board Analysis repo</a></li>
<li><a href="https://tensionclimbing.com/products/tension-board-2" target="_blank" rel="noreferrer">Tension Board 2</a></li>
<li><a href="https://settercloset.com/collections/kilter-board" target="_blank" rel="noreferrer">Kilter Board</a></li>
<li><a href="https://github.com/karpathy/nanoGPT" target="_blank" rel="noreferrer">nanoGPT</a></li>
</ul>
</div>
<div class="card note" id="angle-scope-card">
<h2>Angle scope</h2>
<p>
The physical boards can be used at steeper angles than this demo exposes. This model snapshot is intentionally restricted to the angle range used in training/evaluation: TB2 up to 50° and Kilter up to 55°.
</p>
<p>
The restriction avoids asking the models to extrapolate into sparse, noisier high-angle data where grades and route distributions are less reliable.
</p>
</div>
<div class="card note" id="data-acknowledgement-card">
<h2>Data acknowledgement</h2>
<p>
Board layouts, hold metadata, and route data are derived from Tension Board 2 and Kilter Board datasets.
This project is unaffiliated with Tension Climbing or Kilter.
The route generator is inspired by Andrej Karpathy's
<a href="https://github.com/karpathy/nanoGPT" target="_blank" rel="noreferrer">nanoGPT</a>.
</p>
</div>
</section>
<!-- Right column: generated/predicted route result and SVG board overlay. -->
<section class="viewer">
<section class="viewer" id="col-viewer">
<div class="result-card">
<div class="result-header">
<h2 id="result-title">Choose a board and run a request</h2>
@@ -175,9 +104,61 @@
</details>
</div>
</section>
<!-- Left column, row 2: info cards (below board on mobile, left column on desktop). -->
<section class="controls" id="col-info">
<div class="card explain">
<h2>What the controls mean</h2>
<dl>
<dt>Temperature</dt>
<dd>Sampling randomness. Lower values are more conservative; higher values are more exploratory.</dd>
<dt>Target V-grade</dt>
<dd>The grade token given to the generator. The generated route is also checked by the grade predictor.</dd>
<dt>Known climb</dt>
<dd>An exact match against the tokenized dataset: same board, same angle, and same hold-role set.</dd>
<dt>Validity</dt>
<dd>A structural check: enough holds, no duplicate placements, at least one start, and at least one finish.</dd>
</dl>
</div>
<div class="card note" id="model-disclaimer-card">
<h2>Research demo caveat</h2>
<p>This is an experimental model demo. Generated climbs and predicted grades may be wrong, especially at rare grades or sparse board/angle combinations.</p>
</div>
<div class="card explain">
<h2>How this works</h2>
<p>Routes are converted into tokens such as <code>&lt;BOARD_TB2&gt;</code>, <code>&lt;ANGLE_40&gt;</code>, and <code>&lt;TB2_p652_start&gt;</code>.</p>
<p>The generator samples a token sequence. The grade predictor removes the grade token and estimates difficulty from the board, angle, and hold-role tokens.</p>
</div>
<div class="card note">
<h2>Links</h2>
<ul class="link-list">
<li><a href="https://pawelsarkowicz.xyz" target="_blank" rel="noreferrer">pawelsarkowicz.xyz</a></li>
<li><a href="https://github.com/psark007/ClimbingBoardGPT" target="_blank" rel="noreferrer">ClimbingBoardGPT repo</a></li>
<li><a href="https://github.com/psark007/ClimbingBoardGPT/blob/main/LICENSE" target="_blank" rel="noreferrer">License</a></li>
<li><a href="https://github.com/psark007/Tension-Board-2-Analysis" target="_blank" rel="noreferrer">Tension Board 2 Analysis repo</a></li>
<li><a href="https://github.com/psark007/Kilter-Board-Analysis" target="_blank" rel="noreferrer">Kilter Board Analysis repo</a></li>
<li><a href="https://tensionclimbing.com/products/tension-board-2" target="_blank" rel="noreferrer">Tension Board 2</a></li>
<li><a href="https://settercloset.com/collections/kilter-board" target="_blank" rel="noreferrer">Kilter Board</a></li>
<li><a href="https://github.com/karpathy/nanoGPT" target="_blank" rel="noreferrer">nanoGPT</a></li>
</ul>
</div>
<div class="card note" id="angle-scope-card">
<h2>Angle scope</h2>
<p>The physical boards can be used at steeper angles than this demo exposes. This model snapshot is intentionally restricted to the angle range used in training/evaluation: TB2 up to 50° and Kilter up to 55°.</p>
<p>The restriction avoids asking the models to extrapolate into sparse, noisier high-angle data where grades and route distributions are less reliable.</p>
</div>
<div class="card note" id="data-acknowledgement-card">
<h2>Data acknowledgement</h2>
<p>Board layouts, hold metadata, and route data are derived from Tension Board 2 and Kilter Board datasets. This project is unaffiliated with Tension Climbing or Kilter. The route generator is inspired by Andrej Karpathy's <a href="https://github.com/karpathy/nanoGPT" target="_blank" rel="noreferrer">nanoGPT</a>.</p>
</div>
</section>
</main>
<!-- External project links and license metadata. -->
<footer class="site-footer">
<span>© Pawel Sarkowicz</span>
<a href="https://pawelsarkowicz.xyz" target="_blank" rel="noreferrer">pawelsarkowicz.xyz</a>
@@ -187,4 +168,4 @@
<script src="/static/app.js?v=17"></script>
</body>
</html>
</html>