# Fix #120: faded open/close race leaves an unclosable modal

Closes #120.

## The bug

With `fadeDuration` set, `open()` defers `show()` behind a timer:

```js
if (this.options.doFade) {
  setTimeout(function () { m.show(); }, this.options.fadeDuration * this.options.fadeDelay);
}
```

`close()` does not wait for that timer. It unblocks, hides, and pops the modal
immediately. Close the modal before the fade timer fires and the deferred
`show()` still runs against a torn-down instance. The result:

- the dialog is visible (`display: inline-block`)
- the blocker is gone from the DOM
- `$.modal.isActive()` is `false`
- body scroll is restored
- the escape / click close handlers are unbound

A modal on screen that nothing can close.

## The fix (25 lines)

- `open()` keeps the fade timer on the instance
- `close()` clears a pending fade timer
- `show()` bails if the modal is no longer current or its blocker is gone
- `unblock()` carries the blocker it tears down, so a late `fadeOut` callback
  from a previous open cannot null the blocker of a re-opened instance

## Verification

Standalone jsdom harness, 13 checks, no test framework needed:

https://pub-a941bfd863a24f91a60e6c4979c18a84.r2.dev/pi-sandbox-uploads/359428210112335872/2026-09-19/1789794376349-7d6b2e12-7baf-4a1d-8be5-579516761555-repro.js

Run it against either build:

```
node repro.js path/to/jquery.modal.js
```

- current `master` (8b8a7ce): **11/13** — fails "dialog is not displayed" after
  open-then-early-close, and a 10x open/close stress loop leaves the dialog shown
- this branch: **13/13**

The checks cover: open-then-early-close, close-then-immediate-reopen, a normal
open/close, a 10x stress loop, and the non-fade path.

I left the harness out of the repo since there's no existing test setup here.
Happy to add it under `test/` (plus `jsdom` as a devDependency) if you want it
in-tree.
