feat: scroll-before-HTML, asset download, image metadata, CLI unification, nodriver docs — before cloudflare headache

## Scroll before HTML capture (crawler.py ~3030)
One scroll-to-bottom before get_tab_content() triggers lazy images
(btlazyloading, carousels) and font downloads. Previously scroll only
happened in capture_page_screenshots(), after HTML was already captured.
Now images_and_ fonts are in browser cache when image_manifest runs.

## Asset download from browser cache (crawler.py ~2049)
New download_assets pipeline extracts CSS (<link rel=stylesheet>), fonts
(@font-face url() inside CSS), and JS (<script src>) from HTML, fetches
via the same CDP Network.loadNetworkResource pipeline as images, saves to
assets/{css,fonts,js}/ with content-hash naming, and rewrites HTML paths.
Asset manifest (_asset_manifest.json) deduplicates across pages.

### Bugs found and fixed:
- _normalized_crawl_url() filtered .css/.js/.woff as static resources
  (designed for page link discovery) — fixed with allow_static_resources
- CDP IO.read returned CSS as UTF-8 strings but data.encode("latin-1")
  crashed on Unicode characters (arrows, BOM) — changed to utf-8
- HTML replacement missed relative href paths (absolute URLs in manifest
  but HTML had /frontend.kw/...) — added rel_href replacement

## Image metadata (crawler.py ~1970)
DOM query now collects alt, title, viewport_width/height, natural_width/
height per image and saves them in images.json manifest entries. Verified
with BBC (alt: "Donald Trump looking into camera...").

## CLI unification (crawler.py ~4180)
All boolean CLI flags now use parse_bool type with =true/=false syntax.
Converted: --headless, --fresh, --force-vision, --no-recursive, --verbose
from action="store_true". Added: --download-assets, --max-css-per-page,
--max-js-per-page. max-images default raised to 250.

## include_subdomains option (crawler.py ~2262)
_is_crawlable_child() now accepts include_subdomains parameter. When true,
also follows links to subdomains of start URL hostname (e.g. shop.prolicht.at
from www.prolicht.at).

## viewport_initial screenshot mode (crawler.py ~2079)
capture_page_screenshots() gets viewport_only flag. When true, captures only
viewport screenshot (skips scroll-to-bottom, full-page, tiles). API accepts
screenshots: "viewport_initial" as string value across all request models.

## API additions (server.py)
- download_assets, max_css_per_page, max_js_per_page, max_asset_bytes
- keep_crawl added to CrawlSitemapRequest (was only in CrawlSingleRequest)
- include_subdomains on CrawlSitemapRequest

## Nodriver documentation (documentation/nodriver/)
- NODRIVER-AGENTS.MD — Architecture, module map, browser lifecycle,
  tab methods by frequency, 3-path JS evaluation, troubleshooting
- NODRIVER-PAGE-CACHING.md — CDP Network.loadNetworkResource pipeline,
  content-type filtering, Latin-1 fix, asset manifest format, CDP API notes
- NODRIVER-CDP-DIRECT.md — Typed vs raw CDP generators, cookie persistence,
  evaluate_value() 3-path strategy, result normalization

## Cloudflare protection task (tasks/)
- Implementation plan, startup prompt, DB report, related files zip
  (for LLM-assisted implementation later)

## Client script (crawler_client.py)
Added --download-assets, --keep-crawl, --include-subdomains, --screenshots,
--interactive-mode CLI flags
