kanban-local

star 164

Local markdown-file kanban for toy and personal projects. No server, no PostgreSQL, no auth — all state lives in KANBAN.md in the project root. Use for solo/small projects where the remote kanban board is overkill. Auto-trigger when KANBAN.md exists and user says "태스크 추가", "add task", "칸반 보여줘", "다음 할 일", "task list", or similar task-management phrases. Sub-commands: init, list, add, move, done, show, edit, refine, run, rm, stats.

cyanluna-git By cyanluna-git schedule Updated 6/2/2026

name: kanban-local description: Local markdown-file kanban for toy and personal projects. No server, no PostgreSQL, no auth — all state lives in KANBAN.md in the project root. Use for solo/small projects where the remote kanban board is overkill. Auto-trigger when KANBAN.md exists and user says "태스크 추가", "add task", "칸반 보여줘", "다음 할 일", "task list", or similar task-management phrases. Sub-commands: init, list, add, move, done, show, edit, refine, run, rm, stats.

kanban-local

로컬 KANBAN.md 한 파일로 모든 태스크 상태 관리. 원격 DB, 인증, 서버 불필요.

Safety principles: read ../kanban/principles.mdmandatory, not optional.

When to Use

  • 토이 프로젝트, 개인 실험 레포 — SQLite 칸반이 과한 경우
  • KANBAN.md가 있는 디렉터리에서 태스크 관련 요청이 오는 경우
  • 사용하지 말 것: 팀 공유, 멀티-프로젝트 통합 뷰, /kanban-run 에이전트 파이프라인 필요 시 → /kanban 사용

KANBAN.md 파일 포맷

파일은 상단: 보드 개요, 하단: 태스크 상세 두 섹션으로 구성된다.

---
project: <name>
last_id: 0
updated: YYYY-MM-DD
---

## todo
## doing
## review
## done

---

<!-- details -->

보드 개요 — 태스크 라인 포맷

- #001 [high] Task title · tag1,tag2
  • #NNN: 3자리 zero-padded ID
  • [high|med|low]: 우선순위
  • · tags: 옵션, 쉼표 구분

완료 태스크는 strikethrough:

- ~~#004~~ [low] ~~Completed task~~ · 2026-05-23

상세 블록 포맷 (<!-- details --> 아래)

### #001 · Task title
priority: high · tags: feat · created: 2026-05-23 · status: todo

**Goal**
What needs to happen.

**Acceptance Criteria**
- [ ] Criterion 1

---

각 상세 블록은 ---로 닫는다.


Commands

/kanban-local init [name]

현재 디렉터리에 KANBAN.md 생성.

  1. KANBAN.md가 이미 있으면 현재 상태 표시 후 중단.
  2. 프로젝트 이름: 인자 제공 시 사용, 없으면 basename $(pwd).
  3. Write 툴로 빈 KANBAN.md 생성 (frontmatter + 4개 섹션 헤더 + <!-- details -->).
  4. "KANBAN.md created for project <name>." 확인.

초기 KANBAN.md 템플릿:

---
project: <name>
last_id: 0
updated: <YYYY-MM-DD>
---

## todo

## doing

## review

## done

---

<!-- details -->

/kanban-local 또는 /kanban-local list

