Spaces:
Running on Zero
Running on Zero
Deploy Karate Wiener (kimodo kata maker)
Browse files- app.py +36 -22
- tabs/dojo.drawer.html +9 -4
- tabs/dojo.tab.js +16 -4
app.py
CHANGED
|
@@ -250,11 +250,23 @@ APP_CSS = """
|
|
| 250 |
.kimodo-cz-katafeat:hover:not(:disabled) { background: #7c6226 !important; color: #fff1c4 !important; }
|
| 251 |
.kimodo-cz-katafeat:disabled { cursor: default; }
|
| 252 |
.kimodo-cz-katafeat.featured { background: #8a6d12 !important; border-color: #d9ad33 !important; color: #fff6d6 !important; cursor: default; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
/* Library browse controls: search filter + sort select. */
|
| 254 |
-
.kimodo-cz-libctrls { display: flex; gap: 6px;
|
| 255 |
.kimodo-cz-cfilter { flex: 1; margin-bottom: 0 !important; }
|
| 256 |
.kimodo-cz-csort { flex: 0 0 auto; padding: 8px 8px; font-size: 12px; color: #f2e6ff; background: #1c1622; border: 1px solid #4a3a60; border-radius: 8px; cursor: pointer; }
|
| 257 |
.kimodo-cz-csort:focus { outline: none; border-color: #a86fe0; }
|
|
|
|
|
|
|
|
|
|
| 258 |
/* Card footer: upvote on the left, "★ Featured" badge pushed to the lower-right. */
|
| 259 |
.kimodo-cz-cardfoot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
| 260 |
/* Per-card upvote: ▲ + count; filled when this browser has voted. */
|
|
@@ -737,8 +749,8 @@ html, body, gradio-app, .gradio-container { height: 100%; margin: 0; overflow: h
|
|
| 737 |
.kimodo-cz-tlcard.end .kimodo-cz-tlnode.endn { border-color: #d9776a; }
|
| 738 |
.kimodo-cz-tlcard.gen { background: #1d1730; border-color: #4a3a6a; } /* saved move = darker purple */
|
| 739 |
.kimodo-cz-tlcard.cz-pending { background: #232026; border-color: #4a474f; }
|
| 740 |
-
.kimodo-cz-tlcard.cz-active { border-color: #
|
| 741 |
-
.kimodo-cz-tlcard.sel { box-shadow: 0 0 0 2px #
|
| 742 |
.kimodo-cz-tlcard.cz-justgen { animation: kimodo-cz-fromgray-card .55s ease; }
|
| 743 |
.kimodo-cz-pcardrow.cz-pop .kimodo-cz-tlcard { animation: kimodo-cz-pop .5s cubic-bezier(.34,1.56,.64,1) both; }
|
| 744 |
.kimodo-cz-tlmeta { flex: 1; min-width: 0; }
|
|
@@ -749,7 +761,7 @@ html, body, gradio-app, .gradio-container { height: 100%; margin: 0; overflow: h
|
|
| 749 |
.kimodo-cz-tlx { position: absolute; top: 3px; right: 5px; }
|
| 750 |
.kimodo-cz-tlgrip { position: absolute; left: 0; right: 0; bottom: 0; height: 15px; display: flex; align-items: center; justify-content: center; cursor: ns-resize; touch-action: none; z-index: 5; }
|
| 751 |
.kimodo-cz-tlgrip span { width: 28px; height: 3px; border-radius: 2px; background: #6a5a86; transition: background .15s ease; }
|
| 752 |
-
.kimodo-cz-tlgrip:hover span, .kimodo-cz-tlcard.cz-active .kimodo-cz-tlgrip span { background: #
|
| 753 |
/* active-move controls below the timeline */
|
| 754 |
.kimodo-cz-controls { margin-top: 12px; padding-top: 10px; border-top: 1px solid #2a2233; }
|
| 755 |
.kimodo-cz-controlslab { font-size: 11px; font-weight: 600; color: #c9b6ea; margin-bottom: 6px; }
|
|
@@ -757,7 +769,7 @@ html, body, gradio-app, .gradio-container { height: 100%; margin: 0; overflow: h
|
|
| 757 |
to set the length = the gap between them), GENERATE right under the end, then the
|
| 758 |
collapsed refine prompt + the move/rotation numbers. ---- */
|
| 759 |
.kimodo-cz-activerow { overflow: visible; }
|
| 760 |
-
.kimodo-cz-editcard { position: relative; border: 1px solid #
|
| 761 |
/* A move edited after it was generated → dirty (amber) so it's clearly "needs Refresh". */
|
| 762 |
.kimodo-cz-editcard.cz-dirty { border-color: #5a5a64; background: #26262c; box-shadow: 0 0 0 1px rgba(90,90,100,0.35); } /* unsaved edit = gray (like a new move) */
|
| 763 |
/* Not generated yet → gray. Once generated it pulses gray→purple; selecting a generated
|
|
@@ -766,14 +778,14 @@ html, body, gradio-app, .gradio-container { height: 100%; margin: 0; overflow: h
|
|
| 766 |
.kimodo-cz-editcard.cz-genpulse { animation: kimodo-cz-gray2purple .6s ease both; }
|
| 767 |
.kimodo-cz-editcard.cz-selpulse { animation: kimodo-cz-green2purple .6s ease both; }
|
| 768 |
@keyframes kimodo-cz-gray2purple {
|
| 769 |
-
0% { background: #26262c; border-color: #5a5a64; box-shadow: 0 0 0 0 rgba(
|
| 770 |
-
45% { box-shadow: 0 0 0 5px rgba(
|
| 771 |
-
100% { background: #241a30; border-color: #
|
| 772 |
}
|
| 773 |
@keyframes kimodo-cz-green2purple {
|
| 774 |
-
0% { background: #1d1730; border-color: #4a3a6a; box-shadow: 0 0 0 0 rgba(
|
| 775 |
-
45% { box-shadow: 0 0 0 5px rgba(
|
| 776 |
-
100% { background: #241a30; border-color: #
|
| 777 |
}
|
| 778 |
.kimodo-cz-emmovelab { font-size: 10px; font-weight: 700; letter-spacing: .04em; color: #c9b6ea; margin: 0 0 5px 2px; }
|
| 779 |
.kimodo-cz-emregion { position: relative; margin: 2px 2px 8px; }
|
|
@@ -4197,20 +4209,25 @@ def _drawers_html() -> str:
|
|
| 4197 |
# Library view (home): Mine/Community filter + a "+ Create a kata" card on
|
| 4198 |
# top of the list, then the saved-kata cards.
|
| 4199 |
'<div id="kimodo-cz-library" class="kimodo-cz-view cz-active">',
|
|
|
|
|
|
|
|
|
|
| 4200 |
'<div class="kimodo-cz-seg kimodo-cz-seg--sub" id="kimodo-cz-scope">',
|
| 4201 |
'<button type="button" class="kimodo-cz-segbtn active" data-scope="mine">Mine</button>',
|
| 4202 |
'<button type="button" class="kimodo-cz-segbtn" data-scope="community">Community</button>',
|
| 4203 |
'</div>',
|
| 4204 |
-
#
|
| 4205 |
-
|
|
|
|
| 4206 |
'<div class="kimodo-cz-libctrls" id="kimodo-cz-libctrls">',
|
| 4207 |
-
'<input id="kimodo-cz-cfilter" class="kimodo-
|
| 4208 |
'<select id="kimodo-cz-csort" class="kimodo-cz-csort" title="Sort / filter">',
|
| 4209 |
'<option value="top">▲ Top</option>',
|
| 4210 |
'<option value="newest">Newest</option>',
|
| 4211 |
'<option value="featured">★ Featured</option>',
|
| 4212 |
'</select>',
|
| 4213 |
'</div>',
|
|
|
|
| 4214 |
'<div id="kimodo-cz-katalist" class="kimodo-cz-katalist"></div>',
|
| 4215 |
'</div>',
|
| 4216 |
# Kata view (unified): tap a kata → view/edit it here with a scrubbable
|
|
@@ -5397,8 +5414,9 @@ _COMPOSE_JS = r"""
|
|
| 5397 |
function renderComposeLibrary() {
|
| 5398 |
const list = document.getElementById('kimodo-cz-katalist'); if (!list) return;
|
| 5399 |
document.querySelectorAll('#kimodo-cz-scope .kimodo-cz-segbtn').forEach((b) => b.classList.toggle('active', b.dataset.scope === composeScope));
|
| 5400 |
-
//
|
| 5401 |
const ctrls = document.getElementById('kimodo-cz-libctrls'); if (ctrls) ctrls.style.display = (composeScope === 'community') ? '' : 'none';
|
|
|
|
| 5402 |
// Mine = the localStorage entries (recipe-backed, instant); Community = the
|
| 5403 |
// server manifest. Mine cards are always recipe-editable; their filmstrips draw
|
| 5404 |
// from stance ids, so they don't depend on per-clip record files.
|
|
@@ -5419,13 +5437,7 @@ _COMPOSE_JS = r"""
|
|
| 5419 |
return (b.created || 0) - (a.created || 0);
|
| 5420 |
});
|
| 5421 |
list.innerHTML = '';
|
| 5422 |
-
// + Create a kata
|
| 5423 |
-
if (composeScope === 'mine') {
|
| 5424 |
-
const createCard = document.createElement('div'); createCard.className = 'kimodo-cz-createcard';
|
| 5425 |
-
createCard.innerHTML = '<span style="font-size:16px">+</span><span>Create a kata</span>';
|
| 5426 |
-
createCard.onclick = () => newComposeKata();
|
| 5427 |
-
list.appendChild(createCard);
|
| 5428 |
-
}
|
| 5429 |
if (!shown.length) {
|
| 5430 |
const e = document.createElement('div'); e.className = 'kimodo-cz-libempty';
|
| 5431 |
e.textContent = composeScope === 'mine'
|
|
@@ -6589,6 +6601,8 @@ _COMPOSE_JS = r"""
|
|
| 6589 |
if (cfilter) cfilter.addEventListener('input', () => { composeFilter = cfilter.value || ''; renderComposeLibrary(); });
|
| 6590 |
const csort = document.getElementById('kimodo-cz-csort');
|
| 6591 |
if (csort) { csort.value = composeSort; csort.addEventListener('change', () => { composeSort = csort.value || 'top'; renderComposeLibrary(); }); }
|
|
|
|
|
|
|
| 6592 |
// Move-mode pill: only changes the mode while the kata is empty (then it's locked).
|
| 6593 |
const modeSeg = document.getElementById('kimodo-cz-mode');
|
| 6594 |
if (modeSeg) modeSeg.addEventListener('click', (e) => {
|
|
|
|
| 250 |
.kimodo-cz-katafeat:hover:not(:disabled) { background: #7c6226 !important; color: #fff1c4 !important; }
|
| 251 |
.kimodo-cz-katafeat:disabled { cursor: default; }
|
| 252 |
.kimodo-cz-katafeat.featured { background: #8a6d12 !important; border-color: #d9ad33 !important; color: #fff6d6 !important; cursor: default; }
|
| 253 |
+
/* SHARED browse header (kata + dojo libraries): the Mine/Community pill + create/add
|
| 254 |
+
button + search/sort, anchored (sticky) at the top of the scrolling list so they don't
|
| 255 |
+
scroll away. Both libraries are their own scroll container, so `position:sticky` works. */
|
| 256 |
+
.kimodo-libhead { position: sticky; top: 0; z-index: 8; display: flex; flex-direction: column; gap: 8px; padding: 2px 0 9px; margin-bottom: 4px; background: #16161b; border-bottom: 1px solid #2a2233; }
|
| 257 |
+
.kimodo-libhead .kimodo-cz-seg, .kimodo-libhead .kimodo-cz-libctrls, .kimodo-libhead .kimodo-cz-createbtn, .kimodo-libhead .kimodo-dojo-addbtn { margin: 0 !important; }
|
| 258 |
+
/* Shared search input (kata + dojo). */
|
| 259 |
+
.kimodo-libsearch { width: 100%; box-sizing: border-box; padding: 8px 10px; font-size: 13px; color: #f2e6ff; background: #1c1622; border: 1px solid #4a3a60; border-radius: 8px; }
|
| 260 |
+
.kimodo-libsearch::placeholder { color: #8a7aa0; }
|
| 261 |
+
.kimodo-libsearch:focus { outline: none; border-color: #a86fe0; }
|
| 262 |
/* Library browse controls: search filter + sort select. */
|
| 263 |
+
.kimodo-cz-libctrls { display: flex; gap: 6px; }
|
| 264 |
.kimodo-cz-cfilter { flex: 1; margin-bottom: 0 !important; }
|
| 265 |
.kimodo-cz-csort { flex: 0 0 auto; padding: 8px 8px; font-size: 12px; color: #f2e6ff; background: #1c1622; border: 1px solid #4a3a60; border-radius: 8px; cursor: pointer; }
|
| 266 |
.kimodo-cz-csort:focus { outline: none; border-color: #a86fe0; }
|
| 267 |
+
/* Header "Create a kata" button (was an in-list card; now anchored, Mine only). */
|
| 268 |
+
.kimodo-cz-createbtn { display: flex; align-items: center; justify-content: center; gap: 7px; width: 100%; padding: 11px; border-radius: 9px; cursor: pointer; font-size: 13px; font-weight: 600; color: #e6c7ff !important; background: #3a2a55 !important; border: 1px dashed #a86fe0 !important; box-shadow: none !important; }
|
| 269 |
+
.kimodo-cz-createbtn:hover { background: #46356a !important; }
|
| 270 |
/* Card footer: upvote on the left, "★ Featured" badge pushed to the lower-right. */
|
| 271 |
.kimodo-cz-cardfoot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
| 272 |
/* Per-card upvote: ▲ + count; filled when this browser has voted. */
|
|
|
|
| 749 |
.kimodo-cz-tlcard.end .kimodo-cz-tlnode.endn { border-color: #d9776a; }
|
| 750 |
.kimodo-cz-tlcard.gen { background: #1d1730; border-color: #4a3a6a; } /* saved move = darker purple */
|
| 751 |
.kimodo-cz-tlcard.cz-pending { background: #232026; border-color: #4a474f; }
|
| 752 |
+
.kimodo-cz-tlcard.cz-active { border-color: #7dd3fc; box-shadow: 0 0 0 1px #7dd3fc; }
|
| 753 |
+
.kimodo-cz-tlcard.sel { box-shadow: 0 0 0 2px #7dd3fc; }
|
| 754 |
.kimodo-cz-tlcard.cz-justgen { animation: kimodo-cz-fromgray-card .55s ease; }
|
| 755 |
.kimodo-cz-pcardrow.cz-pop .kimodo-cz-tlcard { animation: kimodo-cz-pop .5s cubic-bezier(.34,1.56,.64,1) both; }
|
| 756 |
.kimodo-cz-tlmeta { flex: 1; min-width: 0; }
|
|
|
|
| 761 |
.kimodo-cz-tlx { position: absolute; top: 3px; right: 5px; }
|
| 762 |
.kimodo-cz-tlgrip { position: absolute; left: 0; right: 0; bottom: 0; height: 15px; display: flex; align-items: center; justify-content: center; cursor: ns-resize; touch-action: none; z-index: 5; }
|
| 763 |
.kimodo-cz-tlgrip span { width: 28px; height: 3px; border-radius: 2px; background: #6a5a86; transition: background .15s ease; }
|
| 764 |
+
.kimodo-cz-tlgrip:hover span, .kimodo-cz-tlcard.cz-active .kimodo-cz-tlgrip span { background: #7dd3fc; }
|
| 765 |
/* active-move controls below the timeline */
|
| 766 |
.kimodo-cz-controls { margin-top: 12px; padding-top: 10px; border-top: 1px solid #2a2233; }
|
| 767 |
.kimodo-cz-controlslab { font-size: 11px; font-weight: 600; color: #c9b6ea; margin-bottom: 6px; }
|
|
|
|
| 769 |
to set the length = the gap between them), GENERATE right under the end, then the
|
| 770 |
collapsed refine prompt + the move/rotation numbers. ---- */
|
| 771 |
.kimodo-cz-activerow { overflow: visible; }
|
| 772 |
+
.kimodo-cz-editcard { position: relative; border: 1px solid #7dd3fc; border-radius: 10px; background: #241a30; padding: 8px; box-shadow: 0 0 0 1px rgba(125,211,252,0.35); }
|
| 773 |
/* A move edited after it was generated → dirty (amber) so it's clearly "needs Refresh". */
|
| 774 |
.kimodo-cz-editcard.cz-dirty { border-color: #5a5a64; background: #26262c; box-shadow: 0 0 0 1px rgba(90,90,100,0.35); } /* unsaved edit = gray (like a new move) */
|
| 775 |
/* Not generated yet → gray. Once generated it pulses gray→purple; selecting a generated
|
|
|
|
| 778 |
.kimodo-cz-editcard.cz-genpulse { animation: kimodo-cz-gray2purple .6s ease both; }
|
| 779 |
.kimodo-cz-editcard.cz-selpulse { animation: kimodo-cz-green2purple .6s ease both; }
|
| 780 |
@keyframes kimodo-cz-gray2purple {
|
| 781 |
+
0% { background: #26262c; border-color: #5a5a64; box-shadow: 0 0 0 0 rgba(125,211,252,0); }
|
| 782 |
+
45% { box-shadow: 0 0 0 5px rgba(125,211,252,0.5); transform: scale(1.02); }
|
| 783 |
+
100% { background: #241a30; border-color: #7dd3fc; box-shadow: 0 0 0 1px rgba(125,211,252,0.35); transform: scale(1); }
|
| 784 |
}
|
| 785 |
@keyframes kimodo-cz-green2purple {
|
| 786 |
+
0% { background: #1d1730; border-color: #4a3a6a; box-shadow: 0 0 0 0 rgba(125,211,252,0); } /* from the darker-purple saved card */
|
| 787 |
+
45% { box-shadow: 0 0 0 5px rgba(125,211,252,0.5); transform: scale(1.02); }
|
| 788 |
+
100% { background: #241a30; border-color: #7dd3fc; box-shadow: 0 0 0 1px rgba(125,211,252,0.35); transform: scale(1); }
|
| 789 |
}
|
| 790 |
.kimodo-cz-emmovelab { font-size: 10px; font-weight: 700; letter-spacing: .04em; color: #c9b6ea; margin: 0 0 5px 2px; }
|
| 791 |
.kimodo-cz-emregion { position: relative; margin: 2px 2px 8px; }
|
|
|
|
| 4209 |
# Library view (home): Mine/Community filter + a "+ Create a kata" card on
|
| 4210 |
# top of the list, then the saved-kata cards.
|
| 4211 |
'<div id="kimodo-cz-library" class="kimodo-cz-view cz-active">',
|
| 4212 |
+
# Anchored (sticky) browse header: pill + Create button (Mine) + search/sort
|
| 4213 |
+
# (Community). renderComposeLibrary toggles which controls show per scope.
|
| 4214 |
+
'<div class="kimodo-libhead">',
|
| 4215 |
'<div class="kimodo-cz-seg kimodo-cz-seg--sub" id="kimodo-cz-scope">',
|
| 4216 |
'<button type="button" class="kimodo-cz-segbtn active" data-scope="mine">Mine</button>',
|
| 4217 |
'<button type="button" class="kimodo-cz-segbtn" data-scope="community">Community</button>',
|
| 4218 |
'</div>',
|
| 4219 |
+
# + Create a kata — Mine only.
|
| 4220 |
+
'<button type="button" id="kimodo-cz-create" class="kimodo-cz-createbtn"><span style="font-size:16px">+</span> Create a kata</button>',
|
| 4221 |
+
# Search + sort (Top by votes / Newest / Featured) — Community only.
|
| 4222 |
'<div class="kimodo-cz-libctrls" id="kimodo-cz-libctrls">',
|
| 4223 |
+
'<input id="kimodo-cz-cfilter" class="kimodo-libsearch kimodo-cz-cfilter" type="text" placeholder="🔍 search" autocomplete="off" />',
|
| 4224 |
'<select id="kimodo-cz-csort" class="kimodo-cz-csort" title="Sort / filter">',
|
| 4225 |
'<option value="top">▲ Top</option>',
|
| 4226 |
'<option value="newest">Newest</option>',
|
| 4227 |
'<option value="featured">★ Featured</option>',
|
| 4228 |
'</select>',
|
| 4229 |
'</div>',
|
| 4230 |
+
'</div>', # /libhead
|
| 4231 |
'<div id="kimodo-cz-katalist" class="kimodo-cz-katalist"></div>',
|
| 4232 |
'</div>',
|
| 4233 |
# Kata view (unified): tap a kata → view/edit it here with a scrubbable
|
|
|
|
| 5414 |
function renderComposeLibrary() {
|
| 5415 |
const list = document.getElementById('kimodo-cz-katalist'); if (!list) return;
|
| 5416 |
document.querySelectorAll('#kimodo-cz-scope .kimodo-cz-segbtn').forEach((b) => b.classList.toggle('active', b.dataset.scope === composeScope));
|
| 5417 |
+
// Anchored header: Create button is Mine-only; search + sort/featured are Community-only.
|
| 5418 |
const ctrls = document.getElementById('kimodo-cz-libctrls'); if (ctrls) ctrls.style.display = (composeScope === 'community') ? '' : 'none';
|
| 5419 |
+
const createBtn = document.getElementById('kimodo-cz-create'); if (createBtn) createBtn.style.display = (composeScope === 'mine') ? '' : 'none';
|
| 5420 |
// Mine = the localStorage entries (recipe-backed, instant); Community = the
|
| 5421 |
// server manifest. Mine cards are always recipe-editable; their filmstrips draw
|
| 5422 |
// from stance ids, so they don't depend on per-clip record files.
|
|
|
|
| 5437 |
return (b.created || 0) - (a.created || 0);
|
| 5438 |
});
|
| 5439 |
list.innerHTML = '';
|
| 5440 |
+
// (The "+ Create a kata" button now lives in the anchored header, Mine only.)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5441 |
if (!shown.length) {
|
| 5442 |
const e = document.createElement('div'); e.className = 'kimodo-cz-libempty';
|
| 5443 |
e.textContent = composeScope === 'mine'
|
|
|
|
| 6601 |
if (cfilter) cfilter.addEventListener('input', () => { composeFilter = cfilter.value || ''; renderComposeLibrary(); });
|
| 6602 |
const csort = document.getElementById('kimodo-cz-csort');
|
| 6603 |
if (csort) { csort.value = composeSort; csort.addEventListener('change', () => { composeSort = csort.value || 'top'; renderComposeLibrary(); }); }
|
| 6604 |
+
const ccreate = document.getElementById('kimodo-cz-create');
|
| 6605 |
+
if (ccreate) ccreate.addEventListener('click', () => newComposeKata());
|
| 6606 |
// Move-mode pill: only changes the mode while the kata is empty (then it's locked).
|
| 6607 |
const modeSeg = document.getElementById('kimodo-cz-mode');
|
| 6608 |
if (modeSeg) modeSeg.addEventListener('click', (e) => {
|
tabs/dojo.drawer.html
CHANGED
|
@@ -2,11 +2,16 @@
|
|
| 2 |
|
| 3 |
<!-- ============ LIBRARY VIEW: list of dojos (Mine / Community) ============ -->
|
| 4 |
<div id="kimodo-dojo-library" class="kimodo-dojo-view">
|
| 5 |
-
<
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
</div>
|
| 9 |
-
<button id="kimodo-dojo-add" class="kimodo-dojo-addbtn" type="button">+ Add dojo</button>
|
| 10 |
<div id="kimodo-dojo-grid" class="kimodo-dojo-cards" aria-label="Dojo list"></div>
|
| 11 |
<div id="kimodo-dojo-empty" class="kimodo-drawer-sub kimodo-dojo-empty"></div>
|
| 12 |
</div>
|
|
|
|
| 2 |
|
| 3 |
<!-- ============ LIBRARY VIEW: list of dojos (Mine / Community) ============ -->
|
| 4 |
<div id="kimodo-dojo-library" class="kimodo-dojo-view">
|
| 5 |
+
<!-- Anchored (sticky) browse header — same pattern as the kata library: pill + Add
|
| 6 |
+
(Mine only) + search (Community only). Toggled per scope in renderLibrary. -->
|
| 7 |
+
<div class="kimodo-libhead">
|
| 8 |
+
<div class="kimodo-cz-seg kimodo-cz-seg--sub kimodo-dojo-seg" id="kimodo-dojo-scope" aria-label="Dojo scope">
|
| 9 |
+
<button type="button" class="kimodo-cz-segbtn active" data-scope="mine">Mine</button>
|
| 10 |
+
<button type="button" class="kimodo-cz-segbtn" data-scope="community">Community</button>
|
| 11 |
+
</div>
|
| 12 |
+
<button id="kimodo-dojo-add" class="kimodo-dojo-addbtn" type="button">+ Add dojo</button>
|
| 13 |
+
<input id="kimodo-dojo-search" class="kimodo-libsearch" type="text" placeholder="🔍 search" autocomplete="off" />
|
| 14 |
</div>
|
|
|
|
| 15 |
<div id="kimodo-dojo-grid" class="kimodo-dojo-cards" aria-label="Dojo list"></div>
|
| 16 |
<div id="kimodo-dojo-empty" class="kimodo-drawer-sub kimodo-dojo-empty"></div>
|
| 17 |
</div>
|
tabs/dojo.tab.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
let dojoMode = 'splat';
|
| 4 |
let lastScene = null;
|
| 5 |
let dojoScope = 'mine';
|
|
|
|
| 6 |
let improvedByAya = false; // current prompt came from tiny-aya → button reads "reroll"
|
| 7 |
let improving = false; // a tiny-aya suggestion call is in flight
|
| 8 |
let _communityCache = null; // canonical + contributed dojos fetched from the dataset
|
|
@@ -25,6 +26,7 @@
|
|
| 25 |
create: document.getElementById('kimodo-dojo-create'),
|
| 26 |
scope: document.getElementById('kimodo-dojo-scope'),
|
| 27 |
add: document.getElementById('kimodo-dojo-add'),
|
|
|
|
| 28 |
back: document.getElementById('kimodo-dojo-back'),
|
| 29 |
grid: document.getElementById('kimodo-dojo-grid'),
|
| 30 |
emptyMsg: document.getElementById('kimodo-dojo-empty'),
|
|
@@ -449,14 +451,22 @@
|
|
| 449 |
return card;
|
| 450 |
}
|
| 451 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
function renderLibrary() {
|
| 453 |
const els = dojoEls();
|
| 454 |
if (els.scope) els.scope.querySelectorAll('.kimodo-cz-segbtn').forEach((b) => b.classList.toggle('active', b.dataset.scope === dojoScope));
|
|
|
|
|
|
|
| 455 |
if (!els.grid) return;
|
| 456 |
els.grid.innerHTML = '';
|
| 457 |
if (dojoScope === 'mine') {
|
| 458 |
els.grid.appendChild(clearCard()); // first card: clear + back to the grid floor
|
| 459 |
-
const arr = loadMyDojos();
|
| 460 |
arr.forEach((e) => els.grid.appendChild(mineCard(e)));
|
| 461 |
if (els.emptyMsg) els.emptyMsg.textContent = arr.length ? '' : 'No dojos yet — tap + Add dojo to create one.';
|
| 462 |
return;
|
|
@@ -467,9 +477,10 @@
|
|
| 467 |
if (dojoScope !== 'community' || !els.grid) return;
|
| 468 |
els.grid.innerHTML = '';
|
| 469 |
els.grid.appendChild(clearCard());
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
|
|
|
| 473 |
});
|
| 474 |
}
|
| 475 |
|
|
@@ -609,6 +620,7 @@
|
|
| 609 |
renderLibrary();
|
| 610 |
});
|
| 611 |
if (els.add) els.add.addEventListener('click', () => openCreate(null));
|
|
|
|
| 612 |
if (els.back) els.back.addEventListener('click', () => showView('library'));
|
| 613 |
if (els.improve) els.improve.addEventListener('click', () => { if (!busy && !improving) improvePrompt(); });
|
| 614 |
if (els.save) els.save.addEventListener('click', () => { if (!els.save.disabled) saveDojoToDataset(); });
|
|
|
|
| 3 |
let dojoMode = 'splat';
|
| 4 |
let lastScene = null;
|
| 5 |
let dojoScope = 'mine';
|
| 6 |
+
let dojoSearch = ''; // library search query (matches dojo name)
|
| 7 |
let improvedByAya = false; // current prompt came from tiny-aya → button reads "reroll"
|
| 8 |
let improving = false; // a tiny-aya suggestion call is in flight
|
| 9 |
let _communityCache = null; // canonical + contributed dojos fetched from the dataset
|
|
|
|
| 26 |
create: document.getElementById('kimodo-dojo-create'),
|
| 27 |
scope: document.getElementById('kimodo-dojo-scope'),
|
| 28 |
add: document.getElementById('kimodo-dojo-add'),
|
| 29 |
+
search: document.getElementById('kimodo-dojo-search'),
|
| 30 |
back: document.getElementById('kimodo-dojo-back'),
|
| 31 |
grid: document.getElementById('kimodo-dojo-grid'),
|
| 32 |
emptyMsg: document.getElementById('kimodo-dojo-empty'),
|
|
|
|
| 451 |
return card;
|
| 452 |
}
|
| 453 |
|
| 454 |
+
// Anchored header (mirrors the kata library): Add button is Mine-only, search is
|
| 455 |
+
// Community-only. Match a dojo by name against the search query.
|
| 456 |
+
function dojoMatches(name) {
|
| 457 |
+
const q = (dojoSearch || '').trim().toLowerCase();
|
| 458 |
+
return !q || String(name || '').toLowerCase().includes(q);
|
| 459 |
+
}
|
| 460 |
function renderLibrary() {
|
| 461 |
const els = dojoEls();
|
| 462 |
if (els.scope) els.scope.querySelectorAll('.kimodo-cz-segbtn').forEach((b) => b.classList.toggle('active', b.dataset.scope === dojoScope));
|
| 463 |
+
if (els.add) els.add.style.display = (dojoScope === 'mine') ? '' : 'none';
|
| 464 |
+
if (els.search) els.search.style.display = (dojoScope === 'community') ? '' : 'none';
|
| 465 |
if (!els.grid) return;
|
| 466 |
els.grid.innerHTML = '';
|
| 467 |
if (dojoScope === 'mine') {
|
| 468 |
els.grid.appendChild(clearCard()); // first card: clear + back to the grid floor
|
| 469 |
+
const arr = loadMyDojos().filter((e) => dojoMatches(e.name));
|
| 470 |
arr.forEach((e) => els.grid.appendChild(mineCard(e)));
|
| 471 |
if (els.emptyMsg) els.emptyMsg.textContent = arr.length ? '' : 'No dojos yet — tap + Add dojo to create one.';
|
| 472 |
return;
|
|
|
|
| 477 |
if (dojoScope !== 'community' || !els.grid) return;
|
| 478 |
els.grid.innerHTML = '';
|
| 479 |
els.grid.appendChild(clearCard());
|
| 480 |
+
const real = (list || []).filter((d) => dojoMatches(d.name));
|
| 481 |
+
if (list && list.length) real.forEach((d) => els.grid.appendChild(communityRealCard(d)));
|
| 482 |
+
else COMMUNITY_SEED.filter((s) => dojoMatches(s.name)).forEach((s) => els.grid.appendChild(communityCard(s)));
|
| 483 |
+
if (els.emptyMsg) els.emptyMsg.textContent = (list && list.length && !real.length) ? 'No dojos match.' : '';
|
| 484 |
});
|
| 485 |
}
|
| 486 |
|
|
|
|
| 620 |
renderLibrary();
|
| 621 |
});
|
| 622 |
if (els.add) els.add.addEventListener('click', () => openCreate(null));
|
| 623 |
+
if (els.search) els.search.addEventListener('input', () => { dojoSearch = els.search.value || ''; renderLibrary(); });
|
| 624 |
if (els.back) els.back.addEventListener('click', () => showView('library'));
|
| 625 |
if (els.improve) els.improve.addEventListener('click', () => { if (!busy && !improving) improvePrompt(); });
|
| 626 |
if (els.save) els.save.addEventListener('click', () => { if (!els.save.disabled) saveDojoToDataset(); });
|