advance-card

star 2

Mutate a card's status (open / active / disproved / superseded — everything except `done`), record relationship edges between cards (`goc advance` / `goc unadvance`), or set the impediment overlay (`goc wait`). AUTO-INVOKE when user says "I'll start on X", "I'm working on", "mark this disproved", "supersede with Z", "this is part of X", "make this depend on Y", "these should be linked", "should this be an edge or a tag?", "remove this dependency", "unlink these", or describes any non-done status change or relationship-modeling intent. For "this is blocked by Y" / "unblock", set or clear the impediment overlay with `goc wait` (Step 6) instead of flipping status. Status transitions and relationship edges are documented agreements (Kanban explicit policies, Anderson).

zauberzeug By zauberzeug schedule Updated 6/4/2026

name: advance-card description: Mutate a card's status (open / active / disproved / superseded — everything except done), record relationship edges between cards (goc advance / goc unadvance), or set the impediment overlay (goc wait). AUTO-INVOKE when user says "I'll start on X", "I'm working on", "mark this disproved", "supersede with Z", "this is part of X", "make this depend on Y", "these should be linked", "should this be an edge or a tag?", "remove this dependency", "unlink these", or describes any non-done status change or relationship-modeling intent. For "this is blocked by Y" / "unblock", set or clear the impediment overlay with goc wait (Step 6) instead of flipping status. Status transitions and relationship edges are documented agreements (Kanban explicit policies, Anderson). argument-hint: " <new-status: active|open|disproved|superseded> [--by <other-title>]"</h2> <h2>Preflight</h2> <p>If any <code>!</code> block below shows <code>goc: command not found</code>, <code>Permission for this action has been denied</code>, or <code>no such file or directory: .game-of-cards/deck/</code>, <strong>stop and invoke <code>Skill(kickoff)</code> first</strong>. Kickoff detects which setup step is missing (CLI not installed, Bash allowance not granted, project state not scaffolded) and walks the user through it. Re-invoke this skill only after kickoff completes.</p> <h2>Advance a card</h2> <p>Kanban's <strong>explicit policies</strong> (Anderson): every status transition is a documented agreement, not a silent flag flip. The lane a card sits in says what's true about it; the move between lanes carries a reason — recorded in the body for terminal transitions, in the commit message for mid-flight ones. A swarm of /loop iterations cannot audit-trail conversational context, so the rule is: the policy lives on disk.</p> <p>Mutate a card's status — everything except <code>done</code>, which is <code>Skill(finish-card)</code>'s DoD-gated contract. Read the card first to confirm the transition is legal under those policies, then run the matching CLI.</p> <p>User argument: $ARGUMENTS — <code><title> <new-status> [--by <other-title>]</code>.</p> <h2>Step 1 — read the card</h2> <p>Run <code>goc show <title></code> yourself with the real title bound. Confirm:</p> <ul> <li>Current status matches the transition you're about to make (e.g. <code>open → active</code> requires current <code>open</code>).</li> <li>The <code>human_gate</code> is appropriate for the new state. If you're flipping a <code>session</code>-gated card to <code>active</code> autonomously, stop — that's a research-impacting move and needs the human in the loop.</li> <li>For <code>disproved</code> / <code>superseded</code>, the body documents the rebuttal / replacement before you flip.</li> </ul> <p><code>status</code> and <code>human_gate</code> are orthogonal — see <code>Skill(card-schema)</code> "human_gate scale". A <code>decision</code> or <code>session</code> gate parks a card for human input; it does NOT depend on a separate "blocked" status.</p> <p>The <strong>impediment overlay</strong> (<code>waiting_on</code> + <code>waiting_until</code>) is the stored signal for exogenous waits the dependency graph cannot derive (vendor delivery, a specific person, a calendar-based defer). It composes with <code>status</code>: a card may be <code>active</code> AND carry <code>waiting_on</code>. Set or clear via <code>goc wait</code> (see "Step 6").</p> <blockquote> <p>Deprecated: the legacy <code>status: blocked</code> value. Three-axis model (see <code>Skill(card-schema)</code> "Three-axis 'stuck' model") splits the old <code>blocked</code> meaning into derived dependency-readiness (an <code>advanced_by</code> prereq still open — self-clears when the prereq closes) and the stored <code>waiting_on</code> overlay (exogenous wait). Authors should set the overlay (<code>goc wait …</code>) or rely on derived readiness instead of flipping status to <code>blocked</code>. The enum value still parses for backwards compatibility but is being removed in a follow-up release.</p> </blockquote> <h2>Step 2 — match the transition to the CLI</h2> <table> <thead> <tr> <th>transition</th> <th>CLI</th> <th>notes</th> </tr> </thead> <tbody><tr> <td><code>open → active</code></td> <td><code>goc status <title> active</code></td> <td>"claiming" the card</td> </tr> <tr> <td><code>active → open</code></td> <td><code>goc status <title> open</code></td> <td>release the claim (re-queue) when stepping away mid-flight without disproving the work</td> </tr> <tr> <td><code>* → open</code></td> <td><code>goc status <title> open</code></td> <td>re-queue (rare)</td> </tr> <tr> <td><code>* → disproved</code></td> <td><code>goc status <title> disproved</code></td> <td>populate rebuttal first; CLI stamps <code>closed_at</code></td> </tr> <tr> <td><code>* → superseded</code></td> <td><code>goc status <title> superseded --by <successor></code></td> <td>sets typed <code>superseded_by</code> / <code>supersedes</code> link bidirectionally; log replacement rationale in old card's <code>log.md</code>; CLI stamps <code>closed_at</code></td> </tr> </tbody></table> <p><code>goc advance</code> and <code>goc unadvance</code> maintain the bidirectional value-flow edge atomically (validator-enforced — if <code>A.advances</code> contains <code>B</code>, <code>B.advanced_by</code> MUST contain <code>A</code>). An <code>advanced_by</code> prereq that is still non-terminal is what the <strong>derived dependency-readiness</strong> signal reads — it self-clears when the prereq closes, so no status flip is needed to park the card on an upstream sibling.</p> <p><strong>Parking a card on an external wait:</strong> use the impediment overlay (Step 6), not <code>status: blocked</code>. For an agent-observable wait (upstream release, PR merge, dependency publication) the card stays <code>status: open</code> with <code>waiting_on: external</code> and an optional <code>waiting_until</code> date — a future autonomous run re-checks the condition and <code>goc wait <title> --clear</code>s the overlay when the wait resolves. For a human-judgement wait, raise <code>human_gate</code> to <code>decision</code> / <code>session</code> and write the framing into the body; no status flip required.</p> <h2>Step 3 — populate the body for transitions</h2> <p>Every transition has two writing surfaces — the README dashboard (latest state) and <code>log.md</code> (the journal of how we got here). Each status flip routes information to the right file:</p> <table> <thead> <tr> <th>Transition</th> <th>README dashboard (rewrite in place)</th> <th><code>log.md</code> journal (append entry)</th> </tr> </thead> <tbody><tr> <td><code>open → active</code></td> <td>no change required (claim adds <code>worker</code> field, not body content)</td> <td>optional one-line "claimed by X on DATE" entry; usually skipped — the git commit suffices</td> </tr> <tr> <td><code>set </code>waiting_on<code> overlay</code></td> <td>update the relevant body section to reflect the wait (e.g. "Fix" → "Waiting on upstream release of X, expected YYYY-MM-DD")</td> <td>append entry: when the overlay was set, what the wait is for, expected return signal</td> </tr> <tr> <td><code>clear </code>waiting_on<code> overlay</code></td> <td>rewrite the body section that named the wait to reflect the new state of the world</td> <td>append entry: when the wait cleared, what changed externally</td> </tr> <tr> <td><code>* → open</code> (re-queue)</td> <td>rewrite the body sections that are no longer accurate to match the new framing</td> <td>append entry: why the card was re-queued (scope reset, evidence superseded, etc.)</td> </tr> <tr> <td><code>* → disproved</code></td> <td>rewrite body to document the rebuttal (see below)</td> <td>append entry: when disproved, by what evidence</td> </tr> <tr> <td><code>* → superseded</code></td> <td>leave the body as the historical record; do NOT rewrite to point at the successor</td> <td>append entry naming and linking the successor card and one-line why</td> </tr> </tbody></table> <p>Rule of thumb: <strong>state-of-the-world updates rewrite the README dashboard; transition narrative, decisions, and timestamps append to <code>log.md</code>.</strong> See <code>Skill(card-schema)</code>'s "What goes where" subsection.</p> <p>The CLI stamps <code>closed_at</code> automatically for every terminal flip (<code>done</code>, <code>disproved</code>, <code>superseded</code>); <code>status</code> names the outcome. The body work below is what the CLI does NOT do for you.</p> <h3>Disproved</h3> <p>Rewrite <code>deck/<title>/README.md</code> body to document the resolved state:</p> <ul> <li>The hypothesis (what was claimed).</li> <li>The verdict (FALSE — what's actually in the code).</li> <li>The source of error (which agent / partial reading triggered it).</li> <li>A one-line lesson if non-obvious.</li> </ul> <p>Then append a journal entry to <code>log.md</code> recording when and how the disproof landed, including the evidence cited. The README rewrite gives a cold reader the verdict; the journal entry gives a forensic reader the disproof chain.</p> <p>This is mandatory. Without it, every scheduled run that spawns the same agent set may re-propose the same false lead and waste a verification cycle.</p> <h3>Superseded</h3> <p>The new card's body explains what it supersedes and why. Run <code>goc status <title> superseded --by <successor></code> on the old card — the <code>--by</code> flag sets the typed bidirectional <code>superseded_by</code> / <code>supersedes</code> link on both endpoints in one atomic operation (same contract <code>goc advance</code> provides for the advances graph).</p> <p>Append an entry to the old card's <code>log.md</code> to record the replacement <em>rationale</em>: one-line why the replacement happened (different approach, scope split, reframing). The typed field is the machine-navigable pointer; the journal entry is the prose-only <em>why</em> a graph edge cannot capture. Both, for different jobs — a cold reader (human or LLM) walks the typed link to find the successor without parsing prose, and reads the log entry for the rationale.</p> <p>Leave the old README body as the historical dashboard; do NOT rewrite it to point at the successor (the typed link does that mechanically). The link to the successor stays a one-line <code>> Later: [<new-title>](../<new-title>/)</code> pointer at the top of the body only if a cold reader would otherwise be misled — see <code>Skill(card-schema)</code> "Replacement axis" for the invariants and emitter conventions.</p> <p>Plain <code>goc status <title> superseded</code> (without <code>--by</code>) is still accepted for backwards compatibility, but leaves the supersession prose-only and forces forensic readers to grep <code>log.md</code>. Prefer the <code>--by</code> form for every new supersession.</p> <h2>Step 4 — run the transition</h2> <pre><code class="language-bash"># Open → active (claiming): goc status <title> active # Add a value-flow edge (other advances title): goc advance <title> --by <other> # Remove a value-flow edge: goc unadvance <title> --by <other> # Disproved / superseded: goc status <title> disproved goc status <title> superseded --by <successor-title> </code></pre> <p>The CLI prints <code><title>: <prior> → <new></code> on success and follows the repo's <code>.game-of-cards/config.yaml</code> <code>workflow.auto_commit</code> policy.</p> <h2>Step 5 — claim is its own commit (multi-branch coordination)</h2> <p>Status flips and edge mutations normally commit immediately, separately from the work commit. Reason: when two branches both work the deck, the soft lock (<code>status: active</code>) should be git-observable so a sibling branch pulling sees "this card is claimed" before it races on the same YAML.</p> <p><code>goc status</code> / <code>advance</code> / <code>unadvance</code> / <code>decide</code> read <code>workflow.auto_commit</code> from <code>.game-of-cards/config.yaml</code> (default: <code>true</code>). Pass <code>--no-commit</code> to skip for one invocation, or <code>--commit</code> to force a state-only commit when the repo config disables automatic commits. The work commit, when it lands later after <code>Skill(finish-card)</code>, contains the actual code/doc changes — NOT the status flip.</p> <p>If the configured/forced auto-commit is skipped (no git repo, mid-merge / mid-rebase, no diff), the CLI prints a one-line note. The on-disk state still mutated; only the visibility-to-other-branches step deferred.</p> <h2>Step 6 — set or clear an impediment overlay (<code>goc wait</code>)</h2> <p>The dependency-readiness predicate covers card-blocks-card, but cannot see exogenous waits. Three kinds need a stored signal:</p> <ul> <li><code>external</code> — vendor, client, hardware, a third party.</li> <li><code>resource</code> — a specific person/skill currently unavailable.</li> <li><code>deferred</code> — deliberately postponed (a calendar-based defer).</li> </ul> <p>Set the overlay with <code>goc wait</code>:</p> <pre><code class="language-bash"># Wait on a vendor; expect to retry on 2026-06-15. goc wait <title> --reason external --until 2026-06-15 # Defer-only (no reason): bare --until implies `deferred`. goc wait <title> --until 2026-06-15 # Open-ended wait on a specific person; no expected return date. goc wait <title> --reason resource # Clear the overlay when the wait resolves. goc wait <title> --clear </code></pre> <p>Effects:</p> <ul> <li>A future <code>waiting_until</code> (or a reason with no date) hides the card from <code>goc --ready</code> / <code>Skill(next-card)</code> / <code>Skill(pull-card)</code>. When the date passes the card re-enters the queue with no manual action.</li> <li>An elapsed <code>waiting_until</code> is surfaced by <code>goc validate</code> as <code>WAITING_OVERDUE</code> — the Kanban SLE escalation: the wait overran its expected return, re-triage or clear.</li> <li>The overlay is orthogonal to <code>status</code> — a card may be <code>active</code> AND carry a <code>waiting_on</code>, e.g. work in progress that is partially gated on an external answer.</li> </ul> <h2>Modeling a relationship: edge vs tag</h2> <p>A reader landing here on a relationship question ("this is part of X", "make this depend on Y", "these should be linked", "should this be an edge or a tag?", "remove this dependency") is asking <em>how to express a link</em>, not <em>how to flip a status</em>. This section is the decision procedure; the canonical taxonomy and the value-flow invariants live in <code>Skill(card-schema)</code> — link, don't re-derive.</p> <h3>Decision procedure</h3> <ol> <li><strong>Same value chain — does the source's closure deliver a piece of the target's value?</strong> → <code>advances</code> edge. The dependent inherits the source's priority and cannot close until the source closes (see <code>Skill(card-schema)</code> "Value-flow axis" for the closure semantics).</li> <li><strong>Same theme, no closure-time dependency — would a future filter ("show me all the X cards") want them grouped?</strong> → shared <strong>tag</strong>. No edge in either direction.</li> <li><strong>One card coordinates many others</strong> → see the three-way fork below before reaching for <code>--advances</code>.</li> </ol> <h3>Three coordinating-card shapes (short form)</h3> <p>Full reasoning, the value-law derivation, and the <code>BACKWARDS_EPIC_EDGE</code> lint live in <code>Skill(card-schema)</code> "Coordinating cards — aggregation epic vs governing cluster". The short form, paired with the verb you reach for:</p> <ul> <li><strong>Aggregation epic</strong> — its value chain <em>is</em> its children; closes when they close. Encoding: <code>child.advances: [epic]</code>. Verb on the child (open or after creation): <code>goc advance <child> --by <epic></code>.</li> <li><strong>Governing cluster</strong> — a decision or standard-setting card that closes when <em>decided</em>, independent of the cluster's work. Encoding: a <strong>shared tag</strong>, no <code>advances</code> edge in either direction. Add the tag at <code>goc new --tag <name></code> time on both the governing card and each instance; for an existing card, edit <code>tags:</code> in the frontmatter directly. To register a new project-specific tag, see <code>Skill(card-schema)</code> "Adding new tags".</li> <li><strong>Backwards aggregation</strong> — <code>epic.advances: [children]</code>. <strong>Never.</strong> Defeats the value law (children stop inheriting the epic's value, so the GRPW sort cannot see the chain) and trips a spurious <code>advanced-by-closed</code> FAIL on every child at attest time. <code>goc validate</code> flags this signature as <code>BACKWARDS_EPIC_EDGE</code>.</li> </ul> <p>The tell: if the coordinating card closes on its own deliverable (typically <code>human_gate: decision</code>) rather than on its cluster's completion, it is a governing cluster → tag, not edge.</p> <h3>Retraction — <code>goc unadvance</code> is the honest fix</h3> <p>When an <code>advanced-by-closed</code> check fires at closure time, the gate is reading the value-chain identity (<code>Skill(card-schema)</code> "Value-flow axis"): a true edge cannot coexist with a closeable target. Two honest resolutions:</p> <ol> <li><strong>Wait</strong> for the upstream contributor(s) to close.</li> <li><strong>Retract</strong> when the edge was false (the upstream was tangential, scope was reframed, or the relationship was authored backwards): <code>goc unadvance <closing-title> --by <upstream></code>.</li> </ol> <p>Retraction is graph maintenance, not a bypass. Prefer it to <code>goc attest --skip advanced-by-closed</code>; the skip leaves a dishonest edge in the deck. Same rule applies in the opposite direction — if you discover a card should depend on another after filing, add the edge with <code>goc advance <title> --by <other></code> rather than letting the relationship live only in prose.</p> <h3>Verbs</h3> <pre><code class="language-bash"># Record a value-flow edge (this advances other): goc advance <title> --by <other> # Retract a value-flow edge: goc unadvance <title> --by <other> # At filing time, both sides at once (--commit so the new card AND # the epic's edge mutation land in one atomic commit): goc new <child-title> --advances <epic-title> --commit </code></pre> <p><code>goc advance</code> / <code>unadvance</code> maintain the bidirectional invariant (<code>A.advances</code> ⇔ <code>B.advanced_by</code>) atomically — same atomicity contract <code>goc status … superseded --by</code> provides for the replacement graph. The validator refuses half-edges. Cycles are forbidden.</p> <p>For grouping (the governing-cluster shape and other soft themes), there is intentionally no <code>goc add-tag</code> verb on existing cards — set tags at <code>goc new --tag <name></code> time, or edit <code>tags:</code> in the frontmatter directly. The unknown-tag error names the file to register a new tag in (see <code>Skill(card-schema)</code> "Adding new tags").</p> <h2>Worker field — populated at claim time</h2> <p><code>goc status <title> active</code> auto-populates the card's <code>worker</code> field with the current identity. The field is optional and free-form; it matters when multiple humans or agents share a deck and you want a runner-scoped queue view.</p> <p><strong>Format:</strong></p> <ul> <li>Flat string for a single identifier: <code>worker: rodja</code>. Sugar for <code>{who: rodja}</code>.</li> <li>Mapping with branch context: <code>worker: {who: rodja, where: feature/foo}</code>.</li> </ul> <p>The value is unregistered — pick a person slug, machine name, or capability tag (<code>gpu-required</code>, <code>human</code>, <code>rendering-expert</code>). The field persists after close as a historical record.</p> <p><strong>Filter the queue by worker:</strong></p> <ul> <li><code>goc --worker <X></code> — limit listings to cards owned by <code>X</code>.</li> <li>Set <code>GOC_WORKER</code> env var so a runner sees only its own queue without typing the flag every time.</li> </ul> <h2>Cross-references</h2> <ul> <li><code>Skill(finish-card)</code> — for <code>done</code> transitions (DoD-gated).</li> <li><code>Skill(card-schema)</code> — full transition semantics, bidirectional edge invariants, <code>human_gate</code> rules.</li> <li><code>Skill(create-card)</code> — when the supersession needs a new card to point at.</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/zauberzeug/game-of-cards --skill advance-card </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>2</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>0</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/2843826?v=4" alt="zauberzeug" 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>zauberzeug</span> <a href="/?creator=zauberzeug" 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>