보드 현황 표시.

  1. Read 툴로 KANBAN.md 읽기.
  2. 각 섹션(## todo, ## doing, ## review, ## done)에서 태스크 라인 파싱.
  3. 아래 형식으로 출력:
## 📋 <project> — Kanban

| ID   | Status | Pri  | Title                          | Tags        |
|------|--------|------|--------------------------------|-------------|
| #003 | doing  | high | Currently working on this      | feat,api    |
| #001 | todo   | high | Task title one                 | feat        |
| #002 | todo   | med  | Another task                   | bug         |

표시 순서: doing → review → todo → done (done은 최근 5개만). 빈 섹션은 "(empty)" 표시.


/kanban-local add <title>

새 태스크 추가.

  1. frontmatter의 last_id를 읽고 +1 → 새 ID (3자리 zero-pad, 예: 007).
  2. AskUserQuestion으로 확인:
    • 우선순위: high / med(기본) / low
    • 태그: 쉼표 구분 (선택)
    • Goal/설명: 1–2문장 (선택, 없으면 "(TBD)")
  3. ## todo 섹션 마지막에 태스크 라인 추가:
    - #NNN [<priority>] <title> · <tags>
    
  4. <!-- details --> 아래에 상세 블록 추가:
    ### #NNN · <title>
    priority: <p> · tags: <tags> · created: <YYYY-MM-DD> · status: todo
    
    **Goal**
    <description>
    
    ---
    
  5. frontmatter의 last_idupdated 업데이트.
  6. "Added #NNN: → todo." 확인.</li> </ol> <hr> <h3><code>/kanban-local move <ID> <status></code></h3> <p>태스크를 다른 열로 이동.</p> <p>유효한 status: <code>todo | doing | review | done</code></p> <ol> <li>Read 툴로 <code>KANBAN.md</code> 읽기.</li> <li>모든 섹션에서 <code>- #NNN</code> 또는 <code>- ~~#NNN~~</code> 라인 탐색.</li> <li>현재 섹션에서 해당 라인 제거.</li> <li><code>done</code>으로 이동 시: strikethrough 적용 (<code>~~#NNN~~ [pri] ~~title~~</code>), 날짜 추가 (<code> · YYYY-MM-DD</code>).</li> <li><code>done</code>에서 다른 상태로 이동 시: strikethrough 제거, 날짜 제거.</li> <li>대상 섹션 마지막에 라인 추가.</li> <li>상세 블록의 <code>status: <old></code> → <code>status: <new></code> 업데이트.</li> <li>frontmatter <code>updated</code> 업데이트.</li> <li>"#NNN moved: <old> → <new>." 확인.</li> </ol> <hr> <h3><code>/kanban-local done <ID></code></h3> <p><code>/kanban-local move <ID> done</code> 단축키.</p> <hr> <h3><code>/kanban-local show <ID></code></h3> <p>태스크 상세 블록 표시.</p> <ol> <li><code>KANBAN.md</code>에서 <code>### #NNN ·</code> 헤딩 탐색.</li> <li>해당 헤딩부터 다음 <code>---</code> 전까지 출력.</li> </ol> <hr> <h3><code>/kanban-local edit <ID></code></h3> <p>태스크 필드 수정.</p> <ol> <li><code>show</code>로 현재 상태 표시.</li> <li>AskUserQuestion으로 수정할 필드 확인 (제목, 우선순위, 태그, goal, 수락 기준).</li> <li>Edit 툴로 상세 블록 수정.</li> <li>보드 개요 라인도 변경사항에 맞게 수정 (제목/우선순위/태그 변경 시).</li> <li>frontmatter <code>updated</code> 업데이트.</li> </ol> <hr> <h3><code>/kanban-local refine <ID></code></h3> <p>구조화된 요구사항 인터뷰 — <code>/kanban-refine</code>의 경량 버전.</p> <p>대상: <code>todo</code> 상태 태스크. <code>doing</code>/<code>review</code> 상태면 경고 후 진행 여부 확인.</p> <p><strong>절차:</strong></p> <p>① <code>show</code>로 현재 상세 블록 읽기.</p> <p>② 아래 차원에서 갭 분석:</p> <ul> <li>WHAT: 정확히 무엇을 만들거나 바꾸는가?</li> <li>WHY: 어떤 문제를 해결하는가?</li> <li>SCOPE: 포함/제외 범위는?</li> <li>ACCEPTANCE: 완료를 어떻게 판단하는가?</li> <li>EDGE CASES: 에러 상태, 경계 조건?</li> </ul> <p>③ AskUserQuestion으로 인터뷰 (1라운드 최대 4개 질문, 최대 2라운드).</p> <ul> <li>이미 명확한 항목은 묻지 않음.</li> <li>사용자가 "충분해", "enough" 하면 조기 종료.</li> </ul> <p>④ 상세 블록을 아래 템플릿으로 재작성:</p> <pre><code class="language-markdown">### #NNN · <title> priority: <p> · tags: <tags> · created: <date> · status: <status> **Goal** 1–2문장: 무엇을, 왜. **Scope** - IN: ... - OUT: ... **Requirements** 1. 구체적이고 테스트 가능한 요구사항 2. ... **Acceptance Criteria** - [ ] 검증 가능한 기준 1 - [ ] ... **Edge Cases** - 에지 케이스 (있는 경우만) --- </code></pre> <p> 내용 없는 섹션은 생략.</p> <p>⑤ 정제된 내용을 사용자에게 보여주고 AskUserQuestion:</p> <ul> <li>"Approve & save" → Edit 툴로 저장</li> <li>"Edit more" → 인터뷰 재개</li> <li>"Cancel" → 변경 폐기</li> </ul> <p>⑥ 저장 시 frontmatter <code>updated</code> 업데이트.</p> <hr> <h3><code>/kanban-local run <ID></code></h3> <p>태스크를 실제로 수행하면서 KANBAN.md에 진행 기록을 남긴다. 에이전트 파이프라인 없이 Claude가 직접 구현하고, 상세 블록에 작업 로그를 기록하는 방식.</p> <p><strong>절차:</strong></p> <p>① KANBAN.md 읽기 → <code>show <ID></code>로 상세 블록 확인.</p> <ul> <li>상태가 <code>todo</code>가 아니면 현재 상태를 알리고 진행 여부 확인.</li> </ul> <p>② 태스크를 <code>doing</code>으로 이동.</p> <ul> <li>보드 개요 라인 이동 (<code>## todo</code> → <code>## doing</code>).</li> <li>상세 블록 <code>status: todo</code> → <code>status: doing</code> 업데이트.</li> <li>상세 블록에 <code>**Log**</code> 섹션이 없으면 추가하고 시작 항목 기록:<pre><code>**Log** - [YYYY-MM-DD] 작업 시작 </code></pre> </li> </ul> <p>③ 실제 작업 수행.</p> <ul> <li>Goal / Requirements / Acceptance Criteria를 기준으로 구현.</li> <li>파일 생성·수정이 필요하면 Write/Edit 툴로 직접 처리.</li> <li><strong>작업 중 중요한 결정이나 발견이 있으면 즉시</strong> Log에 한 줄 추가:<pre><code>- [YYYY-MM-DD] <결정 내용 또는 발견> </code></pre> 예: 접근 방식 변경, 제약 발견, 핵심 설계 결정, 파일 경로 등.</li> <li>Log는 무겁게 쓰지 않는다 — "뭘 했다"가 아니라 "왜 그렇게 했는지, 무엇이 달라졌는지"만.</li> </ul> <p>④ 작업 완료 후 상세 블록 마무리.</p> <ul> <li>Acceptance Criteria 항목 중 완료된 것은 <code>- [ ]</code> → <code>- [x]</code> 체크.</li> <li><code>**Implementation Notes**</code> 섹션을 Log 아래에 추가:<pre><code>**Implementation Notes** 완료된 산출물과 위치를 1–3줄로 요약. 예: "prompts/p1-hypothesis.md 작성. 재질문 조건은 변수·대상·지표 중 하나라도 불명확할 때로 설정." </code></pre> </li> <li>Log에 완료 항목 추가:<pre><code>- [YYYY-MM-DD] 작업 완료 </code></pre> </li> </ul> <p>⑤ 태스크를 <code>done</code>으로 이동.</p> <ul> <li>보드 개요 라인: strikethrough 적용 + 날짜 추가.</li> <li>상세 블록 <code>status: doing</code> → <code>status: done</code>.</li> <li>frontmatter <code>updated</code> 갱신.</li> </ul> <p>⑥ 완료 요약 출력:</p> <pre><code>✅ #NNN done — <title> 산출물: <파일 경로 또는 결과 요약> </code></pre> <p><strong>Log 작성 원칙:</strong></p> <ul> <li>평범한 진행("파일 생성함")은 쓰지 않는다.</li> <li>남길 가치가 있는 것만: 설계 결정, 트레이드오프, 막혔다가 해결한 지점, 다음 태스크가 알아야 할 사실.</li> <li>한 줄로 충분하면 한 줄만.</li> </ul> <p><strong>중단/실패 처리:</strong></p> <ul> <li>작업 중 블로커 발생 시: Log에 블로커 내용 기록 후 <code>status: todo</code>로 되돌리고 사용자에게 알림.<pre><code>- [YYYY-MM-DD] ⚠️ 블로커: <내용> — todo로 복귀 </code></pre> </li> </ul> <hr> <h3><code>/kanban-local stats</code></h3> <p>상태별 태스크 수 요약.</p> <ol> <li><code>KANBAN.md</code> 읽고 각 섹션의 태스크 라인 수 집계.</li> <li>출력:<pre><code>## Stats — <project> todo: N · doing: N · review: N · done: N · total: N </code></pre> </li> </ol> <hr> <h3><code>/kanban-local rm <ID></code> 또는 <code>/kanban-local remove <ID></code></h3> <p>태스크 삭제.</p> <ol> <li>AskUserQuestion으로 삭제 확인.</li> <li>보드 개요에서 해당 라인 제거.</li> <li>상세 블록(<code>### #NNN ·</code>부터 다음 <code>---</code>까지) 제거.</li> <li>frontmatter <code>updated</code> 업데이트.</li> <li><code>last_id</code>는 변경하지 않음 (ID 재사용 금지).</li> </ol> <hr> <h2>파일 조작 규칙</h2> <ul> <li>항상 <strong>Read 툴</strong>로 <code>KANBAN.md</code> 읽은 후 수정.</li> <li>수정은 <strong>Edit 툴</strong>로 타겟 변경. bash <code>sed</code>/<code>awk</code> 사용 금지.</li> <li><code>init</code> 시에만 <strong>Write 툴</strong> 사용.</li> <li>모든 변경 후 frontmatter의 <code>updated</code> 날짜 갱신.</li> <li><code>last_id</code>는 단방향 증가만 — 삭제된 태스크 ID 재사용 금지.</li> <li>태스크 라인 구분: <code>- #NNN</code> 또는 <code>- ~~#NNN~~</code> 패턴.</li> <li>상세 블록 구분: <code>### #NNN ·</code> 헤딩부터 다음 <code>---</code>까지.</li> </ul> <h2>Auto-trigger 조건</h2> <p><code>KANBAN.md</code>가 현재 디렉터리에 있고 아래 표현이 오면 <code>/kanban-local</code> 자동 활성화:</p> <ul> <li>"태스크 추가", "칸반 보여줘", "다음 할 일", "할 일 목록"</li> <li>"add task", "task list", "show board", "what's next"</li> </ul> </article> </div> <!-- Right: Metadata & Command Sidebar --> <div class="w-full lg:w-80 shrink-0 flex flex-col gap-6" data-astro-cid-7zzsworf> <!-- Install Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-mono" data-astro-cid-7zzsworf>Install via CLI</span> <div class="flex flex-col gap-2" data-astro-cid-7zzsworf> <div id="detail-install-cmd" class="font-mono text-[11px] p-3 rounded-lg bg-black/40 border border-border select-all break-all text-primary font-bold leading-relaxed" data-astro-cid-7zzsworf> npx skills add https://github.com/cyanluna-git/cyanluna.skills --skill kanban-local </div> <button id="detail-copy-btn" class="w-full py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-on-primary font-sans font-bold text-sm shadow transition-all active:scale-95 flex items-center justify-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px]" data-astro-cid-7zzsworf>content_copy</span> <span data-astro-cid-7zzsworf>Copy Command</span> </button> </div> </div> <!-- Details & Stats Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm text-on-surface" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>Repository Details</span> <div class="flex flex-col gap-3.5" data-astro-cid-7zzsworf> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>star</span> Stars </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>164</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>call_split</span> Forks </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>41</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>navigation</span> Branch </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant" data-astro-cid-7zzsworf>main</span> </div> <div class="flex justify-between items-start text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5 mt-0.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>article</span> Path </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant truncate max-w-[150px]" title="SKILL.md" data-astro-cid-7zzsworf>SKILL.md</span> </div> </div> </div> <!-- Occupations Tag Card --> <!-- Related Creators Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-3 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>More from Creator</span> <div class="flex items-center gap-2" data-astro-cid-7zzsworf> <img class="w-8 h-8 rounded-full border border-border" src="https://avatars.githubusercontent.com/u/51350627?v=4" alt="cyanluna-git" onerror="this.src='https://avatars.githubusercontent.com/u/9919?v=4'" data-astro-cid-7zzsworf> <div class="flex flex-col min-w-0" data-astro-cid-7zzsworf> <span class="font-bold text-sm truncate text-on-surface" data-astro-cid-7zzsworf>cyanluna-git</span> <a href="/?creator=cyanluna-git" class="text-xs text-primary hover:underline font-semibold transition-all" data-astro-cid-7zzsworf>Explore all skills →</a> </div> </div> </div> </div> </div> </div> </div> <script> const copyBtn = document.getElementById("detail-copy-btn"); const installCmd = document.getElementById("detail-install-cmd"); if (copyBtn && installCmd) { copyBtn.addEventListener("click", () => { const cmd = installCmd.textContent.trim(); navigator.clipboard.writeText(cmd).then(() => { const originalText = copyBtn.innerHTML; copyBtn.innerHTML = ` <span class="material-symbols-outlined text-[16px]">check</span> <span>Copied!</span> `; copyBtn.style.background = "#10b981"; copyBtn.style.borderColor = "#10b981"; setTimeout(() => { copyBtn.innerHTML = originalText; copyBtn.style.background = ""; copyBtn.style.borderColor = ""; }, 1500); }); }); } </script> </div> <!-- Footer --> <footer class="border-t border-border bg-surface-container-low text-on-surface-variant py-8 px-gutter mt-16 rounded-xl"> <div class="max-w-container-max mx-auto flex flex-col md:flex-row justify-between items-center gap-6"> <div class="flex items-center gap-2"> <div class="w-6 h-6 rounded bg-primary bg-opacity-20 flex items-center justify-center"> <span class="material-symbols-outlined text-primary text-sm">code_blocks</span> </div> <span class="font-bold text-on-surface text-sm">SkillMD</span> </div> <div class="flex flex-wrap justify-center gap-6 text-sm"> <a href="/about" class="hover:text-primary transition-colors">About Us</a> <a href="/contact" class="hover:text-primary transition-colors">Contact Us</a> <a href="/privacy" class="hover:text-primary transition-colors">Privacy Policy</a> <a href="/terms" class="hover:text-primary transition-colors">Terms of Service</a> <a href="/support" class="hover:text-primary transition-colors">Support</a> </div> <div class="text-xs text-on-surface-variant/80"> © 2026 SkillMD. All rights reserved. </div> </div> </footer> </main> <!-- Script for Theme Toggle, Mobile Menu, and Sidebar Filter Redirection --> <script> // Theme setup const savedTheme = localStorage.getItem("theme") || "dark"; function applyTheme(theme) { document.documentElement.classList.remove("dark", "green", "dracula", "nord"); if (theme === "dark") { document.documentElement.classList.add("dark"); } else if (theme === "green") { document.documentElement.classList.add("dark", "green"); } else if (theme === "dracula") { document.documentElement.classList.add("dark", "dracula"); } else if (theme === "nord") { document.documentElement.classList.add("dark", "nord"); } document.documentElement.setAttribute("data-theme", theme); const themeMoon = document.getElementById("theme-moon"); const themeSun = document.getElementById("theme-sun"); const themeLeaf = document.getElementById("theme-leaf"); const themeDracula = document.getElementById("theme-dracula"); const themeNord = document.getElementById("theme-nord"); if (themeMoon && themeSun && themeLeaf && themeDracula && themeNord) { themeMoon.style.display = theme === "dark" ? "inline" : "none"; themeSun.style.display = theme === "light" ? "inline" : "none"; themeLeaf.style.display = theme === "green" ? "inline" : "none"; themeDracula.style.display = theme === "dracula" ? "inline" : "none"; themeNord.style.display = theme === "nord" ? "inline" : "none"; } } applyTheme(savedTheme); const themeToggleBtn = document.getElementById("theme-toggle-btn"); if (themeToggleBtn) { themeToggleBtn.addEventListener("click", () => { const currentTheme = document.documentElement.getAttribute("data-theme") || "dark"; let newTheme = "dark"; if (currentTheme === "dark") { newTheme = "light"; } else if (currentTheme === "light") { newTheme = "green"; } else if (currentTheme === "green") { newTheme = "dracula"; } else if (currentTheme === "dracula") { newTheme = "nord"; } else { newTheme = "dark"; } applyTheme(newTheme); localStorage.setItem("theme", newTheme); }); } // Mobile menu toggle and sidebar logic const mobileMenuToggle = document.getElementById("mobile-menu-toggle"); const sidebarMenu = document.getElementById("sidebar-menu"); const sidebarOverlay = document.getElementById("sidebar-overlay"); function isMobile() { return window.innerWidth < 768; // 768px is the 'md' breakpoint in Tailwind } function openSidebar() { if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.remove("hidden"); } } function closeSidebar() { if (sidebarMenu && isMobile()) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } if (mobileMenuToggle && sidebarMenu) { mobileMenuToggle.addEventListener("click", (e) => { e.stopPropagation(); if (isMobile()) { const isClosed = sidebarMenu.classList.contains("-translate-x-full"); if (isClosed) { openSidebar(); } else { closeSidebar(); } } }); document.addEventListener("click", (e) => { if (isMobile()) { if (!sidebarMenu.contains(e.target) && !mobileMenuToggle.contains(e.target)) { closeSidebar(); } } }); if (sidebarOverlay) { sidebarOverlay.addEventListener("click", () => { if (isMobile()) { closeSidebar(); } }); } // Collapse sidebar when clicking a filter button, creator button, or nav item inside it sidebarMenu.addEventListener("click", (e) => { if (isMobile()) { const clickTarget = e.target.closest("button, a"); if (clickTarget) { closeSidebar(); } } }); // Sync sidebar state on window resize window.addEventListener("resize", () => { if (!isMobile()) { // Desktop: sidebar should be visible, no overlay if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } else { // Mobile: start collapsed if (sidebarMenu) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } }); } // If not on homepage, redirect on sidebar filter click const isHomepage = window.location.pathname === "/"; document.querySelectorAll("#occupation-filters .filter-btn").forEach(btn => { btn.addEventListener("click", (e) => { const occ = e.currentTarget.getAttribute("data-occupation"); if (!isHomepage) { window.location.href = occ ? `/?occupation=${encodeURIComponent(occ)}` : "/"; } }); }); document.querySelectorAll("#creator-filters .creator-btn").forEach(btn => { btn.addEventListener("click", (e) => { const creator = e.currentTarget.getAttribute("data-creator"); if (!isHomepage) { window.location.href = `/?creator=${encodeURIComponent(creator)}`; } }); }); </script> </body> </html>