[INFO] cloning repository https://github.com/yamahigashi/hoshiyomi.rs
[INFO] running `Command { std: "git" "-c" "credential.helper=" "-c" "credential.helper=/workspace/cargo-home/bin/git-credential-null" "clone" "--bare" "https://github.com/yamahigashi/hoshiyomi.rs" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fyamahigashi%2Fhoshiyomi.rs", kill_on_drop: false }`
[INFO] [stderr] Cloning into bare repository '/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fyamahigashi%2Fhoshiyomi.rs'...
[INFO] running `Command { std: "git" "rev-parse" "HEAD", kill_on_drop: false }`
[INFO] [stdout] 12017b3c8660cd0406745d056e47a1c41a4b6368
[INFO] testing yamahigashi/hoshiyomi.rs against try#9f93af291970322f4f1c6315ccde4d7078201159 for pr-146098-6
[INFO] running `Command { std: "git" "clone" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fyamahigashi%2Fhoshiyomi.rs" "/workspace/builds/worker-5-tc2/source", kill_on_drop: false }`
[INFO] [stderr] Cloning into '/workspace/builds/worker-5-tc2/source'...
[INFO] [stderr] done.
[INFO] started tweaking git repo https://github.com/yamahigashi/hoshiyomi.rs
[INFO] finished tweaking git repo https://github.com/yamahigashi/hoshiyomi.rs
[INFO] tweaked toml for git repo https://github.com/yamahigashi/hoshiyomi.rs written to /workspace/builds/worker-5-tc2/source/Cargo.toml
[INFO] validating manifest of git repo https://github.com/yamahigashi/hoshiyomi.rs on toolchain 9f93af291970322f4f1c6315ccde4d7078201159
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+9f93af291970322f4f1c6315ccde4d7078201159" "metadata" "--manifest-path" "Cargo.toml" "--no-deps", kill_on_drop: false }`
[INFO] crate git repo https://github.com/yamahigashi/hoshiyomi.rs already has a lockfile, it will not be regenerated
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+9f93af291970322f4f1c6315ccde4d7078201159" "fetch" "--manifest-path" "Cargo.toml", kill_on_drop: false }`
[INFO] [stderr]     Blocking waiting for file lock on package cache
[INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:4848fb76d95f26979359cc7e45710b1dbc8f3acb7aeedee7c460d7702230f228" "/opt/rustwide/cargo-home/bin/cargo" "+9f93af291970322f4f1c6315ccde4d7078201159" "metadata" "--no-deps" "--format-version=1", kill_on_drop: false }`
[INFO] [stdout] 2ceeb3cf104506c22c5708306c8d8c8de247dabda56050ffb0956f0b7687c97c
[INFO] running `Command { std: "docker" "start" "-a" "2ceeb3cf104506c22c5708306c8d8c8de247dabda56050ffb0956f0b7687c97c", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "inspect" "2ceeb3cf104506c22c5708306c8d8c8de247dabda56050ffb0956f0b7687c97c", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "rm" "-f" "2ceeb3cf104506c22c5708306c8d8c8de247dabda56050ffb0956f0b7687c97c", kill_on_drop: false }`
[INFO] [stdout] 2ceeb3cf104506c22c5708306c8d8c8de247dabda56050ffb0956f0b7687c97c
[INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=forbid" "-e" "RUSTDOCFLAGS=--cap-lints=forbid" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:4848fb76d95f26979359cc7e45710b1dbc8f3acb7aeedee7c460d7702230f228" "/opt/rustwide/cargo-home/bin/cargo" "+9f93af291970322f4f1c6315ccde4d7078201159" "build" "--frozen" "--message-format=json", kill_on_drop: false }`
[INFO] [stdout] b6715e61299fb5c71c49a28d4c0d7a3f862b8fb7b7a26d17b82caddfa9b1c611
[INFO] running `Command { std: "docker" "start" "-a" "b6715e61299fb5c71c49a28d4c0d7a3f862b8fb7b7a26d17b82caddfa9b1c611", kill_on_drop: false }`
[INFO] [stderr]    Compiling libc v0.2.177
[INFO] [stderr]    Compiling cfg-if v1.0.4
[INFO] [stderr]    Compiling bytes v1.10.1
[INFO] [stderr]    Compiling futures-core v0.3.31
[INFO] [stderr]    Compiling serde_core v1.0.228
[INFO] [stderr]    Compiling find-msvc-tools v0.1.4
[INFO] [stderr]    Compiling stable_deref_trait v1.2.1
[INFO] [stderr]    Compiling once_cell v1.21.3
[INFO] [stderr]    Compiling slab v0.4.11
[INFO] [stderr]    Compiling smallvec v1.15.1
[INFO] [stderr]    Compiling futures-io v0.3.31
[INFO] [stderr]    Compiling syn v2.0.106
[INFO] [stderr]    Compiling typenum v1.19.0
[INFO] [stderr]    Compiling futures-task v0.3.31
[INFO] [stderr]    Compiling generic-array v0.14.9
[INFO] [stderr]    Compiling indexmap v2.12.0
[INFO] [stderr]    Compiling httparse v1.10.1
[INFO] [stderr]    Compiling percent-encoding v2.3.2
[INFO] [stderr]    Compiling cc v1.2.41
[INFO] [stderr]    Compiling futures-channel v0.3.31
[INFO] [stderr]    Compiling openssl v0.10.74
[INFO] [stderr]    Compiling atomic-waker v1.1.2
[INFO] [stderr]    Compiling crc32fast v1.5.0
[INFO] [stderr]    Compiling alloc-no-stdlib v2.0.4
[INFO] [stderr]    Compiling zerocopy v0.8.27
[INFO] [stderr]    Compiling base64 v0.22.1
[INFO] [stderr]    Compiling tracing-core v0.1.34
[INFO] [stderr]    Compiling alloc-stdlib v0.2.2
[INFO] [stderr]    Compiling simd-adler32 v0.3.7
[INFO] [stderr]    Compiling adler2 v2.0.1
[INFO] [stderr]    Compiling brotli-decompressor v5.0.0
[INFO] [stderr]    Compiling form_urlencoded v1.2.2
[INFO] [stderr]    Compiling encoding_rs v0.8.35
[INFO] [stderr]    Compiling miniz_oxide v0.8.9
[INFO] [stderr]    Compiling unicase v2.8.1
[INFO] [stderr]    Compiling iana-time-zone v0.1.64
[INFO] [stderr]    Compiling anstyle-parse v0.2.7
[INFO] [stderr]    Compiling http v1.3.1
[INFO] [stderr]    Compiling mime_guess v2.0.5
[INFO] [stderr]    Compiling sync_wrapper v1.0.2
[INFO] [stderr]    Compiling anstyle-query v1.1.4
[INFO] [stderr]    Compiling colorchoice v1.0.4
[INFO] [stderr]    Compiling tracing v0.1.41
[INFO] [stderr]    Compiling foldhash v0.1.5
[INFO] [stderr]    Compiling compression-core v0.4.29
[INFO] [stderr]    Compiling tower-layer v0.3.3
[INFO] [stderr]    Compiling hashbrown v0.15.5
[INFO] [stderr]    Compiling anstream v0.6.21
[INFO] [stderr]    Compiling zeroize v1.8.2
[INFO] [stderr]    Compiling mio v1.1.0
[INFO] [stderr]    Compiling socket2 v0.6.1
[INFO] [stderr]    Compiling signal-hook-registry v1.4.6
[INFO] [stderr]    Compiling flate2 v1.1.4
[INFO] [stderr]    Compiling getrandom v0.2.16
[INFO] [stderr]    Compiling option-ext v0.2.0
[INFO] [stderr]    Compiling rand_core v0.6.4
[INFO] [stderr]    Compiling toml_write v0.1.2
[INFO] [stderr]    Compiling clap_lex v0.7.6
[INFO] [stderr]    Compiling never v0.1.0
[INFO] [stderr]    Compiling iri-string v0.7.8
[INFO] [stderr]    Compiling brotli v8.0.2
[INFO] [stderr]    Compiling anyhow v1.0.100
[INFO] [stderr]    Compiling thiserror v2.0.17
[INFO] [stderr]    Compiling http-body v1.0.1
[INFO] [stderr]    Compiling headers-core v0.3.0
[INFO] [stderr]    Compiling quick-xml v0.37.5
[INFO] [stderr]    Compiling http-body-util v0.1.3
[INFO] [stderr]    Compiling hashlink v0.10.0
[INFO] [stderr]    Compiling clap_builder v4.5.49
[INFO] [stderr]    Compiling dirs-sys v0.4.1
[INFO] [stderr]    Compiling block-buffer v0.10.4
[INFO] [stderr]    Compiling crypto-common v0.1.6
[INFO] [stderr]    Compiling rustls-pki-types v1.12.0
[INFO] [stderr]    Compiling openssl-sys v0.9.110
[INFO] [stderr]    Compiling libsqlite3-sys v0.35.0
[INFO] [stderr]    Compiling digest v0.10.7
[INFO] [stderr]    Compiling Hoshiyomi v0.1.0 (/opt/rustwide/workdir)
[INFO] [stderr]    Compiling scoped-tls v1.0.1
[INFO] [stderr]    Compiling fallible-iterator v0.3.0
[INFO] [stderr]    Compiling fallible-streaming-iterator v0.1.9
[INFO] [stderr]    Compiling utf8-width v0.1.7
[INFO] [stderr]    Compiling html-escape v0.2.13
[INFO] [stderr]    Compiling native-tls v0.2.14
[INFO] [stderr]    Compiling sha1 v0.10.6
[INFO] [stderr]    Compiling dirs v5.0.1
[INFO] [stderr]    Compiling headers v0.4.1
[INFO] [stderr]    Compiling ppv-lite86 v0.2.21
[INFO] [stderr]    Compiling serde_json v1.0.145
[INFO] [stderr]    Compiling rand_chacha v0.3.1
[INFO] [stderr]    Compiling rand v0.8.5
[INFO] [stderr]    Compiling synstructure v0.13.2
[INFO] [stderr]    Compiling darling_core v0.20.11
[INFO] [stderr]    Compiling zerofrom-derive v0.1.6
[INFO] [stderr]    Compiling yoke-derive v0.8.0
[INFO] [stderr]    Compiling serde_derive v1.0.228
[INFO] [stderr]    Compiling zerovec-derive v0.11.1
[INFO] [stderr]    Compiling tokio-macros v2.6.0
[INFO] [stderr]    Compiling displaydoc v0.2.5
[INFO] [stderr]    Compiling futures-macro v0.3.31
[INFO] [stderr]    Compiling openssl-macros v0.1.1
[INFO] [stderr]    Compiling pin-project-internal v1.1.10
[INFO] [stderr]    Compiling clap_derive v4.5.49
[INFO] [stderr]    Compiling thiserror-impl v2.0.17
[INFO] [stderr]    Compiling compression-codecs v0.4.31
[INFO] [stderr]    Compiling tokio v1.48.0
[INFO] [stderr]    Compiling pin-project v1.1.10
[INFO] [stderr]    Compiling futures-util v0.3.31
[INFO] [stderr]    Compiling zerofrom v0.1.6
[INFO] [stderr]    Compiling yoke v0.8.0
[INFO] [stderr]    Compiling zerotrie v0.2.2
[INFO] [stderr]    Compiling clap v4.5.49
[INFO] [stderr]    Compiling zerovec v0.11.4
[INFO] [stderr]    Compiling darling_macro v0.20.11
[INFO] [stderr]    Compiling darling v0.20.11
[INFO] [stderr]    Compiling derive_builder_core v0.20.2
[INFO] [stderr]    Compiling derive_builder_macro v0.20.2
[INFO] [stderr]    Compiling tinystr v0.8.1
[INFO] [stderr]    Compiling potential_utf v0.1.3
[INFO] [stderr]    Compiling derive_builder v0.20.2
[INFO] [stderr]    Compiling icu_collections v2.0.0
[INFO] [stderr]    Compiling icu_locale_core v2.0.0
[INFO] [stderr]    Compiling serde v1.0.228
[INFO] [stderr]    Compiling icu_provider v2.0.0
[INFO] [stderr]    Compiling chrono v0.4.42
[INFO] [stderr]    Compiling serde_spanned v0.6.9
[INFO] [stderr]    Compiling toml_datetime v0.6.11
[INFO] [stderr]    Compiling serde_urlencoded v0.7.1
[INFO] [stderr]    Compiling icu_properties v2.0.1
[INFO] [stderr]    Compiling icu_normalizer v2.0.0
[INFO] [stderr]    Compiling toml_edit v0.22.27
[INFO] [stderr]    Compiling futures-executor v0.3.31
[INFO] [stderr]    Compiling futures v0.3.31
[INFO] [stderr]    Compiling idna_adapter v1.2.1
[INFO] [stderr]    Compiling idna v1.1.0
[INFO] [stderr]    Compiling diligent-date-parser v0.1.5
[INFO] [stderr]    Compiling atom_syndication v0.12.7
[INFO] [stderr]    Compiling url v2.5.7
[INFO] [stderr]    Compiling rss v2.0.12
[INFO] [stderr]    Compiling tokio-util v0.7.16
[INFO] [stderr]    Compiling tower v0.5.2
[INFO] [stderr]    Compiling tokio-native-tls v0.3.1
[INFO] [stderr]    Compiling async-compression v0.4.32
[INFO] [stderr]    Compiling h2 v0.4.12
[INFO] [stderr]    Compiling tower-http v0.6.6
[INFO] [stderr]    Compiling toml v0.8.23
[INFO] [stderr]    Compiling rusqlite v0.37.0
[INFO] [stderr]    Compiling hyper v1.7.0
[INFO] [stderr]    Compiling hyper-util v0.1.17
[INFO] [stderr]    Compiling hyper-tls v0.6.0
[INFO] [stderr]    Compiling warp v0.4.2
[INFO] [stderr]    Compiling reqwest v0.12.24
[INFO] [stderr]     Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 28s
[INFO] running `Command { std: "docker" "inspect" "b6715e61299fb5c71c49a28d4c0d7a3f862b8fb7b7a26d17b82caddfa9b1c611", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "rm" "-f" "b6715e61299fb5c71c49a28d4c0d7a3f862b8fb7b7a26d17b82caddfa9b1c611", kill_on_drop: false }`
[INFO] [stdout] b6715e61299fb5c71c49a28d4c0d7a3f862b8fb7b7a26d17b82caddfa9b1c611
[INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=forbid" "-e" "RUSTDOCFLAGS=--cap-lints=forbid" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:4848fb76d95f26979359cc7e45710b1dbc8f3acb7aeedee7c460d7702230f228" "/opt/rustwide/cargo-home/bin/cargo" "+9f93af291970322f4f1c6315ccde4d7078201159" "test" "--frozen" "--no-run" "--message-format=json", kill_on_drop: false }`
[INFO] [stdout] 8c3e24f64ab652d7255a2af0a1faf2cdcc0bf2e0410b8710530e72a42e58331d
[INFO] running `Command { std: "docker" "start" "-a" "8c3e24f64ab652d7255a2af0a1faf2cdcc0bf2e0410b8710530e72a42e58331d", kill_on_drop: false }`
[INFO] [stderr]    Compiling futures-io v0.3.31
[INFO] [stderr]    Compiling bitflags v2.9.4
[INFO] [stderr]    Compiling value-bag v1.11.1
[INFO] [stderr]    Compiling crossbeam-utils v0.8.21
[INFO] [stderr]    Compiling parking v2.2.1
[INFO] [stderr]    Compiling fastrand v2.3.0
[INFO] [stderr]    Compiling rustix v1.1.2
[INFO] [stderr]    Compiling linux-raw-sys v0.11.0
[INFO] [stderr]    Compiling memchr v2.7.6
[INFO] [stderr]    Compiling parking_lot_core v0.9.12
[INFO] [stderr]    Compiling async-io v2.6.0
[INFO] [stderr]    Compiling dirs-sys-next v0.1.2
[INFO] [stderr]    Compiling scopeguard v1.2.0
[INFO] [stderr]    Compiling regex-syntax v0.8.8
[INFO] [stderr]    Compiling smallvec v1.15.1
[INFO] [stderr]    Compiling crunchy v0.2.4
[INFO] [stderr]    Compiling lock_api v0.4.14
[INFO] [stderr]    Compiling futures-util v0.3.31
[INFO] [stderr]    Compiling dirs-next v2.0.0
[INFO] [stderr]    Compiling async-task v4.7.1
[INFO] [stderr]    Compiling siphasher v1.0.1
[INFO] [stderr]    Compiling tiny-keccak v2.0.2
[INFO] [stderr]    Compiling openssl v0.10.74
[INFO] [stderr]    Compiling log v0.4.28
[INFO] [stderr]    Compiling futures-lite v2.6.1
[INFO] [stderr]    Compiling term v0.7.0
[INFO] [stderr]    Compiling same-file v1.0.6
[INFO] [stderr]    Compiling either v1.15.0
[INFO] [stderr]    Compiling aho-corasick v1.1.3
[INFO] [stderr]    Compiling phf_shared v0.11.3
[INFO] [stderr]    Compiling precomputed-hash v0.1.1
[INFO] [stderr]    Compiling syn v1.0.109
[INFO] [stderr]    Compiling fixedbitset v0.4.2
[INFO] [stderr]    Compiling tracing v0.1.41
[INFO] [stderr]    Compiling parking_lot v0.12.5
[INFO] [stderr]    Compiling bit-vec v0.6.3
[INFO] [stderr]    Compiling new_debug_unreachable v1.0.6
[INFO] [stderr]    Compiling ascii-canvas v3.0.0
[INFO] [stderr]    Compiling itertools v0.11.0
[INFO] [stderr]    Compiling concurrent-queue v2.5.0
[INFO] [stderr]    Compiling h2 v0.4.12
[INFO] [stderr]    Compiling petgraph v0.6.5
[INFO] [stderr]    Compiling ena v0.14.3
[INFO] [stderr]    Compiling walkdir v2.5.0
[INFO] [stderr]    Compiling bit-set v0.5.3
[INFO] [stderr]    Compiling piper v0.2.4
[INFO] [stderr]    Compiling event-listener v5.4.1
[INFO] [stderr]    Compiling string_cache v0.8.9
[INFO] [stderr]    Compiling unicode-xid v0.2.6
[INFO] [stderr]    Compiling pico-args v0.5.0
[INFO] [stderr]    Compiling event-listener-strategy v0.5.4
[INFO] [stderr]    Compiling event-listener v2.5.3
[INFO] [stderr]    Compiling kv-log-macro v1.0.7
[INFO] [stderr]    Compiling async-channel v2.5.0
[INFO] [stderr]    Compiling async-lock v3.4.1
[INFO] [stderr]    Compiling http v0.2.12
[INFO] [stderr]    Compiling async-executor v1.13.3
[INFO] [stderr]    Compiling async-channel v1.9.0
[INFO] [stderr]    Compiling getrandom v0.3.4
[INFO] [stderr]    Compiling blocking v1.6.2
[INFO] [stderr]    Compiling rusqlite v0.37.0
[INFO] [stderr]    Compiling socket2 v0.5.10
[INFO] [stderr]    Compiling lalrpop-util v0.20.2
[INFO] [stderr]    Compiling assert-json-diff v2.0.2
[INFO] [stderr]    Compiling async-trait v0.1.89
[INFO] [stderr]    Compiling http-body v0.4.6
[INFO] [stderr]    Compiling levenshtein v1.0.5
[INFO] [stderr]    Compiling similar v2.7.0
[INFO] [stderr]    Compiling regex-automata v0.4.13
[INFO] [stderr]    Compiling native-tls v0.2.14
[INFO] [stderr]    Compiling tokio-native-tls v0.3.1
[INFO] [stderr]    Compiling tower v0.5.2
[INFO] [stderr]    Compiling futures-executor v0.3.31
[INFO] [stderr]    Compiling hyper v0.14.32
[INFO] [stderr]    Compiling async-attributes v1.1.2
[INFO] [stderr]    Compiling regex v1.12.2
[INFO] [stderr]    Compiling futures v0.3.31
[INFO] [stderr]    Compiling lalrpop v0.20.2
[INFO] [stderr]    Compiling tower-http v0.6.6
[INFO] [stderr]    Compiling polling v3.11.0
[INFO] [stderr]    Compiling tempfile v3.23.0
[INFO] [stderr]    Compiling hyper v1.7.0
[INFO] [stderr]    Compiling async-signal v0.2.13
[INFO] [stderr]    Compiling async-global-executor v2.4.1
[INFO] [stderr]    Compiling async-process v2.5.0
[INFO] [stderr]    Compiling async-std v1.13.2
[INFO] [stderr]    Compiling serde_regex v1.1.0
[INFO] [stderr]    Compiling hyper-util v0.1.17
[INFO] [stderr]    Compiling hyper-tls v0.6.0
[INFO] [stderr]    Compiling warp v0.4.2
[INFO] [stderr]    Compiling reqwest v0.12.24
[INFO] [stderr]    Compiling async-object-pool v0.1.5
[INFO] [stderr]    Compiling Hoshiyomi v0.1.0 (/opt/rustwide/workdir)
[INFO] [stderr]    Compiling basic-cookies v0.1.5
[INFO] [stderr]    Compiling httpmock v0.7.0
[INFO] [stderr]     Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 03s
[INFO] running `Command { std: "docker" "inspect" "8c3e24f64ab652d7255a2af0a1faf2cdcc0bf2e0410b8710530e72a42e58331d", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "rm" "-f" "8c3e24f64ab652d7255a2af0a1faf2cdcc0bf2e0410b8710530e72a42e58331d", kill_on_drop: false }`
[INFO] [stdout] 8c3e24f64ab652d7255a2af0a1faf2cdcc0bf2e0410b8710530e72a42e58331d
[INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-5-tc2/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=forbid" "-e" "RUSTDOCFLAGS=--cap-lints=forbid" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:4848fb76d95f26979359cc7e45710b1dbc8f3acb7aeedee7c460d7702230f228" "/opt/rustwide/cargo-home/bin/cargo" "+9f93af291970322f4f1c6315ccde4d7078201159" "test" "--frozen", kill_on_drop: false }`
[INFO] [stdout] b649b6c388117571766223a354e2b7d1e5d89c97b01d4570281619b09e324263
[INFO] running `Command { std: "docker" "start" "-a" "b649b6c388117571766223a354e2b7d1e5d89c97b01d4570281619b09e324263", kill_on_drop: false }`
[INFO] [stderr]     Finished `test` profile [unoptimized + debuginfo] target(s) in 0.42s
[INFO] [stderr]      Running unittests src/lib.rs (/opt/rustwide/target/debug/deps/hoshiyomi-166b3f200120c975)
[INFO] [stdout] 
[INFO] [stdout] running 16 tests
[INFO] [stdout] test db::tests::activity_tier_thresholds ... ok
[INFO] [stdout] test db::tests::jitter_respects_bounds ... ok
[INFO] [stdout] test config::tests::flag_overrides_config_file ... ok
[INFO] [stdout] test config::tests::env_overrides_config_file ... ok
[INFO] [stdout] test config::tests::invalid_value_reports_config_source ... ok
[INFO] [stdout] test db::tests::ema_updates_with_smoothing ... ok
[INFO] [stdout] test db::tests::ema_fallback_for_sparse_history ... ok
[INFO] [stdout] test db::tests::zero_star_users_use_max_interval ... ok
[INFO] [stdout] test server::tests::feed_handler_returns_xml ... ok
[INFO] [stdout] test server::tests::status_endpoint_reports_scheduler_and_next_checks ... ok
[INFO] [stdout] test db::star_query::tests::options_snapshot_counts_entities ... ok
[INFO] [stdout] test db::star_query::tests::next_check_summary_groups_by_tier ... ok
[INFO] [stdout] test db::tests::ema_bootstrap_on_third_event ... ok
[INFO] [stdout] test db::star_query::tests::query_filters_and_paginates ... ok
[INFO] [stdout] test server::tests::options_endpoint_returns_counts_and_cache_headers ... ok
[INFO] [stdout] test server::tests::stars_endpoint_paginates_and_filters ... ok
[INFO] [stdout] 
[INFO] [stdout] test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
[INFO] [stdout] 
[INFO] [stderr]      Running unittests src/main.rs (/opt/rustwide/target/debug/deps/Hoshiyomi-49a073e1792fcfdd)
[INFO] [stdout] 
[INFO] [stdout] running 0 tests
[INFO] [stdout] 
[INFO] [stdout] test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
[INFO] [stdout] 
[INFO] [stderr]      Running tests/frontend_snapshot.rs (/opt/rustwide/target/debug/deps/frontend_snapshot-c0716c8c78ba3553)
[INFO] [stdout] 
[INFO] [stdout] running 1 test
[INFO] [stdout] test bundled_frontend_matches_fixture ... FAILED
[INFO] [stdout] 
[INFO] [stdout] failures:
[INFO] [stdout] 
[INFO] [stdout] ---- bundled_frontend_matches_fixture stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'bundled_frontend_matches_fixture' (55) panicked at tests/frontend_snapshot.rs:6:5:
[INFO] [stdout] assertion `left == right` failed: Bundled frontend template diverged from recorded snapshot
[INFO] [stdout]   left: "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>GitHub Followings Stars</title>\n    <style>\n:root {\n        color-scheme: light dark;\n        --bg: #ffffff;\n        --fg: #1b1f23;\n        --muted: #4a5568;\n        --border: #d0d7de;\n        --accent: #0366d6;\n        --badge-bg: #ddeeff;\n        --badge-fg: #054da7;\n        --card-bg: #ffffff;\n        --card-border: #d0d7de;\n        --card-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);\n        --card-new-bg: #eaf2ff;\n        --card-new-border: #1f6feb;\n        --card-pinned-border: #f2c744;\n        --chip-bg: #eef2ff;\n        --chip-fg: #102242;\n        --chip-border: #c3d1ff;\n        --chip-muted-bg: #f6f8fa;\n        --chip-muted-fg: #4a5568;\n        --timestamp-label: #5a6472;\n      }\n      body {\n        font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n        margin: 0 auto;\n        padding: 1.5rem;\n        max-width: 960px;\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .skip-link {\n        position: absolute;\n        top: 0.5rem;\n        left: 0.5rem;\n        padding: 0.5rem 0.75rem;\n        background: var(--accent);\n        color: #ffffff;\n        border-radius: 6px;\n        transform: translateY(-150%);\n        transition: transform 0.2s ease;\n        z-index: 1000;\n      }\n      .skip-link:focus {\n        transform: translateY(0);\n        outline: none;\n      }\n      header {\n        display: flex;\n        flex-direction: column;\n        gap: 0.25rem;\n        margin-bottom: 1.5rem;\n      }\n      h1 {\n        margin: 0;\n        font-size: 1.8rem;\n      }\n      .timestamp {\n        color: var(--muted);\n        font-size: 0.9rem;\n      }\n      .summary-strip {\n        display: flex;\n        flex-direction: column;\n        gap: 0.55rem;\n        margin-bottom: 1rem;\n        padding: 0.85rem 1rem;\n        border: 1px solid var(--border);\n        border-radius: 12px;\n        background: var(--chip-muted-bg);\n      }\n      .summary-strip--empty {\n        opacity: 0.85;\n      }\n      .summary-primary {\n        font-size: 1rem;\n        font-weight: 600;\n        color: var(--fg);\n      }\n      .summary-meta {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.5rem 0.75rem;\n        font-size: 0.85rem;\n        color: var(--muted);\n      }\n      .summary-badge {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.35rem;\n        padding: 0.25rem 0.6rem;\n        border-radius: 999px;\n        font-weight: 600;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        background: var(--badge-bg);\n        color: var(--badge-fg);\n      }\n      .summary-filters {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.4rem;\n      }\n      .summary-chip {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.3rem;\n        padding: 0.25rem 0.65rem;\n        border-radius: 999px;\n        border: 1px solid var(--chip-border);\n        background: var(--chip-bg);\n        color: var(--chip-fg);\n        font-weight: 500;\n      }\n      .summary-chip--search {\n        background: var(--badge-bg);\n        border-color: var(--badge-bg);\n        color: var(--badge-fg);\n      }\n      .summary-chip--user {\n        background: rgba(3, 102, 214, 0.12);\n        border-color: rgba(3, 102, 214, 0.24);\n        color: var(--accent);\n      }\n      @media (min-width: 640px) {\n        .summary-strip {\n          flex-direction: row;\n          align-items: center;\n          justify-content: space-between;\n        }\n        .summary-primary {\n          font-size: 1.05rem;\n        }\n        .summary-meta {\n          justify-content: flex-end;\n        }\n      }\n      .controls {\n        display: grid;\n        gap: 0.75rem;\n        grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n        margin-bottom: 1rem;\n      }\n      label.control {\n        display: flex;\n        flex-direction: column;\n        gap: 0.35rem;\n        font-size: 0.85rem;\n        text-transform: uppercase;\n        letter-spacing: 0.04em;\n        color: var(--muted);\n      }\n      .controls input,\n      .controls select,\n      .controls button {\n        font: inherit;\n        padding: 0.45rem 0.6rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .controls button {\n        cursor: pointer;\n        justify-self: start;\n      }\n      .preset-bar {\n        border: 1px solid var(--border);\n        border-radius: 12px;\n        padding: 1rem;\n        margin-bottom: 1rem;\n        background: var(--chip-muted-bg);\n        display: flex;\n        flex-direction: column;\n        gap: 0.75rem;\n      }\n      .preset-bar__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        gap: 0.5rem;\n      }\n      .preset-bar__title {\n        font-size: 1rem;\n        margin: 0;\n      }\n      .preset-bar__hint {\n        margin: 0;\n        font-size: 0.85rem;\n        color: var(--muted);\n      }\n      .preset-bar__empty {\n        margin: 0;\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n      .preset-list {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n      }\n      .preset-chip-wrapper {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.35rem;\n      }\n      .preset-chip {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.35rem;\n        padding: 0.35rem 0.65rem;\n        border-radius: 999px;\n        border: 1px solid var(--chip-border);\n        background: var(--chip-bg);\n        color: var(--chip-fg);\n        font-size: 0.9rem;\n        cursor: pointer;\n        position: relative;\n      }\n      .preset-chip.is-active {\n        border-color: var(--accent);\n        color: var(--accent);\n        background: rgba(3, 102, 214, 0.12);\n      }\n      .preset-chip__label {\n        font-weight: 600;\n      }\n      .preset-chip__keys {\n        font-size: 0.75rem;\n        text-transform: uppercase;\n        color: var(--muted);\n      }\n      .preset-chip__actions {\n        display: inline-flex;\n        gap: 0.25rem;\n      }\n      .preset-chip__icon-btn {\n        border: none;\n        background: transparent;\n        color: inherit;\n        padding: 0;\n        font-size: 0.85rem;\n        cursor: pointer;\n      }\n      .preset-chip__icon-btn:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .virtual-spacer {\n        list-style: none;\n        pointer-events: none;\n      }\n      .status-line {\n        font-size: 0.9rem;\n        color: var(--muted);\n        margin-bottom: 0.75rem;\n      }\n      .sync-bar {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.75rem;\n        margin-bottom: 0.75rem;\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n      .sync-badge {\n        background: var(--badge-bg);\n        color: var(--badge-fg);\n        padding: 0.15rem 0.6rem;\n        border-radius: 999px;\n        font-size: 0.7rem;\n        text-transform: uppercase;\n        letter-spacing: 0.06em;\n      }\n      .sync-badge[hidden] {\n        display: none;\n      }\n      .sync-actions {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n      }\n      .sync-actions button {\n        font: inherit;\n        padding: 0.35rem 0.8rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .sync-actions button:disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n      }\n      .sync-actions button:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .quick-filters {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n        margin-bottom: 0.75rem;\n        align-items: center;\n        font-size: 0.85rem;\n        color: var(--muted);\n      }\n      .quick-filter-label {\n        font-weight: 600;\n        text-transform: uppercase;\n        letter-spacing: 0.05em;\n      }\n      .quick-filter-chip {\n        font: inherit;\n        padding: 0.25rem 0.65rem;\n        border-radius: 999px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .quick-filter-chip.is-active {\n        background: var(--accent);\n        color: #ffffff;\n        border-color: var(--accent);\n      }\n      .quick-filter-chip:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .user-filter-banner {\n        display: flex;\n        align-items: center;\n        gap: 0.75rem;\n        margin-bottom: 0.75rem;\n        padding: 0.6rem 0.8rem;\n        border: 1px solid var(--border);\n        border-radius: 8px;\n        background: rgba(3, 102, 214, 0.08);\n        color: var(--accent);\n      }\n      .user-filter-clear {\n        font: inherit;\n        padding: 0.3rem 0.75rem;\n        border-radius: 6px;\n        border: 1px solid var(--accent);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .user-filter-clear:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .error-banner {\n        background: #fdecea;\n        color: #611a15;\n        border: 1px solid #f5c6cb;\n        padding: 0.75rem;\n        border-radius: 6px;\n        margin-bottom: 1rem;\n        display: none;\n        align-items: center;\n        gap: 0.75rem;\n      }\n      .error-banner[aria-hidden=\"false\"] {\n        display: flex;\n      }\n      .error-banner button {\n        font: inherit;\n        padding: 0.3rem 0.75rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .error-banner button:disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n      }\n      .shortcut-modal {\n        position: fixed;\n        inset: 0;\n        background: rgba(0, 0, 0, 0.45);\n        display: flex;\n        justify-content: center;\n        align-items: center;\n        padding: 1.5rem;\n        z-index: 2000;\n      }\n      .shortcut-modal[hidden] {\n        display: none;\n      }\n      .shortcut-modal__panel {\n        background: var(--bg);\n        color: var(--fg);\n        border-radius: 12px;\n        border: 1px solid var(--border);\n        max-width: 480px;\n        width: 100%;\n        box-shadow: 0 18px 45px rgba(15, 23, 42, 0.25);\n        padding: 1.25rem 1.5rem;\n        display: flex;\n        flex-direction: column;\n        gap: 1rem;\n      }\n      .shortcut-modal__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n      }\n      .shortcut-modal__close {\n        background: transparent;\n        border: none;\n        font-size: 1.5rem;\n        line-height: 1;\n        cursor: pointer;\n        color: var(--muted);\n      }\n      .shortcut-modal__close:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .shortcut-modal__list {\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n        gap: 0.75rem 1rem;\n        margin: 0;\n      }\n      .shortcut-modal__list dt {\n        font-weight: 600;\n      }\n      .shortcut-modal__list dd {\n        margin: 0;\n        color: var(--muted);\n      }\n      .preset-modal {\n        position: fixed;\n        inset: 0;\n        background: rgba(0, 0, 0, 0.45);\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        padding: 1rem;\n        z-index: 2100;\n      }\n      .preset-modal[hidden] {\n        display: none;\n      }\n      .preset-modal__panel {\n        background: var(--bg);\n        color: var(--fg);\n        border-radius: 16px;\n        padding: 1.25rem;\n        width: min(420px, 100%);\n        box-shadow: var(--card-shadow);\n        border: 1px solid var(--border);\n      }\n      .preset-modal__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        margin-bottom: 0.75rem;\n      }\n      .preset-modal__close {\n        border: none;\n        background: transparent;\n        font-size: 1.25rem;\n        line-height: 1;\n        cursor: pointer;\n        color: var(--muted);\n      }\n      .preset-modal__close:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .preset-form {\n        display: flex;\n        flex-direction: column;\n        gap: 0.85rem;\n      }\n      .preset-form__label {\n        display: flex;\n        flex-direction: column;\n        gap: 0.4rem;\n        font-size: 0.9rem;\n      }\n      .preset-form__label input {\n        font: inherit;\n        padding: 0.5rem 0.6rem;\n        border-radius: 8px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .preset-form__actions {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n        align-items: center;\n      }\n      .preset-form__secondary {\n        border: 1px solid var(--border);\n        background: transparent;\n        color: var(--fg);\n        padding: 0.35rem 0.8rem;\n        border-radius: 6px;\n        cursor: pointer;\n      }\n      .preset-form__spacer {\n        flex: 1;\n      }\n      .star-list {\n        list-style: none;\n        padding: 0;\n        margin: 0;\n        display: flex;\n        flex-direction: column;\n        gap: 1rem;\n      }\n      .star-list.is-grid {\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n        gap: 1rem;\n      }\n      .star-list.is-compact {\n        gap: 0.75rem;\n      }\n      .star-list.is-grid.is-compact {\n        gap: 0.75rem;\n      }\n      @media (max-width: 1023px) {\n        .star-list.is-grid {\n          display: flex;\n          flex-direction: column;\n        }\n      }\n      .star-card {\n        border: 1px solid var(--card-border);\n        border-radius: 12px;\n        padding: 1rem 1.15rem;\n        display: flex;\n        flex-direction: column;\n        gap: 0.75rem;\n        background: var(--card-bg);\n        box-shadow: var(--card-shadow);\n        transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;\n      }\n      .star-card--compact {\n        padding: 0.75rem 0.9rem;\n        gap: 0.55rem;\n      }\n      .star-card--new {\n        border-color: var(--card-new-border);\n        background: var(--card-new-bg);\n        box-shadow: 0 0 0 1px var(--card-new-border) inset, var(--card-shadow);\n      }\n      .star-card--pinned {\n        border-color: var(--card-pinned-border);\n        box-shadow: 0 0 0 1px var(--card-pinned-border) inset, var(--card-shadow);\n      }\n      .star-card--excluded {\n        opacity: 0.6;\n      }\n      .star-card__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: flex-start;\n        gap: 0.5rem;\n        flex-wrap: wrap;\n      }\n      .star-card__signals {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.4rem;\n      }\n      .star-user-button {\n        border: 1px solid transparent;\n        background: none;\n        padding: 0.2rem 0.55rem;\n        font: inherit;\n        font-weight: 600;\n        border-radius: 999px;\n        color: var(--fg);\n        cursor: pointer;\n        text-align: left;\n        transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;\n      }\n      .star-user-button:hover {\n        border-color: rgba(3, 102, 214, 0.25);\n      }\n      .star-user-button:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .star-user-button.is-pinned {\n        border-color: rgba(3, 102, 214, 0.3);\n        background: rgba(3, 102, 214, 0.12);\n        color: var(--accent);\n      }\n      .star-user-button.is-excluded {\n        border-color: rgba(110, 118, 129, 0.4);\n        color: var(--muted);\n        text-decoration: line-through;\n        opacity: 0.7;\n      }\n      .new-badge {\n        font-size: 0.68rem;\n        font-weight: 700;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        background: var(--accent);\n        color: #ffffff;\n        border-radius: 999px;\n        padding: 0.15rem 0.45rem;\n      }\n      .activity-tag {\n        font-size: 0.7rem;\n        padding: 0.2rem 0.5rem;\n        border-radius: 999px;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        background: var(--badge-bg);\n        color: var(--badge-fg);\n        font-weight: 600;\n      }\n      .repo-link {\n        color: var(--accent);\n        font-size: 1.1rem;\n        font-weight: 650;\n        text-decoration: none;\n      }\n      .repo-link:hover {\n        text-decoration: underline;\n      }\n      .star-card__body {\n        display: flex;\n        flex-direction: column;\n        gap: 0.5rem;\n      }\n      .star-description {\n        margin: 0;\n        font-size: 0.95rem;\n        color: var(--fg);\n      }\n      .star-card__footer {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: flex-start;\n        justify-content: space-between;\n        gap: 0.75rem 1rem;\n        border-top: 1px solid var(--border);\n        padding-top: 0.75rem;\n      }\n      .star-meta {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.45rem;\n        font-size: 0.85rem;\n        color: var(--muted);\n        align-items: center;\n      }\n      .meta-pill {\n        background: var(--chip-muted-bg);\n        color: var(--chip-muted-fg);\n        padding: 0.22rem 0.55rem;\n        border-radius: 999px;\n        font-size: 0.78rem;\n        font-weight: 600;\n      }\n      .topics {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.35rem;\n      }\n      .topic-tag {\n        background: var(--chip-bg);\n        border: 1px solid var(--chip-border);\n        color: var(--chip-fg);\n        padding: 0.2rem 0.55rem;\n        border-radius: 999px;\n        font-size: 0.75rem;\n        font-weight: 500;\n      }\n      .star-card__timestamps {\n        display: grid;\n        grid-template-columns: auto 1fr;\n        gap: 0.25rem 0.6rem;\n        align-items: center;\n        font-size: 0.82rem;\n        color: var(--muted);\n        min-width: 180px;\n      }\n      .timestamp-label {\n        font-size: 0.7rem;\n        font-weight: 700;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        color: var(--timestamp-label);\n      }\n      .timestamp-value {\n        font-size: 0.82rem;\n        color: var(--muted);\n      }\n      @media (max-width: 640px) {\n        .star-card__timestamps {\n          grid-template-columns: 1fr;\n          gap: 0.15rem;\n        }\n      }\n      .pagination {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.75rem;\n        margin-bottom: 0.75rem;\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n      .pagination-info {\n        font-weight: 500;\n      }\n      .pagination-controls {\n        display: flex;\n        gap: 0.5rem;\n      }\n      .pagination-controls button {\n        font: inherit;\n        padding: 0.35rem 0.8rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .pagination-controls button:disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n      }\n      .pagination-controls button:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .page-size-control {\n        display: flex;\n        align-items: center;\n        gap: 0.35rem;\n        font-size: 0.85rem;\n        text-transform: uppercase;\n        letter-spacing: 0.05em;\n      }\n      .page-size-control select {\n        font: inherit;\n        padding: 0.3rem 0.5rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .page-size-control select:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .empty-state {\n        text-align: center;\n        padding: 2rem;\n        border: 1px dashed var(--border);\n        border-radius: 10px;\n        color: var(--muted);\n      }\n      @media (prefers-color-scheme: dark) {\n        :root {\n          --bg: #0d1117;\n          --fg: #e6edf3;\n          --muted: #8b949e;\n          --border: #30363d;\n          --accent: #58a6ff;\n          --badge-bg: rgba(88, 166, 255, 0.18);\n          --badge-fg: #58a6ff;\n          --card-bg: #161b22;\n          --card-border: #30363d;\n          --card-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);\n          --card-new-bg: rgba(56, 139, 253, 0.22);\n          --card-new-border: #58a6ff;\n          --card-pinned-border: #f2c744;\n          --chip-bg: rgba(56, 139, 253, 0.2);\n          --chip-fg: #c9d1d9;\n          --chip-border: rgba(56, 139, 253, 0.45);\n          --chip-muted-bg: rgba(110, 118, 129, 0.2);\n          --chip-muted-fg: #8b949e;\n          --timestamp-label: #9aa4b6;\n        }\n        .summary-strip {\n          background: rgba(22, 27, 34, 0.9);\n        }\n        .summary-chip {\n          border-color: var(--chip-border);\n        }\n        .summary-chip--search {\n          background: var(--badge-bg);\n        }\n        .summary-chip--user {\n          background: rgba(56, 139, 253, 0.3);\n          border-color: rgba(56, 139, 253, 0.5);\n        }\n        .error-banner {\n          background: rgba(248, 81, 73, 0.2);\n          color: #ffa198;\n          border-color: rgba(248, 81, 73, 0.45);\n        }\n        .error-banner button {\n          border-color: rgba(88, 166, 255, 0.5);\n          color: var(--accent);\n        }\n        .quick-filter-chip {\n          border-color: rgba(88, 166, 255, 0.5);\n          background: rgba(56, 139, 253, 0.15);\n        }\n        .quick-filter-chip.is-active {\n          color: #0d1117;\n          background: var(--accent);\n        }\n        .user-filter-banner {\n          background: rgba(88, 166, 255, 0.22);\n          border-color: rgba(88, 166, 255, 0.45);\n        }\n        .topic-tag {\n          background: var(--chip-bg);\n          border-color: var(--chip-border);\n          color: var(--chip-fg);\n        }\n        .new-badge {\n          color: #0d1117;\n        }\n        .pagination-controls button {\n          border-color: rgba(88, 166, 255, 0.45);\n        }\n        .page-size-control select {\n          border-color: rgba(88, 166, 255, 0.45);\n        }\n        .shortcut-modal__panel {\n          box-shadow: 0 18px 45px rgba(10, 132, 255, 0.25);\n        }\n        .star-card {\n          box-shadow: var(--card-shadow);\n        }\n        .star-card--new {\n          box-shadow: 0 0 0 1px var(--card-new-border) inset, var(--card-shadow);\n        }\n        .star-card--pinned {\n          box-shadow: 0 0 0 1px var(--card-pinned-border) inset, var(--card-shadow);\n        }\n      }\n\n      @media (prefers-reduced-motion: reduce) {\n        .skip-link {\n          transition: none;\n        }\n        .star-card--new {\n          box-shadow: none;\n        }\n        .sync-bar,\n        .pagination,\n        .quick-filters {\n          scroll-behavior: auto;\n        }\n        .shortcut-modal {\n          transition: none;\n        }\n      }\n    </style>\n  </head>\n  <body>\n    <a class=\"skip-link\" href=\"#star-list\">Skip to results</a>\n\n    <header>\n      <h1>GitHub Followings Stars</h1>\n      <p class=\"timestamp\">Last updated: __LAST_UPDATED__</p>\n    </header>\n    <section id=\"summary-strip\" class=\"summary-strip\" aria-live=\"polite\">\n      <div id=\"summary-count\" class=\"summary-primary\">Preparing summary…</div>\n      <div class=\"summary-meta\">\n        <span id=\"summary-unseen\" class=\"summary-badge\" hidden></span>\n        <div id=\"summary-filters\" class=\"summary-filters\" hidden></div>\n      </div>\n    </section>\n    <div id=\"sync-bar\" class=\"sync-bar\">\n      <span id=\"sync-status\">Last synced: never</span>\n      <span id=\"sync-stale-badge\" class=\"sync-badge\" hidden>Stale</span>\n      <div class=\"sync-actions\">\n        <button id=\"mark-seen-button\" type=\"button\" disabled>Mark all seen</button>\n        <button id=\"refresh-button\" type=\"button\">Refresh now</button>\n      </div>\n    </div>\n    <section class=\"controls\" aria-label=\"Filtering controls\">\n      <label class=\"control\" for=\"search-input\">Search\n        <input id=\"search-input\" type=\"search\" placeholder=\"Search by repo, user, description…\" autocomplete=\"off\">\n      </label>\n      <label class=\"control\" for=\"language-filter\">Language\n        <select id=\"language-filter\">\n          <option value=\"all\">All languages</option>\n        </select>\n      </label>\n      <label class=\"control\" for=\"activity-filter\">Activity\n        <select id=\"activity-filter\">\n          <option value=\"all\">All activity levels</option>\n        </select>\n      </label>\n      <button id=\"sort-toggle\" type=\"button\" aria-pressed=\"false\">Sort: Newest</button>\n      <button id=\"density-toggle\" type=\"button\" aria-pressed=\"false\">Layout: Comfortable</button>\n    </section>\n\n    <section id=\"preset-bar\" class=\"preset-bar\" aria-label=\"Saved view presets\">\n      <div class=\"preset-bar__header\">\n        <h2 class=\"preset-bar__title\">Saved views</h2>\n        <button id=\"preset-save-button\" type=\"button\">Save current view</button>\n      </div>\n      <p class=\"preset-bar__hint\" id=\"preset-hint\">Capture your favorite filter combinations and recall them later or via keyboard shortcuts (Alt+1…Alt+5).</p>\n      <div id=\"preset-list\" class=\"preset-list\" role=\"list\" aria-describedby=\"preset-hint\"></div>\n    </section>\n\n    <div id=\"quick-filters\" class=\"quick-filters\" hidden></div>\n    <div id=\"user-filter-banner\" class=\"user-filter-banner\" hidden>\n      <span id=\"user-filter-label\"></span>\n      <button id=\"user-filter-clear\" type=\"button\" class=\"user-filter-clear\">Clear</button>\n    </div>\n    <div id=\"status-line\" class=\"status-line\">Loading…</div>\n    <div id=\"error-banner\" class=\"error-banner\" role=\"alert\" aria-hidden=\"true\">\n      <span id=\"error-message\"></span>\n      <button id=\"retry-button\" type=\"button\">Retry</button>\n    </div>\n    <div id=\"result-count\" class=\"status-line\" hidden></div>\n    <div id=\"pagination\" class=\"pagination\" hidden>\n      <div id=\"pagination-info\" class=\"pagination-info\"></div>\n      <div class=\"pagination-controls\">\n        <button id=\"page-prev\" type=\"button\" aria-label=\"Previous page\">Previous</button>\n        <button id=\"page-next\" type=\"button\" aria-label=\"Next page\">Next</button>\n      </div>\n      <label class=\"page-size-control\">Page size\n        <select id=\"page-size\">\n          <option value=\"10\">10</option>\n          <option value=\"25\">25</option>\n          <option value=\"50\">50</option>\n          <option value=\"100\">100</option>\n        </select>\n      </label>\n    </div>\n    <ul id=\"star-list\" class=\"star-list\" aria-live=\"polite\"></ul>\n    <div id=\"virtual-status\" class=\"status-line\" aria-live=\"polite\" hidden></div>\n    <noscript>\n      <p class=\"empty-state\">JavaScript is required to explore stars interactively. Enable it and reload to search and filter.</p>\n    </noscript>\n    <div id=\"shortcut-modal\" class=\"shortcut-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"shortcut-modal-title\" hidden>\n      <div class=\"shortcut-modal__panel\" role=\"document\">\n        <div class=\"shortcut-modal__header\">\n          <h2 id=\"shortcut-modal-title\">Keyboard shortcuts</h2>\n          <button id=\"shortcut-close\" type=\"button\" class=\"shortcut-modal__close\" aria-label=\"Close shortcuts\">×</button>\n        </div>\n        <dl class=\"shortcut-modal__list\">\n          <div>\n            <dt>/</dt>\n            <dd>Focus search</dd>\n          </div>\n          <div>\n            <dt>?</dt>\n            <dd>Toggle this help</dd>\n          </div>\n          <div>\n            <dt>[ / ]</dt>\n            <dd>Previous / next page</dd>\n          </div>\n          <div>\n            <dt>L</dt>\n            <dd>Cycle language filter</dd>\n          </div>\n          <div>\n            <dt>M</dt>\n            <dd>Mark new items as seen</dd>\n          </div>\n          <div>\n            <dt>R</dt>\n            <dd>Refresh now</dd>\n          </div>\n          <div>\n            <dt>Alt + 1…5</dt>\n            <dd>Apply saved view 1–5</dd>\n          </div>\n        </dl>\n      </div>\n    </div>\n\n    <div id=\"preset-modal\" class=\"preset-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"preset-modal-title\" hidden>\n      <div class=\"preset-modal__panel\" role=\"document\">\n        <div class=\"preset-modal__header\">\n          <h2 id=\"preset-modal-title\">Save current view</h2>\n          <button id=\"preset-modal-close\" type=\"button\" class=\"preset-modal__close\" aria-label=\"Close saved view dialog\">×</button>\n        </div>\n        <form id=\"preset-form\" class=\"preset-form\">\n          <label class=\"preset-form__label\" for=\"preset-name-input\">Preset name\n            <input id=\"preset-name-input\" name=\"name\" type=\"text\" maxlength=\"40\" required autocomplete=\"off\" placeholder=\"e.g., Rust, High activity\">\n          </label>\n          <div class=\"preset-form__actions\">\n            <button id=\"preset-delete-button\" type=\"button\" class=\"preset-form__secondary\" hidden>Delete preset</button>\n            <div class=\"preset-form__spacer\"></div>\n            <button id=\"preset-cancel-button\" type=\"button\" class=\"preset-form__secondary\">Cancel</button>\n            <button id=\"preset-confirm-button\" type=\"submit\">Save</button>\n          </div>\n        </form>\n      </div>\n    </div>\n    <script>\n(() => {\n\tconst REFRESH_INTERVAL_MS = 5 * 60 * 1000;\n\tconst MAX_BACKOFF_MS = 30 * 60 * 1000;\n\tconst ACK_STORAGE_KEY = \"starchaser:lastAckFetchedAt\";\n\tconst UI_STORAGE_KEY = \"starchaser:uiState\";\n\tconst DEFAULT_PAGE_SIZE = 25;\n\tconst PAGE_SIZE_OPTIONS = [10, 25, 50, 100];\n\tconst QUICK_FILTER_LANG_LIMIT = 6;\n\tconst GRID_BREAKPOINT = 1024;\n\tconst VIRTUALIZE_LENGTH_THRESHOLD = 500;\n\tconst VIRTUAL_WINDOW = 40;\n\tconst VIRTUAL_OVERSCAN = 10;\n\tconst PRESET_STORAGE_KEY = \"starchaser:viewPresets\";\n\tconst PRESET_LAST_KEY = \"starchaser:lastPresetId\";\n\tconst MAX_PRESETS = 5;\n\tconst PAGE_CACHE_LIMIT = 3;\n\n\tlet fetchImpl = window.fetch.bind(window);\n\tlet refreshTimer = null;\n\tlet syncTicker = null;\n\tlet backoffMs = REFRESH_INTERVAL_MS;\n\tlet isFetching = false;\n\tlet lastSyncedAt = null;\n\tlet newestFetchedMs = 0;\n\tconst pageCache = new Map();\n\tconst etagCache = new Map();\n\tlet cacheSignature = \"\";\n\tlet currentFilterSignature = \"\";\n\tlet optionsEtag = null;\n\tlet searchDebounce = null;\n\tconst virtualState = {\n\t\tenabled: false,\n\t\titems: [],\n\t\tstartIndex: 0,\n\t\tendIndex: 0,\n\t\titemHeight: 320,\n\t\tlistOffset: 0,\n\t\tlistenersAttached: false,\n\t\tstatusTimer: null,\n\t};\n\n\tconst dom = {\n\t\tsearchInput: document.getElementById(\"search-input\"),\n\t\tlanguageFilter: document.getElementById(\"language-filter\"),\n\t\tactivityFilter: document.getElementById(\"activity-filter\"),\n\t\tsortToggle: document.getElementById(\"sort-toggle\"),\n\t\tstatusLine: document.getElementById(\"status-line\"),\n\t\terrorBanner: document.getElementById(\"error-banner\"),\n\t\terrorMessage: document.getElementById(\"error-message\"),\n\t\tretryButton: document.getElementById(\"retry-button\"),\n\t\tresultCount: document.getElementById(\"result-count\"),\n\t\tsummaryStrip: document.getElementById(\"summary-strip\"),\n\t\tsummaryCount: document.getElementById(\"summary-count\"),\n\t\tsummaryUnseen: document.getElementById(\"summary-unseen\"),\n\t\tsummaryFilters: document.getElementById(\"summary-filters\"),\n\t\tlist: document.getElementById(\"star-list\"),\n\t\tsyncStatus: document.getElementById(\"sync-status\"),\n\t\tstaleBadge: document.getElementById(\"sync-stale-badge\"),\n\t\tmarkSeenButton: document.getElementById(\"mark-seen-button\"),\n\t\trefreshButton: document.getElementById(\"refresh-button\"),\n\t\tquickFilters: document.getElementById(\"quick-filters\"),\n\t\tuserFilterBanner: document.getElementById(\"user-filter-banner\"),\n\t\tuserFilterLabel: document.getElementById(\"user-filter-label\"),\n\t\tuserFilterClear: document.getElementById(\"user-filter-clear\"),\n\t\tpagination: document.getElementById(\"pagination\"),\n\t\tpaginationInfo: document.getElementById(\"pagination-info\"),\n\t\tpagePrev: document.getElementById(\"page-prev\"),\n\t\tpageNext: document.getElementById(\"page-next\"),\n\t\tpageSize: document.getElementById(\"page-size\"),\n\t\tdensityToggle: document.getElementById(\"density-toggle\"),\n\t\tshortcutModal: document.getElementById(\"shortcut-modal\"),\n\t\tshortcutClose: document.getElementById(\"shortcut-close\"),\n\t\tvirtualStatus: document.getElementById(\"virtual-status\"),\n\t\tpresetBar: document.getElementById(\"preset-bar\"),\n\t\tpresetList: document.getElementById(\"preset-list\"),\n\t\tpresetSaveButton: document.getElementById(\"preset-save-button\"),\n\t\tpresetModal: document.getElementById(\"preset-modal\"),\n\t\tpresetModalClose: document.getElementById(\"preset-modal-close\"),\n\t\tpresetModalTitle: document.getElementById(\"preset-modal-title\"),\n\t\tpresetForm: document.getElementById(\"preset-form\"),\n\t\tpresetNameInput: document.getElementById(\"preset-name-input\"),\n\t\tpresetCancelButton: document.getElementById(\"preset-cancel-button\"),\n\t\tpresetDeleteButton: document.getElementById(\"preset-delete-button\"),\n\t\tpresetConfirmButton: document.getElementById(\"preset-confirm-button\"),\n\t};\n\n\tconst state = {\n\t\titems: [],\n\t\tsearch: \"\",\n\t\tlanguage: \"all\",\n\t\tactivity: \"all\",\n\t\tsort: \"newest\",\n\t\tpage: 1,\n\t\tpageSize: DEFAULT_PAGE_SIZE,\n\t\tuserMode: \"none\", // none | pin | exclude\n\t\tuserValue: null,\n\t\thasNew: false,\n\t\tquickFilters: {\n\t\t\tlanguages: [],\n\t\t},\n\t\tdensity: \"comfortable\",\n\t\tpageMeta: null,\n\t\tfilterOptions: {\n\t\t\tlanguages: [],\n\t\t\tactivity: [],\n\t\t},\n\t\tpresets: [],\n\t\tactivePresetId: null,\n\t};\n\n\tconst tierLabels = {\n\t\thigh: \"High activity\",\n\t\tmedium: \"Medium activity\",\n\t\tlow: \"Low activity\",\n\t\tunknown: \"Unclassified\",\n\t};\n\n\tlet lastAcknowledgedMs = readAckTimestamp();\n\tlet isApplyingPreset = false;\n\tconst presetDialogState = {\n\t\tmode: \"create\",\n\t\ttargetId: null,\n\t};\n\n\tfunction readAckTimestamp() {\n\t\ttry {\n\t\t\tconst raw = window.localStorage.getItem(ACK_STORAGE_KEY);\n\t\t\tif (raw === null) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tconst parsed = Number.parseInt(raw, 10);\n\t\t\treturn Number.isFinite(parsed) ? parsed : 0;\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to read acknowledged timestamp\", err);\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tfunction persistAckTimestamp(value) {\n\t\ttry {\n\t\t\twindow.localStorage.setItem(ACK_STORAGE_KEY, String(value));\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to persist acknowledged timestamp\", err);\n\t\t}\n\t}\n\n\tfunction loadUiSnapshot() {\n\t\tlet snapshot = {};\n\t\ttry {\n\t\t\tconst raw = window.localStorage.getItem(UI_STORAGE_KEY);\n\t\t\tif (raw) {\n\t\t\t\tsnapshot = JSON.parse(raw);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to parse stored UI state\", err);\n\t\t}\n\n\t\tconst params = new URLSearchParams(window.location.search);\n\t\tif (params.has(\"q\")) {\n\t\t\tsnapshot.search = params.get(\"q\");\n\t\t}\n\t\tif (params.has(\"lang\")) {\n\t\t\tsnapshot.language = params.get(\"lang\");\n\t\t}\n\t\tif (params.has(\"activity\")) {\n\t\t\tsnapshot.activity = params.get(\"activity\");\n\t\t}\n\t\tif (params.has(\"sort\")) {\n\t\t\tsnapshot.sort = params.get(\"sort\");\n\t\t}\n\t\tif (params.has(\"page\")) {\n\t\t\tconst parsed = Number.parseInt(params.get(\"page\"), 10);\n\t\t\tif (Number.isFinite(parsed) && parsed > 0) {\n\t\t\t\tsnapshot.page = parsed;\n\t\t\t}\n\t\t}\n\t\tif (params.has(\"pageSize\")) {\n\t\t\tconst parsed = Number.parseInt(params.get(\"pageSize\"), 10);\n\t\t\tif (Number.isFinite(parsed) && parsed > 0) {\n\t\t\t\tsnapshot.pageSize = parsed;\n\t\t\t}\n\t\t}\n\t\tif (params.has(\"user\")) {\n\t\t\tsnapshot.userValue = params.get(\"user\");\n\t\t}\n\t\tif (params.has(\"userMode\")) {\n\t\t\tsnapshot.userMode = params.get(\"userMode\");\n\t\t}\n\t\tif (params.has(\"density\")) {\n\t\t\tsnapshot.density = params.get(\"density\");\n\t\t}\n\t\treturn snapshot;\n\t}\n\n\tfunction applySnapshot(snapshot) {\n\t\tstate.search = typeof snapshot.search === \"string\" ? snapshot.search : \"\";\n\t\tstate.language = snapshot.language || \"all\";\n\t\tstate.activity = snapshot.activity || \"all\";\n\t\tstate.sort = snapshot.sort === \"alpha\" ? \"alpha\" : \"newest\";\n\n\t\tconst sizeCandidate = snapshot.pageSize;\n\t\tif (Number.isInteger(sizeCandidate) && sizeCandidate > 0) {\n\t\t\tstate.pageSize = sizeCandidate;\n\t\t} else {\n\t\t\tstate.pageSize = DEFAULT_PAGE_SIZE;\n\t\t}\n\n\t\tconst pageCandidate = snapshot.page;\n\t\tif (Number.isInteger(pageCandidate) && pageCandidate > 0) {\n\t\t\tstate.page = pageCandidate;\n\t\t} else {\n\t\t\tstate.page = 1;\n\t\t}\n\n\t\tif (snapshot.userMode === \"pin\" || snapshot.userMode === \"exclude\") {\n\t\t\tstate.userMode = snapshot.userMode;\n\t\t\tstate.userValue = snapshot.userValue || null;\n\t\t} else {\n\t\t\tstate.userMode = \"none\";\n\t\t\tstate.userValue = null;\n\t\t}\n\n\t\tif (snapshot.density === \"compact\") {\n\t\t\tstate.density = \"compact\";\n\t\t} else {\n\t\t\tstate.density = \"comfortable\";\n\t\t}\n\t}\n\n\tfunction snapshotFromState() {\n\t\treturn {\n\t\t\tsearch: state.search,\n\t\t\tlanguage: state.language,\n\t\t\tactivity: state.activity,\n\t\t\tsort: state.sort,\n\t\t\tpage: state.page,\n\t\t\tpageSize: state.pageSize,\n\t\t\tuserMode: state.userMode,\n\t\t\tuserValue: state.userValue,\n\t\t\tdensity: state.density,\n\t\t};\n\t}\n\n\tfunction persistUiState() {\n\t\tconst snapshot = { ...snapshotFromState(), page: 1 };\n\t\tif (!isApplyingPreset && state.activePresetId) {\n\t\t\tstate.activePresetId = null;\n\t\t\tpersistLastPresetId(null);\n\t\t\trenderPresets();\n\t\t}\n\t\ttry {\n\t\t\twindow.localStorage.setItem(UI_STORAGE_KEY, JSON.stringify(snapshot));\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to persist UI state\", err);\n\t\t}\n\t}\n\n\tfunction syncUrl() {\n\t\tconst params = new URLSearchParams();\n\t\tconst trimmedSearch = state.search.trim();\n\t\tif (trimmedSearch) {\n\t\t\tparams.set(\"q\", trimmedSearch);\n\t\t}\n\t\tif (state.language !== \"all\") {\n\t\t\tparams.set(\"lang\", state.language);\n\t\t}\n\t\tif (state.activity !== \"all\") {\n\t\t\tparams.set(\"activity\", state.activity);\n\t\t}\n\t\tif (state.sort !== \"newest\") {\n\t\t\tparams.set(\"sort\", state.sort);\n\t\t}\n\t\tif (state.page > 1) {\n\t\t\tparams.set(\"page\", String(state.page));\n\t\t}\n\t\tif (state.pageSize !== DEFAULT_PAGE_SIZE) {\n\t\t\tparams.set(\"pageSize\", String(state.pageSize));\n\t\t}\n\t\tif (state.userMode !== \"none\" && state.userValue) {\n\t\t\tparams.set(\"user\", state.userValue);\n\t\t\tparams.set(\"userMode\", state.userMode);\n\t\t}\n\t\tif (state.density !== \"comfortable\") {\n\t\t\tparams.set(\"density\", state.density);\n\t\t}\n\n\t\tconst query = params.toString();\n\t\tconst newUrl = `${window.location.pathname}${query ? `?${query}` : \"\"}`;\n\t\twindow.history.replaceState(null, \"\", newUrl);\n\t}\n\n\tfunction computeFilterSignature(overrides = {}) {\n\t\tconst searchValue =\n\t\t\ttypeof overrides.search === \"string\" ? overrides.search : state.search;\n\t\tconst languageValue = overrides.language ?? state.language;\n\t\tconst activityValue = overrides.activity ?? state.activity;\n\t\tconst sortValue = overrides.sort ?? state.sort;\n\t\tconst pageSizeValue = overrides.pageSize ?? state.pageSize;\n\t\tconst userModeValue = overrides.userMode ?? state.userMode;\n\t\tconst userValue = overrides.userValue ?? state.userValue;\n\t\treturn JSON.stringify({\n\t\t\tsearch: searchValue.trim().toLowerCase(),\n\t\t\tlanguage: languageValue,\n\t\t\tactivity: activityValue,\n\t\t\tsort: sortValue,\n\t\t\tpageSize: pageSizeValue,\n\t\t\tuserMode: userModeValue,\n\t\t\tuserValue:\n\t\t\t\tuserModeValue === \"pin\" || userModeValue === \"exclude\" ? userValue : \"\",\n\t\t});\n\t}\n\n\tfunction resetPaginationCachesIfNeeded(providedSignature = null) {\n\t\tconst nextSignature = providedSignature ?? computeFilterSignature();\n\t\tif (nextSignature !== currentFilterSignature) {\n\t\t\tcurrentFilterSignature = nextSignature;\n\t\t\tcacheSignature = nextSignature;\n\t\t\tpageCache.clear();\n\t\t\tetagCache.clear();\n\t\t}\n\t}\n\n\tfunction getUserModeParam(mode) {\n\t\tif (mode === \"pin\" || mode === \"exclude\") {\n\t\t\treturn mode;\n\t\t}\n\t\treturn \"all\";\n\t}\n\n\tfunction buildApiQuery(overrides = {}) {\n\t\tconst params = new URLSearchParams();\n\t\tconst searchValue =\n\t\t\ttypeof overrides.search === \"string\" ? overrides.search : state.search;\n\t\tconst trimmedSearch = searchValue.trim();\n\t\tif (trimmedSearch) {\n\t\t\tparams.set(\"q\", trimmedSearch);\n\t\t}\n\t\tconst languageValue = overrides.language ?? state.language;\n\t\tif (languageValue && languageValue !== \"all\") {\n\t\t\tparams.set(\"language\", languageValue);\n\t\t}\n\t\tconst activityValue = overrides.activity ?? state.activity;\n\t\tif (activityValue && activityValue !== \"all\") {\n\t\t\tparams.set(\"activity\", activityValue);\n\t\t}\n\t\tconst userModeValue = overrides.userMode ?? state.userMode;\n\t\tconst normalizedMode = getUserModeParam(userModeValue);\n\t\tparams.set(\"user_mode\", normalizedMode);\n\t\tconst userValue = overrides.userValue ?? state.userValue;\n\t\tif (normalizedMode !== \"all\" && userValue) {\n\t\t\tparams.set(\"user\", userValue);\n\t\t}\n\t\tparams.set(\"sort\", overrides.sort ?? state.sort);\n\t\tparams.set(\"page\", String(overrides.page ?? state.page));\n\t\tparams.set(\"page_size\", String(overrides.pageSize ?? state.pageSize));\n\t\treturn params;\n\t}\n\n\tfunction storePageCacheEntry(pageNumber, rawItems, meta) {\n\t\tif (cacheSignature !== currentFilterSignature) {\n\t\t\tcacheSignature = currentFilterSignature;\n\t\t\tpageCache.clear();\n\t\t}\n\t\tpageCache.set(pageNumber, { rawItems, meta });\n\t\tprunePageCache();\n\t}\n\n\tfunction getCachedPageEntry(pageNumber) {\n\t\tif (cacheSignature !== currentFilterSignature) {\n\t\t\treturn null;\n\t\t}\n\t\treturn pageCache.get(pageNumber) ?? null;\n\t}\n\n\tfunction prunePageCache() {\n\t\tif (pageCache.size <= PAGE_CACHE_LIMIT) {\n\t\t\treturn;\n\t\t}\n\t\tconst pages = Array.from(pageCache.keys());\n\t\tpages.sort((a, b) => {\n\t\t\tconst distA = Math.abs(a - state.page);\n\t\t\tconst distB = Math.abs(b - state.page);\n\t\t\tif (distA === distB) {\n\t\t\t\treturn a - b;\n\t\t\t}\n\t\t\treturn distA - distB;\n\t\t});\n\t\twhile (pageCache.size > PAGE_CACHE_LIMIT && pages.length > 0) {\n\t\t\tconst removed = pages.pop();\n\t\t\tif (typeof removed === \"number\") {\n\t\t\t\tpageCache.delete(removed);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction loadPresetsFromStorage() {\n\t\ttry {\n\t\t\tconst raw = window.localStorage.getItem(PRESET_STORAGE_KEY);\n\t\t\tif (!raw) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\tif (Array.isArray(parsed)) {\n\t\t\t\treturn parsed\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(entry) =>\n\t\t\t\t\t\t\tentry &&\n\t\t\t\t\t\t\ttypeof entry.id === \"string\" &&\n\t\t\t\t\t\t\ttypeof entry.name === \"string\" &&\n\t\t\t\t\t\t\tentry.snapshot &&\n\t\t\t\t\t\t\ttypeof entry.snapshot === \"object\",\n\t\t\t\t\t)\n\t\t\t\t\t.slice(0, MAX_PRESETS);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to load presets\", err);\n\t\t}\n\t\treturn [];\n\t}\n\n\tfunction persistPresets(presets) {\n\t\ttry {\n\t\t\twindow.localStorage.setItem(PRESET_STORAGE_KEY, JSON.stringify(presets));\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to persist presets\", err);\n\t\t}\n\t}\n\n\tfunction loadLastPresetId() {\n\t\ttry {\n\t\t\treturn window.localStorage.getItem(PRESET_LAST_KEY);\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to load last preset id\", err);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tfunction persistLastPresetId(value) {\n\t\ttry {\n\t\t\tif (value) {\n\t\t\t\twindow.localStorage.setItem(PRESET_LAST_KEY, value);\n\t\t\t} else {\n\t\t\t\twindow.localStorage.removeItem(PRESET_LAST_KEY);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to persist last preset id\", err);\n\t\t}\n\t}\n\n\tfunction renderPresets() {\n\t\tconst list = dom.presetList;\n\t\tif (!list) {\n\t\t\treturn;\n\t\t}\n\t\tlist.replaceChildren();\n\t\tif (!Array.isArray(state.presets) || state.presets.length === 0) {\n\t\t\tconst empty = document.createElement(\"p\");\n\t\t\tempty.className = \"preset-bar__empty\";\n\t\t\tempty.textContent = \"No saved views yet.\";\n\t\t\tlist.appendChild(empty);\n\t\t\treturn;\n\t\t}\n\n\t\tstate.presets.forEach((preset, index) => {\n\t\t\tconst wrapper = document.createElement(\"div\");\n\t\t\twrapper.className = \"preset-chip-wrapper\";\n\t\t\twrapper.setAttribute(\"role\", \"listitem\");\n\t\t\tconst button = document.createElement(\"button\");\n\t\t\tbutton.type = \"button\";\n\t\t\tbutton.className = \"preset-chip\";\n\t\t\tbutton.setAttribute(\n\t\t\t\t\"aria-pressed\",\n\t\t\t\tpreset.id === state.activePresetId ? \"true\" : \"false\",\n\t\t\t);\n\t\t\tif (preset.id === state.activePresetId) {\n\t\t\t\tbutton.classList.add(\"is-active\");\n\t\t\t}\n\t\t\tbutton.textContent = \"\";\n\t\t\tconst label = document.createElement(\"span\");\n\t\t\tlabel.className = \"preset-chip__label\";\n\t\t\tlabel.textContent = preset.name;\n\t\t\tbutton.appendChild(label);\n\t\t\tbutton.addEventListener(\"click\", () => {\n\t\t\t\tapplyPresetById(preset.id);\n\t\t\t});\n\n\t\t\tif (index < 5) {\n\t\t\t\tconst keySpan = document.createElement(\"span\");\n\t\t\t\tkeySpan.className = \"preset-chip__keys\";\n\t\t\t\tkeySpan.textContent = `Alt+${index + 1}`;\n\t\t\t\tbutton.appendChild(keySpan);\n\t\t\t}\n\n\t\t\tconst actions = document.createElement(\"div\");\n\t\t\tactions.className = \"preset-chip__actions\";\n\n\t\t\tconst editBtn = document.createElement(\"button\");\n\t\t\teditBtn.type = \"button\";\n\t\t\teditBtn.className = \"preset-chip__icon-btn\";\n\t\t\teditBtn.textContent = \"Edit\";\n\t\t\teditBtn.setAttribute(\"aria-label\", `Rename preset ${preset.name}`);\n\t\t\teditBtn.addEventListener(\"click\", (event) => {\n\t\t\t\tevent.stopPropagation();\n\t\t\t\topenPresetModal({ mode: \"edit\", preset });\n\t\t\t});\n\n\t\t\tconst deleteBtn = document.createElement(\"button\");\n\t\t\tdeleteBtn.type = \"button\";\n\t\t\tdeleteBtn.className = \"preset-chip__icon-btn\";\n\t\t\tdeleteBtn.textContent = \"Delete\";\n\t\t\tdeleteBtn.setAttribute(\"aria-label\", `Delete preset ${preset.name}`);\n\t\t\tdeleteBtn.addEventListener(\"click\", (event) => {\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tdeletePreset(preset.id);\n\t\t\t});\n\n\t\t\tactions.appendChild(editBtn);\n\t\t\tactions.appendChild(deleteBtn);\n\n\t\t\twrapper.appendChild(button);\n\t\t\twrapper.appendChild(actions);\n\t\t\tlist.appendChild(wrapper);\n\t\t});\n\t}\n\n\tfunction openPresetModal(options = {}) {\n\t\tif (\n\t\t\t!dom.presetModal ||\n\t\t\t!dom.presetNameInput ||\n\t\t\t!dom.presetConfirmButton ||\n\t\t\t!dom.presetDeleteButton\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tconst preset = options.preset;\n\t\tpresetDialogState.mode = options.mode === \"edit\" ? \"edit\" : \"create\";\n\t\tpresetDialogState.targetId = preset?.id ?? null;\n\t\tconst initialName = preset?.name ?? \"\";\n\t\tdom.presetModal.hidden = false;\n\t\tdom.presetModal.setAttribute(\"aria-hidden\", \"false\");\n\t\tif (dom.presetModalTitle) {\n\t\t\tdom.presetModalTitle.textContent =\n\t\t\t\tpresetDialogState.mode === \"edit\"\n\t\t\t\t\t? \"Update saved view\"\n\t\t\t\t\t: \"Save current view\";\n\t\t}\n\t\tdom.presetNameInput.value = initialName;\n\t\tdom.presetDeleteButton.hidden = presetDialogState.mode !== \"edit\";\n\t\tdom.presetConfirmButton.textContent =\n\t\t\tpresetDialogState.mode === \"edit\" ? \"Update\" : \"Save\";\n\t\twindow.setTimeout(() => {\n\t\t\tdom.presetNameInput.focus();\n\t\t\tdom.presetNameInput.select();\n\t\t}, 0);\n\t}\n\n\tfunction closePresetModal() {\n\t\tif (!dom.presetModal || dom.presetModal.hidden) {\n\t\t\treturn;\n\t\t}\n\t\tdom.presetModal.hidden = true;\n\t\tdom.presetModal.setAttribute(\"aria-hidden\", \"true\");\n\t\tif (dom.presetForm) {\n\t\t\tdom.presetForm.reset();\n\t\t}\n\t\tpresetDialogState.mode = \"create\";\n\t\tpresetDialogState.targetId = null;\n\t}\n\n\tfunction generatePresetId() {\n\t\tif (window.crypto && window.crypto.randomUUID) {\n\t\t\treturn window.crypto.randomUUID();\n\t\t}\n\t\treturn `preset-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;\n\t}\n\n\tfunction upsertPreset(id, name) {\n\t\tconst snapshot = snapshotFromState();\n\t\tconst filtered = state.presets.filter((preset) => preset.id !== id);\n\t\tconst entry = { id, name, snapshot };\n\t\tstate.presets = [entry, ...filtered];\n\t\tif (state.presets.length > MAX_PRESETS) {\n\t\t\tstate.presets.length = MAX_PRESETS;\n\t\t}\n\t\tpersistPresets(state.presets);\n\t\tstate.activePresetId = id;\n\t\tpersistLastPresetId(id);\n\t\trenderPresets();\n\t}\n\n\tfunction deletePreset(id) {\n\t\tstate.presets = state.presets.filter((preset) => preset.id !== id);\n\t\tpersistPresets(state.presets);\n\t\tif (state.activePresetId === id) {\n\t\t\tstate.activePresetId = null;\n\t\t\tpersistLastPresetId(null);\n\t\t}\n\t\trenderPresets();\n\t}\n\n\tfunction handlePresetFormSubmit(event) {\n\t\tevent.preventDefault();\n\t\tif (!dom.presetNameInput) {\n\t\t\treturn;\n\t\t}\n\t\tconst name = dom.presetNameInput.value.trim();\n\t\tif (!name) {\n\t\t\tdom.presetNameInput.focus();\n\t\t\treturn;\n\t\t}\n\t\tif (presetDialogState.mode === \"edit\" && presetDialogState.targetId) {\n\t\t\tupsertPreset(presetDialogState.targetId, name);\n\t\t} else {\n\t\t\tupsertPreset(generatePresetId(), name);\n\t\t}\n\t\tclosePresetModal();\n\t}\n\n\tfunction handlePresetDeleteFromModal() {\n\t\tif (!presetDialogState.targetId) {\n\t\t\treturn;\n\t\t}\n\t\tdeletePreset(presetDialogState.targetId);\n\t\tclosePresetModal();\n\t}\n\n\tasync function applyPresetById(id) {\n\t\tconst preset = state.presets.find((entry) => entry.id === id);\n\t\tif (!preset) {\n\t\t\treturn;\n\t\t}\n\t\tisApplyingPreset = true;\n\t\ttry {\n\t\t\tconst presetSnapshot = { ...(preset.snapshot || {}), page: 1 };\n\t\t\tapplySnapshot(presetSnapshot);\n\t\t\tsyncControlsToState();\n\t\t\tstate.activePresetId = preset.id;\n\t\t\tpersistLastPresetId(preset.id);\n\t\t\tawait requestFilterUpdate();\n\t\t} finally {\n\t\t\tisApplyingPreset = false;\n\t\t}\n\t\trenderPresets();\n\t}\n\n\tfunction applyPresetByShortcut(index) {\n\t\tif (index < 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst preset = state.presets[index];\n\t\tif (preset) {\n\t\t\tapplyPresetById(preset.id);\n\t\t}\n\t}\n\n\tfunction setupPresetUi() {\n\t\tstate.presets = loadPresetsFromStorage();\n\t\tconst lastPresetId = loadLastPresetId();\n\t\tif (\n\t\t\tlastPresetId &&\n\t\t\tstate.presets.some((preset) => preset.id === lastPresetId)\n\t\t) {\n\t\t\tstate.activePresetId = lastPresetId;\n\t\t} else {\n\t\t\tstate.activePresetId = null;\n\t\t\tpersistLastPresetId(null);\n\t\t}\n\t\tif (!dom.presetBar) {\n\t\t\treturn;\n\t\t}\n\t\trenderPresets();\n\n\t\tif (dom.presetSaveButton) {\n\t\t\tdom.presetSaveButton.addEventListener(\"click\", () => {\n\t\t\t\topenPresetModal({ mode: \"create\" });\n\t\t\t});\n\t\t}\n\t\tif (dom.presetCancelButton) {\n\t\t\tdom.presetCancelButton.addEventListener(\"click\", () => {\n\t\t\t\tclosePresetModal();\n\t\t\t});\n\t\t}\n\t\tif (dom.presetModalClose) {\n\t\t\tdom.presetModalClose.addEventListener(\"click\", () => {\n\t\t\t\tclosePresetModal();\n\t\t\t});\n\t\t}\n\t\tif (dom.presetModal) {\n\t\t\tdom.presetModal.addEventListener(\"click\", (event) => {\n\t\t\t\tif (event.target === dom.presetModal) {\n\t\t\t\t\tclosePresetModal();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tif (dom.presetForm) {\n\t\t\tdom.presetForm.addEventListener(\"submit\", handlePresetFormSubmit);\n\t\t}\n\t\tif (dom.presetDeleteButton) {\n\t\t\tdom.presetDeleteButton.addEventListener(\n\t\t\t\t\"click\",\n\t\t\t\thandlePresetDeleteFromModal,\n\t\t\t);\n\t\t}\n\t}\n\n\tfunction setStatus(message) {\n\t\tif (!dom.statusLine) {\n\t\t\treturn;\n\t\t}\n\t\tif (message) {\n\t\t\tdom.statusLine.textContent = message;\n\t\t\tdom.statusLine.hidden = false;\n\t\t} else {\n\t\t\tdom.statusLine.textContent = \"\";\n\t\t\tdom.statusLine.hidden = true;\n\t\t}\n\t}\n\n\tfunction showError(message) {\n\t\tdom.errorMessage.textContent = message;\n\t\tdom.errorBanner.setAttribute(\"aria-hidden\", \"false\");\n\t\tdom.retryButton.disabled = false;\n\t}\n\n\tfunction clearError() {\n\t\tdom.errorMessage.textContent = \"\";\n\t\tdom.errorBanner.setAttribute(\"aria-hidden\", \"true\");\n\t\tdom.retryButton.disabled = false;\n\t}\n\n\tfunction updateSyncStatus() {\n\t\tif (!dom.syncStatus) {\n\t\t\treturn;\n\t\t}\n\t\tif (!lastSyncedAt) {\n\t\t\tdom.syncStatus.textContent = \"Last synced: never\";\n\t\t\tdom.staleBadge.hidden = true;\n\t\t\treturn;\n\t\t}\n\t\tconst ageMs = Date.now() - lastSyncedAt.getTime();\n\t\tconst staleThreshold = REFRESH_INTERVAL_MS * 1.5;\n\t\tconst isStale = ageMs > staleThreshold;\n\t\tdom.syncStatus.textContent = `Last synced: ${lastSyncedAt.toLocaleString()}`;\n\t\tif (isStale) {\n\t\t\tconst ageMinutes = Math.round(ageMs / 60000);\n\t\t\tdom.staleBadge.textContent =\n\t\t\t\tageMinutes > 0 ? `Stale (≈${ageMinutes} min)` : \"Stale\";\n\t\t\tdom.staleBadge.hidden = false;\n\t\t} else {\n\t\t\tdom.staleBadge.hidden = true;\n\t\t}\n\t}\n\n\tfunction scheduleNextFetch(delayMs) {\n\t\tif (refreshTimer !== null) {\n\t\t\twindow.clearTimeout(refreshTimer);\n\t\t}\n\t\trefreshTimer = window.setTimeout(\n\t\t\t() => loadPage(state.page, { manual: false }),\n\t\t\tdelayMs,\n\t\t);\n\t}\n\n\tfunction updateMarkSeenButton(newCount = null) {\n\t\tif (newCount === null) {\n\t\t\tnewCount = state.items.reduce(\n\t\t\t\t(acc, item) => acc + (item.isNew ? 1 : 0),\n\t\t\t\t0,\n\t\t\t);\n\t\t}\n\t\tstate.hasNew = newCount > 0;\n\t\tif (newCount > 0) {\n\t\t\tdom.markSeenButton.disabled = false;\n\t\t\tdom.markSeenButton.textContent = `Mark ${newCount} new as seen`;\n\t\t} else {\n\t\t\tdom.markSeenButton.disabled = true;\n\t\t\tdom.markSeenButton.textContent = \"Mark all seen\";\n\t\t}\n\t}\n\n\tfunction flagNewItems() {\n\t\tlet newCount = 0;\n\t\tfor (const item of state.items) {\n\t\t\tconst isNew = item.fetched_at_ms > lastAcknowledgedMs;\n\t\t\titem.isNew = isNew;\n\t\t\tif (isNew) {\n\t\t\t\tnewCount += 1;\n\t\t\t}\n\t\t}\n\t\tupdateMarkSeenButton(newCount);\n\t}\n\n\tfunction ensurePageSizeOption() {\n\t\tconst select = dom.pageSize;\n\t\tconst values = new Set([...PAGE_SIZE_OPTIONS, state.pageSize]);\n\t\tselect.replaceChildren();\n\t\tArray.from(values)\n\t\t\t.sort((a, b) => a - b)\n\t\t\t.forEach((value) => {\n\t\t\t\tconst option = document.createElement(\"option\");\n\t\t\t\toption.value = String(value);\n\t\t\t\toption.textContent = String(value);\n\t\t\t\tselect.appendChild(option);\n\t\t\t});\n\t\tselect.value = String(state.pageSize);\n\t}\n\n\tfunction populateFilters() {\n\t\tconst sortedLangs = getLanguageOptions();\n\t\tdom.languageFilter.innerHTML = '<option value=\"all\">All languages</option>';\n\t\tfor (const lang of sortedLangs) {\n\t\t\tconst option = document.createElement(\"option\");\n\t\t\toption.value = lang;\n\t\t\toption.textContent = lang;\n\t\t\tdom.languageFilter.appendChild(option);\n\t\t}\n\t\tif (!sortedLangs.includes(state.language)) {\n\t\t\tstate.language = \"all\";\n\t\t}\n\t\tdom.languageFilter.value = state.language;\n\n\t\tconst sortedTiers = getActivityOptions();\n\t\tdom.activityFilter.innerHTML =\n\t\t\t'<option value=\"all\">All activity levels</option>';\n\t\tfor (const tier of sortedTiers) {\n\t\t\tconst option = document.createElement(\"option\");\n\t\t\toption.value = tier;\n\t\t\toption.textContent = tierLabels[tier] ?? tier;\n\t\t\tdom.activityFilter.appendChild(option);\n\t\t}\n\t\tif (!sortedTiers.includes(state.activity)) {\n\t\t\tstate.activity = \"all\";\n\t\t}\n\t\tdom.activityFilter.value = state.activity;\n\n\t\tensurePageSizeOption();\n\t}\n\n\tfunction getLanguageOptions() {\n\t\tif (state.filterOptions.languages.length > 0) {\n\t\t\treturn state.filterOptions.languages\n\t\t\t\t.map((entry) => entry.name)\n\t\t\t\t.filter(Boolean);\n\t\t}\n\t\tconst languages = new Set();\n\t\tfor (const item of state.items) {\n\t\t\tif (item.repo_language) {\n\t\t\t\tlanguages.add(item.repo_language);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(languages).sort((a, b) => a.localeCompare(b));\n\t}\n\n\tfunction getActivityOptions() {\n\t\tif (state.filterOptions.activity.length > 0) {\n\t\t\treturn state.filterOptions.activity\n\t\t\t\t.map((entry) => entry.tier)\n\t\t\t\t.filter(Boolean);\n\t\t}\n\t\tconst tiers = new Set();\n\t\tfor (const item of state.items) {\n\t\t\tif (item.normalizedTier) {\n\t\t\t\ttiers.add(item.normalizedTier);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(tiers).sort();\n\t}\n\n\tfunction computeQuickFilters() {\n\t\tlet languages;\n\t\tif (state.filterOptions.languages.length > 0) {\n\t\t\tlanguages = state.filterOptions.languages\n\t\t\t\t.map((entry) => ({ lang: entry.name, count: entry.count }))\n\t\t\t\t.filter((entry) => entry.lang)\n\t\t\t\t.sort((a, b) => b.count - a.count)\n\t\t\t\t.slice(0, QUICK_FILTER_LANG_LIMIT);\n\t\t} else {\n\t\t\tconst languageCounts = new Map();\n\t\t\tfor (const item of state.items) {\n\t\t\t\tif (item.repo_language) {\n\t\t\t\t\tlanguageCounts.set(\n\t\t\t\t\t\titem.repo_language,\n\t\t\t\t\t\t(languageCounts.get(item.repo_language) || 0) + 1,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tlanguages = Array.from(languageCounts.entries())\n\t\t\t\t.sort((a, b) => b[1] - a[1])\n\t\t\t\t.slice(0, QUICK_FILTER_LANG_LIMIT)\n\t\t\t\t.map(([lang, count]) => ({ lang, count }));\n\t\t}\n\t\tstate.quickFilters.languages = languages;\n\t}\n\n\tfunction renderQuickFilters() {\n\t\tconst container = dom.quickFilters;\n\t\tcontainer.replaceChildren();\n\n\t\tconst fragment = document.createDocumentFragment();\n\n\t\tconst addChip = (label, handler, options = {}) => {\n\t\t\tconst button = document.createElement(\"button\");\n\t\t\tbutton.type = \"button\";\n\t\t\tbutton.className = \"quick-filter-chip\";\n\t\t\tbutton.textContent = label;\n\t\t\tif (options.active) {\n\t\t\t\tbutton.classList.add(\"is-active\");\n\t\t\t}\n\t\t\tif (options.title) {\n\t\t\t\tbutton.title = options.title;\n\t\t\t}\n\t\t\tbutton.addEventListener(\"click\", handler);\n\t\t\tfragment.appendChild(button);\n\t\t};\n\n\t\tif (state.quickFilters.languages.length > 0) {\n\t\t\tconst groupLabel = document.createElement(\"span\");\n\t\t\tgroupLabel.className = \"quick-filter-label\";\n\t\t\tgroupLabel.textContent = \"Top languages:\";\n\t\t\tfragment.appendChild(groupLabel);\n\t\t\tfor (const entry of state.quickFilters.languages) {\n\t\t\t\taddChip(\n\t\t\t\t\tentry.lang,\n\t\t\t\t\t() => {\n\t\t\t\t\t\tstate.language = entry.lang;\n\t\t\t\t\t\tdom.languageFilter.value = state.language;\n\t\t\t\t\t\trequestFilterUpdate();\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tactive: state.language === entry.lang,\n\t\t\t\t\t\ttitle: `Filter by ${entry.lang}`,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t\taddChip(\n\t\t\t\t\"All\",\n\t\t\t\t() => {\n\t\t\t\t\tstate.language = \"all\";\n\t\t\t\t\tdom.languageFilter.value = state.language;\n\t\t\t\t\trequestFilterUpdate();\n\t\t\t\t},\n\t\t\t\t{ active: state.language === \"all\", title: \"Show all languages\" },\n\t\t\t);\n\t\t}\n\n\t\tconst activityChips = [\"high\", \"medium\", \"low\"];\n\t\tconst availableActivity = new Set(getActivityOptions());\n\t\tif (activityChips.some((tier) => availableActivity.has(tier))) {\n\t\t\tconst groupLabel = document.createElement(\"span\");\n\t\t\tgroupLabel.className = \"quick-filter-label\";\n\t\t\tgroupLabel.textContent = \"Activity:\";\n\t\t\tfragment.appendChild(groupLabel);\n\t\t\tfor (const tier of activityChips) {\n\t\t\t\taddChip(\n\t\t\t\t\ttierLabels[tier] ?? tier,\n\t\t\t\t\t() => {\n\t\t\t\t\t\tstate.activity = tier;\n\t\t\t\t\t\tdom.activityFilter.value = state.activity;\n\t\t\t\t\t\trequestFilterUpdate();\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tactive: state.activity === tier,\n\t\t\t\t\t\ttitle: `Filter ${tierLabels[tier] ?? tier}`,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t\taddChip(\n\t\t\t\t\"All\",\n\t\t\t\t() => {\n\t\t\t\t\tstate.activity = \"all\";\n\t\t\t\t\tdom.activityFilter.value = state.activity;\n\t\t\t\t\trequestFilterUpdate();\n\t\t\t\t},\n\t\t\t\t{ active: state.activity === \"all\", title: \"Show all activity\" },\n\t\t\t);\n\t\t}\n\n\t\tif (fragment.childElementCount > 0) {\n\t\t\tcontainer.appendChild(fragment);\n\t\t\tcontainer.hidden = false;\n\t\t} else {\n\t\t\tcontainer.hidden = true;\n\t\t}\n\t}\n\n\tfunction renderUserBanner() {\n\t\tif (state.userMode === \"none\" || !state.userValue) {\n\t\t\tdom.userFilterBanner.hidden = true;\n\t\t\treturn;\n\t\t}\n\t\tconst modeText =\n\t\t\tstate.userMode === \"pin\"\n\t\t\t\t? `Showing only stars from ${state.userValue}`\n\t\t\t\t: `Hiding stars from ${state.userValue}`;\n\t\tdom.userFilterLabel.textContent = modeText;\n\t\tdom.userFilterBanner.hidden = false;\n\t}\n\n\tfunction renderPagination() {\n\t\tconst meta = state.pageMeta;\n\t\tif (!meta) {\n\t\t\tdom.pagination.hidden = true;\n\t\t\treturn;\n\t\t}\n\t\tconst totalCount =\n\t\t\ttypeof meta.total === \"number\" ? meta.total : state.items.length;\n\t\tconst totalPages = Math.max(\n\t\t\t1,\n\t\t\tMath.ceil(totalCount / (meta.page_size || state.pageSize || 1)),\n\t\t);\n\t\tif (totalPages <= 1) {\n\t\t\tdom.pagination.hidden = true;\n\t\t\treturn;\n\t\t}\n\t\tconst currentPage = meta.page || state.page;\n\t\tconst pageSize = meta.page_size || state.pageSize;\n\t\tconst startIndex = (currentPage - 1) * pageSize;\n\t\tconst endIndex = Math.min(totalCount, startIndex + state.items.length);\n\t\tconst startDisplay = Math.max(1, startIndex + 1);\n\t\tconst endDisplay = Math.max(startDisplay, endIndex);\n\t\tdom.paginationInfo.textContent = `Showing ${startDisplay}–${endDisplay} of ${totalCount} (page ${currentPage} of ${totalPages})`;\n\t\tdom.pagePrev.disabled = !meta.has_prev || isFetching;\n\t\tdom.pageNext.disabled = !meta.has_next || isFetching;\n\t\tdom.pagination.hidden = false;\n\t}\n\n\tfunction renderSummary(pageItemsLength) {\n\t\tconst summaryStrip = dom.summaryStrip;\n\t\tconst summaryCount = dom.summaryCount;\n\t\tconst summaryUnseen = dom.summaryUnseen;\n\t\tconst summaryFilters = dom.summaryFilters;\n\n\t\tif (!summaryStrip || !summaryCount || !summaryUnseen || !summaryFilters) {\n\t\t\tif (dom.resultCount) {\n\t\t\t\tdom.resultCount.hidden = true;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst meta = state.pageMeta;\n\t\tconst totalItems =\n\t\t\tmeta && typeof meta.total === \"number\" ? meta.total : state.items.length;\n\t\tconst hasItems = totalItems > 0;\n\t\tlet summaryText = \"\";\n\t\tif (!hasItems) {\n\t\t\tsummaryText = \"No starred repositories match the current view.\";\n\t\t} else {\n\t\t\tconst pageSize = meta?.page_size ?? state.pageSize;\n\t\t\tconst currentPage = meta?.page ?? state.page;\n\t\t\tconst startIndex = (currentPage - 1) * pageSize + 1;\n\t\t\tconst endIndex = Math.min(totalItems, startIndex + pageItemsLength - 1);\n\t\t\tsummaryText = `${startIndex}–${endIndex} of ${totalItems} starred repositories`;\n\t\t}\n\t\tsummaryCount.textContent = summaryText;\n\t\tsummaryStrip.classList.toggle(\"summary-strip--empty\", !hasItems);\n\n\t\tconst totalNew = state.items.reduce(\n\t\t\t(acc, item) => acc + (item.isNew ? 1 : 0),\n\t\t\t0,\n\t\t);\n\t\tif (totalNew > 0) {\n\t\t\tsummaryUnseen.hidden = false;\n\t\t\tsummaryUnseen.textContent = `${totalNew} new`;\n\t\t} else {\n\t\t\tsummaryUnseen.hidden = true;\n\t\t\tsummaryUnseen.textContent = \"\";\n\t\t}\n\n\t\tconst filters = [];\n\t\tconst trimmedSearch = state.search.trim();\n\t\tif (trimmedSearch) {\n\t\t\tfilters.push({ key: \"search\", label: `Search: \"${trimmedSearch}\"` });\n\t\t}\n\t\tif (state.language !== \"all\") {\n\t\t\tfilters.push({ key: \"language\", label: `Language: ${state.language}` });\n\t\t}\n\t\tif (state.activity !== \"all\") {\n\t\t\tfilters.push({\n\t\t\t\tkey: \"activity\",\n\t\t\t\tlabel: `Activity: ${tierLabels[state.activity] ?? state.activity}`,\n\t\t\t});\n\t\t}\n\t\tif (state.userMode === \"pin\" && state.userValue) {\n\t\t\tfilters.push({ key: \"user\", label: `Pinned: ${state.userValue}` });\n\t\t} else if (state.userMode === \"exclude\" && state.userValue) {\n\t\t\tfilters.push({ key: \"user\", label: `Excluded: ${state.userValue}` });\n\t\t}\n\n\t\tsummaryFilters.replaceChildren();\n\t\tif (filters.length > 0) {\n\t\t\tconst frag = document.createDocumentFragment();\n\t\t\tfor (const filter of filters) {\n\t\t\t\tconst chip = document.createElement(\"span\");\n\t\t\t\tchip.className = `summary-chip summary-chip--${filter.key}`;\n\t\t\t\tchip.textContent = filter.label;\n\t\t\t\tfrag.appendChild(chip);\n\t\t\t}\n\t\t\tsummaryFilters.appendChild(frag);\n\t\t\tsummaryFilters.hidden = false;\n\t\t} else {\n\t\t\tsummaryFilters.hidden = true;\n\t\t}\n\n\t\tif (dom.resultCount) {\n\t\t\tdom.resultCount.textContent = summaryText;\n\t\t\tdom.resultCount.hidden = true;\n\t\t}\n\t}\n\n\tfunction renderList(items) {\n\t\tif (!dom.list) {\n\t\t\treturn;\n\t\t}\n\t\tif (shouldVirtualizeList(items)) {\n\t\t\tenableVirtualScroll(items);\n\t\t} else {\n\t\t\tdisableVirtualScroll();\n\t\t\trenderFullList(items);\n\t\t}\n\t}\n\n\tfunction renderFullList(items) {\n\t\tconst list = dom.list;\n\t\tlist.replaceChildren();\n\t\tconst isCompact = state.density === \"compact\";\n\t\tif (items.length === 0) {\n\t\t\tlist.appendChild(createEmptyStateItem());\n\t\t\treturn;\n\t\t}\n\t\tfor (const item of items) {\n\t\t\tlist.appendChild(createCardElement(item, isCompact));\n\t\t}\n\t}\n\n\tfunction createCardElement(item, isCompact, itemIndex = null) {\n\t\tconst card = document.createElement(\"li\");\n\t\tcard.className = \"star-card\";\n\t\tif (item.isNew) {\n\t\t\tcard.classList.add(\"star-card--new\");\n\t\t}\n\t\tif (isCompact) {\n\t\t\tcard.classList.add(\"star-card--compact\");\n\t\t}\n\t\tconst isPinnedUser =\n\t\t\tstate.userMode === \"pin\" && state.userValue === item.login;\n\t\tif (isPinnedUser) {\n\t\t\tcard.classList.add(\"star-card--pinned\");\n\t\t}\n\n\t\tconst header = document.createElement(\"div\");\n\t\theader.className = \"star-card__header\";\n\n\t\tif (itemIndex !== null) {\n\t\t\tcard.dataset.itemIndex = String(itemIndex);\n\t\t}\n\n\t\tconst userButton = document.createElement(\"button\");\n\t\tuserButton.type = \"button\";\n\t\tuserButton.className = \"star-user-button\";\n\t\tuserButton.textContent = item.login;\n\t\tuserButton.dataset.login = item.login;\n\t\tconst match = state.userValue === item.login;\n\t\tif (match && state.userMode === \"pin\") {\n\t\t\tuserButton.classList.add(\"is-pinned\");\n\t\t\tuserButton.setAttribute(\"aria-pressed\", \"true\");\n\t\t} else if (match && state.userMode === \"exclude\") {\n\t\t\tuserButton.classList.add(\"is-excluded\");\n\t\t\tuserButton.setAttribute(\"aria-pressed\", \"true\");\n\t\t\tcard.classList.add(\"star-card--excluded\");\n\t\t} else {\n\t\t\tuserButton.setAttribute(\"aria-pressed\", \"false\");\n\t\t}\n\t\tuserButton.title = \"Click to cycle user filter\";\n\t\tuserButton.addEventListener(\"click\", () => handleUserToggle(item.login));\n\t\theader.appendChild(userButton);\n\n\t\tconst headerSignals = document.createElement(\"div\");\n\t\theaderSignals.className = \"star-card__signals\";\n\n\t\tif (item.isNew) {\n\t\t\tconst newBadge = document.createElement(\"span\");\n\t\t\tnewBadge.className = \"new-badge\";\n\t\t\tnewBadge.textContent = \"New\";\n\t\t\theaderSignals.appendChild(newBadge);\n\t\t}\n\n\t\tif (item.normalizedTier) {\n\t\t\tconst tierSpan = document.createElement(\"span\");\n\t\t\ttierSpan.className = `activity-tag activity-${item.normalizedTier}`;\n\t\t\ttierSpan.textContent =\n\t\t\t\ttierLabels[item.normalizedTier] ?? item.normalizedTier;\n\t\t\theaderSignals.appendChild(tierSpan);\n\t\t}\n\n\t\tif (headerSignals.childElementCount > 0) {\n\t\t\theader.appendChild(headerSignals);\n\t\t}\n\n\t\tcard.appendChild(header);\n\n\t\tconst body = document.createElement(\"div\");\n\t\tbody.className = \"star-card__body\";\n\n\t\tconst link = document.createElement(\"a\");\n\t\tlink.className = \"repo-link\";\n\t\tlink.href = item.repo_html_url;\n\t\tlink.textContent = item.repo_full_name;\n\t\tlink.target = \"_blank\";\n\t\tlink.rel = \"noopener noreferrer\";\n\t\tbody.appendChild(link);\n\n\t\tif (item.repo_description) {\n\t\t\tconst desc = document.createElement(\"p\");\n\t\t\tdesc.className = \"star-description\";\n\t\t\tdesc.textContent = item.repo_description;\n\t\t\tbody.appendChild(desc);\n\t\t}\n\n\t\tcard.appendChild(body);\n\n\t\tconst footer = document.createElement(\"div\");\n\t\tfooter.className = \"star-card__footer\";\n\n\t\tconst meta = document.createElement(\"div\");\n\t\tmeta.className = \"star-meta\";\n\n\t\tif (item.repo_language) {\n\t\t\tconst lang = document.createElement(\"span\");\n\t\t\tlang.className = \"meta-pill\";\n\t\t\tlang.textContent = item.repo_language;\n\t\t\tmeta.appendChild(lang);\n\t\t}\n\n\t\tif (item.repo_topics.length > 0) {\n\t\t\tconst topicsWrap = document.createElement(\"div\");\n\t\t\ttopicsWrap.className = \"topics\";\n\t\t\tconst limited = item.repo_topics.slice(0, 10);\n\t\t\tfor (const topic of limited) {\n\t\t\t\tconst span = document.createElement(\"span\");\n\t\t\t\tspan.className = \"topic-tag\";\n\t\t\t\tspan.textContent = topic;\n\t\t\t\ttopicsWrap.appendChild(span);\n\t\t\t}\n\t\t\tmeta.appendChild(topicsWrap);\n\t\t}\n\n\t\tfooter.appendChild(meta);\n\n\t\tconst times = document.createElement(\"div\");\n\t\ttimes.className = \"star-card__timestamps\";\n\n\t\tconst starredLabel = document.createElement(\"span\");\n\t\tstarredLabel.className = \"timestamp-label\";\n\t\tstarredLabel.textContent = \"Starred\";\n\t\ttimes.appendChild(starredLabel);\n\n\t\tconst starredTime = document.createElement(\"time\");\n\t\tstarredTime.dateTime = item.starred_at;\n\t\tstarredTime.textContent = new Date(item.starred_at).toLocaleString();\n\t\tstarredTime.className = \"timestamp-value\";\n\t\ttimes.appendChild(starredTime);\n\n\t\tconst fetchedLabel = document.createElement(\"span\");\n\t\tfetchedLabel.className = \"timestamp-label\";\n\t\tfetchedLabel.textContent = \"Fetched\";\n\t\ttimes.appendChild(fetchedLabel);\n\n\t\tconst fetchedTime = document.createElement(\"time\");\n\t\tfetchedTime.dateTime = item.fetched_at;\n\t\tfetchedTime.textContent = new Date(item.fetched_at).toLocaleString();\n\t\tfetchedTime.className = \"timestamp-value\";\n\t\ttimes.appendChild(fetchedTime);\n\n\t\tfooter.appendChild(times);\n\n\t\tcard.appendChild(footer);\n\t\treturn card;\n\t}\n\n\tfunction createEmptyStateItem() {\n\t\tconst empty = document.createElement(\"li\");\n\t\tempty.className = \"empty-state\";\n\t\tempty.textContent = \"No matches found for the current filters.\";\n\t\treturn empty;\n\t}\n\n\tfunction shouldVirtualizeList(items) {\n\t\tconst totalAvailable =\n\t\t\tstate.pageMeta && typeof state.pageMeta.total === \"number\"\n\t\t\t\t? state.pageMeta.total\n\t\t\t\t: items.length;\n\t\treturn (\n\t\t\t(totalAvailable > VIRTUALIZE_LENGTH_THRESHOLD || state.pageSize > 50) &&\n\t\t\titems.length > VIRTUAL_WINDOW\n\t\t);\n\t}\n\n\tfunction enableVirtualScroll(items) {\n\t\tvirtualState.enabled = true;\n\t\tvirtualState.items = items;\n\t\tvirtualState.startIndex = 0;\n\t\tvirtualState.endIndex = Math.min(items.length, VIRTUAL_WINDOW);\n\t\tupdateVirtualOffsets();\n\t\tattachVirtualListeners();\n\t\tdrawVirtualSlice(true);\n\t}\n\n\tfunction disableVirtualScroll() {\n\t\tif (!virtualState.enabled) {\n\t\t\treturn;\n\t\t}\n\t\tvirtualState.enabled = false;\n\t\tvirtualState.items = [];\n\t\tvirtualState.startIndex = 0;\n\t\tvirtualState.endIndex = 0;\n\t\tdetachVirtualListeners();\n\t\tif (dom.virtualStatus) {\n\t\t\tdom.virtualStatus.hidden = true;\n\t\t}\n\t}\n\n\tfunction attachVirtualListeners() {\n\t\tif (virtualState.listenersAttached) {\n\t\t\treturn;\n\t\t}\n\t\twindow.addEventListener(\"scroll\", handleVirtualScroll, { passive: true });\n\t\twindow.addEventListener(\"resize\", handleVirtualResize);\n\t\tvirtualState.listenersAttached = true;\n\t}\n\n\tfunction detachVirtualListeners() {\n\t\tif (!virtualState.listenersAttached) {\n\t\t\treturn;\n\t\t}\n\t\twindow.removeEventListener(\"scroll\", handleVirtualScroll);\n\t\twindow.removeEventListener(\"resize\", handleVirtualResize);\n\t\tvirtualState.listenersAttached = false;\n\t}\n\n\tfunction handleVirtualScroll() {\n\t\tif (!virtualState.enabled) {\n\t\t\treturn;\n\t\t}\n\t\tupdateVirtualWindow(false);\n\t}\n\n\tfunction handleVirtualResize() {\n\t\tif (!virtualState.enabled) {\n\t\t\treturn;\n\t\t}\n\t\tupdateVirtualOffsets();\n\t\tupdateVirtualWindow(true);\n\t}\n\n\tfunction updateVirtualOffsets() {\n\t\tconst listRect = dom.list.getBoundingClientRect();\n\t\tvirtualState.listOffset = listRect.top + window.scrollY;\n\t}\n\n\tfunction updateVirtualWindow(force) {\n\t\tif (!virtualState.enabled || virtualState.items.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst scrollOffset = Math.max(0, window.scrollY - virtualState.listOffset);\n\t\tconst estimatedIndex = Math.floor(\n\t\t\tscrollOffset / Math.max(virtualState.itemHeight, 1),\n\t\t);\n\t\tlet nextStart = Math.max(0, estimatedIndex - VIRTUAL_OVERSCAN);\n\t\tlet nextEnd = Math.min(\n\t\t\tvirtualState.items.length,\n\t\t\tnextStart + VIRTUAL_WINDOW,\n\t\t);\n\t\tconst focusedIndex = getFocusedVirtualIndex();\n\t\tif (focusedIndex !== null) {\n\t\t\tif (focusedIndex < nextStart) {\n\t\t\t\tnextStart = Math.max(0, focusedIndex - VIRTUAL_OVERSCAN);\n\t\t\t} else if (focusedIndex >= nextEnd) {\n\t\t\t\tnextStart = Math.max(0, focusedIndex - Math.floor(VIRTUAL_WINDOW / 2));\n\t\t\t}\n\t\t\tnextEnd = Math.min(virtualState.items.length, nextStart + VIRTUAL_WINDOW);\n\t\t}\n\t\tif (\n\t\t\t!force &&\n\t\t\tnextStart === virtualState.startIndex &&\n\t\t\tnextEnd === virtualState.endIndex\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tvirtualState.startIndex = nextStart;\n\t\tvirtualState.endIndex = nextEnd;\n\t\tdrawVirtualSlice();\n\t}\n\n\tfunction drawVirtualSlice(forceEmptyCheck) {\n\t\tconst list = dom.list;\n\t\tlist.replaceChildren();\n\t\tif (virtualState.items.length === 0) {\n\t\t\tlist.appendChild(createEmptyStateItem());\n\t\t\treturn;\n\t\t}\n\t\tshowVirtualStatus(\"Loading more stars…\");\n\t\tconst isCompact = state.density === \"compact\";\n\t\tconst beforeSpacer = document.createElement(\"li\");\n\t\tbeforeSpacer.className = \"virtual-spacer\";\n\t\tbeforeSpacer.style.height = `${virtualState.startIndex * virtualState.itemHeight}px`;\n\t\tbeforeSpacer.setAttribute(\"aria-hidden\", \"true\");\n\t\tlist.appendChild(beforeSpacer);\n\n\t\tconst slice = virtualState.items.slice(\n\t\t\tvirtualState.startIndex,\n\t\t\tvirtualState.endIndex,\n\t\t);\n\t\tif (slice.length === 0 && forceEmptyCheck) {\n\t\t\tlist.appendChild(createEmptyStateItem());\n\t\t}\n\t\tslice.forEach((item, idx) => {\n\t\t\tlist.appendChild(\n\t\t\t\tcreateCardElement(item, isCompact, virtualState.startIndex + idx),\n\t\t\t);\n\t\t});\n\n\t\tconst afterSpacer = document.createElement(\"li\");\n\t\tafterSpacer.className = \"virtual-spacer\";\n\t\tconst remaining = Math.max(\n\t\t\t0,\n\t\t\tvirtualState.items.length - virtualState.endIndex,\n\t\t);\n\t\tafterSpacer.style.height = `${remaining * virtualState.itemHeight}px`;\n\t\tafterSpacer.setAttribute(\"aria-hidden\", \"true\");\n\t\tlist.appendChild(afterSpacer);\n\n\t\tmeasureVirtualItemHeight();\n\t}\n\n\tfunction measureVirtualItemHeight() {\n\t\tconst cards = dom.list.querySelectorAll(\".star-card\");\n\t\tif (!cards || cards.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst totalHeight = Array.from(cards).reduce(\n\t\t\t(acc, card) => acc + card.getBoundingClientRect().height,\n\t\t\t0,\n\t\t);\n\t\tconst average = totalHeight / cards.length;\n\t\tif (\n\t\t\tNumber.isFinite(average) &&\n\t\t\taverage > 0 &&\n\t\t\tMath.abs(average - virtualState.itemHeight) > 5\n\t\t) {\n\t\t\tvirtualState.itemHeight = average;\n\t\t\tupdateVirtualWindow(true);\n\t\t}\n\t}\n\n\tfunction showVirtualStatus(message, duration = 500) {\n\t\tif (!dom.virtualStatus) {\n\t\t\treturn;\n\t\t}\n\t\tdom.virtualStatus.textContent = message;\n\t\tdom.virtualStatus.hidden = false;\n\t\tif (virtualState.statusTimer) {\n\t\t\twindow.clearTimeout(virtualState.statusTimer);\n\t\t}\n\t\tvirtualState.statusTimer = window.setTimeout(() => {\n\t\t\tdom.virtualStatus.hidden = true;\n\t\t}, duration);\n\t}\n\n\tfunction getFocusedVirtualIndex() {\n\t\tconst active = document.activeElement;\n\t\tif (!active || !dom.list) {\n\t\t\treturn null;\n\t\t}\n\t\tif (!dom.list.contains(active)) {\n\t\t\treturn null;\n\t\t}\n\t\tconst card = active.closest?.(\".star-card\");\n\t\tif (!card) {\n\t\t\treturn null;\n\t\t}\n\t\tconst value = card.getAttribute(\"data-item-index\");\n\t\tif (value === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst parsed = Number.parseInt(value, 10);\n\t\treturn Number.isFinite(parsed) ? parsed : null;\n\t}\n\n\tfunction applyFilters() {\n\t\tconst visible = state.items.slice();\n\t\tif (state.sort === \"alpha\") {\n\t\t\tvisible.sort((a, b) => a.repo_full_name.localeCompare(b.repo_full_name));\n\t\t} else {\n\t\t\tvisible.sort((a, b) => b.fetched_at_ms - a.fetched_at_ms);\n\t\t}\n\n\t\trenderList(visible);\n\t\tupdateListLayout();\n\t\trenderPagination();\n\t\trenderSummary(visible.length);\n\t\trenderUserBanner();\n\t\trenderQuickFilters();\n\t\tupdateMarkSeenButton();\n\t}\n\n\tfunction handleUserToggle(login) {\n\t\tif (state.userValue !== login) {\n\t\t\tstate.userMode = \"pin\";\n\t\t\tstate.userValue = login;\n\t\t} else if (state.userMode === \"pin\") {\n\t\t\tstate.userMode = \"exclude\";\n\t\t} else {\n\t\t\tstate.userMode = \"none\";\n\t\t\tstate.userValue = null;\n\t\t}\n\t\trequestFilterUpdate();\n\t}\n\n\tfunction normalizeItems(rawItems) {\n\t\tlet newest = 0;\n\t\tconst normalized = rawItems.map((item) => {\n\t\t\tconst normalizedTier = item.user_activity_tier\n\t\t\t\t? item.user_activity_tier.toLowerCase()\n\t\t\t\t: null;\n\t\t\tconst topics = Array.isArray(item.repo_topics) ? item.repo_topics : [];\n\t\t\tconst starredMs = Date.parse(item.starred_at) || 0;\n\t\t\tconst fetchedMs = Date.parse(item.fetched_at) || 0;\n\t\t\tif (fetchedMs > newest) {\n\t\t\t\tnewest = fetchedMs;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...item,\n\t\t\t\tnormalizedTier,\n\t\t\t\trepo_topics: topics,\n\t\t\t\tstarred_at_ms: starredMs,\n\t\t\t\tfetched_at_ms: fetchedMs,\n\t\t\t\tisNew: false,\n\t\t\t};\n\t\t});\n\t\treturn { items: normalized, newestFetched: newest };\n\t}\n\n\tfunction applyPageData(normalizedItems, meta, newestMs, options = {}) {\n\t\tnewestFetchedMs = newestMs || 0;\n\t\tstate.items = normalizedItems;\n\t\tstate.pageMeta = meta || null;\n\t\tconst nextPage = meta?.page ?? state.page;\n\t\tconst pageChanged = state.page !== nextPage;\n\t\tstate.page = nextPage;\n\t\tflagNewItems();\n\t\tpopulateFilters();\n\t\tcomputeQuickFilters();\n\t\tapplyFilters();\n\t\tconst shouldPersist =\n\t\t\toptions.shouldPersist ?? options.manual ?? pageChanged;\n\t\tif (shouldPersist) {\n\t\t\tpersistUiState();\n\t\t\tsyncUrl();\n\t\t}\n\t}\n\n\tasync function loadPage(page, options = {}) {\n\t\tconst targetPage = Math.max(1, page || 1);\n\t\tconst manual = Boolean(options.manual);\n\t\tconst initial = Boolean(options.initial);\n\t\tconst bypassCache = Boolean(options.bypassCache);\n\t\tresetPaginationCachesIfNeeded();\n\t\tif (!bypassCache) {\n\t\t\tconst cached = getCachedPageEntry(targetPage);\n\t\t\tif (cached) {\n\t\t\t\tconst normalized = normalizeItems(cached.rawItems);\n\t\t\t\tapplyPageData(normalized.items, cached.meta, normalized.newestFetched, {\n\t\t\t\t\tmanual,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tawait fetchStars({ manual, initial, pageOverride: targetPage });\n\t}\n\n\tasync function fetchStars(options = {}) {\n\t\tif (isFetching) {\n\t\t\treturn;\n\t\t}\n\t\tconst manual = Boolean(options.manual);\n\t\tconst initial = Boolean(options.initial);\n\t\tconst targetPage = Math.max(1, options.pageOverride ?? state.page ?? 1);\n\t\tconst ignoreEtag = Boolean(options.ignoreEtag);\n\t\tisFetching = true;\n\t\tdom.refreshButton.disabled = true;\n\t\tdom.retryButton.disabled = true;\n\n\t\tif (initial) {\n\t\t\tsetStatus(\"Loading…\");\n\t\t} else if (manual) {\n\t\t\tsetStatus(\"Refreshing…\");\n\t\t} else {\n\t\t\tsetStatus(\"Refreshing…\");\n\t\t}\n\n\t\tclearError();\n\n\t\ttry {\n\t\t\tconst headers = {};\n\t\t\tconst etagKey = `${currentFilterSignature}|${targetPage}`;\n\t\t\tif (!ignoreEtag && etagCache.has(etagKey)) {\n\t\t\t\theaders[\"If-None-Match\"] = etagCache.get(etagKey);\n\t\t\t}\n\t\t\tconst queryString = buildApiQuery({ page: targetPage }).toString();\n\t\t\tconst response = await fetchImpl(\n\t\t\t\t`/api/stars${queryString ? `?${queryString}` : \"\"}`,\n\t\t\t\t{ headers },\n\t\t\t);\n\t\t\tconst responseEtag = response.headers.get(\"ETag\");\n\t\t\tif (responseEtag) {\n\t\t\t\tetagCache.set(etagKey, responseEtag);\n\t\t\t}\n\t\t\tif (response.status === 304) {\n\t\t\t\tconst cached = getCachedPageEntry(targetPage);\n\t\t\t\tif (cached) {\n\t\t\t\t\tconst normalized = normalizeItems(cached.rawItems);\n\t\t\t\t\tapplyPageData(\n\t\t\t\t\t\tnormalized.items,\n\t\t\t\t\t\tcached.meta,\n\t\t\t\t\t\tnormalized.newestFetched,\n\t\t\t\t\t\t{ manual },\n\t\t\t\t\t);\n\t\t\t\t\tlastSyncedAt = new Date();\n\t\t\t\t\tbackoffMs = REFRESH_INTERVAL_MS;\n\t\t\t\t\tsetStatus(\"\");\n\t\t\t\t\tupdateSyncStatus();\n\t\t\t\t\tscheduleNextFetch(REFRESH_INTERVAL_MS);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tetagCache.delete(etagKey);\n\t\t\t\tawait fetchStars({\n\t\t\t\t\tmanual,\n\t\t\t\t\tinitial,\n\t\t\t\t\tpageOverride: targetPage,\n\t\t\t\t\tignoreEtag: true,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Request failed with status ${response.status}`);\n\t\t\t}\n\t\t\tconst payload = await response.json();\n\t\t\tconst rawItems = Array.isArray(payload)\n\t\t\t\t? payload\n\t\t\t\t: Array.isArray(payload?.items)\n\t\t\t\t\t? payload.items\n\t\t\t\t\t: [];\n\t\t\tconst meta = payload?.meta ?? null;\n\t\t\tstorePageCacheEntry(meta?.page ?? targetPage, rawItems, meta);\n\t\t\tconst normalized = normalizeItems(rawItems);\n\t\t\tapplyPageData(normalized.items, meta, normalized.newestFetched, {\n\t\t\t\tmanual,\n\t\t\t});\n\t\t\tlastSyncedAt = new Date();\n\t\t\tbackoffMs = REFRESH_INTERVAL_MS;\n\t\t\tsetStatus(\"\");\n\t\t\tupdateSyncStatus();\n\t\t\tscheduleNextFetch(REFRESH_INTERVAL_MS);\n\t\t} catch (err) {\n\t\t\tconsole.error(err);\n\t\t\tshowError(\"Failed to refresh starred repositories. Refresh when ready.\");\n\t\t\tbackoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);\n\t\t\tconst seconds = Math.max(Math.round(backoffMs / 1000), 1);\n\t\t\tsetStatus(`Retrying in ${seconds}s…`);\n\t\t\tscheduleNextFetch(backoffMs);\n\t\t} finally {\n\t\t\tisFetching = false;\n\t\t\tdom.refreshButton.disabled = false;\n\t\t\tdom.retryButton.disabled = false;\n\t\t\tupdateSyncStatus();\n\t\t\trenderPagination();\n\t\t}\n\t}\n\n\tasync function fetchFilterOptions() {\n\t\ttry {\n\t\t\tconst headers = {};\n\t\t\tif (optionsEtag) {\n\t\t\t\theaders[\"If-None-Match\"] = optionsEtag;\n\t\t\t}\n\t\t\tconst response = await fetchImpl(\"/api/options\", { headers });\n\t\t\tif (response.status === 304) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Options request failed with status ${response.status}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst payload = await response.json();\n\t\t\tstate.filterOptions.languages = Array.isArray(payload?.languages)\n\t\t\t\t? payload.languages\n\t\t\t\t: [];\n\t\t\tstate.filterOptions.activity = Array.isArray(payload?.activity_tiers)\n\t\t\t\t? payload.activity_tiers\n\t\t\t\t: [];\n\t\t\toptionsEtag = response.headers.get(\"ETag\") || optionsEtag;\n\t\t\tpopulateFilters();\n\t\t\tcomputeQuickFilters();\n\t\t} catch (err) {\n\t\t\tconsole.warn(\"Failed to fetch filter options\", err);\n\t\t}\n\t}\n\n\tfunction initControls() {\n\t\tdom.searchInput.value = state.search;\n\t\tupdateSortToggle();\n\t\tensurePageSizeOption();\n\t\tupdateDensityToggle();\n\n\t\tdom.searchInput.addEventListener(\"input\", (event) => {\n\t\t\tstate.search = event.target.value;\n\t\t\tif (searchDebounce) {\n\t\t\t\twindow.clearTimeout(searchDebounce);\n\t\t\t}\n\t\t\tsearchDebounce = window.setTimeout(() => {\n\t\t\t\trequestFilterUpdate();\n\t\t\t}, 250);\n\t\t});\n\n\t\tdom.languageFilter.addEventListener(\"change\", (event) => {\n\t\t\tstate.language = event.target.value || \"all\";\n\t\t\trequestFilterUpdate();\n\t\t});\n\n\t\tdom.activityFilter.addEventListener(\"change\", (event) => {\n\t\t\tstate.activity = event.target.value || \"all\";\n\t\t\trequestFilterUpdate();\n\t\t});\n\n\t\tdom.sortToggle.addEventListener(\"click\", () => {\n\t\t\tif (state.sort === \"newest\") {\n\t\t\t\tstate.sort = \"alpha\";\n\t\t\t} else {\n\t\t\t\tstate.sort = \"newest\";\n\t\t\t}\n\t\t\tupdateSortToggle();\n\t\t\trequestFilterUpdate();\n\t\t});\n\n\t\tdom.densityToggle.addEventListener(\"click\", () => {\n\t\t\tstate.density =\n\t\t\t\tstate.density === \"comfortable\" ? \"compact\" : \"comfortable\";\n\t\t\tupdateDensityToggle();\n\t\t\tpersistUiState();\n\t\t\tsyncUrl();\n\t\t\tapplyFilters();\n\t\t});\n\n\t\tdom.pagePrev.addEventListener(\"click\", () => {\n\t\t\tif (state.pageMeta?.has_prev) {\n\t\t\t\tloadPage(Math.max(1, state.page - 1), { manual: true });\n\t\t\t}\n\t\t});\n\n\t\tdom.pageNext.addEventListener(\"click\", () => {\n\t\t\tif (state.pageMeta?.has_next) {\n\t\t\t\tloadPage(state.page + 1, { manual: true });\n\t\t\t}\n\t\t});\n\n\t\tdom.pageSize.addEventListener(\"change\", (event) => {\n\t\t\tconst value = Number.parseInt(event.target.value, 10);\n\t\t\tif (Number.isFinite(value) && value > 0) {\n\t\t\t\tstate.pageSize = value;\n\t\t\t\trequestFilterUpdate();\n\t\t\t}\n\t\t});\n\n\t\tdom.markSeenButton.addEventListener(\"click\", () => {\n\t\t\tif (newestFetchedMs > 0) {\n\t\t\t\tlastAcknowledgedMs = newestFetchedMs;\n\t\t\t\tpersistAckTimestamp(lastAcknowledgedMs);\n\t\t\t\tflagNewItems();\n\t\t\t\tapplyFilters();\n\t\t\t}\n\t\t});\n\n\t\tdom.refreshButton.addEventListener(\"click\", () => {\n\t\t\tbackoffMs = REFRESH_INTERVAL_MS;\n\t\t\tloadPage(state.page, { manual: true, bypassCache: true });\n\t\t});\n\n\t\tdom.retryButton.addEventListener(\"click\", () => {\n\t\t\tbackoffMs = REFRESH_INTERVAL_MS;\n\t\t\tclearError();\n\t\t\tloadPage(state.page, { manual: true, bypassCache: true });\n\t\t});\n\n\t\tdom.userFilterClear.addEventListener(\"click\", () => {\n\t\t\tstate.userMode = \"none\";\n\t\t\tstate.userValue = null;\n\t\t\trequestFilterUpdate();\n\t\t});\n\n\t\tdocument.addEventListener(\"visibilitychange\", () => {\n\t\t\tif (!document.hidden) {\n\t\t\t\tconst age = lastSyncedAt\n\t\t\t\t\t? Date.now() - lastSyncedAt.getTime()\n\t\t\t\t\t: Infinity;\n\t\t\t\tif (age > REFRESH_INTERVAL_MS) {\n\t\t\t\t\tbackoffMs = REFRESH_INTERVAL_MS;\n\t\t\t\t\tloadPage(state.page, { manual: true, bypassCache: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\twindow.addEventListener(\"beforeunload\", () => {\n\t\t\tif (refreshTimer !== null) {\n\t\t\t\twindow.clearTimeout(refreshTimer);\n\t\t\t}\n\t\t\tif (syncTicker !== null) {\n\t\t\t\twindow.clearInterval(syncTicker);\n\t\t\t}\n\t\t});\n\n\t\twindow.addEventListener(\"resize\", () => {\n\t\t\tupdateListLayout();\n\t\t});\n\t}\n\n\tfunction syncControlsToState() {\n\t\tif (dom.searchInput) {\n\t\t\tdom.searchInput.value = state.search;\n\t\t}\n\t\tif (dom.languageFilter) {\n\t\t\tdom.languageFilter.value = state.language;\n\t\t}\n\t\tif (dom.activityFilter) {\n\t\t\tdom.activityFilter.value = state.activity;\n\t\t}\n\t\tensurePageSizeOption();\n\t\tif (dom.pageSize) {\n\t\t\tdom.pageSize.value = String(state.pageSize);\n\t\t}\n\t\tupdateSortToggle();\n\t\tupdateDensityToggle();\n\t}\n\n\tfunction requestFilterUpdate({ resetPage = true, manual = true } = {}) {\n\t\tif (searchDebounce) {\n\t\t\twindow.clearTimeout(searchDebounce);\n\t\t\tsearchDebounce = null;\n\t\t}\n\t\tif (resetPage) {\n\t\t\tstate.page = 1;\n\t\t}\n\t\tresetPaginationCachesIfNeeded();\n\t\tpersistUiState();\n\t\tsyncUrl();\n\t\tloadPage(state.page, { manual, bypassCache: true });\n\t}\n\n\tfunction updateSortToggle() {\n\t\tif (state.sort === \"alpha\") {\n\t\t\tdom.sortToggle.textContent = \"Sort: Alphabetical\";\n\t\t\tdom.sortToggle.setAttribute(\"aria-pressed\", \"true\");\n\t\t} else {\n\t\t\tdom.sortToggle.textContent = \"Sort: Newest\";\n\t\t\tdom.sortToggle.setAttribute(\"aria-pressed\", \"false\");\n\t\t}\n\t}\n\n\tfunction updateDensityToggle() {\n\t\tconst pressed = state.density === \"compact\";\n\t\tdom.densityToggle.textContent = pressed\n\t\t\t? \"Layout: Compact\"\n\t\t\t: \"Layout: Comfortable\";\n\t\tdom.densityToggle.setAttribute(\"aria-pressed\", pressed ? \"true\" : \"false\");\n\t}\n\n\tfunction updateListLayout() {\n\t\tconst list = dom.list;\n\t\tif (!list) {\n\t\t\treturn;\n\t\t}\n\t\tconst wide = window.innerWidth >= GRID_BREAKPOINT;\n\t\tlist.classList.toggle(\"is-grid\", wide);\n\t\tlist.classList.toggle(\"is-compact\", state.density === \"compact\");\n\t}\n\n\tfunction isEditableTarget(target) {\n\t\tif (!target) {\n\t\t\treturn false;\n\t\t}\n\t\tconst nodeName = target.nodeName.toLowerCase();\n\t\tif (\n\t\t\tnodeName === \"input\" ||\n\t\t\tnodeName === \"textarea\" ||\n\t\t\ttarget.isContentEditable\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tfunction cycleLanguageFilter() {\n\t\tconst options = Array.from(dom.languageFilter.options).map(\n\t\t\t(opt) => opt.value,\n\t\t);\n\t\tlet idx = options.indexOf(state.language);\n\t\tif (idx === -1) {\n\t\t\tidx = 0;\n\t\t}\n\t\tidx = (idx + 1) % options.length;\n\t\tstate.language = options[idx];\n\t\tdom.languageFilter.value = state.language;\n\t\trequestFilterUpdate();\n\t}\n\n\tfunction openShortcutModal() {\n\t\tif (!dom.shortcutModal) {\n\t\t\treturn;\n\t\t}\n\t\tdom.shortcutModal.hidden = false;\n\t\tdom.shortcutModal.removeAttribute(\"aria-hidden\");\n\t\tif (dom.shortcutClose) {\n\t\t\tdom.shortcutClose.focus();\n\t\t}\n\t}\n\n\tfunction closeShortcutModal() {\n\t\tif (!dom.shortcutModal || dom.shortcutModal.hidden) {\n\t\t\treturn;\n\t\t}\n\t\tdom.shortcutModal.hidden = true;\n\t\tdom.shortcutModal.setAttribute(\"aria-hidden\", \"true\");\n\t}\n\n\tfunction toggleShortcutModal() {\n\t\tif (!dom.shortcutModal) {\n\t\t\treturn;\n\t\t}\n\t\tif (dom.shortcutModal.hidden) {\n\t\t\topenShortcutModal();\n\t\t} else {\n\t\t\tcloseShortcutModal();\n\t\t}\n\t}\n\n\tfunction registerKeyboardShortcuts() {\n\t\tdocument.addEventListener(\"keydown\", (event) => {\n\t\t\tif (event.defaultPrevented) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst target = event.target;\n\t\t\tconst key = event.key;\n\n\t\t\tif (key === \"Escape\") {\n\t\t\t\tif (dom.presetModal && !dom.presetModal.hidden) {\n\t\t\t\t\tclosePresetModal();\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (dom.shortcutModal && !dom.shortcutModal.hidden) {\n\t\t\t\t\tcloseShortcutModal();\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isEditableTarget(target) && !event.altKey) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.altKey && !event.ctrlKey && !event.metaKey) {\n\t\t\t\tif (dom.presetModal && !dom.presetModal.hidden) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (key >= \"1\" && key <= \"5\") {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tapplyPresetByShortcut(Number.parseInt(key, 10) - 1);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (key === \"/\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tdom.searchInput.focus();\n\t\t\t\tdom.searchInput.select();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (key === \"?\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\ttoggleShortcutModal();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (key === \"[\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tdom.pagePrev.click();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (key === \"]\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tdom.pageNext.click();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (key === \"l\" || key === \"L\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tcycleLanguageFilter();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (key === \"m\" || key === \"M\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tdom.markSeenButton.click();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (key === \"r\" || key === \"R\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tbackoffMs = REFRESH_INTERVAL_MS;\n\t\t\t\tloadPage(state.page, { manual: true, bypassCache: true });\n\t\t\t}\n\t\t});\n\n\t\tif (dom.shortcutClose) {\n\t\t\tdom.shortcutClose.addEventListener(\"click\", () => {\n\t\t\t\tcloseShortcutModal();\n\t\t\t});\n\t\t}\n\t\tif (dom.shortcutModal) {\n\t\t\tdom.shortcutModal.addEventListener(\"click\", (event) => {\n\t\t\t\tif (event.target === dom.shortcutModal) {\n\t\t\t\t\tcloseShortcutModal();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tfunction registerTestHook() {\n\t\tif (window.__STARCHASER_TEST_HOOK__) {\n\t\t\twindow.__STARCHASER_TEST_HOOK__({\n\t\t\t\ttriggerRefresh: () =>\n\t\t\t\t\tloadPage(state.page, { manual: true, bypassCache: true }),\n\t\t\t\toverrideFetch: (fn) => {\n\t\t\t\t\tfetchImpl = fn;\n\t\t\t\t},\n\t\t\t\tstate,\n\t\t\t\tgetLastSyncedAt: () => lastSyncedAt,\n\t\t\t\tacknowledge: () => dom.markSeenButton.click(),\n\t\t\t});\n\t\t}\n\t}\n\n\tfunction initialise() {\n\t\tapplySnapshot(loadUiSnapshot());\n\t\tcurrentFilterSignature = computeFilterSignature();\n\t\tcacheSignature = currentFilterSignature;\n\t\tsetupPresetUi();\n\t\tinitControls();\n\t\tupdateSyncStatus();\n\t\trenderQuickFilters();\n\t\trenderUserBanner();\n\t\tupdateListLayout();\n\n\t\tsyncTicker = window.setInterval(updateSyncStatus, 60 * 1000);\n\t\tregisterTestHook();\n\t\tregisterKeyboardShortcuts();\n\t\tfetchFilterOptions();\n\t\tloadPage(state.page, { initial: true, bypassCache: true });\n\t}\n\n\tinitialise();\n})();\n    </script>\n  </body>\n</html>"
[INFO] [stdout]  right: "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>GitHub Followings Stars</title>\n    <style>\n:root {\n        color-scheme: light dark;\n        --bg: #ffffff;\n        --fg: #1b1f23;\n        --muted: #4a5568;\n        --border: #d0d7de;\n        --accent: #0366d6;\n        --badge-bg: #ddeeff;\n        --badge-fg: #054da7;\n        --card-bg: #ffffff;\n        --card-border: #d0d7de;\n        --card-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);\n        --card-new-bg: #eaf2ff;\n        --card-new-border: #1f6feb;\n        --card-pinned-border: #f2c744;\n        --chip-bg: #eef2ff;\n        --chip-fg: #102242;\n        --chip-border: #c3d1ff;\n        --chip-muted-bg: #f6f8fa;\n        --chip-muted-fg: #4a5568;\n        --timestamp-label: #5a6472;\n      }\n      body {\n        font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n        margin: 0 auto;\n        padding: 1.5rem;\n        max-width: 960px;\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .skip-link {\n        position: absolute;\n        top: 0.5rem;\n        left: 0.5rem;\n        padding: 0.5rem 0.75rem;\n        background: var(--accent);\n        color: #ffffff;\n        border-radius: 6px;\n        transform: translateY(-150%);\n        transition: transform 0.2s ease;\n        z-index: 1000;\n      }\n      .skip-link:focus {\n        transform: translateY(0);\n        outline: none;\n      }\n      header {\n        display: flex;\n        flex-direction: column;\n        gap: 0.25rem;\n        margin-bottom: 1.5rem;\n      }\n      h1 {\n        margin: 0;\n        font-size: 1.8rem;\n      }\n      .timestamp {\n        color: var(--muted);\n        font-size: 0.9rem;\n      }\n      .summary-strip {\n        display: flex;\n        flex-direction: column;\n        gap: 0.55rem;\n        margin-bottom: 1rem;\n        padding: 0.85rem 1rem;\n        border: 1px solid var(--border);\n        border-radius: 12px;\n        background: var(--chip-muted-bg);\n      }\n      .summary-strip--empty {\n        opacity: 0.85;\n      }\n      .summary-primary {\n        font-size: 1rem;\n        font-weight: 600;\n        color: var(--fg);\n      }\n      .summary-meta {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.5rem 0.75rem;\n        font-size: 0.85rem;\n        color: var(--muted);\n      }\n      .summary-badge {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.35rem;\n        padding: 0.25rem 0.6rem;\n        border-radius: 999px;\n        font-weight: 600;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        background: var(--badge-bg);\n        color: var(--badge-fg);\n      }\n      .summary-filters {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.4rem;\n      }\n      .summary-chip {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.3rem;\n        padding: 0.25rem 0.65rem;\n        border-radius: 999px;\n        border: 1px solid var(--chip-border);\n        background: var(--chip-bg);\n        color: var(--chip-fg);\n        font-weight: 500;\n      }\n      .summary-chip--search {\n        background: var(--badge-bg);\n        border-color: var(--badge-bg);\n        color: var(--badge-fg);\n      }\n      .summary-chip--user {\n        background: rgba(3, 102, 214, 0.12);\n        border-color: rgba(3, 102, 214, 0.24);\n        color: var(--accent);\n      }\n      @media (min-width: 640px) {\n        .summary-strip {\n          flex-direction: row;\n          align-items: center;\n          justify-content: space-between;\n        }\n        .summary-primary {\n          font-size: 1.05rem;\n        }\n        .summary-meta {\n          justify-content: flex-end;\n        }\n      }\n      .controls {\n        display: grid;\n        gap: 0.75rem;\n        grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n        margin-bottom: 1rem;\n      }\n      label.control {\n        display: flex;\n        flex-direction: column;\n        gap: 0.35rem;\n        font-size: 0.85rem;\n        text-transform: uppercase;\n        letter-spacing: 0.04em;\n        color: var(--muted);\n      }\n      .controls input,\n      .controls select,\n      .controls button {\n        font: inherit;\n        padding: 0.45rem 0.6rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .controls button {\n        cursor: pointer;\n        justify-self: start;\n      }\n      .preset-bar {\n        border: 1px solid var(--border);\n        border-radius: 12px;\n        padding: 1rem;\n        margin-bottom: 1rem;\n        background: var(--chip-muted-bg);\n        display: flex;\n        flex-direction: column;\n        gap: 0.75rem;\n      }\n      .preset-bar__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        gap: 0.5rem;\n      }\n      .preset-bar__title {\n        font-size: 1rem;\n        margin: 0;\n      }\n      .preset-bar__hint {\n        margin: 0;\n        font-size: 0.85rem;\n        color: var(--muted);\n      }\n      .preset-bar__empty {\n        margin: 0;\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n      .preset-list {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n      }\n      .preset-chip-wrapper {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.35rem;\n      }\n      .preset-chip {\n        display: inline-flex;\n        align-items: center;\n        gap: 0.35rem;\n        padding: 0.35rem 0.65rem;\n        border-radius: 999px;\n        border: 1px solid var(--chip-border);\n        background: var(--chip-bg);\n        color: var(--chip-fg);\n        font-size: 0.9rem;\n        cursor: pointer;\n        position: relative;\n      }\n      .preset-chip.is-active {\n        border-color: var(--accent);\n        color: var(--accent);\n        background: rgba(3, 102, 214, 0.12);\n      }\n      .preset-chip__label {\n        font-weight: 600;\n      }\n      .preset-chip__keys {\n        font-size: 0.75rem;\n        text-transform: uppercase;\n        color: var(--muted);\n      }\n      .preset-chip__actions {\n        display: inline-flex;\n        gap: 0.25rem;\n      }\n      .preset-chip__icon-btn {\n        border: none;\n        background: transparent;\n        color: inherit;\n        padding: 0;\n        font-size: 0.85rem;\n        cursor: pointer;\n      }\n      .preset-chip__icon-btn:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .virtual-spacer {\n        list-style: none;\n        pointer-events: none;\n      }\n      .status-line {\n        font-size: 0.9rem;\n        color: var(--muted);\n        margin-bottom: 0.75rem;\n      }\n      .sync-bar {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.75rem;\n        margin-bottom: 0.75rem;\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n      .sync-badge {\n        background: var(--badge-bg);\n        color: var(--badge-fg);\n        padding: 0.15rem 0.6rem;\n        border-radius: 999px;\n        font-size: 0.7rem;\n        text-transform: uppercase;\n        letter-spacing: 0.06em;\n      }\n      .sync-badge[hidden] {\n        display: none;\n      }\n      .sync-actions {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n      }\n      .sync-actions button {\n        font: inherit;\n        padding: 0.35rem 0.8rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .sync-actions button:disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n      }\n      .sync-actions button:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .quick-filters {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n        margin-bottom: 0.75rem;\n        align-items: center;\n        font-size: 0.85rem;\n        color: var(--muted);\n      }\n      .quick-filter-label {\n        font-weight: 600;\n        text-transform: uppercase;\n        letter-spacing: 0.05em;\n      }\n      .quick-filter-chip {\n        font: inherit;\n        padding: 0.25rem 0.65rem;\n        border-radius: 999px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .quick-filter-chip.is-active {\n        background: var(--accent);\n        color: #ffffff;\n        border-color: var(--accent);\n      }\n      .quick-filter-chip:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .user-filter-banner {\n        display: flex;\n        align-items: center;\n        gap: 0.75rem;\n        margin-bottom: 0.75rem;\n        padding: 0.6rem 0.8rem;\n        border: 1px solid var(--border);\n        border-radius: 8px;\n        background: rgba(3, 102, 214, 0.08);\n        color: var(--accent);\n      }\n      .user-filter-clear {\n        font: inherit;\n        padding: 0.3rem 0.75rem;\n        border-radius: 6px;\n        border: 1px solid var(--accent);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .user-filter-clear:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .error-banner {\n        background: #fdecea;\n        color: #611a15;\n        border: 1px solid #f5c6cb;\n        padding: 0.75rem;\n        border-radius: 6px;\n        margin-bottom: 1rem;\n        display: none;\n        align-items: center;\n        gap: 0.75rem;\n      }\n      .error-banner[aria-hidden=\"false\"] {\n        display: flex;\n      }\n      .error-banner button {\n        font: inherit;\n        padding: 0.3rem 0.75rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .error-banner button:disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n      }\n      .shortcut-modal {\n        position: fixed;\n        inset: 0;\n        background: rgba(0, 0, 0, 0.45);\n        display: flex;\n        justify-content: center;\n        align-items: center;\n        padding: 1.5rem;\n        z-index: 2000;\n      }\n      .shortcut-modal[hidden] {\n        display: none;\n      }\n      .shortcut-modal__panel {\n        background: var(--bg);\n        color: var(--fg);\n        border-radius: 12px;\n        border: 1px solid var(--border);\n        max-width: 480px;\n        width: 100%;\n        box-shadow: 0 18px 45px rgba(15, 23, 42, 0.25);\n        padding: 1.25rem 1.5rem;\n        display: flex;\n        flex-direction: column;\n        gap: 1rem;\n      }\n      .shortcut-modal__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n      }\n      .shortcut-modal__close {\n        background: transparent;\n        border: none;\n        font-size: 1.5rem;\n        line-height: 1;\n        cursor: pointer;\n        color: var(--muted);\n      }\n      .shortcut-modal__close:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .shortcut-modal__list {\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n        gap: 0.75rem 1rem;\n        margin: 0;\n      }\n      .shortcut-modal__list dt {\n        font-weight: 600;\n      }\n      .shortcut-modal__list dd {\n        margin: 0;\n        color: var(--muted);\n      }\n      .preset-modal {\n        position: fixed;\n        inset: 0;\n        background: rgba(0, 0, 0, 0.45);\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        padding: 1rem;\n        z-index: 2100;\n      }\n      .preset-modal[hidden] {\n        display: none;\n      }\n      .preset-modal__panel {\n        background: var(--bg);\n        color: var(--fg);\n        border-radius: 16px;\n        padding: 1.25rem;\n        width: min(420px, 100%);\n        box-shadow: var(--card-shadow);\n        border: 1px solid var(--border);\n      }\n      .preset-modal__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        margin-bottom: 0.75rem;\n      }\n      .preset-modal__close {\n        border: none;\n        background: transparent;\n        font-size: 1.25rem;\n        line-height: 1;\n        cursor: pointer;\n        color: var(--muted);\n      }\n      .preset-modal__close:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .preset-form {\n        display: flex;\n        flex-direction: column;\n        gap: 0.85rem;\n      }\n      .preset-form__label {\n        display: flex;\n        flex-direction: column;\n        gap: 0.4rem;\n        font-size: 0.9rem;\n      }\n      .preset-form__label input {\n        font: inherit;\n        padding: 0.5rem 0.6rem;\n        border-radius: 8px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .preset-form__actions {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.5rem;\n        align-items: center;\n      }\n      .preset-form__secondary {\n        border: 1px solid var(--border);\n        background: transparent;\n        color: var(--fg);\n        padding: 0.35rem 0.8rem;\n        border-radius: 6px;\n        cursor: pointer;\n      }\n      .preset-form__spacer {\n        flex: 1;\n      }\n      .star-list {\n        list-style: none;\n        padding: 0;\n        margin: 0;\n        display: flex;\n        flex-direction: column;\n        gap: 1rem;\n      }\n      .star-list.is-grid {\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n        gap: 1rem;\n      }\n      .star-list.is-compact {\n        gap: 0.75rem;\n      }\n      .star-list.is-grid.is-compact {\n        gap: 0.75rem;\n      }\n      @media (max-width: 1023px) {\n        .star-list.is-grid {\n          display: flex;\n          flex-direction: column;\n        }\n      }\n      .star-card {\n        border: 1px solid var(--card-border);\n        border-radius: 12px;\n        padding: 1rem 1.15rem;\n        display: flex;\n        flex-direction: column;\n        gap: 0.75rem;\n        background: var(--card-bg);\n        box-shadow: var(--card-shadow);\n        transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;\n      }\n      .star-card--compact {\n        padding: 0.75rem 0.9rem;\n        gap: 0.55rem;\n      }\n      .star-card--new {\n        border-color: var(--card-new-border);\n        background: var(--card-new-bg);\n        box-shadow: 0 0 0 1px var(--card-new-border) inset, var(--card-shadow);\n      }\n      .star-card--pinned {\n        border-color: var(--card-pinned-border);\n        box-shadow: 0 0 0 1px var(--card-pinned-border) inset, var(--card-shadow);\n      }\n      .star-card--excluded {\n        opacity: 0.6;\n      }\n      .star-card__header {\n        display: flex;\n        justify-content: space-between;\n        align-items: flex-start;\n        gap: 0.5rem;\n        flex-wrap: wrap;\n      }\n      .star-card__signals {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.4rem;\n      }\n      .star-user-button {\n        border: 1px solid transparent;\n        background: none;\n        padding: 0.2rem 0.55rem;\n        font: inherit;\n        font-weight: 600;\n        border-radius: 999px;\n        color: var(--fg);\n        cursor: pointer;\n        text-align: left;\n        transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;\n      }\n      .star-user-button:hover {\n        border-color: rgba(3, 102, 214, 0.25);\n      }\n      .star-user-button:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .star-user-button.is-pinned {\n        border-color: rgba(3, 102, 214, 0.3);\n        background: rgba(3, 102, 214, 0.12);\n        color: var(--accent);\n      }\n      .star-user-button.is-excluded {\n        border-color: rgba(110, 118, 129, 0.4);\n        color: var(--muted);\n        text-decoration: line-through;\n        opacity: 0.7;\n      }\n      .new-badge {\n        font-size: 0.68rem;\n        font-weight: 700;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        background: var(--accent);\n        color: #ffffff;\n        border-radius: 999px;\n        padding: 0.15rem 0.45rem;\n      }\n      .activity-tag {\n        font-size: 0.7rem;\n        padding: 0.2rem 0.5rem;\n        border-radius: 999px;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        background: var(--badge-bg);\n        color: var(--badge-fg);\n        font-weight: 600;\n      }\n      .repo-link {\n        color: var(--accent);\n        font-size: 1.1rem;\n        font-weight: 650;\n        text-decoration: none;\n      }\n      .repo-link:hover {\n        text-decoration: underline;\n      }\n      .star-card__body {\n        display: flex;\n        flex-direction: column;\n        gap: 0.5rem;\n      }\n      .star-description {\n        margin: 0;\n        font-size: 0.95rem;\n        color: var(--fg);\n      }\n      .star-card__footer {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: flex-start;\n        justify-content: space-between;\n        gap: 0.75rem 1rem;\n        border-top: 1px solid var(--border);\n        padding-top: 0.75rem;\n      }\n      .star-meta {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.45rem;\n        font-size: 0.85rem;\n        color: var(--muted);\n        align-items: center;\n      }\n      .meta-pill {\n        background: var(--chip-muted-bg);\n        color: var(--chip-muted-fg);\n        padding: 0.22rem 0.55rem;\n        border-radius: 999px;\n        font-size: 0.78rem;\n        font-weight: 600;\n      }\n      .topics {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 0.35rem;\n      }\n      .topic-tag {\n        background: var(--chip-bg);\n        border: 1px solid var(--chip-border);\n        color: var(--chip-fg);\n        padding: 0.2rem 0.55rem;\n        border-radius: 999px;\n        font-size: 0.75rem;\n        font-weight: 500;\n      }\n      .star-card__timestamps {\n        display: grid;\n        grid-template-columns: auto 1fr;\n        gap: 0.25rem 0.6rem;\n        align-items: center;\n        font-size: 0.82rem;\n        color: var(--muted);\n        min-width: 180px;\n      }\n      .timestamp-label {\n        font-size: 0.7rem;\n        font-weight: 700;\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n        color: var(--timestamp-label);\n      }\n      .timestamp-value {\n        font-size: 0.82rem;\n        color: var(--muted);\n      }\n      @media (max-width: 640px) {\n        .star-card__timestamps {\n          grid-template-columns: 1fr;\n          gap: 0.15rem;\n        }\n      }\n      .pagination {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        gap: 0.75rem;\n        margin-bottom: 0.75rem;\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n      .pagination-info {\n        font-weight: 500;\n      }\n      .pagination-controls {\n        display: flex;\n        gap: 0.5rem;\n      }\n      .pagination-controls button {\n        font: inherit;\n        padding: 0.35rem 0.8rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--accent);\n        cursor: pointer;\n      }\n      .pagination-controls button:disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n      }\n      .pagination-controls button:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .page-size-control {\n        display: flex;\n        align-items: center;\n        gap: 0.35rem;\n        font-size: 0.85rem;\n        text-transform: uppercase;\n        letter-spacing: 0.05em;\n      }\n      .page-size-control select {\n        font: inherit;\n        padding: 0.3rem 0.5rem;\n        border-radius: 6px;\n        border: 1px solid var(--border);\n        background: var(--bg);\n        color: var(--fg);\n      }\n      .page-size-control select:focus-visible {\n        outline: 2px solid var(--accent);\n        outline-offset: 2px;\n      }\n      .empty-state {\n        text-align: center;\n        padding: 2rem;\n        border: 1px dashed var(--border);\n        border-radius: 10px;\n        color: var(--muted);\n      }\n      @media (prefers-color-scheme: dark) {\n        :root {\n          --bg: #0d1117;\n          --fg: #e6edf3;\n          --muted: #8b949e;\n          --border: #30363d;\n          --accent: #58a6ff;\n          --badge-bg: rgba(88, 166, 255, 0.18);\n          --badge-fg: #58a6ff;\n          --card-bg: #161b22;\n          --card-border: #30363d;\n          --card-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);\n          --card-new-bg: rgba(56, 139, 253, 0.22);\n          --card-new-border: #58a6ff;\n          --card-pinned-border: #f2c744;\n          --chip-bg: rgba(56, 139, 253, 0.2);\n          --chip-fg: #c9d1d9;\n          --chip-border: rgba(56, 139, 253, 0.45);\n          --chip-muted-bg: rgba(110, 118, 129, 0.2);\n          --chip-muted-fg: #8b949e;\n          --timestamp-label: #9aa4b6;\n        }\n        .summary-strip {\n          background: rgba(22, 27, 34, 0.9);\n        }\n        .summary-chip {\n          border-color: var(--chip-border);\n        }\n        .summary-chip--search {\n          background: var(--badge-bg);\n        }\n        .summary-chip--user {\n          background: rgba(56, 139, 253, 0.3);\n          border-color: rgba(56, 139, 253, 0.5);\n        }\n        .error-banner {\n          background: rgba(248, 81, 73, 0.2);\n          color: #ffa198;\n          border-color: rgba(248, 81, 73, 0.45);\n        }\n        .error-banner button {\n          border-color: rgba(88, 166, 255, 0.5);\n          color: var(--accent);\n        }\n        .quick-filter-chip {\n          border-color: rgba(88, 166, 255, 0.5);\n          background: rgba(56, 139, 253, 0.15);\n        }\n        .quick-filter-chip.is-active {\n          color: #0d1117;\n          background: var(--accent);\n        }\n        .user-filter-banner {\n          background: rgba(88, 166, 255, 0.22);\n          border-color: rgba(88, 166, 255, 0.45);\n        }\n        .topic-tag {\n          background: var(--chip-bg);\n          border-color: var(--chip-border);\n          color: var(--chip-fg);\n        }\n        .new-badge {\n          color: #0d1117;\n        }\n        .pagination-controls button {\n          border-color: rgba(88, 166, 255, 0.45);\n        }\n        .page-size-control select {\n          border-color: rgba(88, 166, 255, 0.45);\n        }\n        .shortcut-modal__panel {\n          box-shadow: 0 18px 45px rgba(10, 132, 255, 0.25);\n        }\n        .star-card {\n          box-shadow: var(--card-shadow);\n        }\n        .star-card--new {\n          box-shadow: 0 0 0 1px var(--card-new-border) inset, var(--card-shadow);\n        }\n        .star-card--pinned {\n          box-shadow: 0 0 0 1px var(--card-pinned-border) inset, var(--card-shadow);\n        }\n      }\n\n      @media (prefers-reduced-motion: reduce) {\n        .skip-link {\n          transition: none;\n        }\n        .star-card--new {\n          box-shadow: none;\n        }\n        .sync-bar,\n        .pagination,\n        .quick-filters {\n          scroll-behavior: auto;\n        }\n        .shortcut-modal {\n          transition: none;\n        }\n      }\n    </style>\n  </head>\n  <body>\n    <a class=\"skip-link\" href=\"#star-list\">Skip to results</a>\n\n    <header>\n      <h1>GitHub Followings Stars</h1>\n      <p class=\"timestamp\">Last updated: __LAST_UPDATED__</p>\n    </header>\n    <section id=\"summary-strip\" class=\"summary-strip\" aria-live=\"polite\">\n      <div id=\"summary-count\" class=\"summary-primary\">Preparing summary…</div>\n      <div class=\"summary-meta\">\n        <span id=\"summary-unseen\" class=\"summary-badge\" hidden></span>\n        <div id=\"summary-filters\" class=\"summary-filters\" hidden></div>\n      </div>\n    </section>\n    <div id=\"sync-bar\" class=\"sync-bar\">\n      <span id=\"sync-status\">Last synced: never</span>\n      <span id=\"sync-stale-badge\" class=\"sync-badge\" hidden>Stale</span>\n      <div class=\"sync-actions\">\n        <button id=\"mark-seen-button\" type=\"button\" disabled>Mark all seen</button>\n        <button id=\"refresh-button\" type=\"button\">Refresh now</button>\n      </div>\n    </div>\n    <section class=\"controls\" aria-label=\"Filtering controls\">\n      <label class=\"control\" for=\"search-input\">Search\n        <input id=\"search-input\" type=\"search\" placeholder=\"Search by repo, user, description…\" autocomplete=\"off\">\n      </label>\n      <label class=\"control\" for=\"language-filter\">Language\n        <select id=\"language-filter\">\n          <option value=\"all\">All languages</option>\n        </select>\n      </label>\n      <label class=\"control\" for=\"activity-filter\">Activity\n        <select id=\"activity-filter\">\n          <option value=\"all\">All activity levels</option>\n        </select>\n      </label>\n      <button id=\"sort-toggle\" type=\"button\" aria-pressed=\"false\">Sort: Newest</button>\n      <button id=\"density-toggle\" type=\"button\" aria-pressed=\"false\">Layout: Comfortable</button>\n    </section>\n\n    <section id=\"preset-bar\" class=\"preset-bar\" aria-label=\"Saved view presets\">\n      <div class=\"preset-bar__header\">\n        <h2 class=\"preset-bar__title\">Saved views</h2>\n        <button id=\"preset-save-button\" type=\"button\">Save current view</button>\n      </div>\n      <p class=\"preset-bar__hint\" id=\"preset-hint\">Capture your favorite filter combinations and recall them later or via keyboard shortcuts (Alt+1…Alt+5).</p>\n      <div id=\"preset-list\" class=\"preset-list\" role=\"list\" aria-describedby=\"preset-hint\"></div>\n    </section>\n\n    <div id=\"quick-filters\" class=\"quick-filters\" hidden></div>\n    <div id=\"user-filter-banner\" class=\"user-filter-banner\" hidden>\n      <span id=\"user-filter-label\"></span>\n      <button id=\"user-filter-clear\" type=\"button\" class=\"user-filter-clear\">Clear</button>\n    </div>\n    <div id=\"status-line\" class=\"status-line\">Loading…</div>\n    <div id=\"error-banner\" class=\"error-banner\" role=\"alert\" aria-hidden=\"true\">\n      <span id=\"error-message\"></span>\n      <button id=\"retry-button\" type=\"button\">Retry</button>\n    </div>\n    <div id=\"result-count\" class=\"status-line\" hidden></div>\n    <div id=\"pagination\" class=\"pagination\" hidden>\n      <div id=\"pagination-info\" class=\"pagination-info\"></div>\n      <div class=\"pagination-controls\">\n        <button id=\"page-prev\" type=\"button\" aria-label=\"Previous page\">Previous</button>\n        <button id=\"page-next\" type=\"button\" aria-label=\"Next page\">Next</button>\n      </div>\n      <label class=\"page-size-control\">Page size\n        <select id=\"page-size\">\n          <option value=\"10\">10</option>\n          <option value=\"25\">25</option>\n          <option value=\"50\">50</option>\n          <option value=\"100\">100</option>\n        </select>\n      </label>\n    </div>\n    <ul id=\"star-list\" class=\"star-list\" aria-live=\"polite\"></ul>\n    <div id=\"virtual-status\" class=\"status-line\" hidden></div>\n    <noscript>\n      <p class=\"empty-state\">JavaScript is required to explore stars interactively. Enable it and reload to search and filter.</p>\n    </noscript>\n    <div id=\"shortcut-modal\" class=\"shortcut-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"shortcut-modal-title\" hidden>\n      <div class=\"shortcut-modal__panel\" role=\"document\">\n        <div class=\"shortcut-modal__header\">\n          <h2 id=\"shortcut-modal-title\">Keyboard shortcuts</h2>\n          <button id=\"shortcut-close\" type=\"button\" class=\"shortcut-modal__close\" aria-label=\"Close shortcuts\">×</button>\n        </div>\n        <dl class=\"shortcut-modal__list\">\n          <div>\n            <dt>/</dt>\n            <dd>Focus search</dd>\n          </div>\n          <div>\n            <dt>?</dt>\n            <dd>Toggle this help</dd>\n          </div>\n          <div>\n            <dt>[ / ]</dt>\n            <dd>Previous / next page</dd>\n          </div>\n          <div>\n            <dt>L</dt>\n            <dd>Cycle language filter</dd>\n          </div>\n          <div>\n            <dt>M</dt>\n            <dd>Mark new items as seen</dd>\n          </div>\n          <div>\n            <dt>R</dt>\n            <dd>Refresh now</dd>\n          </div>\n          <div>\n            <dt>Alt + 1…5</dt>\n            <dd>Apply saved view 1–5</dd>\n          </div>\n        </dl>\n      </div>\n    </div>\n\n    <div id=\"preset-modal\" class=\"preset-modal\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"preset-modal-title\" hidden>\n      <div class=\"preset-modal__panel\" role=\"document\">\n        <div class=\"preset-modal__header\">\n          <h2 id=\"preset-modal-title\">Save current view</h2>\n          <button id=\"preset-modal-close\" type=\"button\" class=\"preset-modal__close\" aria-label=\"Close saved view dialog\">×</button>\n        </div>\n        <form id=\"preset-form\" class=\"preset-form\">\n          <label class=\"preset-form__label\" for=\"preset-name-input\">Preset name\n            <input id=\"preset-name-input\" name=\"name\" type=\"text\" maxlength=\"40\" required autocomplete=\"off\" placeholder=\"e.g., Rust, High activity\">\n          </label>\n          <div class=\"preset-form__actions\">\n            <button id=\"preset-delete-button\" type=\"button\" class=\"preset-form__secondary\" hidden>Delete preset</button>\n            <div class=\"preset-form__spacer\"></div>\n            <button id=\"preset-cancel-button\" type=\"button\" class=\"preset-form__secondary\">Cancel</button>\n            <button id=\"preset-confirm-button\" type=\"submit\">Save</button>\n          </div>\n        </form>\n      </div>\n    </div>\n    <script>\n(() => {\n  const REFRESH_INTERVAL_MS = 5 * 60 * 1000;\n  const MAX_BACKOFF_MS = 30 * 60 * 1000;\n  const ACK_STORAGE_KEY = \"starchaser:lastAckFetchedAt\";\n  const UI_STORAGE_KEY = \"starchaser:uiState\";\n  const DEFAULT_PAGE_SIZE = 25;\n  const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];\n  const QUICK_FILTER_LANG_LIMIT = 6;\n  const GRID_BREAKPOINT = 1024;\n  const VIRTUALIZE_LENGTH_THRESHOLD = 500;\n  const VIRTUAL_WINDOW = 40;\n  const VIRTUAL_OVERSCAN = 10;\n  const PRESET_STORAGE_KEY = \"starchaser:viewPresets\";\n  const PRESET_LAST_KEY = \"starchaser:lastPresetId\";\n  const MAX_PRESETS = 5;\n  const PAGE_CACHE_LIMIT = 3;\n\n  let fetchImpl = window.fetch.bind(window);\n  let refreshTimer = null;\n  let syncTicker = null;\n  let backoffMs = REFRESH_INTERVAL_MS;\n  let etag = null;\n  let isFetching = false;\n  let lastSyncedAt = null;\n  let newestFetchedMs = 0;\n  const pageCache = new Map();\n  const etagCache = new Map();\n  let cacheSignature = \"\";\n  let currentFilterSignature = \"\";\n  let optionsEtag = null;\n  let searchDebounce = null;\n  const virtualState = {\n    enabled: false,\n    items: [],\n    startIndex: 0,\n    endIndex: 0,\n    itemHeight: 320,\n    listOffset: 0,\n    listenersAttached: false,\n    statusTimer: null,\n  };\n\n  const dom = {\n    searchInput: document.getElementById(\"search-input\"),\n    languageFilter: document.getElementById(\"language-filter\"),\n    activityFilter: document.getElementById(\"activity-filter\"),\n    sortToggle: document.getElementById(\"sort-toggle\"),\n    statusLine: document.getElementById(\"status-line\"),\n    errorBanner: document.getElementById(\"error-banner\"),\n    errorMessage: document.getElementById(\"error-message\"),\n    retryButton: document.getElementById(\"retry-button\"),\n    resultCount: document.getElementById(\"result-count\"),\n    summaryStrip: document.getElementById(\"summary-strip\"),\n    summaryCount: document.getElementById(\"summary-count\"),\n    summaryUnseen: document.getElementById(\"summary-unseen\"),\n    summaryFilters: document.getElementById(\"summary-filters\"),\n    list: document.getElementById(\"star-list\"),\n    syncStatus: document.getElementById(\"sync-status\"),\n    staleBadge: document.getElementById(\"sync-stale-badge\"),\n    markSeenButton: document.getElementById(\"mark-seen-button\"),\n    refreshButton: document.getElementById(\"refresh-button\"),\n    quickFilters: document.getElementById(\"quick-filters\"),\n    userFilterBanner: document.getElementById(\"user-filter-banner\"),\n    userFilterLabel: document.getElementById(\"user-filter-label\"),\n    userFilterClear: document.getElementById(\"user-filter-clear\"),\n    pagination: document.getElementById(\"pagination\"),\n    paginationInfo: document.getElementById(\"pagination-info\"),\n    pagePrev: document.getElementById(\"page-prev\"),\n    pageNext: document.getElementById(\"page-next\"),\n    pageSize: document.getElementById(\"page-size\"),\n    densityToggle: document.getElementById(\"density-toggle\"),\n    shortcutModal: document.getElementById(\"shortcut-modal\"),\n    shortcutClose: document.getElementById(\"shortcut-close\"),\n    virtualStatus: document.getElementById(\"virtual-status\"),\n    presetBar: document.getElementById(\"preset-bar\"),\n    presetList: document.getElementById(\"preset-list\"),\n    presetSaveButton: document.getElementById(\"preset-save-button\"),\n    presetModal: document.getElementById(\"preset-modal\"),\n    presetModalClose: document.getElementById(\"preset-modal-close\"),\n    presetModalTitle: document.getElementById(\"preset-modal-title\"),\n    presetForm: document.getElementById(\"preset-form\"),\n    presetNameInput: document.getElementById(\"preset-name-input\"),\n    presetCancelButton: document.getElementById(\"preset-cancel-button\"),\n    presetDeleteButton: document.getElementById(\"preset-delete-button\"),\n    presetConfirmButton: document.getElementById(\"preset-confirm-button\"),\n  };\n\n  const state = {\n    items: [],\n    search: \"\",\n    language: \"all\",\n    activity: \"all\",\n    sort: \"newest\",\n    page: 1,\n    pageSize: DEFAULT_PAGE_SIZE,\n    userMode: \"none\", // none | pin | exclude\n    userValue: null,\n    hasNew: false,\n    quickFilters: {\n      languages: [],\n    },\n    density: \"comfortable\",\n    pageMeta: null,\n    filterOptions: {\n      languages: [],\n      activity: [],\n    },\n    presets: [],\n    activePresetId: null,\n  };\n\n  const tierLabels = {\n    high: \"High activity\",\n    medium: \"Medium activity\",\n    low: \"Low activity\",\n    unknown: \"Unclassified\",\n  };\n\n  let lastAcknowledgedMs = readAckTimestamp();\n  let isApplyingPreset = false;\n  const presetDialogState = {\n    mode: \"create\",\n    targetId: null,\n  };\n\n  function readAckTimestamp() {\n    try {\n      const raw = window.localStorage.getItem(ACK_STORAGE_KEY);\n      if (raw === null) {\n        return 0;\n      }\n      const parsed = Number.parseInt(raw, 10);\n      return Number.isFinite(parsed) ? parsed : 0;\n    } catch (err) {\n      console.warn(\"Failed to read acknowledged timestamp\", err);\n      return 0;\n    }\n  }\n\n  function persistAckTimestamp(value) {\n    try {\n      window.localStorage.setItem(ACK_STORAGE_KEY, String(value));\n    } catch (err) {\n      console.warn(\"Failed to persist acknowledged timestamp\", err);\n    }\n  }\n\n  function loadUiSnapshot() {\n    let snapshot = {};\n    try {\n      const raw = window.localStorage.getItem(UI_STORAGE_KEY);\n      if (raw) {\n        snapshot = JSON.parse(raw);\n      }\n    } catch (err) {\n      console.warn(\"Failed to parse stored UI state\", err);\n    }\n\n    const params = new URLSearchParams(window.location.search);\n    if (params.has(\"q\")) {\n      snapshot.search = params.get(\"q\");\n    }\n    if (params.has(\"lang\")) {\n      snapshot.language = params.get(\"lang\");\n    }\n    if (params.has(\"activity\")) {\n      snapshot.activity = params.get(\"activity\");\n    }\n    if (params.has(\"sort\")) {\n      snapshot.sort = params.get(\"sort\");\n    }\n    if (params.has(\"page\")) {\n      const parsed = Number.parseInt(params.get(\"page\"), 10);\n      if (Number.isFinite(parsed) && parsed > 0) {\n        snapshot.page = parsed;\n      }\n    }\n    if (params.has(\"pageSize\")) {\n      const parsed = Number.parseInt(params.get(\"pageSize\"), 10);\n      if (Number.isFinite(parsed) && parsed > 0) {\n        snapshot.pageSize = parsed;\n      }\n    }\n    if (params.has(\"user\")) {\n      snapshot.userValue = params.get(\"user\");\n    }\n    if (params.has(\"userMode\")) {\n      snapshot.userMode = params.get(\"userMode\");\n    }\n    if (params.has(\"density\")) {\n      snapshot.density = params.get(\"density\");\n    }\n    return snapshot;\n  }\n\n  function applySnapshot(snapshot) {\n    state.search = typeof snapshot.search === \"string\" ? snapshot.search : \"\";\n    state.language = snapshot.language || \"all\";\n    state.activity = snapshot.activity || \"all\";\n    state.sort = snapshot.sort === \"alpha\" ? \"alpha\" : \"newest\";\n\n    const sizeCandidate = snapshot.pageSize;\n    if (Number.isInteger(sizeCandidate) && sizeCandidate > 0) {\n      state.pageSize = sizeCandidate;\n    } else {\n      state.pageSize = DEFAULT_PAGE_SIZE;\n    }\n\n    const pageCandidate = snapshot.page;\n    if (Number.isInteger(pageCandidate) && pageCandidate > 0) {\n      state.page = pageCandidate;\n    } else {\n      state.page = 1;\n    }\n\n    if (snapshot.userMode === \"pin\" || snapshot.userMode === \"exclude\") {\n      state.userMode = snapshot.userMode;\n      state.userValue = snapshot.userValue || null;\n    } else {\n      state.userMode = \"none\";\n      state.userValue = null;\n    }\n\n    if (snapshot.density === \"compact\") {\n      state.density = \"compact\";\n    } else {\n      state.density = \"comfortable\";\n    }\n  }\n\n  function snapshotFromState() {\n    return {\n      search: state.search,\n      language: state.language,\n      activity: state.activity,\n      sort: state.sort,\n      page: state.page,\n      pageSize: state.pageSize,\n      userMode: state.userMode,\n      userValue: state.userValue,\n      density: state.density,\n    };\n  }\n\n  function persistUiState() {\n    const snapshot = { ...snapshotFromState(), page: 1 };\n    if (!isApplyingPreset && state.activePresetId) {\n      state.activePresetId = null;\n      persistLastPresetId(null);\n      renderPresets();\n    }\n    try {\n      window.localStorage.setItem(UI_STORAGE_KEY, JSON.stringify(snapshot));\n    } catch (err) {\n      console.warn(\"Failed to persist UI state\", err);\n    }\n  }\n\n  function syncUrl() {\n    const params = new URLSearchParams();\n    const trimmedSearch = state.search.trim();\n    if (trimmedSearch) {\n      params.set(\"q\", trimmedSearch);\n    }\n    if (state.language !== \"all\") {\n      params.set(\"lang\", state.language);\n    }\n    if (state.activity !== \"all\") {\n      params.set(\"activity\", state.activity);\n    }\n    if (state.sort !== \"newest\") {\n      params.set(\"sort\", state.sort);\n    }\n    if (state.page > 1) {\n      params.set(\"page\", String(state.page));\n    }\n    if (state.pageSize !== DEFAULT_PAGE_SIZE) {\n      params.set(\"pageSize\", String(state.pageSize));\n    }\n    if (state.userMode !== \"none\" && state.userValue) {\n      params.set(\"user\", state.userValue);\n      params.set(\"userMode\", state.userMode);\n    }\n    if (state.density !== \"comfortable\") {\n      params.set(\"density\", state.density);\n    }\n\n    const query = params.toString();\n    const newUrl = `${window.location.pathname}${query ? `?${query}` : \"\"}`;\n    window.history.replaceState(null, \"\", newUrl);\n  }\n\n  function computeFilterSignature(overrides = {}) {\n    const searchValue = typeof overrides.search === \"string\" ? overrides.search : state.search;\n    const languageValue = overrides.language ?? state.language;\n    const activityValue = overrides.activity ?? state.activity;\n    const sortValue = overrides.sort ?? state.sort;\n    const pageSizeValue = overrides.pageSize ?? state.pageSize;\n    const userModeValue = overrides.userMode ?? state.userMode;\n    const userValue = overrides.userValue ?? state.userValue;\n    return JSON.stringify({\n      search: searchValue.trim().toLowerCase(),\n      language: languageValue,\n      activity: activityValue,\n      sort: sortValue,\n      pageSize: pageSizeValue,\n      userMode: userModeValue,\n      userValue: userModeValue === \"pin\" || userModeValue === \"exclude\" ? userValue : \"\",\n    });\n  }\n\n  function resetPaginationCachesIfNeeded(providedSignature = null) {\n    const nextSignature = providedSignature ?? computeFilterSignature();\n    if (nextSignature !== currentFilterSignature) {\n      currentFilterSignature = nextSignature;\n      cacheSignature = nextSignature;\n      pageCache.clear();\n      etagCache.clear();\n    }\n  }\n\n  function getUserModeParam(mode) {\n    if (mode === \"pin\" || mode === \"exclude\") {\n      return mode;\n    }\n    return \"all\";\n  }\n\n  function buildApiQuery(overrides = {}) {\n    const params = new URLSearchParams();\n    const searchValue = typeof overrides.search === \"string\" ? overrides.search : state.search;\n    const trimmedSearch = searchValue.trim();\n    if (trimmedSearch) {\n      params.set(\"q\", trimmedSearch);\n    }\n    const languageValue = overrides.language ?? state.language;\n    if (languageValue && languageValue !== \"all\") {\n      params.set(\"language\", languageValue);\n    }\n    const activityValue = overrides.activity ?? state.activity;\n    if (activityValue && activityValue !== \"all\") {\n      params.set(\"activity\", activityValue);\n    }\n    const userModeValue = overrides.userMode ?? state.userMode;\n    const normalizedMode = getUserModeParam(userModeValue);\n    params.set(\"user_mode\", normalizedMode);\n    const userValue = overrides.userValue ?? state.userValue;\n    if (normalizedMode !== \"all\" && userValue) {\n      params.set(\"user\", userValue);\n    }\n    params.set(\"sort\", overrides.sort ?? state.sort);\n    params.set(\"page\", String(overrides.page ?? state.page));\n    params.set(\"page_size\", String(overrides.pageSize ?? state.pageSize));\n    return params;\n  }\n\n  function storePageCacheEntry(pageNumber, rawItems, meta) {\n    if (cacheSignature !== currentFilterSignature) {\n      cacheSignature = currentFilterSignature;\n      pageCache.clear();\n    }\n    pageCache.set(pageNumber, { rawItems, meta });\n    prunePageCache();\n  }\n\n  function getCachedPageEntry(pageNumber) {\n    if (cacheSignature !== currentFilterSignature) {\n      return null;\n    }\n    return pageCache.get(pageNumber) ?? null;\n  }\n\n  function prunePageCache() {\n    if (pageCache.size <= PAGE_CACHE_LIMIT) {\n      return;\n    }\n    const pages = Array.from(pageCache.keys());\n    pages.sort((a, b) => {\n      const distA = Math.abs(a - state.page);\n      const distB = Math.abs(b - state.page);\n      if (distA === distB) {\n        return a - b;\n      }\n      return distA - distB;\n    });\n    while (pageCache.size > PAGE_CACHE_LIMIT && pages.length > 0) {\n      const removed = pages.pop();\n      if (typeof removed === \"number\") {\n        pageCache.delete(removed);\n      }\n    }\n  }\n\n  function loadPresetsFromStorage() {\n    try {\n      const raw = window.localStorage.getItem(PRESET_STORAGE_KEY);\n      if (!raw) {\n        return [];\n      }\n      const parsed = JSON.parse(raw);\n      if (Array.isArray(parsed)) {\n        return parsed\n          .filter(\n            (entry) =>\n              entry &&\n              typeof entry.id === \"string\" &&\n              typeof entry.name === \"string\" &&\n              entry.snapshot &&\n              typeof entry.snapshot === \"object\"\n          )\n          .slice(0, MAX_PRESETS);\n      }\n    } catch (err) {\n      console.warn(\"Failed to load presets\", err);\n    }\n    return [];\n  }\n\n  function persistPresets(presets) {\n    try {\n      window.localStorage.setItem(PRESET_STORAGE_KEY, JSON.stringify(presets));\n    } catch (err) {\n      console.warn(\"Failed to persist presets\", err);\n    }\n  }\n\n  function loadLastPresetId() {\n    try {\n      return window.localStorage.getItem(PRESET_LAST_KEY);\n    } catch (err) {\n      console.warn(\"Failed to load last preset id\", err);\n      return null;\n    }\n  }\n\n  function persistLastPresetId(value) {\n    try {\n      if (value) {\n        window.localStorage.setItem(PRESET_LAST_KEY, value);\n      } else {\n        window.localStorage.removeItem(PRESET_LAST_KEY);\n      }\n    } catch (err) {\n      console.warn(\"Failed to persist last preset id\", err);\n    }\n  }\n\n  function renderPresets() {\n    const list = dom.presetList;\n    if (!list) {\n      return;\n    }\n    list.replaceChildren();\n    if (!Array.isArray(state.presets) || state.presets.length === 0) {\n      const empty = document.createElement(\"p\");\n      empty.className = \"preset-bar__empty\";\n      empty.textContent = \"No saved views yet.\";\n      list.appendChild(empty);\n      return;\n    }\n\n    state.presets.forEach((preset, index) => {\n      const wrapper = document.createElement(\"div\");\n      wrapper.className = \"preset-chip-wrapper\";\n      wrapper.setAttribute(\"role\", \"listitem\");\n      const button = document.createElement(\"button\");\n      button.type = \"button\";\n      button.className = \"preset-chip\";\n      button.setAttribute(\"aria-pressed\", preset.id === state.activePresetId ? \"true\" : \"false\");\n      if (preset.id === state.activePresetId) {\n        button.classList.add(\"is-active\");\n      }\n      button.textContent = \"\";\n      const label = document.createElement(\"span\");\n      label.className = \"preset-chip__label\";\n      label.textContent = preset.name;\n      button.appendChild(label);\n      button.addEventListener(\"click\", () => {\n        applyPresetById(preset.id);\n      });\n\n      if (index < 5) {\n        const keySpan = document.createElement(\"span\");\n        keySpan.className = \"preset-chip__keys\";\n        keySpan.textContent = `Alt+${index + 1}`;\n        button.appendChild(keySpan);\n      }\n\n      const actions = document.createElement(\"div\");\n      actions.className = \"preset-chip__actions\";\n\n      const editBtn = document.createElement(\"button\");\n      editBtn.type = \"button\";\n      editBtn.className = \"preset-chip__icon-btn\";\n      editBtn.textContent = \"Edit\";\n      editBtn.setAttribute(\"aria-label\", `Rename preset ${preset.name}`);\n      editBtn.addEventListener(\"click\", (event) => {\n        event.stopPropagation();\n        openPresetModal({ mode: \"edit\", preset });\n      });\n\n      const deleteBtn = document.createElement(\"button\");\n      deleteBtn.type = \"button\";\n      deleteBtn.className = \"preset-chip__icon-btn\";\n      deleteBtn.textContent = \"Delete\";\n      deleteBtn.setAttribute(\"aria-label\", `Delete preset ${preset.name}`);\n      deleteBtn.addEventListener(\"click\", (event) => {\n        event.stopPropagation();\n        deletePreset(preset.id);\n      });\n\n      actions.appendChild(editBtn);\n      actions.appendChild(deleteBtn);\n\n      wrapper.appendChild(button);\n      wrapper.appendChild(actions);\n      list.appendChild(wrapper);\n    });\n  }\n\n  function openPresetModal(options = {}) {\n    if (!dom.presetModal || !dom.presetNameInput || !dom.presetConfirmButton || !dom.presetDeleteButton) {\n      return;\n    }\n    const preset = options.preset;\n    presetDialogState.mode = options.mode === \"edit\" ? \"edit\" : \"create\";\n    presetDialogState.targetId = preset?.id ?? null;\n    const initialName = preset?.name ?? \"\";\n    dom.presetModal.hidden = false;\n    dom.presetModal.setAttribute(\"aria-hidden\", \"false\");\n    if (dom.presetModalTitle) {\n      dom.presetModalTitle.textContent = presetDialogState.mode === \"edit\" ? \"Update saved view\" : \"Save current view\";\n    }\n    dom.presetNameInput.value = initialName;\n    dom.presetDeleteButton.hidden = presetDialogState.mode !== \"edit\";\n    dom.presetConfirmButton.textContent = presetDialogState.mode === \"edit\" ? \"Update\" : \"Save\";\n    window.setTimeout(() => {\n      dom.presetNameInput.focus();\n      dom.presetNameInput.select();\n    }, 0);\n  }\n\n  function closePresetModal() {\n    if (!dom.presetModal || dom.presetModal.hidden) {\n      return;\n    }\n    dom.presetModal.hidden = true;\n    dom.presetModal.setAttribute(\"aria-hidden\", \"true\");\n    if (dom.presetForm) {\n      dom.presetForm.reset();\n    }\n    presetDialogState.mode = \"create\";\n    presetDialogState.targetId = null;\n  }\n\n  function generatePresetId() {\n    if (window.crypto && window.crypto.randomUUID) {\n      return window.crypto.randomUUID();\n    }\n    return `preset-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;\n  }\n\n  function upsertPreset(id, name) {\n    const snapshot = snapshotFromState();\n    const filtered = state.presets.filter((preset) => preset.id !== id);\n    const entry = { id, name, snapshot };\n    state.presets = [entry, ...filtered];\n    if (state.presets.length > MAX_PRESETS) {\n      state.presets.length = MAX_PRESETS;\n    }\n    persistPresets(state.presets);\n    state.activePresetId = id;\n    persistLastPresetId(id);\n    renderPresets();\n  }\n\n  function deletePreset(id) {\n    state.presets = state.presets.filter((preset) => preset.id !== id);\n    persistPresets(state.presets);\n    if (state.activePresetId === id) {\n      state.activePresetId = null;\n      persistLastPresetId(null);\n    }\n    renderPresets();\n  }\n\n  function handlePresetFormSubmit(event) {\n    event.preventDefault();\n    if (!dom.presetNameInput) {\n      return;\n    }\n    const name = dom.presetNameInput.value.trim();\n    if (!name) {\n      dom.presetNameInput.focus();\n      return;\n    }\n    if (presetDialogState.mode === \"edit\" && presetDialogState.targetId) {\n      upsertPreset(presetDialogState.targetId, name);\n    } else {\n      upsertPreset(generatePresetId(), name);\n    }\n    closePresetModal();\n  }\n\n  function handlePresetDeleteFromModal() {\n    if (!presetDialogState.targetId) {\n      return;\n    }\n    deletePreset(presetDialogState.targetId);\n    closePresetModal();\n  }\n\n  async function applyPresetById(id) {\n    const preset = state.presets.find((entry) => entry.id === id);\n    if (!preset) {\n      return;\n    }\n    isApplyingPreset = true;\n    try {\n      const presetSnapshot = { ...(preset.snapshot || {}), page: 1 };\n      applySnapshot(presetSnapshot);\n      syncControlsToState();\n      state.activePresetId = preset.id;\n      persistLastPresetId(preset.id);\n      await requestFilterUpdate();\n    } finally {\n      isApplyingPreset = false;\n    }\n    renderPresets();\n  }\n\n  function applyPresetByShortcut(index) {\n    if (index < 0) {\n      return;\n    }\n    const preset = state.presets[index];\n    if (preset) {\n      applyPresetById(preset.id);\n    }\n  }\n\n  function setupPresetUi() {\n    state.presets = loadPresetsFromStorage();\n    const lastPresetId = loadLastPresetId();\n    if (lastPresetId && state.presets.some((preset) => preset.id === lastPresetId)) {\n      state.activePresetId = lastPresetId;\n    } else {\n      state.activePresetId = null;\n      persistLastPresetId(null);\n    }\n    if (!dom.presetBar) {\n      return;\n    }\n    renderPresets();\n\n    if (dom.presetSaveButton) {\n      dom.presetSaveButton.addEventListener(\"click\", () => {\n        openPresetModal({ mode: \"create\" });\n      });\n    }\n    if (dom.presetCancelButton) {\n      dom.presetCancelButton.addEventListener(\"click\", () => {\n        closePresetModal();\n      });\n    }\n    if (dom.presetModalClose) {\n      dom.presetModalClose.addEventListener(\"click\", () => {\n        closePresetModal();\n      });\n    }\n    if (dom.presetModal) {\n      dom.presetModal.addEventListener(\"click\", (event) => {\n        if (event.target === dom.presetModal) {\n          closePresetModal();\n        }\n      });\n    }\n    if (dom.presetForm) {\n      dom.presetForm.addEventListener(\"submit\", handlePresetFormSubmit);\n    }\n    if (dom.presetDeleteButton) {\n      dom.presetDeleteButton.addEventListener(\"click\", handlePresetDeleteFromModal);\n    }\n  }\n\n  function setStatus(message) {\n    if (!dom.statusLine) {\n      return;\n    }\n    if (message) {\n      dom.statusLine.textContent = message;\n      dom.statusLine.hidden = false;\n    } else {\n      dom.statusLine.textContent = \"\";\n      dom.statusLine.hidden = true;\n    }\n  }\n\n  function showError(message) {\n    dom.errorMessage.textContent = message;\n    dom.errorBanner.setAttribute(\"aria-hidden\", \"false\");\n    dom.retryButton.disabled = false;\n  }\n\n  function clearError() {\n    dom.errorMessage.textContent = \"\";\n    dom.errorBanner.setAttribute(\"aria-hidden\", \"true\");\n    dom.retryButton.disabled = false;\n  }\n\n  function updateSyncStatus() {\n    if (!dom.syncStatus) {\n      return;\n    }\n    if (!lastSyncedAt) {\n      dom.syncStatus.textContent = \"Last synced: never\";\n      dom.staleBadge.hidden = true;\n      return;\n    }\n    const ageMs = Date.now() - lastSyncedAt.getTime();\n    const staleThreshold = REFRESH_INTERVAL_MS * 1.5;\n    const isStale = ageMs > staleThreshold;\n    dom.syncStatus.textContent = `Last synced: ${lastSyncedAt.toLocaleString()}`;\n    if (isStale) {\n      const ageMinutes = Math.round(ageMs / 60000);\n      dom.staleBadge.textContent = ageMinutes > 0 ? `Stale (≈${ageMinutes} min)` : \"Stale\";\n      dom.staleBadge.hidden = false;\n    } else {\n      dom.staleBadge.hidden = true;\n    }\n  }\n\n  function scheduleNextFetch(delayMs) {\n    if (refreshTimer !== null) {\n      window.clearTimeout(refreshTimer);\n    }\n    refreshTimer = window.setTimeout(() => loadPage(state.page, { manual: false }), delayMs);\n  }\n\n  function updateMarkSeenButton(newCount = null) {\n    if (newCount === null) {\n      newCount = state.items.reduce((acc, item) => acc + (item.isNew ? 1 : 0), 0);\n    }\n    state.hasNew = newCount > 0;\n    if (newCount > 0) {\n      dom.markSeenButton.disabled = false;\n      dom.markSeenButton.textContent = `Mark ${newCount} new as seen`;\n    } else {\n      dom.markSeenButton.disabled = true;\n      dom.markSeenButton.textContent = \"Mark all seen\";\n    }\n  }\n\n  function flagNewItems() {\n    let newCount = 0;\n    for (const item of state.items) {\n      const isNew = item.fetched_at_ms > lastAcknowledgedMs;\n      item.isNew = isNew;\n      if (isNew) {\n        newCount += 1;\n      }\n    }\n    updateMarkSeenButton(newCount);\n  }\n\n  function ensurePageSizeOption() {\n    const select = dom.pageSize;\n    const values = new Set([...PAGE_SIZE_OPTIONS, state.pageSize]);\n    select.replaceChildren();\n    Array.from(values)\n      .sort((a, b) => a - b)\n      .forEach((value) => {\n        const option = document.createElement(\"option\");\n        option.value = String(value);\n        option.textContent = String(value);\n        select.appendChild(option);\n      });\n    select.value = String(state.pageSize);\n  }\n\n  function populateFilters() {\n    const sortedLangs = getLanguageOptions();\n    dom.languageFilter.innerHTML = '<option value=\"all\">All languages</option>';\n    for (const lang of sortedLangs) {\n      const option = document.createElement(\"option\");\n      option.value = lang;\n      option.textContent = lang;\n      dom.languageFilter.appendChild(option);\n    }\n    if (!sortedLangs.includes(state.language)) {\n      state.language = \"all\";\n    }\n    dom.languageFilter.value = state.language;\n\n    const sortedTiers = getActivityOptions();\n    dom.activityFilter.innerHTML = '<option value=\"all\">All activity levels</option>';\n    for (const tier of sortedTiers) {\n      const option = document.createElement(\"option\");\n      option.value = tier;\n      option.textContent = tierLabels[tier] ?? tier;\n      dom.activityFilter.appendChild(option);\n    }\n    if (!sortedTiers.includes(state.activity)) {\n      state.activity = \"all\";\n    }\n    dom.activityFilter.value = state.activity;\n\n    ensurePageSizeOption();\n  }\n\n  function getLanguageOptions() {\n    if (state.filterOptions.languages.length > 0) {\n      return state.filterOptions.languages.map((entry) => entry.name).filter(Boolean);\n    }\n    const languages = new Set();\n    for (const item of state.items) {\n      if (item.repo_language) {\n        languages.add(item.repo_language);\n      }\n    }\n    return Array.from(languages).sort((a, b) => a.localeCompare(b));\n  }\n\n  function getActivityOptions() {\n    if (state.filterOptions.activity.length > 0) {\n      return state.filterOptions.activity.map((entry) => entry.tier).filter(Boolean);\n    }\n    const tiers = new Set();\n    for (const item of state.items) {\n      if (item.normalizedTier) {\n        tiers.add(item.normalizedTier);\n      }\n    }\n    return Array.from(tiers).sort();\n  }\n\n  function computeQuickFilters() {\n    let languages;\n    if (state.filterOptions.languages.length > 0) {\n      languages = state.filterOptions.languages\n        .map((entry) => ({ lang: entry.name, count: entry.count }))\n        .filter((entry) => entry.lang)\n        .sort((a, b) => b.count - a.count)\n        .slice(0, QUICK_FILTER_LANG_LIMIT);\n    } else {\n      const languageCounts = new Map();\n      for (const item of state.items) {\n        if (item.repo_language) {\n          languageCounts.set(item.repo_language, (languageCounts.get(item.repo_language) || 0) + 1);\n        }\n      }\n      languages = Array.from(languageCounts.entries())\n        .sort((a, b) => b[1] - a[1])\n        .slice(0, QUICK_FILTER_LANG_LIMIT)\n        .map(([lang, count]) => ({ lang, count }));\n    }\n    state.quickFilters.languages = languages;\n  }\n\n  function renderQuickFilters() {\n    const container = dom.quickFilters;\n    container.replaceChildren();\n\n    const fragment = document.createDocumentFragment();\n\n    const addChip = (label, handler, options = {}) => {\n      const button = document.createElement(\"button\");\n      button.type = \"button\";\n      button.className = \"quick-filter-chip\";\n      button.textContent = label;\n      if (options.active) {\n        button.classList.add(\"is-active\");\n      }\n      if (options.title) {\n        button.title = options.title;\n      }\n      button.addEventListener(\"click\", handler);\n      fragment.appendChild(button);\n    };\n\n    if (state.quickFilters.languages.length > 0) {\n      const groupLabel = document.createElement(\"span\");\n      groupLabel.className = \"quick-filter-label\";\n      groupLabel.textContent = \"Top languages:\";\n      fragment.appendChild(groupLabel);\n      for (const entry of state.quickFilters.languages) {\n        addChip(entry.lang, () => {\n          state.language = entry.lang;\n          dom.languageFilter.value = state.language;\n          requestFilterUpdate();\n        }, {\n          active: state.language === entry.lang,\n          title: `Filter by ${entry.lang}`,\n        });\n      }\n      addChip(\"All\", () => {\n        state.language = \"all\";\n        dom.languageFilter.value = state.language;\n        requestFilterUpdate();\n      }, { active: state.language === \"all\", title: \"Show all languages\" });\n    }\n\n    const activityChips = [\"high\", \"medium\", \"low\"];\n    const availableActivity = new Set(getActivityOptions());\n    if (activityChips.some((tier) => availableActivity.has(tier))) {\n      const groupLabel = document.createElement(\"span\");\n      groupLabel.className = \"quick-filter-label\";\n      groupLabel.textContent = \"Activity:\";\n      fragment.appendChild(groupLabel);\n      for (const tier of activityChips) {\n        addChip(tierLabels[tier] ?? tier, () => {\n          state.activity = tier;\n          dom.activityFilter.value = state.activity;\n          requestFilterUpdate();\n        }, {\n          active: state.activity === tier,\n          title: `Filter ${tierLabels[tier] ?? tier}`,\n        });\n      }\n      addChip(\"All\", () => {\n        state.activity = \"all\";\n        dom.activityFilter.value = state.activity;\n        requestFilterUpdate();\n      }, { active: state.activity === \"all\", title: \"Show all activity\" });\n    }\n\n    if (fragment.childElementCount > 0) {\n      container.appendChild(fragment);\n      container.hidden = false;\n    } else {\n      container.hidden = true;\n    }\n  }\n\n  function renderUserBanner() {\n    if (state.userMode === \"none\" || !state.userValue) {\n      dom.userFilterBanner.hidden = true;\n      return;\n    }\n    const modeText = state.userMode === \"pin\"\n      ? `Showing only stars from ${state.userValue}`\n      : `Hiding stars from ${state.userValue}`;\n    dom.userFilterLabel.textContent = modeText;\n    dom.userFilterBanner.hidden = false;\n  }\n\n  function renderPagination() {\n    const meta = state.pageMeta;\n    if (!meta) {\n      dom.pagination.hidden = true;\n      return;\n    }\n    const totalCount = typeof meta.total === \"number\" ? meta.total : state.items.length;\n    const totalPages = Math.max(1, Math.ceil(totalCount / (meta.page_size || state.pageSize || 1)));\n    if (totalPages <= 1) {\n      dom.pagination.hidden = true;\n      return;\n    }\n    const currentPage = meta.page || state.page;\n    const pageSize = meta.page_size || state.pageSize;\n    const startIndex = (currentPage - 1) * pageSize;\n    const endIndex = Math.min(totalCount, startIndex + state.items.length);\n    const startDisplay = Math.max(1, startIndex + 1);\n    const endDisplay = Math.max(startDisplay, endIndex);\n    dom.paginationInfo.textContent = `Showing ${startDisplay}–${endDisplay} of ${totalCount} (page ${currentPage} of ${totalPages})`;\n    dom.pagePrev.disabled = !meta.has_prev || isFetching;\n    dom.pageNext.disabled = !meta.has_next || isFetching;\n    dom.pagination.hidden = false;\n  }\n\n  function renderSummary(pageItemsLength) {\n    const summaryStrip = dom.summaryStrip;\n    const summaryCount = dom.summaryCount;\n    const summaryUnseen = dom.summaryUnseen;\n    const summaryFilters = dom.summaryFilters;\n\n    if (!summaryStrip || !summaryCount || !summaryUnseen || !summaryFilters) {\n      if (dom.resultCount) {\n        dom.resultCount.hidden = true;\n      }\n      return;\n    }\n\n    const meta = state.pageMeta;\n    const totalItems = meta && typeof meta.total === \"number\" ? meta.total : state.items.length;\n    const hasItems = totalItems > 0;\n    let summaryText = \"\";\n    if (!hasItems) {\n      summaryText = \"No starred repositories match the current view.\";\n    } else {\n      const pageSize = meta?.page_size ?? state.pageSize;\n      const currentPage = meta?.page ?? state.page;\n      const startIndex = (currentPage - 1) * pageSize + 1;\n      const endIndex = Math.min(totalItems, startIndex + pageItemsLength - 1);\n      summaryText = `${startIndex}–${endIndex} of ${totalItems} starred repositories`;\n    }\n    summaryCount.textContent = summaryText;\n    summaryStrip.classList.toggle(\"summary-strip--empty\", !hasItems);\n\n    const totalNew = state.items.reduce((acc, item) => acc + (item.isNew ? 1 : 0), 0);\n    if (totalNew > 0) {\n      summaryUnseen.hidden = false;\n      summaryUnseen.textContent = `${totalNew} new`;\n    } else {\n      summaryUnseen.hidden = true;\n      summaryUnseen.textContent = \"\";\n    }\n\n    const filters = [];\n    const trimmedSearch = state.search.trim();\n    if (trimmedSearch) {\n      filters.push({ key: \"search\", label: `Search: \"${trimmedSearch}\"` });\n    }\n    if (state.language !== \"all\") {\n      filters.push({ key: \"language\", label: `Language: ${state.language}` });\n    }\n    if (state.activity !== \"all\") {\n      filters.push({\n        key: \"activity\",\n        label: `Activity: ${tierLabels[state.activity] ?? state.activity}`,\n      });\n    }\n    if (state.userMode === \"pin\" && state.userValue) {\n      filters.push({ key: \"user\", label: `Pinned: ${state.userValue}` });\n    } else if (state.userMode === \"exclude\" && state.userValue) {\n      filters.push({ key: \"user\", label: `Excluded: ${state.userValue}` });\n    }\n\n    summaryFilters.replaceChildren();\n    if (filters.length > 0) {\n      const frag = document.createDocumentFragment();\n      for (const filter of filters) {\n        const chip = document.createElement(\"span\");\n        chip.className = `summary-chip summary-chip--${filter.key}`;\n        chip.textContent = filter.label;\n        frag.appendChild(chip);\n      }\n      summaryFilters.appendChild(frag);\n      summaryFilters.hidden = false;\n    } else {\n      summaryFilters.hidden = true;\n    }\n\n    if (dom.resultCount) {\n      dom.resultCount.textContent = summaryText;\n      dom.resultCount.hidden = true;\n    }\n  }\n\n  function renderList(items) {\n    if (!dom.list) {\n      return;\n    }\n    if (shouldVirtualizeList(items)) {\n      enableVirtualScroll(items);\n    } else {\n      disableVirtualScroll();\n      renderFullList(items);\n    }\n  }\n\n  function renderFullList(items) {\n    const list = dom.list;\n    list.replaceChildren();\n    const isCompact = state.density === \"compact\";\n    if (items.length === 0) {\n      list.appendChild(createEmptyStateItem());\n      return;\n    }\n    for (const item of items) {\n      list.appendChild(createCardElement(item, isCompact));\n    }\n  }\n\n  function createCardElement(item, isCompact) {\n    const card = document.createElement(\"li\");\n    card.className = \"star-card\";\n    if (item.isNew) {\n      card.classList.add(\"star-card--new\");\n    }\n    if (isCompact) {\n      card.classList.add(\"star-card--compact\");\n    }\n    const isPinnedUser = state.userMode === \"pin\" && state.userValue === item.login;\n    if (isPinnedUser) {\n      card.classList.add(\"star-card--pinned\");\n    }\n\n    const header = document.createElement(\"div\");\n    header.className = \"star-card__header\";\n\n    const userButton = document.createElement(\"button\");\n    userButton.type = \"button\";\n    userButton.className = \"star-user-button\";\n    userButton.textContent = item.login;\n    userButton.dataset.login = item.login;\n    const match = state.userValue === item.login;\n    if (match && state.userMode === \"pin\") {\n      userButton.classList.add(\"is-pinned\");\n      userButton.setAttribute(\"aria-pressed\", \"true\");\n    } else if (match && state.userMode === \"exclude\") {\n      userButton.classList.add(\"is-excluded\");\n      userButton.setAttribute(\"aria-pressed\", \"true\");\n      card.classList.add(\"star-card--excluded\");\n    } else {\n      userButton.setAttribute(\"aria-pressed\", \"false\");\n    }\n    userButton.title = \"Click to cycle user filter\";\n    userButton.addEventListener(\"click\", () => handleUserToggle(item.login));\n    header.appendChild(userButton);\n\n    const headerSignals = document.createElement(\"div\");\n    headerSignals.className = \"star-card__signals\";\n\n    if (item.isNew) {\n      const newBadge = document.createElement(\"span\");\n      newBadge.className = \"new-badge\";\n      newBadge.textContent = \"New\";\n      headerSignals.appendChild(newBadge);\n    }\n\n    if (item.normalizedTier) {\n      const tierSpan = document.createElement(\"span\");\n      tierSpan.className = `activity-tag activity-${item.normalizedTier}`;\n      tierSpan.textContent = tierLabels[item.normalizedTier] ?? item.normalizedTier;\n      headerSignals.appendChild(tierSpan);\n    }\n\n    if (headerSignals.childElementCount > 0) {\n      header.appendChild(headerSignals);\n    }\n\n    card.appendChild(header);\n\n    const body = document.createElement(\"div\");\n    body.className = \"star-card__body\";\n\n    const link = document.createElement(\"a\");\n    link.className = \"repo-link\";\n    link.href = item.repo_html_url;\n    link.textContent = item.repo_full_name;\n    link.target = \"_blank\";\n    link.rel = \"noopener noreferrer\";\n    body.appendChild(link);\n\n    if (item.repo_description) {\n      const desc = document.createElement(\"p\");\n      desc.className = \"star-description\";\n      desc.textContent = item.repo_description;\n      body.appendChild(desc);\n    }\n\n    card.appendChild(body);\n\n    const footer = document.createElement(\"div\");\n    footer.className = \"star-card__footer\";\n\n    const meta = document.createElement(\"div\");\n    meta.className = \"star-meta\";\n\n    if (item.repo_language) {\n      const lang = document.createElement(\"span\");\n      lang.className = \"meta-pill\";\n      lang.textContent = item.repo_language;\n      meta.appendChild(lang);\n    }\n\n    if (item.repo_topics.length > 0) {\n      const topicsWrap = document.createElement(\"div\");\n      topicsWrap.className = \"topics\";\n      const limited = item.repo_topics.slice(0, 10);\n      for (const topic of limited) {\n        const span = document.createElement(\"span\");\n        span.className = \"topic-tag\";\n        span.textContent = topic;\n        topicsWrap.appendChild(span);\n      }\n      meta.appendChild(topicsWrap);\n    }\n\n    footer.appendChild(meta);\n\n    const times = document.createElement(\"div\");\n    times.className = \"star-card__timestamps\";\n\n    const starredLabel = document.createElement(\"span\");\n    starredLabel.className = \"timestamp-label\";\n    starredLabel.textContent = \"Starred\";\n    times.appendChild(starredLabel);\n\n    const starredTime = document.createElement(\"time\");\n    starredTime.dateTime = item.starred_at;\n    starredTime.textContent = new Date(item.starred_at).toLocaleString();\n    starredTime.className = \"timestamp-value\";\n    times.appendChild(starredTime);\n\n    const fetchedLabel = document.createElement(\"span\");\n    fetchedLabel.className = \"timestamp-label\";\n    fetchedLabel.textContent = \"Fetched\";\n    times.appendChild(fetchedLabel);\n\n    const fetchedTime = document.createElement(\"time\");\n    fetchedTime.dateTime = item.fetched_at;\n    fetchedTime.textContent = new Date(item.fetched_at).toLocaleString();\n    fetchedTime.className = \"timestamp-value\";\n    times.appendChild(fetchedTime);\n\n    footer.appendChild(times);\n\n    card.appendChild(footer);\n    return card;\n  }\n\n  function createEmptyStateItem() {\n    const empty = document.createElement(\"li\");\n    empty.className = \"empty-state\";\n    empty.textContent = \"No matches found for the current filters.\";\n    return empty;\n  }\n\n  function shouldVirtualizeList(items) {\n    const totalAvailable = state.pageMeta && typeof state.pageMeta.total === \"number\" ? state.pageMeta.total : items.length;\n    return (totalAvailable > VIRTUALIZE_LENGTH_THRESHOLD || state.pageSize > 50) && items.length > VIRTUAL_WINDOW;\n  }\n\n  function enableVirtualScroll(items) {\n    virtualState.enabled = true;\n    virtualState.items = items;\n    virtualState.startIndex = 0;\n    virtualState.endIndex = Math.min(items.length, VIRTUAL_WINDOW);\n    updateVirtualOffsets();\n    attachVirtualListeners();\n    drawVirtualSlice(true);\n  }\n\n  function disableVirtualScroll() {\n    if (!virtualState.enabled) {\n      return;\n    }\n    virtualState.enabled = false;\n    virtualState.items = [];\n    virtualState.startIndex = 0;\n    virtualState.endIndex = 0;\n    detachVirtualListeners();\n    if (dom.virtualStatus) {\n      dom.virtualStatus.hidden = true;\n    }\n  }\n\n  function attachVirtualListeners() {\n    if (virtualState.listenersAttached) {\n      return;\n    }\n    window.addEventListener(\"scroll\", handleVirtualScroll, { passive: true });\n    window.addEventListener(\"resize\", handleVirtualResize);\n    virtualState.listenersAttached = true;\n  }\n\n  function detachVirtualListeners() {\n    if (!virtualState.listenersAttached) {\n      return;\n    }\n    window.removeEventListener(\"scroll\", handleVirtualScroll);\n    window.removeEventListener(\"resize\", handleVirtualResize);\n    virtualState.listenersAttached = false;\n  }\n\n  function handleVirtualScroll() {\n    if (!virtualState.enabled) {\n      return;\n    }\n    updateVirtualWindow(false);\n  }\n\n  function handleVirtualResize() {\n    if (!virtualState.enabled) {\n      return;\n    }\n    updateVirtualOffsets();\n    updateVirtualWindow(true);\n  }\n\n  function updateVirtualOffsets() {\n    const listRect = dom.list.getBoundingClientRect();\n    virtualState.listOffset = listRect.top + window.scrollY;\n  }\n\n  function updateVirtualWindow(force) {\n    if (!virtualState.enabled || virtualState.items.length === 0) {\n      return;\n    }\n    const scrollOffset = Math.max(0, window.scrollY - virtualState.listOffset);\n    const estimatedIndex = Math.floor(scrollOffset / Math.max(virtualState.itemHeight, 1));\n    const nextStart = Math.max(0, estimatedIndex - VIRTUAL_OVERSCAN);\n    const nextEnd = Math.min(virtualState.items.length, nextStart + VIRTUAL_WINDOW);\n    if (!force && nextStart === virtualState.startIndex && nextEnd === virtualState.endIndex) {\n      return;\n    }\n    virtualState.startIndex = nextStart;\n    virtualState.endIndex = nextEnd;\n    drawVirtualSlice();\n  }\n\n  function drawVirtualSlice(forceEmptyCheck) {\n    const list = dom.list;\n    list.replaceChildren();\n    if (virtualState.items.length === 0) {\n      list.appendChild(createEmptyStateItem());\n      return;\n    }\n    showVirtualStatus(\"Loading more stars…\");\n    const isCompact = state.density === \"compact\";\n    const beforeSpacer = document.createElement(\"li\");\n    beforeSpacer.className = \"virtual-spacer\";\n    beforeSpacer.style.height = `${virtualState.startIndex * virtualState.itemHeight}px`;\n    beforeSpacer.setAttribute(\"aria-hidden\", \"true\");\n    list.appendChild(beforeSpacer);\n\n    const slice = virtualState.items.slice(virtualState.startIndex, virtualState.endIndex);\n    if (slice.length === 0 && forceEmptyCheck) {\n      list.appendChild(createEmptyStateItem());\n    }\n    for (const item of slice) {\n      list.appendChild(createCardElement(item, isCompact));\n    }\n\n    const afterSpacer = document.createElement(\"li\");\n    afterSpacer.className = \"virtual-spacer\";\n    const remaining = Math.max(0, virtualState.items.length - virtualState.endIndex);\n    afterSpacer.style.height = `${remaining * virtualState.itemHeight}px`;\n    afterSpacer.setAttribute(\"aria-hidden\", \"true\");\n    list.appendChild(afterSpacer);\n\n    measureVirtualItemHeight();\n  }\n\n  function measureVirtualItemHeight() {\n    const cards = dom.list.querySelectorAll(\".star-card\");\n    if (!cards || cards.length === 0) {\n      return;\n    }\n    const totalHeight = Array.from(cards).reduce((acc, card) => acc + card.getBoundingClientRect().height, 0);\n    const average = totalHeight / cards.length;\n    if (Number.isFinite(average) && average > 0 && Math.abs(average - virtualState.itemHeight) > 5) {\n      virtualState.itemHeight = average;\n      updateVirtualWindow(true);\n    }\n  }\n\n  function showVirtualStatus(message, duration = 500) {\n    if (!dom.virtualStatus) {\n      return;\n    }\n    dom.virtualStatus.textContent = message;\n    dom.virtualStatus.hidden = false;\n    if (virtualState.statusTimer) {\n      window.clearTimeout(virtualState.statusTimer);\n    }\n    virtualState.statusTimer = window.setTimeout(() => {\n      dom.virtualStatus.hidden = true;\n    }, duration);\n  }\n\n  function applyFilters() {\n    let visible = state.items.slice();\n    if (state.sort === \"alpha\") {\n      visible.sort((a, b) => a.repo_full_name.localeCompare(b.repo_full_name));\n    } else {\n      visible.sort((a, b) => b.fetched_at_ms - a.fetched_at_ms);\n    }\n\n    renderList(visible);\n    updateListLayout();\n    renderPagination();\n    renderSummary(visible.length);\n    renderUserBanner();\n    renderQuickFilters();\n    updateMarkSeenButton();\n  }\n\n  function handleUserToggle(login) {\n    if (state.userValue !== login) {\n      state.userMode = \"pin\";\n      state.userValue = login;\n    } else if (state.userMode === \"pin\") {\n      state.userMode = \"exclude\";\n    } else {\n      state.userMode = \"none\";\n      state.userValue = null;\n    }\n    requestFilterUpdate();\n  }\n\n  function normalizeItems(rawItems) {\n    let newest = 0;\n    const normalized = rawItems.map((item) => {\n      const normalizedTier = item.user_activity_tier ? item.user_activity_tier.toLowerCase() : null;\n      const topics = Array.isArray(item.repo_topics) ? item.repo_topics : [];\n      const starredMs = Date.parse(item.starred_at) || 0;\n      const fetchedMs = Date.parse(item.fetched_at) || 0;\n      if (fetchedMs > newest) {\n        newest = fetchedMs;\n      }\n      return {\n        ...item,\n        normalizedTier,\n        repo_topics: topics,\n        starred_at_ms: starredMs,\n        fetched_at_ms: fetchedMs,\n        isNew: false,\n      };\n    });\n    return { items: normalized, newestFetched: newest };\n  }\n\n  function applyPageData(normalizedItems, meta, newestMs, options = {}) {\n    newestFetchedMs = newestMs || 0;\n    state.items = normalizedItems;\n    state.pageMeta = meta || null;\n    const nextPage = meta?.page ?? state.page;\n    const pageChanged = state.page !== nextPage;\n    state.page = nextPage;\n    flagNewItems();\n    populateFilters();\n    computeQuickFilters();\n    applyFilters();\n    const shouldPersist = options.shouldPersist ?? options.manual ?? pageChanged;\n    if (shouldPersist) {\n      persistUiState();\n      syncUrl();\n    }\n  }\n\n  async function loadPage(page, options = {}) {\n    const targetPage = Math.max(1, page || 1);\n    const manual = Boolean(options.manual);\n    const initial = Boolean(options.initial);\n    const bypassCache = Boolean(options.bypassCache);\n    resetPaginationCachesIfNeeded();\n    if (!bypassCache) {\n      const cached = getCachedPageEntry(targetPage);\n      if (cached) {\n        const normalized = normalizeItems(cached.rawItems);\n        applyPageData(normalized.items, cached.meta, normalized.newestFetched, { manual });\n        return;\n      }\n    }\n    await fetchStars({ manual, initial, pageOverride: targetPage });\n  }\n\n  async function fetchStars(options = {}) {\n    if (isFetching) {\n      return;\n    }\n    const manual = Boolean(options.manual);\n    const initial = Boolean(options.initial);\n    const targetPage = Math.max(1, options.pageOverride ?? state.page ?? 1);\n    const ignoreEtag = Boolean(options.ignoreEtag);\n    isFetching = true;\n    dom.refreshButton.disabled = true;\n    dom.retryButton.disabled = true;\n\n    if (initial) {\n      setStatus(\"Loading…\");\n    } else if (manual) {\n      setStatus(\"Refreshing…\");\n    } else {\n      setStatus(\"Refreshing…\");\n    }\n\n    clearError();\n\n    try {\n      const headers = {};\n      const etagKey = `${currentFilterSignature}|${targetPage}`;\n      if (!ignoreEtag && etagCache.has(etagKey)) {\n        headers[\"If-None-Match\"] = etagCache.get(etagKey);\n      }\n      const queryString = buildApiQuery({ page: targetPage }).toString();\n      const response = await fetchImpl(`/api/stars${queryString ? `?${queryString}` : \"\"}`, { headers });\n      const responseEtag = response.headers.get(\"ETag\");\n      if (responseEtag) {\n        etagCache.set(etagKey, responseEtag);\n      }\n      if (response.status === 304) {\n        const cached = getCachedPageEntry(targetPage);\n        if (cached) {\n          const normalized = normalizeItems(cached.rawItems);\n          applyPageData(normalized.items, cached.meta, normalized.newestFetched, { manual });\n          lastSyncedAt = new Date();\n          backoffMs = REFRESH_INTERVAL_MS;\n          setStatus(\"\");\n          updateSyncStatus();\n          scheduleNextFetch(REFRESH_INTERVAL_MS);\n          return;\n        }\n        etagCache.delete(etagKey);\n        await fetchStars({ manual, initial, pageOverride: targetPage, ignoreEtag: true });\n        return;\n      }\n      if (!response.ok) {\n        throw new Error(`Request failed with status ${response.status}`);\n      }\n      const payload = await response.json();\n      const rawItems = Array.isArray(payload)\n        ? payload\n        : Array.isArray(payload?.items)\n          ? payload.items\n          : [];\n      const meta = payload?.meta ?? null;\n      storePageCacheEntry(meta?.page ?? targetPage, rawItems, meta);\n      const normalized = normalizeItems(rawItems);\n      applyPageData(normalized.items, meta, normalized.newestFetched, { manual });\n      lastSyncedAt = new Date();\n      backoffMs = REFRESH_INTERVAL_MS;\n      setStatus(\"\");\n      updateSyncStatus();\n      scheduleNextFetch(REFRESH_INTERVAL_MS);\n    } catch (err) {\n      console.error(err);\n      showError(\"Failed to refresh starred repositories. Refresh when ready.\");\n      backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);\n      const seconds = Math.max(Math.round(backoffMs / 1000), 1);\n      setStatus(`Retrying in ${seconds}s…`);\n      scheduleNextFetch(backoffMs);\n    } finally {\n      isFetching = false;\n      dom.refreshButton.disabled = false;\n      dom.retryButton.disabled = false;\n      updateSyncStatus();\n      renderPagination();\n    }\n  }\n\n  async function fetchFilterOptions() {\n    try {\n      const headers = {};\n      if (optionsEtag) {\n        headers[\"If-None-Match\"] = optionsEtag;\n      }\n      const response = await fetchImpl(\"/api/options\", { headers });\n      if (response.status === 304) {\n        return;\n      }\n      if (!response.ok) {\n        throw new Error(`Options request failed with status ${response.status}`);\n      }\n      const payload = await response.json();\n      state.filterOptions.languages = Array.isArray(payload?.languages) ? payload.languages : [];\n      state.filterOptions.activity = Array.isArray(payload?.activity_tiers) ? payload.activity_tiers : [];\n      optionsEtag = response.headers.get(\"ETag\") || optionsEtag;\n      populateFilters();\n      computeQuickFilters();\n    } catch (err) {\n      console.warn(\"Failed to fetch filter options\", err);\n    }\n  }\n\n  function initControls() {\n    dom.searchInput.value = state.search;\n    updateSortToggle();\n    ensurePageSizeOption();\n    updateDensityToggle();\n\n    dom.searchInput.addEventListener(\"input\", (event) => {\n      state.search = event.target.value;\n      if (searchDebounce) {\n        window.clearTimeout(searchDebounce);\n      }\n      searchDebounce = window.setTimeout(() => {\n        requestFilterUpdate();\n      }, 250);\n    });\n\n    dom.languageFilter.addEventListener(\"change\", (event) => {\n      state.language = event.target.value || \"all\";\n      requestFilterUpdate();\n    });\n\n    dom.activityFilter.addEventListener(\"change\", (event) => {\n      state.activity = event.target.value || \"all\";\n      requestFilterUpdate();\n    });\n\n    dom.sortToggle.addEventListener(\"click\", () => {\n      if (state.sort === \"newest\") {\n        state.sort = \"alpha\";\n      } else {\n        state.sort = \"newest\";\n      }\n      updateSortToggle();\n      requestFilterUpdate();\n    });\n\n    dom.densityToggle.addEventListener(\"click\", () => {\n      state.density = state.density === \"comfortable\" ? \"compact\" : \"comfortable\";\n      updateDensityToggle();\n      persistUiState();\n      syncUrl();\n      applyFilters();\n    });\n\n    dom.pagePrev.addEventListener(\"click\", () => {\n      if (state.pageMeta?.has_prev) {\n        loadPage(Math.max(1, state.page - 1), { manual: true });\n      }\n    });\n\n    dom.pageNext.addEventListener(\"click\", () => {\n      if (state.pageMeta?.has_next) {\n        loadPage(state.page + 1, { manual: true });\n      }\n    });\n\n    dom.pageSize.addEventListener(\"change\", (event) => {\n      const value = Number.parseInt(event.target.value, 10);\n      if (Number.isFinite(value) && value > 0) {\n        state.pageSize = value;\n        requestFilterUpdate();\n      }\n    });\n\n    dom.markSeenButton.addEventListener(\"click\", () => {\n      if (newestFetchedMs > 0) {\n        lastAcknowledgedMs = newestFetchedMs;\n        persistAckTimestamp(lastAcknowledgedMs);\n        flagNewItems();\n        applyFilters();\n      }\n    });\n\n    dom.refreshButton.addEventListener(\"click\", () => {\n      backoffMs = REFRESH_INTERVAL_MS;\n      loadPage(state.page, { manual: true, bypassCache: true });\n    });\n\n    dom.retryButton.addEventListener(\"click\", () => {\n      backoffMs = REFRESH_INTERVAL_MS;\n      clearError();\n      loadPage(state.page, { manual: true, bypassCache: true });\n    });\n\n    dom.userFilterClear.addEventListener(\"click\", () => {\n      state.userMode = \"none\";\n      state.userValue = null;\n      requestFilterUpdate();\n    });\n\n    document.addEventListener(\"visibilitychange\", () => {\n      if (!document.hidden) {\n        const age = lastSyncedAt ? Date.now() - lastSyncedAt.getTime() : Infinity;\n        if (age > REFRESH_INTERVAL_MS) {\n          backoffMs = REFRESH_INTERVAL_MS;\n          loadPage(state.page, { manual: true, bypassCache: true });\n        }\n      }\n    });\n\n    window.addEventListener(\"beforeunload\", () => {\n      if (refreshTimer !== null) {\n        window.clearTimeout(refreshTimer);\n      }\n      if (syncTicker !== null) {\n        window.clearInterval(syncTicker);\n      }\n    });\n\n    window.addEventListener(\"resize\", () => {\n      updateListLayout();\n    });\n  }\n\n  function syncControlsToState() {\n    if (dom.searchInput) {\n      dom.searchInput.value = state.search;\n    }\n    if (dom.languageFilter) {\n      dom.languageFilter.value = state.language;\n    }\n    if (dom.activityFilter) {\n      dom.activityFilter.value = state.activity;\n    }\n    ensurePageSizeOption();\n    if (dom.pageSize) {\n      dom.pageSize.value = String(state.pageSize);\n    }\n    updateSortToggle();\n    updateDensityToggle();\n  }\n\n  function requestFilterUpdate({ resetPage = true, manual = true } = {}) {\n    if (resetPage) {\n      state.page = 1;\n    }\n    resetPaginationCachesIfNeeded();\n    persistUiState();\n    syncUrl();\n    loadPage(state.page, { manual, bypassCache: true });\n  }\n\n  function updateSortToggle() {\n    if (state.sort === \"alpha\") {\n      dom.sortToggle.textContent = \"Sort: Alphabetical\";\n      dom.sortToggle.setAttribute(\"aria-pressed\", \"true\");\n    } else {\n      dom.sortToggle.textContent = \"Sort: Newest\";\n      dom.sortToggle.setAttribute(\"aria-pressed\", \"false\");\n    }\n  }\n\n  function updateDensityToggle() {\n    const pressed = state.density === \"compact\";\n    dom.densityToggle.textContent = pressed ? \"Layout: Compact\" : \"Layout: Comfortable\";\n    dom.densityToggle.setAttribute(\"aria-pressed\", pressed ? \"true\" : \"false\");\n  }\n\n  function updateListLayout() {\n    const list = dom.list;\n    if (!list) {\n      return;\n    }\n    const wide = window.innerWidth >= GRID_BREAKPOINT;\n    list.classList.toggle(\"is-grid\", wide);\n    list.classList.toggle(\"is-compact\", state.density === \"compact\");\n  }\n\n  function isEditableTarget(target) {\n    if (!target) {\n      return false;\n    }\n    const nodeName = target.nodeName.toLowerCase();\n    if (nodeName === \"input\" || nodeName === \"textarea\" || target.isContentEditable) {\n      return true;\n    }\n    return false;\n  }\n\n  function cycleLanguageFilter() {\n    const options = Array.from(dom.languageFilter.options).map((opt) => opt.value);\n    let idx = options.indexOf(state.language);\n    if (idx === -1) {\n      idx = 0;\n    }\n    idx = (idx + 1) % options.length;\n    state.language = options[idx];\n    dom.languageFilter.value = state.language;\n    requestFilterUpdate();\n  }\n\n  function openShortcutModal() {\n    if (!dom.shortcutModal) {\n      return;\n    }\n    dom.shortcutModal.hidden = false;\n    dom.shortcutModal.removeAttribute(\"aria-hidden\");\n    if (dom.shortcutClose) {\n      dom.shortcutClose.focus();\n    }\n  }\n\n  function closeShortcutModal() {\n    if (!dom.shortcutModal || dom.shortcutModal.hidden) {\n      return;\n    }\n    dom.shortcutModal.hidden = true;\n    dom.shortcutModal.setAttribute(\"aria-hidden\", \"true\");\n  }\n\n  function toggleShortcutModal() {\n    if (!dom.shortcutModal) {\n      return;\n    }\n    if (dom.shortcutModal.hidden) {\n      openShortcutModal();\n    } else {\n      closeShortcutModal();\n    }\n  }\n\n  function registerKeyboardShortcuts() {\n    document.addEventListener(\"keydown\", (event) => {\n      if (event.defaultPrevented) {\n        return;\n      }\n      const target = event.target;\n      const key = event.key;\n\n      if (key === \"Escape\") {\n        if (dom.presetModal && !dom.presetModal.hidden) {\n          closePresetModal();\n          event.preventDefault();\n          return;\n        }\n        if (dom.shortcutModal && !dom.shortcutModal.hidden) {\n          closeShortcutModal();\n          event.preventDefault();\n        }\n        return;\n      }\n\n      if (isEditableTarget(target) && !event.altKey) {\n        return;\n      }\n\n      if (event.altKey && !event.ctrlKey && !event.metaKey) {\n        if (dom.presetModal && !dom.presetModal.hidden) {\n          return;\n        }\n        if (key >= \"1\" && key <= \"5\") {\n          event.preventDefault();\n          applyPresetByShortcut(Number.parseInt(key, 10) - 1);\n          return;\n        }\n      }\n\n      if (key === \"/\") {\n        event.preventDefault();\n        dom.searchInput.focus();\n        dom.searchInput.select();\n        return;\n      }\n\n      if (key === \"?\") {\n        event.preventDefault();\n        toggleShortcutModal();\n        return;\n      }\n\n      if (key === \"[\") {\n        event.preventDefault();\n        dom.pagePrev.click();\n        return;\n      }\n\n      if (key === \"]\") {\n        event.preventDefault();\n        dom.pageNext.click();\n        return;\n      }\n\n      if (key === \"l\" || key === \"L\") {\n        event.preventDefault();\n        cycleLanguageFilter();\n        return;\n      }\n\n      if (key === \"m\" || key === \"M\") {\n        event.preventDefault();\n        dom.markSeenButton.click();\n        return;\n      }\n\n      if (key === \"r\" || key === \"R\") {\n        event.preventDefault();\n        backoffMs = REFRESH_INTERVAL_MS;\n        loadPage(state.page, { manual: true, bypassCache: true });\n      }\n    });\n\n    if (dom.shortcutClose) {\n      dom.shortcutClose.addEventListener(\"click\", () => {\n        closeShortcutModal();\n      });\n    }\n    if (dom.shortcutModal) {\n      dom.shortcutModal.addEventListener(\"click\", (event) => {\n        if (event.target === dom.shortcutModal) {\n          closeShortcutModal();\n        }\n      });\n    }\n  }\n\n  function registerTestHook() {\n    if (window.__STARCHASER_TEST_HOOK__) {\n      window.__STARCHASER_TEST_HOOK__({\n        triggerRefresh: () => loadPage(state.page, { manual: true, bypassCache: true }),\n        overrideFetch: (fn) => {\n          fetchImpl = fn;\n        },\n        state,\n        getLastSyncedAt: () => lastSyncedAt,\n        acknowledge: () => dom.markSeenButton.click(),\n      });\n    }\n  }\n\n  function initialise() {\n    applySnapshot(loadUiSnapshot());\n    currentFilterSignature = computeFilterSignature();\n    cacheSignature = currentFilterSignature;\n    setupPresetUi();\n    initControls();\n    updateSyncStatus();\n    renderQuickFilters();\n    renderUserBanner();\n    updateListLayout();\n\n    syncTicker = window.setInterval(updateSyncStatus, 60 * 1000);\n    registerTestHook();\n    registerKeyboardShortcuts();\n    fetchFilterOptions();\n    loadPage(state.page, { initial: true, bypassCache: true });\n  }\n\n  initialise();\n})();\n    </script>\n  </body>\n</html>"
[INFO] [stderr] error: test failed, to rerun pass `--test frontend_snapshot`
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x622cb3280f02 - std::backtrace_rs::backtrace::libunwind::trace::h16affffe904e891e
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/../../backtrace/src/backtrace/libunwind.rs:117:9
[INFO] [stdout]    1:     0x622cb3280f02 - std::backtrace_rs::backtrace::trace_unsynchronized::h5c14b13373ed4150
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/../../backtrace/src/backtrace/mod.rs:66:14
[INFO] [stdout]    2:     0x622cb3280f02 - std::sys::backtrace::_print_fmt::hcbb507f162c816cc
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/sys/backtrace.rs:66:9
[INFO] [stdout]    3:     0x622cb3280f02 - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::h8be9aa933f14675f
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/sys/backtrace.rs:39:26
[INFO] [stdout]    4:     0x622cb3290d3f - core::fmt::rt::Argument::fmt::h30ed739d33467c3a
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/fmt/rt.rs:173:76
[INFO] [stdout]    5:     0x622cb3290d3f - core::fmt::write::hfd0efbb002ac7eea
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/fmt/mod.rs:1469:25
[INFO] [stdout]    6:     0x622cb324e9d3 - std::io::default_write_fmt::hd6d24501f2d7f8d3
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/io/mod.rs:639:11
[INFO] [stdout]    7:     0x622cb324e9d3 - std::io::Write::write_fmt::h79eca2f72fc24111
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/io/mod.rs:1954:13
[INFO] [stdout]    8:     0x622cb325a772 - std::sys::backtrace::BacktraceLock::print::hf2554f6030d393f7
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/sys/backtrace.rs:42:9
[INFO] [stdout]    9:     0x622cb325f24f - std::panicking::default_hook::{{closure}}::h8873121c56335b01
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:301:27
[INFO] [stdout]   10:     0x622cb325f0e1 - std::panicking::default_hook::hbafefc2d196267a2
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:325:9
[INFO] [stdout]   11:     0x622cb32222ae - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::ha834d5846f91b30b
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/alloc/src/boxed.rs:2099:9
[INFO] [stdout]   12:     0x622cb32222ae - test::test_main_with_exit_callback::{{closure}}::h63c167737eecb025
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/test/src/lib.rs:145:21
[INFO] [stdout]   13:     0x622cb325f85f - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::hbf9b0f7a281291fd
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/alloc/src/boxed.rs:2099:9
[INFO] [stdout]   14:     0x622cb325f85f - std::panicking::panic_with_hook::h9f5b09d5adc1a745
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:842:13
[INFO] [stdout]   15:     0x622cb325f6ba - std::panicking::panic_handler::{{closure}}::h08111e483bdf6a89
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:707:13
[INFO] [stdout]   16:     0x622cb325a8a9 - std::sys::backtrace::__rust_end_short_backtrace::h1b86e3414ecbbe8d
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/sys/backtrace.rs:174:18
[INFO] [stdout]   17:     0x622cb324319d - __rustc[b292c645e8102103]::rust_begin_unwind
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:698:5
[INFO] [stdout]   18:     0x622cb3298480 - core::panicking::panic_fmt::h31cc490ecc8cc1fa
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/panicking.rs:80:14
[INFO] [stdout]   19:     0x622cb32983b3 - core::panicking::assert_failed_inner::h865658b89c1b891d
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/panicking.rs:439:23
[INFO] [stdout]   20:     0x622cb3201176 - core::panicking::assert_failed::h91a005c6a732186b
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/panicking.rs:399:5
[INFO] [stdout]   21:     0x622cb31ffaf1 - frontend_snapshot::bundled_frontend_matches_fixture::h55d94bb685ba2838
[INFO] [stdout]                                at /opt/rustwide/workdir/tests/frontend_snapshot.rs:6:5
[INFO] [stdout]   22:     0x622cb31ffb17 - frontend_snapshot::bundled_frontend_matches_fixture::{{closure}}::h6691895261c03302
[INFO] [stdout]                                at /opt/rustwide/workdir/tests/frontend_snapshot.rs:5:38
[INFO] [stdout]   23:     0x622cb31ffe06 - core::ops::function::FnOnce::call_once::hac642d4213033b94
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/ops/function.rs:250:5
[INFO] [stdout]   24:     0x622cb32220ab - core::ops::function::FnOnce::call_once::h2b2de5fdd23aab3e
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/ops/function.rs:250:5
[INFO] [stdout]   25:     0x622cb32220ab - test::__rust_begin_short_backtrace::he551dd004770be01
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/test/src/lib.rs:663:18
[INFO] [stdout]   26:     0x622cb323610d - test::run_test_in_process::{{closure}}::h5f0b44080a35ed87
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/test/src/lib.rs:686:74
[INFO] [stdout]   27:     0x622cb323610d - <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::h4dbf65d14893ecf5
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/panic/unwind_safe.rs:274:9
[INFO] [stdout]   28:     0x622cb323610d - std::panicking::catch_unwind::do_call::hc37c563b8a006285
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:590:40
[INFO] [stdout]   29:     0x622cb323610d - std::panicking::catch_unwind::h616b6e2e7a27f612
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:553:19
[INFO] [stdout]   30:     0x622cb323610d - std::panic::catch_unwind::h1e788dd57758e6d8
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panic.rs:359:14
[INFO] [stdout]   31:     0x622cb323610d - test::run_test_in_process::hf073c2764f29f8ad
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/test/src/lib.rs:686:27
[INFO] [stdout]   32:     0x622cb323610d - test::run_test::{{closure}}::h3dc46b7a0c340fa6
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/test/src/lib.rs:607:43
[INFO] [stdout]   33:     0x622cb320f654 - test::run_test::{{closure}}::h6a4da3c57ef4505f
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/test/src/lib.rs:637:41
[INFO] [stdout]   34:     0x622cb320f654 - std::sys::backtrace::__rust_begin_short_backtrace::hfd8e44bc311a5d57
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/sys/backtrace.rs:158:18
[INFO] [stdout]   35:     0x622cb3212eaa - std::thread::Builder::spawn_unchecked_::{{closure}}::{{closure}}::h57c4ddec344fe24c
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/thread/mod.rs:562:17
[INFO] [stdout]   36:     0x622cb3212eaa - <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::hd7cbe09591f06dfb
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/panic/unwind_safe.rs:274:9
[INFO] [stdout]   37:     0x622cb3212eaa - std::panicking::catch_unwind::do_call::hdcd076e8e993dfbc
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:590:40
[INFO] [stdout]   38:     0x622cb3212eaa - std::panicking::catch_unwind::h8f9f675f3756eab1
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panicking.rs:553:19
[INFO] [stdout]   39:     0x622cb3212eaa - std::panic::catch_unwind::he8f74a93abeceb9b
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/panic.rs:359:14
[INFO] [stdout]   40:     0x622cb3212eaa - std::thread::Builder::spawn_unchecked_::{{closure}}::he43db13a2caa41d5
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/thread/mod.rs:560:30
[INFO] [stdout]   41:     0x622cb3212eaa - core::ops::function::FnOnce::call_once{{vtable.shim}}::h8f3531a7e0d83514
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/core/src/ops/function.rs:250:5
[INFO] [stdout]   42:     0x622cb3255caf - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h6ff05134d80ef20e
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/alloc/src/boxed.rs:2085:9
[INFO] [stdout]   43:     0x622cb3255caf - std::sys::thread::unix::Thread::new::thread_start::h9a4a41a076a486e0
[INFO] [stdout]                                at /rustc/9f93af291970322f4f1c6315ccde4d7078201159/library/std/src/sys/thread/unix.rs:124:17
[INFO] [stdout]   44:     0x7ab00efa4aa4 - <unknown>
[INFO] [stdout]   45:     0x7ab00f031a64 - clone
[INFO] [stdout]   46:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] failures:
[INFO] [stdout]     bundled_frontend_matches_fixture
[INFO] [stdout] 
[INFO] [stdout] test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
[INFO] [stdout] 
[INFO] running `Command { std: "docker" "inspect" "b649b6c388117571766223a354e2b7d1e5d89c97b01d4570281619b09e324263", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "rm" "-f" "b649b6c388117571766223a354e2b7d1e5d89c97b01d4570281619b09e324263", kill_on_drop: false }`
[INFO] [stdout] b649b6c388117571766223a354e2b7d1e5d89c97b01d4570281619b09e324263
