Build with AI Β· GitHub

jsKjs with GitHub Copilot

Configure GitHub Copilot for jsKjs with .github/copilot-instructions.md for the whole repo and a file-scoped .github/instructions/jskjs.instructions.md using applyTo.

Set-up

  1. Copy .github/copilot-instructions.md into your repository for repository-wide instructions.
  2. Prefer instructions only for front-end files? Use .github/instructions/jskjs.instructions.md instead. Its applyTo: "**/*.{html,htm,js,php}" frontmatter attaches it to matching files, and description lets the agent load it on demand.
  3. VS Code's Copilot also understands AGENTS.md at the repository root, so the shared file works here too.
  4. In Copilot Chat (agent mode), ask: "Add an infinite-scroll feed to posts.html with jsKjs, loading /api/posts.php?page=N."

The files

.github/copilot-instructions.md β€” GitHub Copilot (repository-wide instructions)

Download copilot-instructions.md Β· curl -fsSLo .github/copilot-instructions.md https://jskjs.org/ai/kit/copilot-instructions.md

Markdown
# Copilot instructions β€” jsKjs

This project uses **jsKjs** (https://jskjs.org) β€” one script (~8.1 KB gzipped, zero dependencies) that provides a jQuery-compatible `$`, htmx-style declarative requests (`kj-*` attributes), Alpine-style reactivity (`kj-data`) and GSAP-style animation (`$.to`). There is no build step.

## Golden rules

1. Load exactly one script: `<script src="https://jskjs.org/cdn/1.0.0/jskjs.min.js"></script>` (or the project's local copy). **Never** add jQuery, htmx, Alpine.js or GSAP next to it.
2. Prefer HTML attributes over JavaScript:
   - Talking to the server β†’ `kj-get` / `kj-post` / `kj-put` / `kj-patch` / `kj-delete` + `kj-target` + `kj-swap`. The server returns HTML fragments.
   - Local UI state β†’ `kj-data` + `kj-text` / `kj-show` / `kj-model` / `@click` / `:attr`.
   - Write `$()` JavaScript only for what attributes cannot express.
3. Units: `$.to`, `$.from`, `$.fromTo`, `$.timeline` use **seconds**. `.fadeIn`, `.fadeOut`, `.slideUp`, `.slideDown`, `.animate` use **milliseconds**.
4. `$.ajax`, `$.get`, `$.post`, `$.getJSON` return native Promises β€” use `async/await` with `try/catch`; errors carry `err.status` and `err.data`.
5. Use only the APIs listed below. Do not invent jQuery plugins or methods from the "Not available" list.
6. Security: put user text in the page with `.text()` or `kj-text`. When building HTML strings, wrap every user value in `$.escape()`. Server endpoints that answer `kj-*` requests must escape their output (e.g. PHP `htmlspecialchars`).
7. Accessibility: real `<button>` and `<label>` elements; keep ARIA in sync with `:aria-*` bindings; `aria-live="polite"` on regions that update; honour `prefers-reduced-motion` for decorative animation.
8. Register `$.component()` and `$.store()` in a script placed right after the jsKjs script tag (before the DOM is ready).
9. Pulse expressions use `new Function`: a strict Content-Security-Policy needs `'unsafe-eval'` on pages that use `kj-data`.

## Decision guide

| Need | Use |
|---|---|
| Load / refresh part of the page from the server | `kj-get` + `kj-target` (+ `kj-trigger="load"`, `"revealed"`, `"every 5s"`) |
| Submit a form without reloading | `<form kj-post="/save" kj-target="#msg">` |
| Search-as-you-type, inline validation | `kj-trigger="keyup delay:300ms"` |
| Delete a row | `kj-delete="/rows/7" kj-target="#row-7" kj-swap="outerHTML"` (server returns empty body) |
| Infinite scroll | last item: `kj-get="/page?p=N" kj-trigger="revealed" kj-swap="outerHTML"` |
| Toggles, tabs, wizards, counters, form-driven UI | `kj-data` with `@click`, `kj-show`, `kj-model`, `kj-text` |
| Reusable widget | `$.component('name', (args) => ({ … }))` + `kj-data="name(args)"` |
| State shared by several components | `$.store('name', { … })`, read as `$.store('name')` |
| Entrance / hover / feedback animation | `$.from()` / `$.to()` with `stagger` and an ease such as `'power2.out'` |
| Sequenced animation | `$.timeline({ paused: true })…` then `await tl.play()`; `'<'` = with previous, `'+=0.3'` = gap |
| Events, classes, traversal, DOM edits | the jQuery API β€” it behaves as expected |

## API (complete list)

**Core:** `$(selector[, context])`, `$(element)`, `$('<html>')`, `$(fn)` (DOM ready). Collections are real Arrays (native `map`/`filter`/spread return plain arrays). `.each((i, el) => {})`, `.get(i)`, `.eq(i)`, `.first()`, `.last()`, `.index()`, `.add(sel)`.
**Content:** `.html(v?)`, `.text(v?)`, `.val(v?)` (checkbox β†’ boolean, multi-select β†’ array).
**Attributes:** `.attr(name, v? | obj)`, `.removeAttr()`, `.prop(name, v?)`, `.data(key?, v? | obj)` (parses data-* JSON; WeakMap storage), `.removeData()`.
**Classes / CSS:** `.addClass()`, `.removeClass()`, `.toggleClass(names, force?)`, `.hasClass()`, `.css(name, v? | obj)` (numbers β†’ px except unitless; `--custom` properties read/write without px).
**Geometry:** `.width(v?)`, `.height(v?)`, `.offset()`, `.position()`, `.scrollTop(v?)`, `.scrollLeft(v?)`, `.show()`, `.hide()`, `.toggle(state?)`.
**Insert / remove:** `.append(...)`, `.prepend(...)`, `.before(...)`, `.after(...)`, `.appendTo()`, `.prependTo()`, `.wrap(html)`, `.remove()`, `.detach()`, `.empty()`, `.clone()`, `.replaceWith()`.
**Traversal:** `.find()`, `.filter(sel | fn(i, el))`, `.not(sel | fn | el)`, `.is(sel | fn | el)`, `.closest()`, `.parent()`, `.parents(sel?)`, `.children(sel?)`, `.siblings(sel?)`, `.next(sel?)`, `.prev(sel?)`.
**Events:** `.on(types[, delegateSelector], handler)`, `.one(...)`, `.off(types?, handler?)` (namespaces: `'click.menu'`, `.off('.menu')`), `.trigger(type, detail)` β†’ handler `(e, detail)`. Shortcuts: `.click .dblclick .change .input .submit .keydown .keyup .keypress .mousedown .mouseup .mousemove .mouseenter .mouseleave .scroll .resize .touchstart .touchend .contextmenu .focus .blur`, `.hover(in, out)`. Handlers receive the native Event.
**AJAX:** `$.ajax(url?, { url, method|type, data, json: true, dataType: 'json'|'auto', headers, timeout, credentials, success, error, complete })` β†’ Promise (+ `.abort()`, `.done`, `.fail`, `.always`). `$.get(url, data?, cb?)`, `$.post(url, data?, cb?)`, `$.getJSON(url, data?, cb?)`, `$(el).load('/url [selector]', cb?)`, `$(form).serialize()`, `$(form).serializeObject()`.
**Hyper attributes:** `kj-get|post|put|patch|delete="/url"`, `kj-target="#sel"`, `kj-swap="innerHTML|outerHTML|textContent|beforebegin|afterbegin|beforeend|afterend|none"`, `kj-trigger="<event> [delay:Nms] | load | revealed | every Ns"`, `kj-confirm="msg"`, `kj-indicator="#sel"`, `kj-include="#form"`, `kj-vals='{"k":"v"}'`, `kj-push-url="true|/path"`. Defaults: form β†’ submit (sends fields), input/select β†’ change (sends its name=value), others β†’ click. Bodies are urlencoded (multipart when a form has file inputs). Events: `kj:before`, `kj:after` (response received, before swap), `kj:error` (detail.error.status), `kj:load` (after swap). New `kj-*` content is wired automatically. Polling stops when its element is removed.
**Pulse directives:** `kj-data="{ … }"` or `kj-data="component(args)"` (optional `init(el)`), `kj-text`, `kj-html`, `kj-show`, `kj-model` (text, number, range, textarea, select, checkbox, radio), `@event` / `kj-on:event` with one modifier: `.prevent .stop .enter .escape`; `$event`, `$el` available. `:attr` / `kj-bind:attr` (false/null removes; `:class` merges with static classes). `$.component(name, factory)`, `$.store(name, initial?)`, `$.reactive(obj)`.
**Motion:** `$.to(target, { x, y, rotate|rotation, scale, scaleX, scaleY, skewX, skewY, opacity, <numeric css>, duration, delay, ease, stagger, onComplete })` β†’ Promise. `$.from()`, `$.fromTo(target, from, to)`, `$(sel).to(props)`. `$.timeline({ paused?, onComplete?, ...defaults })` with `.to .from .fromTo .add(fn)` and positions `'<'` / `'+=N'`; `await tl.play()`. Eases (`$.easings`): linear, ease-in, ease-out, ease-in-out, power2.in, power2.out, power2.inOut, back.out, elastic.out, bounce.out, or a function `t => …`.
**Effects (ms):** `.fadeIn(ms?, cb?)`, `.fadeOut`, `.fadeToggle`, `.slideDown`, `.slideUp`, `.slideToggle`, `.animate(props, ms?, ease?, cb?)`.
**Utilities:** `$.each`, `$.map`, `$.extend(deep?, target, ...src)` (prototype-pollution safe), `$.debounce(fn, ms)`, `$.throttle(fn, ms)`, `$.param`, `$.parseHTML`, `$.trim`, `$.isArray`, `$.isFunction`, `$.isPlainObject`, `$.uuid()`, `$.escape()`, `$.noConflict()`, `$.version`. Aliases: `window.kj`, `window.jsKjs`.

## Not available β€” do not use

- jQuery: `.stop() .finish() .delay() .queue() .fadeTo()`, `$.Deferred $.Callbacks $.when $.grep`, `.live() .bind() .delegate()`, `$.ajaxSetup`, JSONP, `.nextAll() .prevAll() .nextUntil() .parentsUntil() .has() .contents() .end() .addBack()`, `.wrapAll() .wrapInner() .unwrap()`, `.innerWidth() .outerWidth()`, offset setter, `.serializeArray()`.
- htmx: `hx-select`, `hx-swap-oob`, `hx-boost`, `hx-headers`, `hx-params`, `hx-sync`, `hx-on`, `hx-ext`, swap modifiers (`swap:1s`, `settle`, `scroll`), extended targets (`closest tr`), trigger modifiers (`changed`, `once`, `throttle`, `from:`, filters), response headers (`HX-Redirect`…), SSE/WebSockets.
- Alpine: `x-for`, `x-if`, `x-init`, `x-effect`, `x-ref`/`$refs`, `x-cloak`, `x-transition`, `x-teleport`, `$watch`, `$dispatch`, `$nextTick`, `$store` magic (use `$.store('name')`), object syntax for `:class`/`:style`, model modifiers. For lists use `kj-html` with `.map(…).join('')` and `$.escape()`.
- GSAP: `repeat`/`yoyo`, `pause/reverse/seek/progress/timeScale`, labels and absolute/`-=` positions, stagger objects, colour/SVG-attribute tweens, `gsap.set` (use `.css()`), `quickTo`, plugins (ScrollTrigger, Flip, Draggable, SplitText, MorphSVG…). If a project truly needs these, say so instead of faking them.

## Canonical snippets

```html
<!-- Live search -->
<input name="q" kj-get="/search.php" kj-trigger="keyup delay:300ms" kj-target="#results">
<ul id="results" aria-live="polite"></ul>

<!-- Form without reload -->
<form kj-post="/subscribe.php" kj-target="#msg"><input name="email" type="email" required><button>Join</button></form>
<p id="msg" aria-live="polite"></p>

<!-- Reactive counter -->
<div kj-data="{ n: 0 }"><button @click="n++">+1</button> <span kj-text="n"></span></div>
```

```js
// Entrance animation (respects reduced motion)
if (!matchMedia('(prefers-reduced-motion: reduce)').matches)
  $.from('.card', { y: 24, opacity: 0, duration: .5, stagger: .08, ease: 'power2.out' });

// Fetch + render safely
const items = await $.getJSON('/api/items.json');
$('#list').html(items.map(i => '<li>' + $.escape(i.name) + '</li>').join(''));

// Delegation survives DOM swaps
$('#list').on('click', '.remove', function () { $(this).closest('li').remove(); });
```

Full reference: https://jskjs.org/docs/ Β· Machine-readable: https://jskjs.org/llms-full.txt
.github/instructions/jskjs.instructions.md β€” GitHub Copilot in VS Code (file-scoped instructions)

Download jskjs.instructions.md Β· curl -fsSLo .github/instructions/jskjs.instructions.md https://jskjs.org/ai/kit/jskjs.instructions.md

Markdown
---
name: jsKjs
description: Frontend code in this project uses jsKjs (jQuery-compatible $ + kj-* declarative requests + kj-data reactivity + $.to animation). Follow these rules for any HTML/JS/PHP UI work.
applyTo: "**/*.{html,htm,js,php}"
---

# jsKjs

This project uses **jsKjs** (https://jskjs.org) β€” one script (~8.1 KB gzipped, zero dependencies) that provides a jQuery-compatible `$`, htmx-style declarative requests (`kj-*` attributes), Alpine-style reactivity (`kj-data`) and GSAP-style animation (`$.to`). There is no build step.

## Golden rules

1. Load exactly one script: `<script src="https://jskjs.org/cdn/1.0.0/jskjs.min.js"></script>` (or the project's local copy). **Never** add jQuery, htmx, Alpine.js or GSAP next to it.
2. Prefer HTML attributes over JavaScript:
   - Talking to the server β†’ `kj-get` / `kj-post` / `kj-put` / `kj-patch` / `kj-delete` + `kj-target` + `kj-swap`. The server returns HTML fragments.
   - Local UI state β†’ `kj-data` + `kj-text` / `kj-show` / `kj-model` / `@click` / `:attr`.
   - Write `$()` JavaScript only for what attributes cannot express.
3. Units: `$.to`, `$.from`, `$.fromTo`, `$.timeline` use **seconds**. `.fadeIn`, `.fadeOut`, `.slideUp`, `.slideDown`, `.animate` use **milliseconds**.
4. `$.ajax`, `$.get`, `$.post`, `$.getJSON` return native Promises β€” use `async/await` with `try/catch`; errors carry `err.status` and `err.data`.
5. Use only the APIs listed below. Do not invent jQuery plugins or methods from the "Not available" list.
6. Security: put user text in the page with `.text()` or `kj-text`. When building HTML strings, wrap every user value in `$.escape()`. Server endpoints that answer `kj-*` requests must escape their output (e.g. PHP `htmlspecialchars`).
7. Accessibility: real `<button>` and `<label>` elements; keep ARIA in sync with `:aria-*` bindings; `aria-live="polite"` on regions that update; honour `prefers-reduced-motion` for decorative animation.
8. Register `$.component()` and `$.store()` in a script placed right after the jsKjs script tag (before the DOM is ready).
9. Pulse expressions use `new Function`: a strict Content-Security-Policy needs `'unsafe-eval'` on pages that use `kj-data`.

## Decision guide

| Need | Use |
|---|---|
| Load / refresh part of the page from the server | `kj-get` + `kj-target` (+ `kj-trigger="load"`, `"revealed"`, `"every 5s"`) |
| Submit a form without reloading | `<form kj-post="/save" kj-target="#msg">` |
| Search-as-you-type, inline validation | `kj-trigger="keyup delay:300ms"` |
| Delete a row | `kj-delete="/rows/7" kj-target="#row-7" kj-swap="outerHTML"` (server returns empty body) |
| Infinite scroll | last item: `kj-get="/page?p=N" kj-trigger="revealed" kj-swap="outerHTML"` |
| Toggles, tabs, wizards, counters, form-driven UI | `kj-data` with `@click`, `kj-show`, `kj-model`, `kj-text` |
| Reusable widget | `$.component('name', (args) => ({ … }))` + `kj-data="name(args)"` |
| State shared by several components | `$.store('name', { … })`, read as `$.store('name')` |
| Entrance / hover / feedback animation | `$.from()` / `$.to()` with `stagger` and an ease such as `'power2.out'` |
| Sequenced animation | `$.timeline({ paused: true })…` then `await tl.play()`; `'<'` = with previous, `'+=0.3'` = gap |
| Events, classes, traversal, DOM edits | the jQuery API β€” it behaves as expected |

## API (complete list)

**Core:** `$(selector[, context])`, `$(element)`, `$('<html>')`, `$(fn)` (DOM ready). Collections are real Arrays (native `map`/`filter`/spread return plain arrays). `.each((i, el) => {})`, `.get(i)`, `.eq(i)`, `.first()`, `.last()`, `.index()`, `.add(sel)`.
**Content:** `.html(v?)`, `.text(v?)`, `.val(v?)` (checkbox β†’ boolean, multi-select β†’ array).
**Attributes:** `.attr(name, v? | obj)`, `.removeAttr()`, `.prop(name, v?)`, `.data(key?, v? | obj)` (parses data-* JSON; WeakMap storage), `.removeData()`.
**Classes / CSS:** `.addClass()`, `.removeClass()`, `.toggleClass(names, force?)`, `.hasClass()`, `.css(name, v? | obj)` (numbers β†’ px except unitless; `--custom` properties read/write without px).
**Geometry:** `.width(v?)`, `.height(v?)`, `.offset()`, `.position()`, `.scrollTop(v?)`, `.scrollLeft(v?)`, `.show()`, `.hide()`, `.toggle(state?)`.
**Insert / remove:** `.append(...)`, `.prepend(...)`, `.before(...)`, `.after(...)`, `.appendTo()`, `.prependTo()`, `.wrap(html)`, `.remove()`, `.detach()`, `.empty()`, `.clone()`, `.replaceWith()`.
**Traversal:** `.find()`, `.filter(sel | fn(i, el))`, `.not(sel | fn | el)`, `.is(sel | fn | el)`, `.closest()`, `.parent()`, `.parents(sel?)`, `.children(sel?)`, `.siblings(sel?)`, `.next(sel?)`, `.prev(sel?)`.
**Events:** `.on(types[, delegateSelector], handler)`, `.one(...)`, `.off(types?, handler?)` (namespaces: `'click.menu'`, `.off('.menu')`), `.trigger(type, detail)` β†’ handler `(e, detail)`. Shortcuts: `.click .dblclick .change .input .submit .keydown .keyup .keypress .mousedown .mouseup .mousemove .mouseenter .mouseleave .scroll .resize .touchstart .touchend .contextmenu .focus .blur`, `.hover(in, out)`. Handlers receive the native Event.
**AJAX:** `$.ajax(url?, { url, method|type, data, json: true, dataType: 'json'|'auto', headers, timeout, credentials, success, error, complete })` β†’ Promise (+ `.abort()`, `.done`, `.fail`, `.always`). `$.get(url, data?, cb?)`, `$.post(url, data?, cb?)`, `$.getJSON(url, data?, cb?)`, `$(el).load('/url [selector]', cb?)`, `$(form).serialize()`, `$(form).serializeObject()`.
**Hyper attributes:** `kj-get|post|put|patch|delete="/url"`, `kj-target="#sel"`, `kj-swap="innerHTML|outerHTML|textContent|beforebegin|afterbegin|beforeend|afterend|none"`, `kj-trigger="<event> [delay:Nms] | load | revealed | every Ns"`, `kj-confirm="msg"`, `kj-indicator="#sel"`, `kj-include="#form"`, `kj-vals='{"k":"v"}'`, `kj-push-url="true|/path"`. Defaults: form β†’ submit (sends fields), input/select β†’ change (sends its name=value), others β†’ click. Bodies are urlencoded (multipart when a form has file inputs). Events: `kj:before`, `kj:after` (response received, before swap), `kj:error` (detail.error.status), `kj:load` (after swap). New `kj-*` content is wired automatically. Polling stops when its element is removed.
**Pulse directives:** `kj-data="{ … }"` or `kj-data="component(args)"` (optional `init(el)`), `kj-text`, `kj-html`, `kj-show`, `kj-model` (text, number, range, textarea, select, checkbox, radio), `@event` / `kj-on:event` with one modifier: `.prevent .stop .enter .escape`; `$event`, `$el` available. `:attr` / `kj-bind:attr` (false/null removes; `:class` merges with static classes). `$.component(name, factory)`, `$.store(name, initial?)`, `$.reactive(obj)`.
**Motion:** `$.to(target, { x, y, rotate|rotation, scale, scaleX, scaleY, skewX, skewY, opacity, <numeric css>, duration, delay, ease, stagger, onComplete })` β†’ Promise. `$.from()`, `$.fromTo(target, from, to)`, `$(sel).to(props)`. `$.timeline({ paused?, onComplete?, ...defaults })` with `.to .from .fromTo .add(fn)` and positions `'<'` / `'+=N'`; `await tl.play()`. Eases (`$.easings`): linear, ease-in, ease-out, ease-in-out, power2.in, power2.out, power2.inOut, back.out, elastic.out, bounce.out, or a function `t => …`.
**Effects (ms):** `.fadeIn(ms?, cb?)`, `.fadeOut`, `.fadeToggle`, `.slideDown`, `.slideUp`, `.slideToggle`, `.animate(props, ms?, ease?, cb?)`.
**Utilities:** `$.each`, `$.map`, `$.extend(deep?, target, ...src)` (prototype-pollution safe), `$.debounce(fn, ms)`, `$.throttle(fn, ms)`, `$.param`, `$.parseHTML`, `$.trim`, `$.isArray`, `$.isFunction`, `$.isPlainObject`, `$.uuid()`, `$.escape()`, `$.noConflict()`, `$.version`. Aliases: `window.kj`, `window.jsKjs`.

## Not available β€” do not use

- jQuery: `.stop() .finish() .delay() .queue() .fadeTo()`, `$.Deferred $.Callbacks $.when $.grep`, `.live() .bind() .delegate()`, `$.ajaxSetup`, JSONP, `.nextAll() .prevAll() .nextUntil() .parentsUntil() .has() .contents() .end() .addBack()`, `.wrapAll() .wrapInner() .unwrap()`, `.innerWidth() .outerWidth()`, offset setter, `.serializeArray()`.
- htmx: `hx-select`, `hx-swap-oob`, `hx-boost`, `hx-headers`, `hx-params`, `hx-sync`, `hx-on`, `hx-ext`, swap modifiers (`swap:1s`, `settle`, `scroll`), extended targets (`closest tr`), trigger modifiers (`changed`, `once`, `throttle`, `from:`, filters), response headers (`HX-Redirect`…), SSE/WebSockets.
- Alpine: `x-for`, `x-if`, `x-init`, `x-effect`, `x-ref`/`$refs`, `x-cloak`, `x-transition`, `x-teleport`, `$watch`, `$dispatch`, `$nextTick`, `$store` magic (use `$.store('name')`), object syntax for `:class`/`:style`, model modifiers. For lists use `kj-html` with `.map(…).join('')` and `$.escape()`.
- GSAP: `repeat`/`yoyo`, `pause/reverse/seek/progress/timeScale`, labels and absolute/`-=` positions, stagger objects, colour/SVG-attribute tweens, `gsap.set` (use `.css()`), `quickTo`, plugins (ScrollTrigger, Flip, Draggable, SplitText, MorphSVG…). If a project truly needs these, say so instead of faking them.

## Canonical snippets

```html
<!-- Live search -->
<input name="q" kj-get="/search.php" kj-trigger="keyup delay:300ms" kj-target="#results">
<ul id="results" aria-live="polite"></ul>

<!-- Form without reload -->
<form kj-post="/subscribe.php" kj-target="#msg"><input name="email" type="email" required><button>Join</button></form>
<p id="msg" aria-live="polite"></p>

<!-- Reactive counter -->
<div kj-data="{ n: 0 }"><button @click="n++">+1</button> <span kj-text="n"></span></div>
```

```js
// Entrance animation (respects reduced motion)
if (!matchMedia('(prefers-reduced-motion: reduce)').matches)
  $.from('.card', { y: 24, opacity: 0, duration: .5, stagger: .08, ease: 'power2.out' });

// Fetch + render safely
const items = await $.getJSON('/api/items.json');
$('#list').html(items.map(i => '<li>' + $.escape(i.name) + '</li>').join(''));

// Delegation survives DOM swaps
$('#list').on('click', '.remove', function () { $(this).closest('li').remove(); });
```

Full reference: https://jskjs.org/docs/ Β· Machine-readable: https://jskjs.org/llms-full.txt
AGENTS.md β€” Codex Β· GitHub Copilot Β· Cursor Β· Cline Β· Roo Code Β· Windsurf/Devin Β· Gemini (via setting) Β· many others

Download AGENTS.md Β· curl -fsSLo AGENTS.md https://jskjs.org/ai/kit/AGENTS.md

Markdown
# AGENTS.md β€” jsKjs project rules

This project uses **jsKjs** (https://jskjs.org) β€” one script (~8.1 KB gzipped, zero dependencies) that provides a jQuery-compatible `$`, htmx-style declarative requests (`kj-*` attributes), Alpine-style reactivity (`kj-data`) and GSAP-style animation (`$.to`). There is no build step.

## Golden rules

1. Load exactly one script: `<script src="https://jskjs.org/cdn/1.0.0/jskjs.min.js"></script>` (or the project's local copy). **Never** add jQuery, htmx, Alpine.js or GSAP next to it.
2. Prefer HTML attributes over JavaScript:
   - Talking to the server β†’ `kj-get` / `kj-post` / `kj-put` / `kj-patch` / `kj-delete` + `kj-target` + `kj-swap`. The server returns HTML fragments.
   - Local UI state β†’ `kj-data` + `kj-text` / `kj-show` / `kj-model` / `@click` / `:attr`.
   - Write `$()` JavaScript only for what attributes cannot express.
3. Units: `$.to`, `$.from`, `$.fromTo`, `$.timeline` use **seconds**. `.fadeIn`, `.fadeOut`, `.slideUp`, `.slideDown`, `.animate` use **milliseconds**.
4. `$.ajax`, `$.get`, `$.post`, `$.getJSON` return native Promises β€” use `async/await` with `try/catch`; errors carry `err.status` and `err.data`.
5. Use only the APIs listed below. Do not invent jQuery plugins or methods from the "Not available" list.
6. Security: put user text in the page with `.text()` or `kj-text`. When building HTML strings, wrap every user value in `$.escape()`. Server endpoints that answer `kj-*` requests must escape their output (e.g. PHP `htmlspecialchars`).
7. Accessibility: real `<button>` and `<label>` elements; keep ARIA in sync with `:aria-*` bindings; `aria-live="polite"` on regions that update; honour `prefers-reduced-motion` for decorative animation.
8. Register `$.component()` and `$.store()` in a script placed right after the jsKjs script tag (before the DOM is ready).
9. Pulse expressions use `new Function`: a strict Content-Security-Policy needs `'unsafe-eval'` on pages that use `kj-data`.

## Decision guide

| Need | Use |
|---|---|
| Load / refresh part of the page from the server | `kj-get` + `kj-target` (+ `kj-trigger="load"`, `"revealed"`, `"every 5s"`) |
| Submit a form without reloading | `<form kj-post="/save" kj-target="#msg">` |
| Search-as-you-type, inline validation | `kj-trigger="keyup delay:300ms"` |
| Delete a row | `kj-delete="/rows/7" kj-target="#row-7" kj-swap="outerHTML"` (server returns empty body) |
| Infinite scroll | last item: `kj-get="/page?p=N" kj-trigger="revealed" kj-swap="outerHTML"` |
| Toggles, tabs, wizards, counters, form-driven UI | `kj-data` with `@click`, `kj-show`, `kj-model`, `kj-text` |
| Reusable widget | `$.component('name', (args) => ({ … }))` + `kj-data="name(args)"` |
| State shared by several components | `$.store('name', { … })`, read as `$.store('name')` |
| Entrance / hover / feedback animation | `$.from()` / `$.to()` with `stagger` and an ease such as `'power2.out'` |
| Sequenced animation | `$.timeline({ paused: true })…` then `await tl.play()`; `'<'` = with previous, `'+=0.3'` = gap |
| Events, classes, traversal, DOM edits | the jQuery API β€” it behaves as expected |

## API (complete list)

**Core:** `$(selector[, context])`, `$(element)`, `$('<html>')`, `$(fn)` (DOM ready). Collections are real Arrays (native `map`/`filter`/spread return plain arrays). `.each((i, el) => {})`, `.get(i)`, `.eq(i)`, `.first()`, `.last()`, `.index()`, `.add(sel)`.
**Content:** `.html(v?)`, `.text(v?)`, `.val(v?)` (checkbox β†’ boolean, multi-select β†’ array).
**Attributes:** `.attr(name, v? | obj)`, `.removeAttr()`, `.prop(name, v?)`, `.data(key?, v? | obj)` (parses data-* JSON; WeakMap storage), `.removeData()`.
**Classes / CSS:** `.addClass()`, `.removeClass()`, `.toggleClass(names, force?)`, `.hasClass()`, `.css(name, v? | obj)` (numbers β†’ px except unitless; `--custom` properties read/write without px).
**Geometry:** `.width(v?)`, `.height(v?)`, `.offset()`, `.position()`, `.scrollTop(v?)`, `.scrollLeft(v?)`, `.show()`, `.hide()`, `.toggle(state?)`.
**Insert / remove:** `.append(...)`, `.prepend(...)`, `.before(...)`, `.after(...)`, `.appendTo()`, `.prependTo()`, `.wrap(html)`, `.remove()`, `.detach()`, `.empty()`, `.clone()`, `.replaceWith()`.
**Traversal:** `.find()`, `.filter(sel | fn(i, el))`, `.not(sel | fn | el)`, `.is(sel | fn | el)`, `.closest()`, `.parent()`, `.parents(sel?)`, `.children(sel?)`, `.siblings(sel?)`, `.next(sel?)`, `.prev(sel?)`.
**Events:** `.on(types[, delegateSelector], handler)`, `.one(...)`, `.off(types?, handler?)` (namespaces: `'click.menu'`, `.off('.menu')`), `.trigger(type, detail)` β†’ handler `(e, detail)`. Shortcuts: `.click .dblclick .change .input .submit .keydown .keyup .keypress .mousedown .mouseup .mousemove .mouseenter .mouseleave .scroll .resize .touchstart .touchend .contextmenu .focus .blur`, `.hover(in, out)`. Handlers receive the native Event.
**AJAX:** `$.ajax(url?, { url, method|type, data, json: true, dataType: 'json'|'auto', headers, timeout, credentials, success, error, complete })` β†’ Promise (+ `.abort()`, `.done`, `.fail`, `.always`). `$.get(url, data?, cb?)`, `$.post(url, data?, cb?)`, `$.getJSON(url, data?, cb?)`, `$(el).load('/url [selector]', cb?)`, `$(form).serialize()`, `$(form).serializeObject()`.
**Hyper attributes:** `kj-get|post|put|patch|delete="/url"`, `kj-target="#sel"`, `kj-swap="innerHTML|outerHTML|textContent|beforebegin|afterbegin|beforeend|afterend|none"`, `kj-trigger="<event> [delay:Nms] | load | revealed | every Ns"`, `kj-confirm="msg"`, `kj-indicator="#sel"`, `kj-include="#form"`, `kj-vals='{"k":"v"}'`, `kj-push-url="true|/path"`. Defaults: form β†’ submit (sends fields), input/select β†’ change (sends its name=value), others β†’ click. Bodies are urlencoded (multipart when a form has file inputs). Events: `kj:before`, `kj:after` (response received, before swap), `kj:error` (detail.error.status), `kj:load` (after swap). New `kj-*` content is wired automatically. Polling stops when its element is removed.
**Pulse directives:** `kj-data="{ … }"` or `kj-data="component(args)"` (optional `init(el)`), `kj-text`, `kj-html`, `kj-show`, `kj-model` (text, number, range, textarea, select, checkbox, radio), `@event` / `kj-on:event` with one modifier: `.prevent .stop .enter .escape`; `$event`, `$el` available. `:attr` / `kj-bind:attr` (false/null removes; `:class` merges with static classes). `$.component(name, factory)`, `$.store(name, initial?)`, `$.reactive(obj)`.
**Motion:** `$.to(target, { x, y, rotate|rotation, scale, scaleX, scaleY, skewX, skewY, opacity, <numeric css>, duration, delay, ease, stagger, onComplete })` β†’ Promise. `$.from()`, `$.fromTo(target, from, to)`, `$(sel).to(props)`. `$.timeline({ paused?, onComplete?, ...defaults })` with `.to .from .fromTo .add(fn)` and positions `'<'` / `'+=N'`; `await tl.play()`. Eases (`$.easings`): linear, ease-in, ease-out, ease-in-out, power2.in, power2.out, power2.inOut, back.out, elastic.out, bounce.out, or a function `t => …`.
**Effects (ms):** `.fadeIn(ms?, cb?)`, `.fadeOut`, `.fadeToggle`, `.slideDown`, `.slideUp`, `.slideToggle`, `.animate(props, ms?, ease?, cb?)`.
**Utilities:** `$.each`, `$.map`, `$.extend(deep?, target, ...src)` (prototype-pollution safe), `$.debounce(fn, ms)`, `$.throttle(fn, ms)`, `$.param`, `$.parseHTML`, `$.trim`, `$.isArray`, `$.isFunction`, `$.isPlainObject`, `$.uuid()`, `$.escape()`, `$.noConflict()`, `$.version`. Aliases: `window.kj`, `window.jsKjs`.

## Not available β€” do not use

- jQuery: `.stop() .finish() .delay() .queue() .fadeTo()`, `$.Deferred $.Callbacks $.when $.grep`, `.live() .bind() .delegate()`, `$.ajaxSetup`, JSONP, `.nextAll() .prevAll() .nextUntil() .parentsUntil() .has() .contents() .end() .addBack()`, `.wrapAll() .wrapInner() .unwrap()`, `.innerWidth() .outerWidth()`, offset setter, `.serializeArray()`.
- htmx: `hx-select`, `hx-swap-oob`, `hx-boost`, `hx-headers`, `hx-params`, `hx-sync`, `hx-on`, `hx-ext`, swap modifiers (`swap:1s`, `settle`, `scroll`), extended targets (`closest tr`), trigger modifiers (`changed`, `once`, `throttle`, `from:`, filters), response headers (`HX-Redirect`…), SSE/WebSockets.
- Alpine: `x-for`, `x-if`, `x-init`, `x-effect`, `x-ref`/`$refs`, `x-cloak`, `x-transition`, `x-teleport`, `$watch`, `$dispatch`, `$nextTick`, `$store` magic (use `$.store('name')`), object syntax for `:class`/`:style`, model modifiers. For lists use `kj-html` with `.map(…).join('')` and `$.escape()`.
- GSAP: `repeat`/`yoyo`, `pause/reverse/seek/progress/timeScale`, labels and absolute/`-=` positions, stagger objects, colour/SVG-attribute tweens, `gsap.set` (use `.css()`), `quickTo`, plugins (ScrollTrigger, Flip, Draggable, SplitText, MorphSVG…). If a project truly needs these, say so instead of faking them.

## Canonical snippets

```html
<!-- Live search -->
<input name="q" kj-get="/search.php" kj-trigger="keyup delay:300ms" kj-target="#results">
<ul id="results" aria-live="polite"></ul>

<!-- Form without reload -->
<form kj-post="/subscribe.php" kj-target="#msg"><input name="email" type="email" required><button>Join</button></form>
<p id="msg" aria-live="polite"></p>

<!-- Reactive counter -->
<div kj-data="{ n: 0 }"><button @click="n++">+1</button> <span kj-text="n"></span></div>
```

```js
// Entrance animation (respects reduced motion)
if (!matchMedia('(prefers-reduced-motion: reduce)').matches)
  $.from('.card', { y: 24, opacity: 0, duration: .5, stagger: .08, ease: 'power2.out' });

// Fetch + render safely
const items = await $.getJSON('/api/items.json');
$('#list').html(items.map(i => '<li>' + $.escape(i.name) + '</li>').join(''));

// Delegation survives DOM swaps
$('#list').on('click', '.remove', function () { $(this).closest('li').remove(); });
```

Full reference: https://jskjs.org/docs/ Β· Machine-readable: https://jskjs.org/llms-full.txt

Prompts to try

Live search

Prompt
Add a search input that queries search.php?q= as the user types (kj-trigger="keyup delay:300ms") and renders results into a list with aria-live="polite".

Form without reload

Prompt
Make the contact form post with kj-post to contact.php, show the server's message under the form, and disable the button while the request runs (kj-indicator + aria-busy).

More prompts β†’

Official documentation