160 Commits
v2.1.2 ... main

Author SHA1 Message Date
Sped0n
222d97c97c ci: update bundled artifacts [skip ci] 2026-04-14 10:42:36 +00:00
Ryan
b9cbe289c2 Merge pull request #632 from Sped0n/dependabot/npm_and_yarn/gsap-3.15.0
build(deps): bump gsap from 3.14.2 to 3.15.0
2026-04-14 18:42:02 +08:00
dependabot[bot]
765bbcb201 build(deps): bump gsap from 3.14.2 to 3.15.0
Bumps [gsap](https://github.com/greensock/GSAP) from 3.14.2 to 3.15.0.
- [Commits](https://github.com/greensock/GSAP/compare/3.14.2...3.15.0)

---
updated-dependencies:
- dependency-name: gsap
  dependency-version: 3.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-13 23:39:18 +00:00
Ryan
960e473893 Merge pull request #626 from Sped0n/dependabot/npm_and_yarn/types/node-25.5.2
build(deps-dev): bump @types/node from 25.5.0 to 25.5.2
2026-04-12 00:53:34 +08:00
dependabot[bot]
4d4cad18b6 build(deps-dev): bump @types/node from 25.5.0 to 25.5.2
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.5.0 to 25.5.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 13:41:38 +00:00
Ryan
d8611b834b Merge pull request #625 from Sped0n/dependabot/npm_and_yarn/vite-plugin-solid-2.11.12
build(deps-dev): bump vite-plugin-solid from 2.11.11 to 2.11.12
2026-04-08 21:39:57 +08:00
dependabot[bot]
7e7c96be55 build(deps-dev): bump vite-plugin-solid from 2.11.11 to 2.11.12
Bumps [vite-plugin-solid](https://github.com/solidjs/vite-plugin-solid) from 2.11.11 to 2.11.12.
- [Release notes](https://github.com/solidjs/vite-plugin-solid/releases)
- [Changelog](https://github.com/solidjs/vite-plugin-solid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/solidjs/vite-plugin-solid/compare/vite-plugin-solid@2.11.11...vite-plugin-solid@2.11.12)

---
updated-dependencies:
- dependency-name: vite-plugin-solid
  dependency-version: 2.11.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 13:39:15 +00:00
Ryan
dc2948a84c Merge pull request #627 from Sped0n/dependabot/npm_and_yarn/vitefu-1.1.3
build(deps-dev): bump vitefu from 1.1.2 to 1.1.3
2026-04-08 21:38:04 +08:00
dependabot[bot]
ac774c68ee build(deps-dev): bump vitefu from 1.1.2 to 1.1.3
Bumps [vitefu](https://github.com/svitejs/vitefu) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/svitejs/vitefu/releases)
- [Changelog](https://github.com/svitejs/vitefu/blob/main/CHANGELOG.md)
- [Commits](https://github.com/svitejs/vitefu/compare/v1.1.2...v1.1.3)

---
updated-dependencies:
- dependency-name: vitefu
  dependency-version: 1.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 13:37:18 +00:00
Ryan
a606497735 Merge pull request #624 from Sped0n/dependabot/npm_and_yarn/sass-embedded-1.99.0
build(deps-dev): bump sass-embedded from 1.98.0 to 1.99.0
2026-04-08 21:36:05 +08:00
dependabot[bot]
4b2af94540 build(deps-dev): bump sass-embedded from 1.98.0 to 1.99.0
Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.98.0 to 1.99.0.
- [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/embedded-host-node/compare/1.98.0...1.99.0)

---
updated-dependencies:
- dependency-name: sass-embedded
  dependency-version: 1.99.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 13:35:19 +00:00
Ryan
e2d5887912 Merge pull request #618 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.58.0
build(deps-dev): bump typescript-eslint from 8.57.2 to 8.58.0
2026-04-08 21:34:40 +08:00
dependabot[bot]
27837bcf07 build(deps-dev): bump typescript-eslint from 8.57.2 to 8.58.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.57.2 to 8.58.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.58.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 13:34:03 +00:00
Ryan
f7c811374e Merge pull request #623 from Sped0n/dependabot/npm_and_yarn/vite-8.0.5
build(deps-dev): bump vite from 8.0.3 to 8.0.5
2026-04-08 21:32:32 +08:00
dependabot[bot]
cb5b0ba312 build(deps-dev): bump vite from 8.0.3 to 8.0.5
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.3 to 8.0.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 18:37:40 +00:00
Ryan
f87d28ed93 Merge pull request #621 from Sped0n/dependabot/npm_and_yarn/vite-8.0.3
build(deps-dev): bump vite from 8.0.2 to 8.0.3
2026-04-02 10:31:50 +08:00
dependabot[bot]
4abc531e4b build(deps-dev): bump vite from 8.0.2 to 8.0.3
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.2 to 8.0.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.0.3/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-02 02:31:14 +00:00
Ryan
f69492c2b4 Merge pull request #620 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.58.0
build(deps-dev): bump @typescript-eslint/parser from 8.57.2 to 8.58.0
2026-04-02 10:30:02 +08:00
dependabot[bot]
6839eb2e7d build(deps-dev): bump @typescript-eslint/parser from 8.57.2 to 8.58.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.57.2 to 8.58.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.58.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-02 02:29:22 +00:00
Sped0n
6d99db9e38 ci: update bundled artifacts [skip ci] 2026-04-02 02:28:28 +00:00
Ryan
7515704301 Merge pull request #619 from Sped0n/dependabot/npm_and_yarn/swiper-12.1.3
build(deps): bump swiper from 12.1.2 to 12.1.3
2026-04-02 10:28:03 +08:00
dependabot[bot]
71acaeff02 build(deps): bump swiper from 12.1.2 to 12.1.3
Bumps [swiper](https://github.com/nolimits4web/Swiper) from 12.1.2 to 12.1.3.
- [Release notes](https://github.com/nolimits4web/Swiper/releases)
- [Changelog](https://github.com/nolimits4web/swiper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nolimits4web/Swiper/compare/v12.1.2...v12.1.3)

---
updated-dependencies:
- dependency-name: swiper
  dependency-version: 12.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-02 02:27:32 +00:00
Sped0n
b538d984df ci: update bundled artifacts [skip ci] 2026-04-02 02:26:41 +00:00
Ryan
c694cb13b2 Merge pull request #622 from Sped0n/dependabot/npm_and_yarn/solid-js-1.9.12
build(deps): bump solid-js from 1.9.11 to 1.9.12
2026-04-02 10:26:17 +08:00
dependabot[bot]
3a75206ef2 build(deps): bump solid-js from 1.9.11 to 1.9.12
Bumps [solid-js](https://github.com/solidjs/solid) from 1.9.11 to 1.9.12.
- [Release notes](https://github.com/solidjs/solid/releases)
- [Changelog](https://github.com/solidjs/solid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/solidjs/solid/commits)

---
updated-dependencies:
- dependency-name: solid-js
  dependency-version: 1.9.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 23:37:15 +00:00
Ryan
a89a551013 Merge pull request #617 from Sped0n/bump-mise-to-v4
ci: upgrade mise-action to v4
2026-03-26 14:07:19 +08:00
Sped0n
472f9172ca ci: upgrade mise-action to v4
Signed-off-by: Sped0n <hi@sped0n.com>
2026-03-26 14:05:32 +08:00
Ryan
44be2c4a50 Merge pull request #616 from Sped0n/fix-readme-maintenance-typo
docs: fix typo in README.md
2026-03-26 13:59:49 +08:00
Sped0n
9589c369f0 docs: fix typo in README.md
Signed-off-by: Sped0n <hi@sped0n.com>
2026-03-26 13:58:12 +08:00
Ryan
3e0a489d7d Merge pull request #614 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.57.2
build(deps-dev): bump typescript-eslint from 8.53.1 to 8.57.2
2026-03-24 12:10:53 +08:00
dependabot[bot]
5f1204a889 build(deps-dev): bump typescript-eslint from 8.53.1 to 8.57.2
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.53.1 to 8.57.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.2/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.57.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-24 04:10:18 +00:00
Ryan
0f96c7a8a8 Merge pull request #615 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.57.2
build(deps-dev): bump @typescript-eslint/parser from 8.57.1 to 8.57.2
2026-03-24 12:09:56 +08:00
dependabot[bot]
0057c472ef build(deps-dev): bump @typescript-eslint/parser from 8.57.1 to 8.57.2
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.57.1 to 8.57.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.2/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.57.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-24 04:09:23 +00:00
Ryan
6d68202b83 Merge pull request #613 from Sped0n/dependabot/npm_and_yarn/vite-8.0.2
build(deps-dev): bump vite from 8.0.1 to 8.0.2
2026-03-24 12:08:00 +08:00
dependabot[bot]
010406e77e build(deps-dev): bump vite from 8.0.1 to 8.0.2
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.1 to 8.0.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.0.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 23:34:01 +00:00
Sped0n
758a2d1a62 ci: update bundled artifacts [skip ci] 2026-03-22 13:16:08 +00:00
Ryan
ec1df7f070 Merge pull request #612 from Sped0n/vite-8
build(deps-dev): migrate to vite 8
2026-03-22 21:15:44 +08:00
Sped0n
f09988f32d build(deps-dev): migrate to vite 8
Signed-off-by: Sped0n <hi@sped0n.com>
2026-03-22 21:13:40 +08:00
Ryan
e16aaca42b Merge pull request #596 from Sped0n/dependabot/npm_and_yarn/eslint-config-love-151.0.0
build(deps-dev): bump eslint-config-love from 149.0.0 to 151.0.0
2026-03-22 20:34:26 +08:00
dependabot[bot]
80442eb569 build(deps-dev): bump eslint-config-love from 149.0.0 to 151.0.0
Bumps [eslint-config-love](https://github.com/mightyiam/eslint-config-love) from 149.0.0 to 151.0.0.
- [Release notes](https://github.com/mightyiam/eslint-config-love/releases)
- [Changelog](https://github.com/mightyiam/eslint-config-love/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mightyiam/eslint-config-love/compare/v149.0.0...v151.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-love
  dependency-version: 151.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 12:33:58 +00:00
Ryan
9039e04b38 Merge pull request #609 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.57.1
build(deps-dev): bump typescript-eslint from 8.53.1 to 8.57.1
2026-03-22 20:32:47 +08:00
dependabot[bot]
56304e09f1 build(deps-dev): bump typescript-eslint from 8.53.1 to 8.57.1
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.53.1 to 8.57.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.57.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 12:32:15 +00:00
Ryan
0cbaacbc0e Merge pull request #610 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.57.1
build(deps-dev): bump @typescript-eslint/parser from 8.53.1 to 8.57.1
2026-03-22 20:31:25 +08:00
dependabot[bot]
f78449adb9 build(deps-dev): bump @typescript-eslint/parser from 8.53.1 to 8.57.1
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.53.1 to 8.57.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.57.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 12:30:32 +00:00
Ryan
4d55bca248 Merge pull request #604 from Sped0n/dependabot/npm_and_yarn/eslint/js-9.39.4
build(deps-dev): bump @eslint/js from 9.39.2 to 9.39.4
2026-03-22 20:30:00 +08:00
dependabot[bot]
ad998ba153 build(deps-dev): bump @eslint/js from 9.39.2 to 9.39.4
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.39.2 to 9.39.4.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.4/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 12:29:25 +00:00
Ryan
bd95ab861b Merge pull request #606 from Sped0n/dependabot/npm_and_yarn/vite-plugin-solid-2.11.11
build(deps-dev): bump vite-plugin-solid from 2.11.10 to 2.11.11
2026-03-22 20:28:20 +08:00
dependabot[bot]
919489c7e9 build(deps-dev): bump vite-plugin-solid from 2.11.10 to 2.11.11
Bumps [vite-plugin-solid](https://github.com/solidjs/vite-plugin-solid) from 2.11.10 to 2.11.11.
- [Release notes](https://github.com/solidjs/vite-plugin-solid/releases)
- [Changelog](https://github.com/solidjs/vite-plugin-solid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/solidjs/vite-plugin-solid/compare/vite-plugin-solid@2.11.10...vite-plugin-solid@2.11.11)

---
updated-dependencies:
- dependency-name: vite-plugin-solid
  dependency-version: 2.11.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 12:27:47 +00:00
Ryan
efa72bb763 Merge pull request #608 from Sped0n/dependabot/npm_and_yarn/types/node-25.5.0
build(deps-dev): bump @types/node from 25.0.10 to 25.5.0
2026-03-22 20:27:08 +08:00
dependabot[bot]
90f79113c7 build(deps-dev): bump @types/node from 25.0.10 to 25.5.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.0.10 to 25.5.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 12:26:40 +00:00
Ryan
b5d0754c45 Merge pull request #607 from Sped0n/dependabot/npm_and_yarn/sass-embedded-1.98.0
build(deps-dev): bump sass-embedded from 1.97.3 to 1.98.0
2026-03-22 20:25:20 +08:00
dependabot[bot]
8a751b7437 build(deps-dev): bump sass-embedded from 1.97.3 to 1.98.0
Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.97.3 to 1.98.0.
- [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/embedded-host-node/compare/1.97.3...1.98.0)

---
updated-dependencies:
- dependency-name: sass-embedded
  dependency-version: 1.98.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 12:24:52 +00:00
Sped0n
212dca53e8 ci: update bundled artifacts [skip ci] 2026-03-22 12:17:14 +00:00
Ryan
989a7f4951 Merge pull request #611 from Sped0n/fix-432
Fix 432
2026-03-22 20:16:50 +08:00
Sped0n
f25b71a858 refactor: split monolithic state into context-based modules
Extract image, desktop, mobile, and config state into separate context
providers to improve modularity and reduce unnecessary re-renders.

Signed-off-by: Sped0n <hi@sped0n.com>
2026-03-22 19:45:05 +08:00
Sped0n
1c386386f3 refactor: extract image reveal logic and improve GSAP loading reliability
Signed-off-by: Sped0n <hi@sped0n.com>
2026-03-22 17:30:41 +08:00
Sped0n
797b59a38a fix: improve mobile device detection reliability
Signed-off-by: Sped0n <hi@sped0n.com>
2026-03-22 17:29:11 +08:00
Sped0n
dbbc063353 ci: update bundled artifacts [skip ci] 2026-02-22 19:34:18 +00:00
Ryan
f7e87c3c15 Merge pull request #595 from Sped0n/dependabot/npm_and_yarn/swiper-12.1.2 2026-02-23 03:33:56 +08:00
dependabot[bot]
1242146140 build(deps): bump swiper from 12.1.1 to 12.1.2
Bumps [swiper](https://github.com/nolimits4web/Swiper) from 12.1.1 to 12.1.2.
- [Release notes](https://github.com/nolimits4web/Swiper/releases)
- [Changelog](https://github.com/nolimits4web/swiper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nolimits4web/Swiper/compare/v12.1.1...v12.1.2)

---
updated-dependencies:
- dependency-name: swiper
  dependency-version: 12.1.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-20 21:55:28 +00:00
Sped0n
a8c1f948db ci: update bundled artifacts [skip ci] 2026-02-17 20:05:08 +00:00
Ryan
dc30a8cd16 Merge pull request #594 from Sped0n/dependabot/npm_and_yarn/swiper-12.1.1 2026-02-18 04:04:43 +08:00
dependabot[bot]
71c7b02c1d build(deps): bump swiper from 12.1.0 to 12.1.1
Bumps [swiper](https://github.com/nolimits4web/Swiper) from 12.1.0 to 12.1.1.
- [Release notes](https://github.com/nolimits4web/Swiper/releases)
- [Changelog](https://github.com/nolimits4web/swiper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nolimits4web/Swiper/compare/v12.1.0...v12.1.1)

---
updated-dependencies:
- dependency-name: swiper
  dependency-version: 12.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 23:34:24 +00:00
Ryan
6cfcfb272a Merge pull request #583 from Sped0n/dependabot/npm_and_yarn/eslint-config-love-149.0.0 2026-02-12 02:08:27 +08:00
dependabot[bot]
c239112cb2 build(deps-dev): bump eslint-config-love from 147.0.0 to 149.0.0
Bumps [eslint-config-love](https://github.com/mightyiam/eslint-config-love) from 147.0.0 to 149.0.0.
- [Release notes](https://github.com/mightyiam/eslint-config-love/releases)
- [Changelog](https://github.com/mightyiam/eslint-config-love/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mightyiam/eslint-config-love/compare/v147.0.0...v149.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-love
  dependency-version: 149.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 20:30:02 +00:00
Sped0n
637391bb34 ci: update bundled artifacts [skip ci] 2026-02-03 20:29:01 +00:00
Ryan
fe38e1289a Merge pull request #584 from Sped0n/dependabot/npm_and_yarn/swiper-12.1.0
build(deps): bump swiper from 12.0.3 to 12.1.0
2026-02-04 04:28:37 +08:00
dependabot[bot]
08bd9a76ef build(deps): bump swiper from 12.0.3 to 12.1.0
Bumps [swiper](https://github.com/nolimits4web/Swiper) from 12.0.3 to 12.1.0.
- [Release notes](https://github.com/nolimits4web/Swiper/releases)
- [Changelog](https://github.com/nolimits4web/swiper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nolimits4web/Swiper/compare/v12.0.3...v12.1.0)

---
updated-dependencies:
- dependency-name: swiper
  dependency-version: 12.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 20:25:47 +00:00
Ryan
14f481b2c0 Merge pull request #586 from Sped0n/fix-bundle
build(ci): add pre-build cleanup for bundled directory
2026-02-04 04:23:40 +08:00
Sped0n
546791e90b ci: add pre-build cleanup step for artifact updates
Add a cleanup step to remove the bundled directory before building when
running the latest Hugo version on push or merged PR events. This ensures
old or unused JavaScript files are not included in the updated artifacts.

Signed-off-by: Sped0n <hi@sped0n.com>
2026-02-04 04:22:40 +08:00
Ryan
136c2303f9 Merge pull request #577 from Sped0n/dependabot/npm_and_yarn/sass-embedded-1.97.3
build(deps-dev): bump sass-embedded from 1.97.2 to 1.97.3
2026-01-31 18:12:11 +08:00
dependabot[bot]
b13f1bf454 build(deps-dev): bump sass-embedded from 1.97.2 to 1.97.3
Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.97.2 to 1.97.3.
- [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/embedded-host-node/compare/1.97.2...1.97.3)

---
updated-dependencies:
- dependency-name: sass-embedded
  dependency-version: 1.97.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-31 10:11:41 +00:00
Ryan
313a6f294a Merge pull request #580 from Sped0n/dependabot/npm_and_yarn/prettier-3.8.1
build(deps-dev): bump prettier from 3.8.0 to 3.8.1
2026-01-31 18:10:25 +08:00
dependabot[bot]
2804be174d build(deps-dev): bump prettier from 3.8.0 to 3.8.1
Bumps [prettier](https://github.com/prettier/prettier) from 3.8.0 to 3.8.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.0...3.8.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.8.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-31 10:07:58 +00:00
Ryan
fec976ad9e Merge pull request #582 from Sped0n/update-flake-and-codeql
Update flake and codeql
2026-01-31 18:04:22 +08:00
Sped0n
582433874c ci: update CodeQL action
Bumps checkout to v6 and codeql-action to v3

Signed-off-by: Sped0n <hi@sped0n.com>
2026-01-31 18:03:43 +08:00
Sped0n
037cdbd679 build(deps): bump nixpkgs from 891611 to 935279
Bumps nixpkgs flake input (https://github.com/NixOS/nixpkgs) from
revision b6a8526db03f735b89dd5ff348f53f752e7ddc8e (891611) to
bfc1b8a4574108ceef22f02bafcf6611380c100d (935279).

Signed-off-by: Sped0n <hi@sped0n.com>
2026-01-31 18:03:43 +08:00
Ryan
50ff7f62bb Merge pull request #581 from Sped0n/update-ci
ci: migrate workflows to mise-action and update actions
2026-01-31 17:50:43 +08:00
Sped0n
3fe8477646 ci: migrate workflows to mise-action and update actions
Signed-off-by: Sped0n <hi@sped0n.com>
2026-01-31 17:41:57 +08:00
Ryan
64b43597a4 Merge pull request #578 from Sped0n/dependabot/npm_and_yarn/solid-js-1.9.11 2026-01-28 15:41:34 +08:00
dependabot[bot]
94603999d7 build(deps): bump solid-js from 1.9.10 to 1.9.11
Bumps [solid-js](https://github.com/solidjs/solid) from 1.9.10 to 1.9.11.
- [Release notes](https://github.com/solidjs/solid/releases)
- [Changelog](https://github.com/solidjs/solid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/solidjs/solid/commits)

---
updated-dependencies:
- dependency-name: solid-js
  dependency-version: 1.9.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 00:36:06 +00:00
Ryan
c3c42e4aca Merge pull request #573 from Sped0n/dependabot/npm_and_yarn/types/node-25.0.9 2026-01-24 03:54:26 +08:00
dependabot[bot]
38f4895233 build(deps-dev): bump @types/node from 25.0.3 to 25.0.9
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.0.3 to 25.0.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-23 19:50:16 +00:00
Ryan
636fea3496 Merge pull request #575 from Sped0n/dependabot/npm_and_yarn/eslint-plugin-prettier-5.5.5 2026-01-24 03:48:51 +08:00
dependabot[bot]
c15b8a3e46 build(deps-dev): bump eslint-plugin-prettier from 5.5.4 to 5.5.5
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 5.5.4 to 5.5.5.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v5.5.4...v5.5.5)

---
updated-dependencies:
- dependency-name: eslint-plugin-prettier
  dependency-version: 5.5.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-23 19:44:50 +00:00
Ryan
0cd891d16b Merge pull request #571 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.53.1 2026-01-24 03:43:32 +08:00
dependabot[bot]
4670c00157 build(deps-dev): bump @typescript-eslint/parser from 8.52.0 to 8.53.1
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.52.0 to 8.53.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.53.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.53.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-23 19:39:18 +00:00
Ryan
032a75f581 Merge pull request #570 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.53.1 2026-01-24 03:37:59 +08:00
dependabot[bot]
5e40e9041e build(deps-dev): bump typescript-eslint from 8.53.0 to 8.53.1
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.53.0 to 8.53.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.53.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.53.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-23 19:33:44 +00:00
Ryan
9c7731b8e5 Merge pull request #574 from Sped0n/dependabot/npm_and_yarn/eslint-config-love-145.0.0 2026-01-24 03:32:43 +08:00
dependabot[bot]
d59475fbdc build(deps-dev): bump eslint-config-love from 140.0.0 to 145.0.0
Bumps [eslint-config-love](https://github.com/mightyiam/eslint-config-love) from 140.0.0 to 145.0.0.
- [Release notes](https://github.com/mightyiam/eslint-config-love/releases)
- [Changelog](https://github.com/mightyiam/eslint-config-love/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mightyiam/eslint-config-love/compare/v140.0.0...v145.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-love
  dependency-version: 145.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-23 19:28:38 +00:00
Ryan
27c3b754ac Merge pull request #572 from Sped0n/dependabot/npm_and_yarn/prettier-3.8.0 2026-01-24 03:26:53 +08:00
dependabot[bot]
1359adee59 build(deps-dev): bump prettier from 3.7.4 to 3.8.0
Bumps [prettier](https://github.com/prettier/prettier) from 3.7.4 to 3.8.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.7.4...3.8.0)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 00:06:53 +00:00
Ryan
b8d7193fb9 Merge pull request #566 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.53.0 2026-01-18 14:00:28 +08:00
dependabot[bot]
a06027401b build(deps-dev): bump typescript-eslint from 8.52.0 to 8.53.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.52.0 to 8.53.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.53.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.53.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 03:46:44 +00:00
Ryan
5501d10cd0 Merge pull request #564 from Sped0n/dependabot/npm_and_yarn/vite-7.3.1 2026-01-09 22:31:06 +08:00
dependabot[bot]
31e20f752a build(deps-dev): bump vite from 7.3.0 to 7.3.1
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.1/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 14:26:53 +00:00
Ryan
fd300d8104 Merge pull request #565 from Sped0n/dependabot/npm_and_yarn/sass-embedded-1.97.2 2026-01-08 18:43:00 +08:00
dependabot[bot]
e91a7c6633 build(deps-dev): bump sass-embedded from 1.97.1 to 1.97.2
Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.97.1 to 1.97.2.
- [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/embedded-host-node/compare/1.97.1...1.97.2)

---
updated-dependencies:
- dependency-name: sass-embedded
  dependency-version: 1.97.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 16:56:59 +00:00
Ryan
321e4b618a Merge pull request #562 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.52.0 2026-01-07 18:45:19 +08:00
dependabot[bot]
f630b85669 build(deps-dev): bump @typescript-eslint/parser from 8.51.0 to 8.52.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.51.0 to 8.52.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.52.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.52.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 10:40:59 +00:00
Ryan
a431d74e8c Merge pull request #563 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.52.0 2026-01-07 03:49:05 +08:00
dependabot[bot]
975e084ea8 build(deps-dev): bump typescript-eslint from 8.51.0 to 8.52.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.51.0 to 8.52.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.52.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.52.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 23:05:28 +00:00
Ryan
771b9b34ab Merge pull request #561 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.51.0 2025-12-30 13:11:39 +08:00
dependabot[bot]
1e5155c49e build(deps-dev): bump @typescript-eslint/parser from 8.50.1 to 8.51.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.50.1 to 8.51.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.51.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.51.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 05:07:27 +00:00
Ryan
44750037f3 Merge pull request #560 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.51.0 2025-12-30 13:06:04 +08:00
dependabot[bot]
9af0090644 build(deps-dev): bump typescript-eslint from 8.49.0 to 8.51.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.49.0 to 8.51.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.51.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.51.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 03:20:49 +00:00
Ryan
b05eec64cb Merge pull request #558 from Sped0n/dependabot/npm_and_yarn/eslint-config-love-140.0.0 2025-12-30 11:09:43 +08:00
dependabot[bot]
284cfa4e84 build(deps-dev): bump eslint-config-love from 136.0.0 to 140.0.0
Bumps [eslint-config-love](https://github.com/mightyiam/eslint-config-love) from 136.0.0 to 140.0.0.
- [Release notes](https://github.com/mightyiam/eslint-config-love/releases)
- [Changelog](https://github.com/mightyiam/eslint-config-love/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mightyiam/eslint-config-love/compare/v136.0.0...v140.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-love
  dependency-version: 140.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 03:05:34 +00:00
Ryan
11ea5c101b Merge pull request #557 from Sped0n/dependabot/npm_and_yarn/types/node-25.0.3 2025-12-30 11:04:28 +08:00
dependabot[bot]
d4b9a4588a build(deps-dev): bump @types/node from 24.10.2 to 25.0.3
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.2 to 25.0.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 03:00:19 +00:00
Ryan
4ed332314d Merge pull request #549 from Sped0n/dependabot/npm_and_yarn/eslint-9.39.2 2025-12-30 10:58:59 +08:00
dependabot[bot]
82ab5b996f build(deps-dev): bump eslint from 9.39.1 to 9.39.2
Bumps [eslint](https://github.com/eslint/eslint) from 9.39.1 to 9.39.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.1...v9.39.2)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 02:54:52 +00:00
Ryan
81c14d9f2b Merge pull request #556 from Sped0n/dependabot/npm_and_yarn/sass-embedded-1.97.1 2025-12-30 10:53:48 +08:00
dependabot[bot]
7cf5f9ad2d build(deps-dev): bump sass-embedded from 1.93.3 to 1.97.1
Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.93.3 to 1.97.1.
- [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/embedded-host-node/compare/1.93.3...1.97.1)

---
updated-dependencies:
- dependency-name: sass-embedded
  dependency-version: 1.97.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 02:49:39 +00:00
Ryan
0f69ec5f96 Merge pull request #546 from Sped0n/dependabot/npm_and_yarn/eslint/js-9.39.2 2025-12-30 10:13:08 +08:00
dependabot[bot]
e6a9cacfef build(deps-dev): bump @eslint/js from 9.39.1 to 9.39.2
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.39.1 to 9.39.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.2/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 02:09:03 +00:00
Ryan
b24e401a28 Merge pull request #559 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.50.1 2025-12-23 13:15:33 +08:00
dependabot[bot]
ca9fef6c2d build(deps-dev): bump @typescript-eslint/parser from 8.49.0 to 8.50.1
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.49.0 to 8.50.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.50.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.50.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 23:05:03 +00:00
Ryan
2835d46a57 Merge pull request #554 from Sped0n/dependabot/npm_and_yarn/vite-7.3.0 2025-12-23 03:22:07 +08:00
dependabot[bot]
3779bc7ce6 build(deps-dev): bump vite from 7.2.7 to 7.3.0
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.2.7 to 7.3.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.0/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 12:38:14 +00:00
Sped0n
de16c6b443 ci: update bundled artifacts [skip ci] 2025-12-16 01:50:14 +00:00
Ryan
d536303f8e Merge pull request #547 from Sped0n/dependabot/npm_and_yarn/gsap-3.14.2 2025-12-16 09:49:41 +08:00
dependabot[bot]
76d420c2c9 build(deps): bump gsap from 3.14.1 to 3.14.2
Bumps [gsap](https://github.com/greensock/GSAP) from 3.14.1 to 3.14.2.
- [Commits](https://github.com/greensock/GSAP/compare/3.14.1...3.14.2)

---
updated-dependencies:
- dependency-name: gsap
  dependency-version: 3.14.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 23:05:55 +00:00
Ryan
c448648127 Merge pull request #542 from Sped0n/dependabot/npm_and_yarn/typescript-eslint-8.49.0 2025-12-12 22:55:30 +08:00
dependabot[bot]
7e0e6244b0 build(deps-dev): bump typescript-eslint from 8.48.0 to 8.49.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.48.0 to 8.49.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.49.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.49.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 09:45:18 +00:00
Ryan
c76835d474 Merge pull request #544 from Sped0n/dependabot/npm_and_yarn/typescript-eslint/parser-8.49.0 2025-12-09 17:08:25 +08:00
dependabot[bot]
39c13e242d build(deps-dev): bump @typescript-eslint/parser from 8.48.0 to 8.49.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.48.0 to 8.49.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.49.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.49.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 09:07:44 +00:00
Ryan
61c86692ee Merge pull request #543 from Sped0n/dependabot/npm_and_yarn/types/node-24.10.2 2025-12-09 16:08:59 +08:00
dependabot[bot]
cd63e9fec5 build(deps-dev): bump @types/node from 24.10.1 to 24.10.2
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.1 to 24.10.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.10.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 08:08:21 +00:00
Ryan
8bc8fded81 Merge pull request #541 from Sped0n/dependabot/npm_and_yarn/eslint-config-love-136.0.0 2025-12-09 16:07:53 +08:00
dependabot[bot]
8bc54835e5 build(deps-dev): bump eslint-config-love from 133.0.0 to 136.0.0
Bumps [eslint-config-love](https://github.com/mightyiam/eslint-config-love) from 133.0.0 to 136.0.0.
- [Release notes](https://github.com/mightyiam/eslint-config-love/releases)
- [Changelog](https://github.com/mightyiam/eslint-config-love/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mightyiam/eslint-config-love/compare/v133.0.0...v136.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-love
  dependency-version: 136.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 08:07:10 +00:00
Ryan
bd632b0ca4 Merge pull request #539 from Sped0n/dependabot/npm_and_yarn/vite-7.2.7 2025-12-09 15:11:28 +08:00
dependabot[bot]
b550dcd236 build(deps-dev): bump vite from 7.2.6 to 7.2.7
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.2.6 to 7.2.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.2.7/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 07:10:19 +00:00
Ryan
1b067c26d0 Merge pull request #540 from Sped0n/dependabot/npm_and_yarn/prettier-3.7.4 2025-12-09 15:09:56 +08:00
dependabot[bot]
665522c8d1 build(deps-dev): bump prettier from 3.7.3 to 3.7.4
Bumps [prettier](https://github.com/prettier/prettier) from 3.7.3 to 3.7.4.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.7.3...3.7.4)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.7.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 07:09:12 +00:00
Sped0n
e67865b82b ci: update bundled artifacts [skip ci] 2025-12-09 04:30:31 +00:00
Ryan
6440556242 Merge pull request #545 from Sped0n/dependabot/npm_and_yarn/gsap-3.14.1 2025-12-09 12:29:49 +08:00
dependabot[bot]
59227fb265 build(deps): bump gsap from 3.13.0 to 3.14.1
Bumps [gsap](https://github.com/greensock/GSAP) from 3.13.0 to 3.14.1.
- [Commits](https://github.com/greensock/GSAP/compare/3.13.0...3.14.1)

---
updated-dependencies:
- dependency-name: gsap
  dependency-version: 3.14.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 23:06:02 +00:00
dependabot[bot]
d291aab64f build(deps-dev): bump vite from 7.2.4 to 7.2.6 (#537) 2025-12-02 20:41:51 +00:00
dependabot[bot]
c290595a35 build(deps-dev): bump prettier from 3.6.2 to 3.7.3 (#538) 2025-12-03 04:39:45 +08:00
Sped0n
a6a576246f ci: update bundled artifacts [skip ci] 2025-11-25 17:52:23 +00:00
Ryan
93629a4e6b refactor(gallery): refine navClose with flexbox alignment and z-index (#536)
Replace text-align: right with display: flex, justify-content: flex-end for
consistent right alignment. Add height, z-index for layering, and adjust
margin/padding for better mobile gallery nav positioning.

Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 17:51:47 +00:00
Sped0n
a909afee97 ci: update bundled artifacts [skip ci] 2025-11-25 13:21:07 +00:00
Ryan
9c15a367ea refactor(gallery): adjust navClose min-width to 25% (#535)
Reduce min-width from 30% to 25% for better mobile gallery layout balance.

Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 13:20:22 +00:00
Sped0n
73ee16c6fb ci: update bundled artifacts [skip ci] 2025-11-25 13:14:31 +00:00
Sped0n
91b0314c5d ci(vercel): fix vercel preview
Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 21:13:53 +08:00
Sped0n
d1a1dba210 feat(gallery): enhance close button accessibility
Add .navClose styles for 30% min-width, right alignment, and pointer cursor.
Apply class to close div in mobile gallery nav for consistent keyboard support.

Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 21:13:53 +08:00
Sped0n
110ff665e7 fix(gallery): add overflow hidden to prevent scrolling
Ensure the fixed gallery modal doesn't allow overflow beyond viewport.

Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 21:13:53 +08:00
Ryan
b39d563e77 chore: remove dummy bundled/js/critical.js (#533)
Update .gitignore to reflect the correct path before deletion.

Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 12:07:21 +00:00
Sped0n
0e74655820 ci: update bundled artifacts [skip ci] 2025-11-25 11:59:43 +00:00
Sped0n
8926caed69 ci(build): add labels to Hugo matrix for better job names
Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 19:59:06 +08:00
Sped0n
19f54640f9 build(deps-dev): bump various dev dependencies
Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 19:59:06 +08:00
Sped0n
56b87d6393 fix(gallery): adjust image sizing to fit window height
Update gallery image styles to use max-height based on window minus nav,
max-width 100%, and auto width for proper scaling with contain fit.

Signed-off-by: Sped0n <hi@sped0n.com>
2025-11-25 19:59:06 +08:00
dependabot[bot]
75d8310953 build(deps-dev): bump vite from 7.2.2 to 7.2.4
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.2.2 to 7.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-25 19:28:09 +08:00
dependabot[bot]
a9f164f2af build(deps-dev): bump @typescript-eslint/parser from 8.47.0 to 8.48.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.47.0 to 8.48.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.48.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.48.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-25 19:26:57 +08:00
dependabot[bot]
7773f184aa build(deps-dev): bump typescript-eslint from 8.47.0 to 8.48.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.47.0 to 8.48.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.48.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.48.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-25 19:24:50 +08:00
dependabot[bot]
bc501934ae build(deps-dev): bump typescript-eslint from 8.46.4 to 8.47.0
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.46.4 to 8.47.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.47.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.47.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 17:56:48 +08:00
dependabot[bot]
44b619e49b build(deps-dev): bump @types/node from 24.10.0 to 24.10.1
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.0 to 24.10.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.10.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 17:55:45 +08:00
dependabot[bot]
024d013219 build(deps-dev): bump @typescript-eslint/parser from 8.46.4 to 8.47.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.46.4 to 8.47.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.47.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.47.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 17:53:41 +08:00
44 changed files with 2760 additions and 2102 deletions

View File

@@ -12,91 +12,72 @@ permissions:
contents: write
jobs:
filter:
runs-on: ubuntu-latest
name: Filter
outputs:
any_changed: ${{ steps.changed-files-specific.outputs.any_changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed files in scope
id: changed-files-specific
uses: tj-actions/changed-files@v47
with:
files: |
package.json
pnpm-lock.yaml
tsconfig.json
vite.config.ts
assets/**
build:
timeout-minutes: 30
runs-on: ubuntu-latest
name: Build (Hugo ${{ matrix.hugo-version }})
needs: [filter]
if: |
github.event.repository.fork == false
name: Build (Hugo ${{ matrix.hugo-label }})
if: github.event.repository.fork == false
strategy:
matrix:
hugo-version: ['latest', '0.114.0']
include:
- hugo-version: latest
hugo-label: Latest
- hugo-version: '0.114.0'
hugo-label: 'v0.114.0'
steps:
- name: Set current date as env variable
run: |
echo "builddate=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
id: version
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
token: ${{ secrets.PAT }}
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2.6.0
- name: Setup Mise
uses: jdx/mise-action@v4
with:
hugo-version: ${{ matrix.hugo-version }}
extended: true
install_args: node@latest pnpm@10 hugo-extended@${{ matrix.hugo-version }}
tool_versions: |
node latest
pnpm 10
hugo-extended ${{ matrix.hugo-version }}
cache: true
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
- name: Get pnpm store directory
- name: Get pnpm store path
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
run: 'echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT'
- name: Setup pnpm cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('./pnpm-lock.yaml') }}
key: pnpm-store-${{ hashFiles('./pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
pnpm-store-
- name: Setup hugo cache
uses: actions/cache@v4
- name: Setup Hugo cache
uses: actions/cache@v5
with:
path: ./exampleSite/resources
key: ${{ runner.os }}-hugo-${{ matrix.hugo-version }}-${{ hashFiles('./exampleSite') }}
path: exampleSite/resources/_gen
key: hugo-${{ matrix.hugo-version }}-${{ hashFiles('./exampleSite/**/*.jpg') }}
restore-keys: |
${{ runner.os }}-hugo-${{ matrix.hugo-version }}-
hugo-${{ matrix.hugo-version }}-
- name: Install dependencies
run: pnpm install
- name: Install project dependencies
run: 'pnpm install'
- name: Pre-build cleanup
if: >
matrix.hugo-version == 'latest' &&
(github.event_name == 'push' || github.event.pull_request.merged == true)
run: 'rm -rf bundled'
- name: Build
run: |
pnpm run build
run: 'pnpm run build'
- name: Push artifacts
if: >
matrix.hugo-version == 'latest' &&
(github.event_name == 'push' || github.event.pull_request.merged == true) &&
needs.filter.outputs.any_changed == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
(github.event_name == 'push' || github.event.pull_request.merged == true)
uses: stefanzweifel/git-auto-commit-action@v7
with:
file_pattern: 'bundled/**/*.js bundled/**/*.css'
commit_message: 'ci: update bundled artifacts [skip ci]'

View File

@@ -46,11 +46,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -64,7 +64,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -77,6 +77,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: '/language:${{matrix.language}}'

View File

@@ -1,57 +0,0 @@
name: 'ESLint && Prettier'
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
lint:
runs-on: ubuntu-latest
name: Lint
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.PAT || github.token }}
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('./pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Lint Check
continue-on-error: true
id: check
run: pnpm run lint:check
- name: Format manually
id: format
if: ${{ steps.check.outcome == 'failure' }}
run: pnpm run lint
- name: Commit
if: ${{ steps.format.outcome == 'success' }}
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'ci: format code [skip ci]'

58
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: 'Lint'
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
lint:
runs-on: ubuntu-latest
name: Lint
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# github.token as a fallback, since other user might trigger this
# workflow in their pull request
token: ${{ secrets.PAT || github.token }}
- name: Setup Mise
uses: jdx/mise-action@v4
with:
install_args: node@latest pnpm@10
tool_versions: |
node latest
pnpm 10
cache: true
- name: Get pnpm store path
id: pnpm-cache
run: 'echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT'
- name: Setup pnpm cache
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: pnpm-store-${{ hashFiles('./pnpm-lock.yaml') }}
restore-keys: |
pnpm-store-
- name: Install project dependencies
run: 'pnpm install'
- name: Lint
id: lint
run: 'pnpm run lint:check || pnpm run lint'
- name: Commit
if: ${{ steps.format.lint == 'success' }}
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: 'ci: lint [skip ci]'

2
.gitignore vendored
View File

@@ -27,4 +27,4 @@ jsconfig.json
*.css.map
# dummmy file
bundled/css/critical.js
bundled/js/critical.js

View File

@@ -9,7 +9,7 @@ Here is a [live demo](https://bridget-demo.sped0n.com).
![thumbnail](https://raw.githubusercontent.com/Sped0n/bridget/main/images/tn.jpg)
> [!NOTE]
> This repository is currently in **maintaince mode** for two reasons:
> This repository is currently in **maintenance mode** for two reasons:
>
> 1. I want to keep this theme minimal.
> 2. My bandwith after work is limited.

View File

@@ -4,6 +4,7 @@
position: fixed;
top: var(--nav-height);
z-index: var(--z-nav-gallery);
overflow: hidden;
display: flex;
flex-direction: column;
@@ -25,8 +26,9 @@
}
img {
width: 100%;
height: 100%;
max-height: calc(var(--window-height) - 2 * var(--nav-height));
max-width: 100%;
width: auto;
object-fit: contain;
}
@@ -49,6 +51,21 @@
display: flex;
justify-content: space-between;
align-items: center;
.navClose {
cursor: pointer;
z-index: calc(var(--z-nav-gallery) + 1);
min-width: 25%;
height: calc(var(--nav-height) * 2.5);
display: flex;
align-items: center;
justify-content: flex-end;
margin-right: calc(var(--space-standard) * -1);
padding-right: var(--space-standard);
}
}
}

91
assets/ts/configState.tsx Normal file
View File

@@ -0,0 +1,91 @@
import {
createContext,
createMemo,
createSignal,
useContext,
type Accessor,
type JSX
} from 'solid-js'
import invariant from 'tiny-invariant'
import { getThresholdSessionIndex } from './utils'
export interface ThresholdRelated {
threshold: number
trailLength: number
}
export interface ConfigState {
thresholdIndex: number
threshold: number
trailLength: number
}
export type ConfigStateContextType = readonly [
Accessor<ConfigState>,
{
readonly incThreshold: () => void
readonly decThreshold: () => void
}
]
const thresholds: ThresholdRelated[] = [
{ threshold: 20, trailLength: 20 },
{ threshold: 40, trailLength: 10 },
{ threshold: 80, trailLength: 5 },
{ threshold: 140, trailLength: 5 },
{ threshold: 200, trailLength: 5 }
]
const ConfigStateContext = createContext<ConfigStateContextType>()
function getSafeThresholdIndex(): number {
const index = getThresholdSessionIndex()
if (index < 0 || index >= thresholds.length) return 2
return index
}
export function ConfigStateProvider(props: { children?: JSX.Element }): JSX.Element {
const [thresholdIndex, setThresholdIndex] = createSignal(getSafeThresholdIndex())
const state = createMemo<ConfigState>(() => {
const current = thresholds[thresholdIndex()]
return {
thresholdIndex: thresholdIndex(),
threshold: current.threshold,
trailLength: current.trailLength
}
})
const updateThreshold = (stride: number): void => {
const nextIndex = thresholdIndex() + stride
if (nextIndex < 0 || nextIndex >= thresholds.length) return
sessionStorage.setItem('thresholdsIndex', nextIndex.toString())
setThresholdIndex(nextIndex)
}
return (
<ConfigStateContext.Provider
value={[
state,
{
incThreshold: () => {
updateThreshold(1)
},
decThreshold: () => {
updateThreshold(-1)
}
}
]}
>
{props.children}
</ConfigStateContext.Provider>
)
}
export function useConfigState(): ConfigStateContextType {
const context = useContext(ConfigStateContext)
invariant(context, 'undefined config context')
return context
}

View File

@@ -4,7 +4,6 @@ export default function CustomCursor(props: {
children?: JSX.Element
active: Accessor<boolean>
cursorText: Accessor<string>
isOpen: Accessor<boolean>
}): JSX.Element {
// types
interface XY {

View File

@@ -1,12 +1,12 @@
import { Show, createMemo, createSignal, type JSX } from 'solid-js'
import { Show, createMemo, type JSX } from 'solid-js'
import type { ImageJSON } from '../resources'
import type { Vector } from '../utils'
import { useImageState } from '../imageState'
import CustomCursor from './customCursor'
import Nav from './nav'
import Stage from './stage'
import StageNav from './stageNav'
import { useDesktopState } from './state'
/**
* interfaces and types
@@ -23,65 +23,36 @@ export interface DesktopImage extends HTMLImageElement {
}
}
export interface HistoryItem {
i: number
x: number
y: number
}
/**
* components
*/
export default function Desktop(props: {
children?: JSX.Element
ijs: ImageJSON[]
prevText: string
closeText: string
nextText: string
loadingText: string
}): JSX.Element {
const [cordHist, setCordHist] = createSignal<HistoryItem[]>([])
const [isLoading, setIsLoading] = createSignal(false)
const [isOpen, setIsOpen] = createSignal(false)
const [isAnimating, setIsAnimating] = createSignal(false)
const [hoverText, setHoverText] = createSignal('')
const [navVector, setNavVector] = createSignal<Vector>('none')
const imageState = useImageState()
const [desktop] = useDesktopState()
const active = createMemo(() => isOpen() && !isAnimating())
const cursorText = createMemo(() => (isLoading() ? props.loadingText : hoverText()))
const active = createMemo(() => desktop.isOpen() && !desktop.isAnimating())
const cursorText = createMemo(() =>
desktop.isLoading() ? props.loadingText : desktop.hoverText()
)
return (
<>
<Nav />
<Show when={props.ijs.length > 0}>
<Stage
ijs={props.ijs}
setIsLoading={setIsLoading}
isOpen={isOpen}
setIsOpen={setIsOpen}
isAnimating={isAnimating}
setIsAnimating={setIsAnimating}
cordHist={cordHist}
setCordHist={setCordHist}
navVector={navVector}
setNavVector={setNavVector}
/>
<Show when={isOpen()}>
<CustomCursor cursorText={cursorText} active={active} isOpen={isOpen} />
<Show when={imageState().length > 0}>
<Stage />
<Show when={desktop.isOpen()}>
<CustomCursor cursorText={cursorText} active={active} />
<StageNav
prevText={props.prevText}
closeText={props.closeText}
nextText={props.nextText}
loadingText={props.loadingText}
active={active}
isAnimating={isAnimating}
setCordHist={setCordHist}
isOpen={isOpen}
setIsOpen={setIsOpen}
setHoverText={setHoverText}
navVector={navVector}
setNavVector={setNavVector}
/>
</Show>
</Show>

View File

@@ -1,66 +1,68 @@
import { createEffect } from 'solid-js'
import { createEffect, onCleanup, onMount } from 'solid-js'
import { useState } from '../state'
import { useConfigState } from '../configState'
import { useImageState } from '../imageState'
import { expand } from '../utils'
/**
* constants
*/
// threshold div
const thresholdDiv = document.getElementsByClassName('threshold')[0] as HTMLDivElement
// threshold nums span
const thresholdDispNums = Array.from(
thresholdDiv.getElementsByClassName('num')
) as HTMLSpanElement[]
// threshold buttons
const decButton = thresholdDiv
.getElementsByClassName('dec')
.item(0) as HTMLButtonElement
const incButton = thresholdDiv
.getElementsByClassName('inc')
.item(0) as HTMLButtonElement
// index div
const indexDiv = document.getElementsByClassName('index').item(0) as HTMLDivElement
// index nums span
const indexDispNums = Array.from(
indexDiv.getElementsByClassName('num')
) as HTMLSpanElement[]
/**
* helper functions
*/
function updateThresholdText(thresholdValue: string): void {
thresholdDispNums.forEach((e: HTMLSpanElement, i: number) => {
e.innerText = thresholdValue[i]
})
}
function updateIndexText(indexValue: string, indexLength: string): void {
indexDispNums.forEach((e: HTMLSpanElement, i: number) => {
if (i < 4) {
e.innerText = indexValue[i]
} else {
e.innerText = indexLength[i - 4]
}
})
}
/**
* Nav component
*/
import { useDesktopState } from './state'
export default function Nav(): null {
const [state, { incThreshold, decThreshold }] = useState()
let thresholdNums: HTMLSpanElement[] = []
let indexNums: HTMLSpanElement[] = []
let decButton: HTMLButtonElement | undefined
let incButton: HTMLButtonElement | undefined
let controller: AbortController | undefined
createEffect(() => {
updateIndexText(expand(state().index + 1), expand(state().length))
updateThresholdText(expand(state().threshold))
const imageState = useImageState()
const [config, { incThreshold, decThreshold }] = useConfigState()
const [desktop] = useDesktopState()
const updateThresholdText = (thresholdValue: string): void => {
thresholdNums.forEach((element, i) => {
element.innerText = thresholdValue[i]
})
}
const updateIndexText = (indexValue: string, indexLength: string): void => {
indexNums.forEach((element, i) => {
if (i < 4) {
element.innerText = indexValue[i]
} else {
element.innerText = indexLength[i - 4]
}
})
}
onMount(() => {
const thresholdDiv = document.getElementsByClassName(
'threshold'
)[0] as HTMLDivElement
const indexDiv = document.getElementsByClassName('index').item(0) as HTMLDivElement
thresholdNums = Array.from(
thresholdDiv.getElementsByClassName('num')
) as HTMLSpanElement[]
indexNums = Array.from(indexDiv.getElementsByClassName('num')) as HTMLSpanElement[]
decButton = thresholdDiv.getElementsByClassName('dec').item(0) as HTMLButtonElement
incButton = thresholdDiv.getElementsByClassName('inc').item(0) as HTMLButtonElement
controller = new AbortController()
const signal = controller.signal
decButton.addEventListener('click', decThreshold, { signal })
incButton.addEventListener('click', incThreshold, { signal })
})
decButton.onclick = decThreshold
incButton.onclick = incThreshold
createEffect(() => {
if (thresholdNums.length === 0 || indexNums.length === 0) return
updateIndexText(expand(desktop.index() + 1), expand(imageState().length))
updateThresholdText(expand(config().threshold))
})
onCleanup(() => {
controller?.abort()
})
return null
}

View File

@@ -1,409 +1,167 @@
import { type gsap } from 'gsap'
import {
For,
createEffect,
on,
onMount,
type Accessor,
type JSX,
type Setter
} from 'solid-js'
import { For, createEffect, on, onMount, type JSX } from 'solid-js'
import type { ImageJSON } from '../resources'
import { useState, type State } from '../state'
import { decrement, increment, loadGsap, type Vector } from '../utils'
import { useConfigState } from '../configState'
import { useImageState } from '../imageState'
import { increment, loadGsap } from '../utils'
import type { DesktopImage, HistoryItem } from './layout'
import type { DesktopImage } from './layout'
import { expandStage, minimizeStage, syncStagePosition } from './stageAnimations'
import { onMutation } from './stageUtils'
import { useDesktopState } from './state'
/**
* helper functions
*/
function getTrailElsIndex(cordHistValue: HistoryItem[]): number[] {
return cordHistValue.map((el) => el.i)
}
function getTrailCurrentElsIndex(
cordHistValue: HistoryItem[],
stateValue: State
): number[] {
return getTrailElsIndex(cordHistValue).slice(-stateValue.trailLength)
}
function getTrailInactiveElsIndex(
cordHistValue: HistoryItem[],
stateValue: State
): number[] {
return getTrailCurrentElsIndex(cordHistValue, stateValue).slice(0, -1)
}
function getCurrentElIndex(cordHistValue: HistoryItem[]): number {
return getTrailElsIndex(cordHistValue).slice(-1)[0]
}
function getPrevElIndex(cordHistValue: HistoryItem[], stateValue: State): number {
return decrement(cordHistValue.slice(-1)[0].i, stateValue.length)
}
function getNextElIndex(cordHistValue: HistoryItem[], stateValue: State): number {
return increment(cordHistValue.slice(-1)[0].i, stateValue.length)
}
function getImagesFromIndexes(imgs: DesktopImage[], indexes: number[]): DesktopImage[] {
return indexes.map((i) => imgs[i])
}
function hires(imgs: DesktopImage[]): void {
imgs.forEach((img) => {
if (img.src === img.dataset.hiUrl) return
img.src = img.dataset.hiUrl
img.height = parseInt(img.dataset.hiImgH)
img.width = parseInt(img.dataset.hiImgW)
})
}
function lores(imgs: DesktopImage[]): void {
imgs.forEach((img) => {
if (img.src === img.dataset.loUrl) return
img.src = img.dataset.loUrl
img.height = parseInt(img.dataset.loImgH)
img.width = parseInt(img.dataset.loImgW)
})
}
function onMutation<T extends HTMLElement>(
element: T,
trigger: (arg0: MutationRecord) => boolean,
observeOptions: MutationObserverInit = { attributes: true }
): void {
new MutationObserver((mutations, observer) => {
for (const mutation of mutations) {
if (trigger(mutation)) {
observer.disconnect()
break
}
}
}).observe(element, observeOptions)
}
/**
* Stage component
*/
export default function Stage(props: {
ijs: ImageJSON[]
setIsLoading: Setter<boolean>
isOpen: Accessor<boolean>
setIsOpen: Setter<boolean>
isAnimating: Accessor<boolean>
setIsAnimating: Setter<boolean>
cordHist: Accessor<HistoryItem[]>
setCordHist: Setter<HistoryItem[]>
navVector: Accessor<Vector>
setNavVector: Setter<Vector>
}): JSX.Element {
// variables
export default function Stage(): JSX.Element {
let _gsap: typeof gsap
let gsapPromise: Promise<void> | undefined
// eslint-disable-next-line solid/reactivity
const imgs: DesktopImage[] = Array<DesktopImage>(props.ijs.length)
const imageState = useImageState()
const [config] = useConfigState()
const [
desktop,
{ setIndex, setCordHist, setIsOpen, setIsAnimating, setIsLoading, setNavVector }
] = useDesktopState()
const imgs: DesktopImage[] = Array<DesktopImage>(imageState().length)
let last = { x: 0, y: 0 }
let abortController: AbortController | undefined
// states
let gsapLoaded = false
const [state, { incIndex }] = useState()
const stateLength = state().length
let mounted = false
const ensureGsapReady: () => Promise<void> = async () => {
if (gsapPromise !== undefined) return await gsapPromise
gsapPromise = loadGsap()
.then((g) => {
_gsap = g
gsapLoaded = true
})
.catch((e) => {
gsapPromise = undefined
console.log(e)
})
await gsapPromise
}
const onMouse: (e: MouseEvent) => void = (e) => {
if (props.isOpen() || props.isAnimating() || !gsapLoaded || !mounted) return
if (desktop.isOpen() || desktop.isAnimating() || !gsapLoaded || !mounted) return
const length = imageState().length
if (length <= 0) return
const cord = { x: e.clientX, y: e.clientY }
const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y)
if (travelDist > state().threshold) {
last = cord
incIndex()
if (travelDist > config().threshold) {
const nextIndex = increment(desktop.index(), length)
const _state = state()
const newHist = { i: _state.index, ...cord }
props.setCordHist((prev) => [...prev, newHist].slice(-stateLength))
last = cord
setIndex(nextIndex)
setCordHist((prev) => [...prev, { i: nextIndex, ...cord }].slice(-length))
}
}
const onClick: () => void = () => {
if (!props.isAnimating()) props.setIsOpen(true)
const onClick: () => Promise<void> = async () => {
if (!gsapLoaded) {
await ensureGsapReady()
}
if (desktop.isAnimating() || !gsapLoaded) return
if (desktop.index() < 0 || desktop.cordHist().length === 0) return
setIsOpen(true)
}
const setPosition: () => void = () => {
if (!mounted) return
if (imgs.length === 0) return
const _cordHist = props.cordHist()
const trailElsIndex = getTrailElsIndex(_cordHist)
if (trailElsIndex.length === 0) return
const elsTrail = getImagesFromIndexes(imgs, trailElsIndex)
const _isOpen = props.isOpen()
const _state = state()
_gsap.set(elsTrail, {
x: (i: number) => _cordHist[i].x - window.innerWidth / 2,
y: (i: number) => _cordHist[i].y - window.innerHeight / 2,
opacity: (i: number) =>
Math.max(
(i + 1 + _state.trailLength <= _cordHist.length ? 0 : 1) - (_isOpen ? 1 : 0),
0
),
zIndex: (i: number) => i,
scale: 0.6
})
if (_isOpen) {
const elc = getImagesFromIndexes(imgs, [getCurrentElIndex(_cordHist)])[0]
const indexArrayToHires: number[] = []
const indexArrayToCleanup: number[] = []
switch (props.navVector()) {
case 'prev':
indexArrayToHires.push(getPrevElIndex(_cordHist, _state))
indexArrayToCleanup.push(getNextElIndex(_cordHist, _state))
break
case 'next':
indexArrayToHires.push(getNextElIndex(_cordHist, _state))
indexArrayToCleanup.push(getPrevElIndex(_cordHist, _state))
break
default:
break
}
hires(getImagesFromIndexes(imgs, indexArrayToHires)) // preload
_gsap.set(getImagesFromIndexes(imgs, indexArrayToCleanup), { opacity: 0 })
_gsap.set(elc, { x: 0, y: 0, scale: 1 }) // set current to center
setLoaderForHiresImage(elc) // set loader, if loaded set current opacity to 1
} else {
lores(elsTrail)
}
}
const expandImage: () => Promise<
gsap.core.Omit<gsap.core.Timeline, 'then'>
> = async () => {
// isAnimating is prechecked in isOpen effect
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
props.setIsAnimating(true)
const _cordHist = props.cordHist()
const _state = state()
const elcIndex = getCurrentElIndex(_cordHist)
const elc = imgs[elcIndex]
// don't hide here because we want a better transition
hires(
getImagesFromIndexes(imgs, [
elcIndex,
getPrevElIndex(_cordHist, _state),
getNextElIndex(_cordHist, _state)
])
)
setLoaderForHiresImage(elc)
const tl = _gsap.timeline()
const trailInactiveEls = getImagesFromIndexes(
syncStagePosition({
gsap: _gsap,
imgs,
getTrailInactiveElsIndex(_cordHist, _state)
)
// move down and hide trail inactive
tl.to(trailInactiveEls, {
y: '+=20',
ease: 'power3.in',
stagger: 0.075,
duration: 0.3,
delay: 0.1,
opacity: 0
})
// current move to center
tl.to(elc, {
x: 0,
y: 0,
ease: 'power3.inOut',
duration: 0.7,
delay: 0.3
})
// current expand
tl.to(elc, {
delay: 0.1,
scale: 1,
ease: 'power3.inOut'
})
// finished
// eslint-disable-next-line solid/reactivity
return await tl.then(() => {
props.setIsAnimating(false)
cordHist: desktop.cordHist(),
trailLength: config().trailLength,
length: imageState().length,
isOpen: desktop.isOpen(),
navVector: desktop.navVector(),
mounted,
setIsLoading
})
}
const minimizeImage: () => Promise<
gsap.core.Omit<gsap.core.Timeline, 'then'>
> = async () => {
const expandImage: () => Promise<void> = async () => {
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
props.setIsAnimating(true)
props.setNavVector('none') // cleanup
const _cordHist = props.cordHist()
const _state = state()
const elcIndex = getCurrentElIndex(_cordHist)
const elsTrailInactiveIndexes = getTrailInactiveElsIndex(_cordHist, _state)
lores(getImagesFromIndexes(imgs, [...elsTrailInactiveIndexes, elcIndex]))
const tl = _gsap.timeline()
const elc = getImagesFromIndexes(imgs, [elcIndex])[0]
const elsTrailInactive = getImagesFromIndexes(imgs, elsTrailInactiveIndexes)
// shrink current
tl.to(elc, {
scale: 0.6,
duration: 0.6,
ease: 'power3.inOut'
})
// move current to original position
tl.to(elc, {
delay: 0.3,
duration: 0.7,
ease: 'power3.inOut',
x: _cordHist.slice(-1)[0].x - window.innerWidth / 2,
y: _cordHist.slice(-1)[0].y - window.innerHeight / 2
})
// show trail inactive
tl.to(elsTrailInactive, {
y: '-=20',
ease: 'power3.out',
stagger: -0.1,
duration: 0.3,
opacity: 1
})
// eslint-disable-next-line solid/reactivity
return await tl.then(() => {
props.setIsAnimating(false)
await expandStage({
gsap: _gsap,
imgs,
cordHist: desktop.cordHist(),
trailLength: config().trailLength,
length: imageState().length,
mounted,
setIsLoading,
setIsAnimating
})
}
function setLoaderForHiresImage(img: DesktopImage): void {
if (!mounted || !gsapLoaded) return
if (!img.complete) {
props.setIsLoading(true)
// abort controller for cleanup
const controller = new AbortController()
const abortSignal = controller.signal
// event listeners
img.addEventListener(
'load',
() => {
_gsap
.to(img, { opacity: 1, ease: 'power3.out', duration: 0.5 })
// eslint-disable-next-line solid/reactivity
.then(() => {
props.setIsLoading(false)
})
.catch((e) => {
console.log(e)
})
.finally(() => {
controller.abort()
})
},
{ once: true, passive: true, signal: abortSignal }
)
img.addEventListener(
'error',
() => {
_gsap
.set(img, { opacity: 1 })
// eslint-disable-next-line solid/reactivity
.then(() => {
props.setIsLoading(false)
})
.catch((e) => {
console.log(e)
})
.finally(() => {
controller.abort()
})
},
{ once: true, passive: true, signal: abortSignal }
)
} else {
_gsap
.set(img, { opacity: 1 })
// eslint-disable-next-line solid/reactivity
.then(() => {
props.setIsLoading(false)
})
.catch((e) => {
console.log(e)
})
}
const minimizeImage: () => Promise<void> = async () => {
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
setNavVector('none')
await minimizeStage({
gsap: _gsap,
imgs,
cordHist: desktop.cordHist(),
trailLength: config().trailLength,
mounted,
setIsAnimating
})
}
onMount(() => {
// preload logic
imgs.forEach((img, i) => {
// preload first 5 images on page load
if (i < 5) {
img.src = img.dataset.loUrl
}
// lores preloader for rest of the images
// eslint-disable-next-line solid/reactivity
onMutation(img, (mutation) => {
// if open or animating, hold
if (props.isOpen() || props.isAnimating()) return false
// if mutation is not about style attribute, hold
if (desktop.isOpen() || desktop.isAnimating()) return false
if (mutation.attributeName !== 'style') return false
const opacity = parseFloat(img.style.opacity)
// if opacity is not 1, hold
if (opacity !== 1) return false
// preload the i + 5th image, if it exists
if (i + 5 < imgs.length) {
imgs[i + 5].src = imgs[i + 5].dataset.loUrl
}
// triggered
return true
})
})
// load gsap on mousemove
window.addEventListener(
'mousemove',
() => {
loadGsap()
.then((g) => {
_gsap = g
gsapLoaded = true
})
.catch((e) => {
console.log(e)
})
},
{ passive: true, once: true }
)
// event listeners
window.addEventListener('pointermove', () => void ensureGsapReady(), {
passive: true,
once: true
})
window.addEventListener('pointerdown', () => void ensureGsapReady(), {
passive: true,
once: true
})
window.addEventListener('click', () => void ensureGsapReady(), {
passive: true,
once: true
})
abortController = new AbortController()
const abortSignal = abortController.signal
window.addEventListener('mousemove', onMouse, {
passive: true,
signal: abortSignal
})
// mounted
mounted = true
})
createEffect(
on(
() => props.cordHist(),
() => desktop.cordHist(),
() => {
setPosition()
},
@@ -413,36 +171,38 @@ export default function Stage(props: {
createEffect(
on(
() => props.isOpen(),
async () => {
if (props.isAnimating()) return
if (props.isOpen()) {
// expand image
desktop.isOpen,
async (isOpen) => {
if (desktop.isAnimating()) return
if (isOpen) {
if (desktop.index() < 0 || desktop.cordHist().length === 0) {
setIsOpen(false)
return
}
await expandImage()
.catch(() => {
void 0
setIsOpen(false)
setIsAnimating(false)
setIsLoading(false)
})
.then(() => {
// abort controller for cleanup
abortController?.abort()
})
} else {
// minimize image
await minimizeImage()
.catch(() => {
void 0
})
// eslint-disable-next-line solid/reactivity
.then(() => {
// event listeners and its abort controller
abortController = new AbortController()
const abortSignal = abortController.signal
window.addEventListener('mousemove', onMouse, {
passive: true,
signal: abortSignal
})
// cleanup isLoading
props.setIsLoading(false)
setIsLoading(false)
})
}
},
@@ -453,7 +213,7 @@ export default function Stage(props: {
return (
<>
<div class="stage" onClick={onClick} onKeyDown={onClick}>
<For each={props.ijs}>
<For each={imageState().images}>
{(ij, i) => (
<img
ref={imgs[i()]}

View File

@@ -0,0 +1,263 @@
import { type gsap } from 'gsap'
import type { Vector } from '../utils'
import type { DesktopImage } from './layout'
import {
getCurrentElIndex,
getImagesFromIndexes,
getNextElIndex,
getPrevElIndex,
getTrailElsIndex,
getTrailInactiveElsIndex,
hires,
lores
} from './stageUtils'
import type { HistoryItem } from './state'
type SetLoading = (value: boolean) => void
export function setLoaderForHiresImage(args: {
gsap: typeof gsap
img: DesktopImage
mounted: boolean
setIsLoading: SetLoading
}): void {
const { gsap, img, mounted, setIsLoading } = args
if (!mounted) return
if (img.complete) {
gsap
.set(img, { opacity: 1 })
.then(() => {
setIsLoading(false)
})
.catch((e) => {
console.log(e)
})
return
}
setIsLoading(true)
const controller = new AbortController()
const abortSignal = controller.signal
img.addEventListener(
'load',
() => {
gsap
.to(img, { opacity: 1, ease: 'power3.out', duration: 0.5 })
.then(() => {
setIsLoading(false)
})
.catch((e) => {
console.log(e)
})
.finally(() => {
controller.abort()
})
},
{ once: true, passive: true, signal: abortSignal }
)
img.addEventListener(
'error',
() => {
gsap
.set(img, { opacity: 1 })
.then(() => {
setIsLoading(false)
})
.catch((e) => {
console.log(e)
})
.finally(() => {
controller.abort()
})
},
{ once: true, passive: true, signal: abortSignal }
)
}
export function syncStagePosition(args: {
gsap: typeof gsap
imgs: DesktopImage[]
cordHist: HistoryItem[]
trailLength: number
length: number
isOpen: boolean
navVector: Vector
mounted: boolean
setIsLoading: SetLoading
}): void {
const {
gsap,
imgs,
cordHist,
trailLength,
length,
isOpen,
navVector,
mounted,
setIsLoading
} = args
if (!mounted || imgs.length === 0) return
const trailElsIndex = getTrailElsIndex(cordHist)
if (trailElsIndex.length === 0) return
const elsTrail = getImagesFromIndexes(imgs, trailElsIndex)
gsap.set(elsTrail, {
x: (i: number) => cordHist[i].x - window.innerWidth / 2,
y: (i: number) => cordHist[i].y - window.innerHeight / 2,
opacity: (i: number) =>
Math.max((i + 1 + trailLength <= cordHist.length ? 0 : 1) - (isOpen ? 1 : 0), 0),
zIndex: (i: number) => i,
scale: 0.6
})
if (!isOpen) {
lores(elsTrail)
return
}
const current = getImagesFromIndexes(imgs, [getCurrentElIndex(cordHist)])[0]
const indexArrayToHires: number[] = []
const indexArrayToCleanup: number[] = []
switch (navVector) {
case 'prev':
indexArrayToHires.push(getPrevElIndex(cordHist, length))
indexArrayToCleanup.push(getNextElIndex(cordHist, length))
break
case 'next':
indexArrayToHires.push(getNextElIndex(cordHist, length))
indexArrayToCleanup.push(getPrevElIndex(cordHist, length))
break
default:
break
}
hires(getImagesFromIndexes(imgs, indexArrayToHires))
gsap.set(getImagesFromIndexes(imgs, indexArrayToCleanup), { opacity: 0 })
gsap.set(current, { x: 0, y: 0, scale: 1 })
setLoaderForHiresImage({ gsap, img: current, mounted, setIsLoading })
}
export async function expandStage(args: {
gsap: typeof gsap
imgs: DesktopImage[]
cordHist: HistoryItem[]
trailLength: number
length: number
mounted: boolean
setIsLoading: SetLoading
setIsAnimating: (value: boolean) => void
}): Promise<void> {
const {
gsap,
imgs,
cordHist,
trailLength,
length,
mounted,
setIsLoading,
setIsAnimating
} = args
if (!mounted) throw new Error('not mounted')
setIsAnimating(true)
const currentIndex = getCurrentElIndex(cordHist)
const current = imgs[currentIndex]
hires(
getImagesFromIndexes(imgs, [
currentIndex,
getPrevElIndex(cordHist, length),
getNextElIndex(cordHist, length)
])
)
setLoaderForHiresImage({ gsap, img: current, mounted, setIsLoading })
const tl = gsap.timeline()
const trailInactiveEls = getImagesFromIndexes(
imgs,
getTrailInactiveElsIndex(cordHist, trailLength)
)
tl.to(trailInactiveEls, {
y: '+=20',
ease: 'power3.in',
stagger: 0.075,
duration: 0.3,
delay: 0.1,
opacity: 0
})
tl.to(current, {
x: 0,
y: 0,
ease: 'power3.inOut',
duration: 0.7,
delay: 0.3
})
tl.to(current, {
delay: 0.1,
scale: 1,
ease: 'power3.inOut'
})
await tl.then(() => {
setIsAnimating(false)
})
}
export async function minimizeStage(args: {
gsap: typeof gsap
imgs: DesktopImage[]
cordHist: HistoryItem[]
trailLength: number
mounted: boolean
setIsAnimating: (value: boolean) => void
}): Promise<void> {
const { gsap, imgs, cordHist, trailLength, mounted, setIsAnimating } = args
if (!mounted) throw new Error('not mounted')
setIsAnimating(true)
const currentIndex = getCurrentElIndex(cordHist)
const trailInactiveIndexes = getTrailInactiveElsIndex(cordHist, trailLength)
lores(getImagesFromIndexes(imgs, [...trailInactiveIndexes, currentIndex]))
const tl = gsap.timeline()
const current = getImagesFromIndexes(imgs, [currentIndex])[0]
const trailInactiveEls = getImagesFromIndexes(imgs, trailInactiveIndexes)
tl.to(current, {
scale: 0.6,
duration: 0.6,
ease: 'power3.inOut'
})
tl.to(current, {
delay: 0.3,
duration: 0.7,
ease: 'power3.inOut',
x: cordHist.slice(-1)[0].x - window.innerWidth / 2,
y: cordHist.slice(-1)[0].y - window.innerHeight / 2
})
tl.to(trailInactiveEls, {
y: '-=20',
ease: 'power3.out',
stagger: -0.1,
duration: 0.3,
opacity: 1
})
await tl.then(() => {
setIsAnimating(false)
})
}

View File

@@ -1,24 +1,15 @@
import { For, createEffect, type Accessor, type JSX, type Setter } from 'solid-js'
import { For, createEffect, createMemo, on, onCleanup, type JSX } from 'solid-js'
import { useState } from '../state'
import { decrement, increment, type Vector } from '../utils'
import { useImageState } from '../imageState'
import { decrement, increment } from '../utils'
import type { HistoryItem } from './layout'
import { useDesktopState } from './state'
export default function StageNav(props: {
children?: JSX.Element
prevText: string
closeText: string
nextText: string
loadingText: string
active: Accessor<boolean>
isAnimating: Accessor<boolean>
setCordHist: Setter<HistoryItem[]>
isOpen: Accessor<boolean>
setIsOpen: Setter<boolean>
setHoverText: Setter<string>
navVector: Accessor<Vector>
setNavVector: Setter<Vector>
}): JSX.Element {
// types
type NavItem = (typeof navItems)[number]
@@ -29,64 +20,74 @@ export default function StageNav(props: {
const navItems = [props.prevText, props.closeText, props.nextText] as const
// states
const [state, { incIndex, decIndex }] = useState()
const imageState = useImageState()
const [
desktop,
{ incIndex, decIndex, setCordHist, setHoverText, setIsOpen, setNavVector }
] = useDesktopState()
const stateLength = state().length
const active = createMemo(() => desktop.isOpen() && !desktop.isAnimating())
const prevImage: () => void = () => {
props.setNavVector('prev')
props.setCordHist((c) =>
setNavVector('prev')
setCordHist((c) =>
c.map((item) => {
return { ...item, i: decrement(item.i, stateLength) }
return { ...item, i: decrement(item.i, imageState().length) }
})
)
decIndex()
}
const closeImage: () => void = () => {
props.setIsOpen(false)
setIsOpen(false)
}
const nextImage: () => void = () => {
props.setNavVector('next')
props.setCordHist((c) =>
setNavVector('next')
setCordHist((c) =>
c.map((item) => {
return { ...item, i: increment(item.i, stateLength) }
return { ...item, i: increment(item.i, imageState().length) }
})
)
incIndex()
}
const handleClick: (item: NavItem) => void = (item) => {
if (!props.isOpen() || props.isAnimating()) return
if (!desktop.isOpen() || desktop.isAnimating()) return
if (item === navItems[0]) prevImage()
else if (item === navItems[1]) closeImage()
else nextImage()
}
const handleKey: (e: KeyboardEvent) => void = (e) => {
if (!props.isOpen() || props.isAnimating()) return
if (!desktop.isOpen() || desktop.isAnimating()) return
if (e.key === 'ArrowLeft') prevImage()
else if (e.key === 'Escape') closeImage()
else if (e.key === 'ArrowRight') nextImage()
}
createEffect(() => {
if (props.isOpen()) {
controller = new AbortController()
const abortSignal = controller.signal
window.addEventListener('keydown', handleKey, {
passive: true,
signal: abortSignal
})
} else {
createEffect(
on(desktop.isOpen, (isOpen) => {
controller?.abort()
}
if (isOpen) {
controller = new AbortController()
const abortSignal = controller.signal
window.addEventListener('keydown', handleKey, {
passive: true,
signal: abortSignal
})
}
})
)
onCleanup(() => {
controller?.abort()
})
return (
<>
<div class="navOverlay" classList={{ active: props.active() }}>
<div class="navOverlay" classList={{ active: active() }}>
<For each={navItems}>
{(item) => (
<div
@@ -94,8 +95,8 @@ export default function StageNav(props: {
onClick={() => {
handleClick(item)
}}
onFocus={() => props.setHoverText(item)}
onMouseOver={() => props.setHoverText(item)}
onFocus={() => setHoverText(item)}
onMouseOver={() => setHoverText(item)}
tabIndex="-1"
/>
)}

View File

@@ -0,0 +1,67 @@
import { decrement, increment } from '../utils'
import type { DesktopImage } from './layout'
import type { HistoryItem } from './state'
export function getTrailElsIndex(cordHistValue: HistoryItem[]): number[] {
return cordHistValue.map((el) => el.i)
}
export function getTrailInactiveElsIndex(
cordHistValue: HistoryItem[],
trailLength: number
): number[] {
return getTrailElsIndex(cordHistValue).slice(-trailLength).slice(0, -1)
}
export function getCurrentElIndex(cordHistValue: HistoryItem[]): number {
return getTrailElsIndex(cordHistValue).slice(-1)[0]
}
export function getPrevElIndex(cordHistValue: HistoryItem[], length: number): number {
return decrement(cordHistValue.slice(-1)[0].i, length)
}
export function getNextElIndex(cordHistValue: HistoryItem[], length: number): number {
return increment(cordHistValue.slice(-1)[0].i, length)
}
export function getImagesFromIndexes(
imgs: DesktopImage[],
indexes: number[]
): DesktopImage[] {
return indexes.map((i) => imgs[i])
}
export function hires(imgs: DesktopImage[]): void {
imgs.forEach((img) => {
if (img.src === img.dataset.hiUrl) return
img.src = img.dataset.hiUrl
img.height = parseInt(img.dataset.hiImgH)
img.width = parseInt(img.dataset.hiImgW)
})
}
export function lores(imgs: DesktopImage[]): void {
imgs.forEach((img) => {
if (img.src === img.dataset.loUrl) return
img.src = img.dataset.loUrl
img.height = parseInt(img.dataset.loImgH)
img.width = parseInt(img.dataset.loImgW)
})
}
export function onMutation<T extends HTMLElement>(
element: T,
trigger: (arg0: MutationRecord) => boolean,
observeOptions: MutationObserverInit = { attributes: true }
): void {
new MutationObserver((mutations, observer) => {
for (const mutation of mutations) {
if (trigger(mutation)) {
observer.disconnect()
break
}
}
}).observe(element, observeOptions)
}

View File

@@ -0,0 +1,96 @@
import {
createComponent,
createContext,
createSignal,
useContext,
type Accessor,
type JSX,
type Setter
} from 'solid-js'
import invariant from 'tiny-invariant'
import { useImageState } from '../imageState'
import { decrement, increment, type Vector } from '../utils'
export interface HistoryItem {
i: number
x: number
y: number
}
export interface DesktopState {
index: Accessor<number>
cordHist: Accessor<HistoryItem[]>
hoverText: Accessor<string>
isOpen: Accessor<boolean>
isAnimating: Accessor<boolean>
isLoading: Accessor<boolean>
navVector: Accessor<Vector>
}
export type DesktopStateContextType = readonly [
DesktopState,
{
readonly setIndex: Setter<number>
readonly incIndex: () => void
readonly decIndex: () => void
readonly setCordHist: Setter<HistoryItem[]>
readonly setHoverText: Setter<string>
readonly setIsOpen: Setter<boolean>
readonly setIsAnimating: Setter<boolean>
readonly setIsLoading: Setter<boolean>
readonly setNavVector: Setter<Vector>
}
]
const DesktopStateContext = createContext<DesktopStateContextType>()
export function DesktopStateProvider(props: { children?: JSX.Element }): JSX.Element {
const imageState = useImageState()
const [index, setIndex] = createSignal(-1)
const [cordHist, setCordHist] = createSignal<HistoryItem[]>([])
const [hoverText, setHoverText] = createSignal('')
const [isOpen, setIsOpen] = createSignal(false)
const [isAnimating, setIsAnimating] = createSignal(false)
const [isLoading, setIsLoading] = createSignal(false)
const [navVector, setNavVector] = createSignal<Vector>('none')
const updateIndex = (stride: 1 | -1): void => {
const length = imageState().length
if (length <= 0) return
setIndex((current) =>
stride === 1 ? increment(current, length) : decrement(current, length)
)
}
return createComponent(DesktopStateContext.Provider, {
value: [
{ index, cordHist, hoverText, isOpen, isAnimating, isLoading, navVector },
{
setIndex,
incIndex: () => {
updateIndex(1)
},
decIndex: () => {
updateIndex(-1)
},
setCordHist,
setHoverText,
setIsOpen,
setIsAnimating,
setIsLoading,
setNavVector
}
],
get children() {
return props.children
}
})
}
export function useDesktopState(): DesktopStateContextType {
const context = useContext(DesktopStateContext)
invariant(context, 'undefined desktop context')
return context
}

41
assets/ts/imageState.tsx Normal file
View File

@@ -0,0 +1,41 @@
import {
createContext,
createMemo,
useContext,
type Accessor,
type JSX
} from 'solid-js'
import invariant from 'tiny-invariant'
import type { ImageJSON } from './resources'
export interface ImageState {
images: ImageJSON[]
length: number
}
type ImageStateContextType = Accessor<ImageState>
const ImageStateContext = createContext<ImageStateContextType>()
export function ImageStateProvider(props: {
children?: JSX.Element
images: ImageJSON[]
}): JSX.Element {
const state = createMemo<ImageState>(() => ({
images: props.images,
length: props.images.length
}))
return (
<ImageStateContext.Provider value={state}>
{props.children}
</ImageStateContext.Provider>
)
}
export function useImageState(): ImageStateContextType {
const context = useContext(ImageStateContext)
invariant(context, 'undefined image context')
return context
}

View File

@@ -1,17 +1,11 @@
import {
Match,
Show,
Switch,
createEffect,
createResource,
createSignal,
lazy,
type JSX
} from 'solid-js'
import { Match, Show, Switch, createResource, lazy, type JSX } from 'solid-js'
import { render } from 'solid-js/web'
import { ConfigStateProvider } from './configState'
import { DesktopStateProvider } from './desktop/state'
import { ImageStateProvider } from './imageState'
import { MobileStateProvider } from './mobile/state'
import { getImageJSON } from './resources'
import { StateProvider } from './state'
import '../scss/style.scss'
@@ -35,48 +29,60 @@ const container = document.getElementsByClassName('container')[0] as Container
const Desktop = lazy(async () => await import('./desktop/layout'))
const Mobile = lazy(async () => await import('./mobile/layout'))
function AppContent(props: {
isMobile: boolean
prevText: string
closeText: string
nextText: string
loadingText: string
}): JSX.Element {
return (
<Switch fallback={<div>Error</div>}>
<Match when={props.isMobile}>
<MobileStateProvider>
<Mobile closeText={props.closeText} loadingText={props.loadingText} />
</MobileStateProvider>
</Match>
<Match when={!props.isMobile}>
<DesktopStateProvider>
<Desktop
prevText={props.prevText}
closeText={props.closeText}
nextText={props.nextText}
loadingText={props.loadingText}
/>
</DesktopStateProvider>
</Match>
</Switch>
)
}
function Main(): JSX.Element {
// variables
const [ijs] = createResource(getImageJSON)
const isMobile =
window.matchMedia('(hover: none)').matches &&
!window.navigator.userAgent.includes('Win')
// states
const [scrollable, setScollable] = createSignal(true)
createEffect(() => {
if (scrollable()) {
container.classList.remove('disableScroll')
} else {
container.classList.add('disableScroll')
}
})
const ua = window.navigator.userAgent.toLowerCase()
const hasTouchInput = 'ontouchstart' in window || window.navigator.maxTouchPoints > 0
const hasTouchLayout =
window.matchMedia('(pointer: coarse)').matches ||
window.matchMedia('(hover: none)').matches
const isMobileUA = /android|iphone|ipad|ipod|mobile/.test(ua)
const isWindowsDesktop = /windows nt/.test(ua)
const isMobile = isMobileUA || (hasTouchInput && hasTouchLayout && !isWindowsDesktop)
return (
<>
<Show when={ijs.state === 'ready'}>
<StateProvider length={ijs()?.length ?? 0}>
<Switch fallback={<div>Error</div>}>
<Match when={isMobile}>
<Mobile
ijs={ijs() ?? []}
closeText={container.dataset.close}
loadingText={container.dataset.loading}
setScrollable={setScollable}
/>
</Match>
<Match when={!isMobile}>
<Desktop
ijs={ijs() ?? []}
prevText={container.dataset.prev}
closeText={container.dataset.close}
nextText={container.dataset.next}
loadingText={container.dataset.loading}
/>
</Match>
</Switch>
</StateProvider>
<ImageStateProvider images={ijs() ?? []}>
<ConfigStateProvider>
<AppContent
isMobile={isMobile}
prevText={container.dataset.prev}
closeText={container.dataset.close}
nextText={container.dataset.next}
loadingText={container.dataset.loading}
/>
</ConfigStateProvider>
</ImageStateProvider>
</Show>
</>
)

View File

@@ -1,17 +1,9 @@
import {
For,
createEffect,
on,
onMount,
type Accessor,
type JSX,
type Setter
} from 'solid-js'
import { For, createEffect, on, onMount, type JSX } from 'solid-js'
import type { ImageJSON } from '../resources'
import { useState } from '../state'
import { useImageState } from '../imageState'
import type { MobileImage } from './layout'
import { useMobileState } from './state'
function getRandom(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min
@@ -31,29 +23,26 @@ function onIntersection<T extends HTMLElement>(
}).observe(element)
}
export default function Collection(props: {
children?: JSX.Element
ijs: ImageJSON[]
isAnimating: Accessor<boolean>
isOpen: Accessor<boolean>
setIsOpen: Setter<boolean>
}): JSX.Element {
export default function Collection(): JSX.Element {
// variables
// eslint-disable-next-line solid/reactivity
const imgs: MobileImage[] = Array<MobileImage>(props.ijs.length)
const imageState = useImageState()
const imgs: MobileImage[] = Array<MobileImage>(imageState().length)
// states
const [state, { setIndex }] = useState()
const [mobile, { setIndex, setIsOpen }] = useMobileState()
// helper functions
const handleClick: (i: number) => void = (i) => {
if (props.isAnimating()) return
if (mobile.isAnimating()) return
setIndex(i)
props.setIsOpen(true)
setIsOpen(true)
}
const scrollToActive: () => void = () => {
imgs[state().index].scrollIntoView({ behavior: 'auto', block: 'center' })
const index = mobile.index()
if (index < 0) return
imgs[index].scrollIntoView({ behavior: 'auto', block: 'center' })
}
// effects
@@ -94,11 +83,9 @@ export default function Collection(props: {
createEffect(
on(
mobile.isOpen,
() => {
props.isOpen()
},
() => {
if (!props.isOpen()) scrollToActive() // scroll to active when closed
if (!mobile.isOpen()) scrollToActive() // scroll to active when closed
},
{ defer: true }
)
@@ -107,7 +94,7 @@ export default function Collection(props: {
return (
<>
<div class="collection">
<For each={props.ijs}>
<For each={imageState().images}>
{(ij, i) => (
<img
ref={imgs[i()]}

View File

@@ -1,209 +1,170 @@
import { type gsap } from 'gsap'
import {
createEffect,
createMemo,
createSignal,
For,
on,
onMount,
Show,
type Accessor,
type JSX,
type Setter
untrack,
type JSX
} from 'solid-js'
import { createStore } from 'solid-js/store'
import { type Swiper } from 'swiper'
import invariant from 'tiny-invariant'
import { type ImageJSON } from '../resources'
import { useState } from '../state'
import { loadGsap, type Vector } from '../utils'
import { useImageState } from '../imageState'
import { loadGsap, removeDuplicates, type Vector } from '../utils'
import GalleryImage from './galleryImage'
import GalleryNav, { capitalizeFirstLetter } from './galleryNav'
function removeDuplicates<T>(arr: T[]): T[] {
if (arr.length < 2) return arr // optimization
return [...new Set(arr)]
}
async function loadSwiper(): Promise<typeof Swiper> {
const s = await import('swiper')
return s.Swiper
}
import { closeGallery, openGallery } from './galleryTransitions'
import { getActiveImageIndexes, loadSwiper } from './galleryUtils'
import { useMobileState } from './state'
export default function Gallery(props: {
children?: JSX.Element
ijs: ImageJSON[]
closeText: string
loadingText: string
isAnimating: Accessor<boolean>
setIsAnimating: Setter<boolean>
isOpen: Accessor<boolean>
setIsOpen: Setter<boolean>
setScrollable: Setter<boolean>
}): JSX.Element {
// variables
let _gsap: typeof gsap
let _swiper: Swiper
let _swiper: Swiper | undefined
let initPromise: Promise<void> | undefined
let curtain: HTMLDivElement | undefined
let gallery: HTMLDivElement | undefined
let galleryInner: HTMLDivElement | undefined
// eslint-disable-next-line solid/reactivity
const _loadingText = capitalizeFirstLetter(props.loadingText)
const imageState = useImageState()
const [mobile, { setIndex, setIsAnimating, setIsScrollLocked }] = useMobileState()
const loadingText = createMemo(() => capitalizeFirstLetter(props.loadingText))
// states
let lastIndex = -1
let mounted = false
let navigateVector: Vector = 'none'
const [state, { setIndex }] = useState()
const [libLoaded, setLibLoaded] = createSignal(false)
// eslint-disable-next-line solid/reactivity
const [loads, setLoads] = createStore(Array<boolean>(props.ijs.length).fill(false))
const [swiperReady, setSwiperReady] = createSignal(false)
const [loads, setLoads] = createStore(Array<boolean>(imageState().length).fill(false))
// helper functions
const slideUp: () => void = () => {
// isAnimating is prechecked in isOpen effect
if (!libLoaded() || !mounted) return
props.setIsAnimating(true)
invariant(curtain, 'curtain is not defined')
invariant(gallery, 'gallery is not defined')
_gsap.to(curtain, {
opacity: 1,
duration: 1
openGallery({
gsap: _gsap,
curtain,
gallery,
setIsAnimating,
setIsScrollLocked
})
_gsap.to(gallery, {
y: 0,
ease: 'power3.inOut',
duration: 1,
delay: 0.4
})
setTimeout(() => {
props.setScrollable(false)
props.setIsAnimating(false)
}, 1200)
}
const slideDown: () => void = () => {
// isAnimating is prechecked in isOpen effect
props.setIsAnimating(true)
invariant(gallery, 'curtain is not defined')
invariant(curtain, 'gallery is not defined')
_gsap.to(gallery, {
y: '100%',
ease: 'power3.inOut',
duration: 1
closeGallery({
gsap: _gsap,
curtain,
gallery,
setIsAnimating,
setIsScrollLocked,
onClosed: () => {
lastIndex = -1
}
})
_gsap.to(curtain, {
opacity: 0,
duration: 1.2,
delay: 0.4
})
setTimeout(() => {
// cleanup
props.setScrollable(true)
props.setIsAnimating(false)
lastIndex = -1
}, 1400)
}
const galleryLoadImages: () => void = () => {
let activeImagesIndex: number[] = []
const _state = state()
const currentIndex = _state.index
const nextIndex = Math.min(currentIndex + 1, _state.length - 1)
const prevIndex = Math.max(currentIndex - 1, 0)
switch (navigateVector) {
case 'next':
activeImagesIndex = [nextIndex]
break
case 'prev':
activeImagesIndex = [prevIndex]
break
case 'none':
activeImagesIndex = [currentIndex, nextIndex, prevIndex]
break
}
setLoads(removeDuplicates(activeImagesIndex), true)
const currentIndex = mobile.index()
setLoads(
removeDuplicates(
getActiveImageIndexes(currentIndex, imageState().length, navigateVector)
),
true
)
}
const changeSlide: (slide: number) => void = (slide) => {
// we are already in the gallery, don't need to
// check mounted or libLoaded
if (!swiperReady() || _swiper === undefined) return
galleryLoadImages()
_swiper.slideTo(slide, 0)
}
// effects
onMount(() => {
window.addEventListener(
'touchstart',
() => {
loadGsap()
.then((g) => {
_gsap = g
})
.catch((e) => {
console.log(e)
})
loadSwiper()
.then((S) => {
invariant(galleryInner, 'galleryInner is not defined')
_swiper = new S(galleryInner, { spaceBetween: 20 })
_swiper.on('slideChange', ({ realIndex }) => {
setIndex(realIndex)
})
})
.catch((e) => {
console.log(e)
})
const ensureGalleryReady: () => Promise<void> = async () => {
if (initPromise !== undefined) return await initPromise
initPromise = (async () => {
try {
const [g, S] = await Promise.all([loadGsap(), loadSwiper()])
_gsap = g
invariant(galleryInner, 'galleryInner is not defined')
_swiper = new S(galleryInner, { spaceBetween: 20 })
_swiper.on('slideChange', ({ realIndex }) => {
setIndex(realIndex)
})
setLibLoaded(true)
},
{ once: true, passive: true }
)
setSwiperReady(true)
const initialIndex = untrack(mobile.index)
if (initialIndex >= 0) {
changeSlide(initialIndex)
lastIndex = initialIndex
}
} catch (e) {
initPromise = undefined
setSwiperReady(false)
console.log(e)
}
})()
await initPromise
}
onMount(() => {
window.addEventListener('touchstart', () => void ensureGalleryReady(), {
once: true,
passive: true
})
mounted = true
})
createEffect(
on(
() => {
state()
},
() => {
const i = state().index
if (i === lastIndex)
return // change slide only when index is changed
else if (lastIndex === -1)
navigateVector = 'none' // lastIndex before set
else if (i < lastIndex)
navigateVector = 'prev' // set navigate vector for galleryLoadImages
else if (i > lastIndex)
navigateVector = 'next' // set navigate vector for galleryLoadImages
else navigateVector = 'none' // default
changeSlide(i) // change slide to new index
lastIndex = i // update last index
() => [swiperReady(), mobile.index()] as const,
([ready, index]) => {
if (!ready || index < 0) return
if (index === lastIndex) return
if (lastIndex === -1) navigateVector = 'none'
else if (index < lastIndex) navigateVector = 'prev'
else if (index > lastIndex) navigateVector = 'next'
else navigateVector = 'none'
changeSlide(index)
lastIndex = index
}
)
)
createEffect(
on(
() => {
props.isOpen()
},
() => {
if (props.isAnimating()) return
if (props.isOpen()) slideUp()
() => mobile.isOpen(),
async (isOpen) => {
if (isOpen && !swiperReady()) {
await ensureGalleryReady()
}
if (!libLoaded() || !swiperReady()) return
if (mobile.isAnimating()) return
if (isOpen) slideUp()
else slideDown()
},
{ defer: true }
@@ -215,26 +176,16 @@ export default function Gallery(props: {
<div ref={gallery} class="gallery">
<div ref={galleryInner} class="galleryInner">
<div class="swiper-wrapper">
<Show when={libLoaded()}>
<For each={props.ijs}>
{(ij, i) => (
<div class="swiper-slide">
<GalleryImage
load={loads[i()]}
ij={ij}
loadingText={_loadingText}
/>
</div>
)}
</For>
</Show>
<For each={imageState().images}>
{(ij, i) => (
<div class="swiper-slide">
<GalleryImage load={loads[i()]} ij={ij} loadingText={loadingText()} />
</div>
)}
</For>
</div>
</div>
<GalleryNav
closeText={props.closeText}
isAnimating={props.isAnimating}
setIsOpen={props.setIsOpen}
/>
<GalleryNav closeText={props.closeText} />
</div>
<div ref={curtain} class="curtain" />
</>

View File

@@ -1,10 +1,12 @@
import { onMount, type JSX } from 'solid-js'
import { type gsap } from 'gsap'
import { createEffect, on, onMount, type JSX } from 'solid-js'
import invariant from 'tiny-invariant'
import type { ImageJSON } from '../resources'
import { useState } from '../state'
import { loadGsap } from '../utils'
import { useMobileState } from './state'
export default function GalleryImage(props: {
children?: JSX.Element
load: boolean
@@ -14,40 +16,83 @@ export default function GalleryImage(props: {
let img: HTMLImageElement | undefined
let loadingDiv: HTMLDivElement | undefined
let _gsap: typeof gsap
let _gsap: typeof gsap | undefined
let gsapPromise: Promise<typeof gsap> | undefined
let revealed = false
const [state] = useState()
const [mobile] = useMobileState()
const revealImage = async (): Promise<void> => {
if (revealed) return
revealed = true
invariant(img, 'ref must be defined')
invariant(loadingDiv, 'loadingDiv must be defined')
gsapPromise ??= loadGsap()
try {
_gsap ??= await gsapPromise
} catch (e) {
console.log(e)
}
if (_gsap === undefined) {
img.style.opacity = '1'
loadingDiv.style.opacity = '0'
return
}
if (mobile.index() !== props.ij.index) {
_gsap.set(img, { opacity: 1 })
_gsap.set(loadingDiv, { opacity: 0 })
return
}
_gsap.to(img, {
opacity: 1,
delay: 0.5,
duration: 0.5,
ease: 'power3.out'
})
_gsap.to(loadingDiv, { opacity: 0, duration: 0.5, ease: 'power3.in' })
}
onMount(() => {
loadGsap()
gsapPromise = loadGsap()
.then((g) => {
_gsap = g
return g
})
.catch((e) => {
console.log(e)
throw e
})
img?.addEventListener(
'load',
() => {
invariant(img, 'ref must be defined')
invariant(loadingDiv, 'loadingDiv must be defined')
if (state().index !== props.ij.index) {
_gsap.set(img, { opacity: 1 })
_gsap.set(loadingDiv, { opacity: 0 })
} else {
_gsap.to(img, {
opacity: 1,
delay: 0.5,
duration: 0.5,
ease: 'power3.out'
})
_gsap.to(loadingDiv, { opacity: 0, duration: 0.5, ease: 'power3.in' })
}
void revealImage()
},
{ once: true, passive: true }
)
if (props.load && img?.complete && img.currentSrc !== '') {
void revealImage()
}
})
createEffect(
on(
() => props.load,
(load) => {
if (!load || img === undefined || !img.complete || img.currentSrc === '') return
void revealImage()
},
{ defer: true }
)
)
return (
<>
<div class="slideContainer">

View File

@@ -1,8 +1,10 @@
import { createMemo, type Accessor, type JSX, type Setter } from 'solid-js'
import { createMemo, type JSX } from 'solid-js'
import { useState } from '../state'
import { useImageState } from '../imageState'
import { expand } from '../utils'
import { useMobileState } from './state'
export function capitalizeFirstLetter(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1)
}
@@ -10,17 +12,16 @@ export function capitalizeFirstLetter(str: string): string {
export default function GalleryNav(props: {
children?: JSX.Element
closeText: string
isAnimating: Accessor<boolean>
setIsOpen: Setter<boolean>
}): JSX.Element {
// states
const [state] = useState()
const indexValue = createMemo(() => expand(state().index + 1))
const indexLength = createMemo(() => expand(state().length))
const imageState = useImageState()
const [mobile, { setIsOpen }] = useMobileState()
const indexValue = createMemo(() => expand(mobile.index() + 1))
const indexLength = createMemo(() => expand(imageState().length))
const onClick: () => void = () => {
if (props.isAnimating()) return
props.setIsOpen(false)
if (mobile.isAnimating()) return
setIsOpen(false)
}
return (
@@ -37,7 +38,14 @@ export default function GalleryNav(props: {
<span class="num">{indexLength()[2]}</span>
<span class="num">{indexLength()[3]}</span>
</div>
<div onClick={onClick} onKeyDown={onClick}>
<div
class="navClose"
onClick={onClick}
onTouchEnd={onClick}
onKeyDown={onClick}
role="button"
tabIndex="0"
>
{capitalizeFirstLetter(props.closeText)}
</div>
</div>

View File

@@ -0,0 +1,64 @@
import { type gsap } from 'gsap'
const OPEN_DELAY_MS = 1200
const CLOSE_DELAY_MS = 1400
export function openGallery(args: {
gsap: typeof gsap
curtain: HTMLDivElement
gallery: HTMLDivElement
setIsAnimating: (value: boolean) => void
setIsScrollLocked: (value: boolean) => void
}): void {
const { gsap, curtain, gallery, setIsAnimating, setIsScrollLocked } = args
setIsAnimating(true)
gsap.to(curtain, {
opacity: 1,
duration: 1
})
gsap.to(gallery, {
y: 0,
ease: 'power3.inOut',
duration: 1,
delay: 0.4
})
setTimeout(() => {
setIsScrollLocked(true)
setIsAnimating(false)
}, OPEN_DELAY_MS)
}
export function closeGallery(args: {
gsap: typeof gsap
curtain: HTMLDivElement
gallery: HTMLDivElement
setIsAnimating: (value: boolean) => void
setIsScrollLocked: (value: boolean) => void
onClosed: () => void
}): void {
const { gsap, curtain, gallery, setIsAnimating, setIsScrollLocked, onClosed } = args
setIsAnimating(true)
gsap.to(gallery, {
y: '100%',
ease: 'power3.inOut',
duration: 1
})
gsap.to(curtain, {
opacity: 0,
duration: 1.2,
delay: 0.4
})
setTimeout(() => {
setIsScrollLocked(false)
setIsAnimating(false)
onClosed()
}, CLOSE_DELAY_MS)
}

View File

@@ -0,0 +1,26 @@
import { type Swiper } from 'swiper'
import type { Vector } from '../utils'
export async function loadSwiper(): Promise<typeof Swiper> {
const swiper = await import('swiper')
return swiper.Swiper
}
export function getActiveImageIndexes(
currentIndex: number,
length: number,
navigateVector: Vector
): number[] {
const nextIndex = Math.min(currentIndex + 1, length - 1)
const prevIndex = Math.max(currentIndex - 1, 0)
switch (navigateVector) {
case 'next':
return [nextIndex]
case 'prev':
return [prevIndex]
case 'none':
return [currentIndex, nextIndex, prevIndex]
}
}

View File

@@ -1,9 +1,10 @@
import { Show, createSignal, type JSX, type Setter } from 'solid-js'
import { Show, createEffect, onCleanup, type JSX } from 'solid-js'
import type { ImageJSON } from '../resources'
import { useImageState } from '../imageState'
import Collection from './collection'
import Gallery from './gallery'
import { useMobileState } from './state'
/**
* interfaces
@@ -18,34 +19,33 @@ export interface MobileImage extends HTMLImageElement {
export default function Mobile(props: {
children?: JSX.Element
ijs: ImageJSON[]
closeText: string
loadingText: string
setScrollable: Setter<boolean>
}): JSX.Element {
// states
const [isOpen, setIsOpen] = createSignal(false)
const [isAnimating, setIsAnimating] = createSignal(false)
const imageState = useImageState()
const [mobile] = useMobileState()
createEffect(() => {
const container = document.getElementsByClassName('container').item(0)
if (container === null) return
if (mobile.isScrollLocked()) {
container.classList.add('disableScroll')
} else {
container.classList.remove('disableScroll')
}
})
onCleanup(() => {
const container = document.getElementsByClassName('container').item(0)
container?.classList.remove('disableScroll')
})
return (
<>
<Show when={props.ijs.length > 0}>
<Collection
ijs={props.ijs}
isAnimating={isAnimating}
isOpen={isOpen}
setIsOpen={setIsOpen}
/>
<Gallery
ijs={props.ijs}
closeText={props.closeText}
loadingText={props.loadingText}
isAnimating={isAnimating}
setIsAnimating={setIsAnimating}
isOpen={isOpen}
setIsOpen={setIsOpen}
setScrollable={props.setScrollable}
/>
<Show when={imageState().length > 0}>
<Collection />
<Gallery closeText={props.closeText} loadingText={props.loadingText} />
</Show>
</>
)

78
assets/ts/mobile/state.ts Normal file
View File

@@ -0,0 +1,78 @@
import {
createComponent,
createContext,
createSignal,
useContext,
type Accessor,
type JSX,
type Setter
} from 'solid-js'
import invariant from 'tiny-invariant'
import { useImageState } from '../imageState'
import { decrement, increment } from '../utils'
export interface MobileState {
index: Accessor<number>
isOpen: Accessor<boolean>
isAnimating: Accessor<boolean>
isScrollLocked: Accessor<boolean>
}
export type MobileStateContextType = readonly [
MobileState,
{
readonly setIndex: Setter<number>
readonly incIndex: () => void
readonly decIndex: () => void
readonly setIsOpen: Setter<boolean>
readonly setIsAnimating: Setter<boolean>
readonly setIsScrollLocked: Setter<boolean>
}
]
const MobileStateContext = createContext<MobileStateContextType>()
export function MobileStateProvider(props: { children?: JSX.Element }): JSX.Element {
const imageState = useImageState()
const [index, setIndex] = createSignal(-1)
const [isOpen, setIsOpen] = createSignal(false)
const [isAnimating, setIsAnimating] = createSignal(false)
const [isScrollLocked, setIsScrollLocked] = createSignal(false)
const updateIndex = (stride: 1 | -1): void => {
const length = imageState().length
if (length <= 0) return
setIndex((current) =>
stride === 1 ? increment(current, length) : decrement(current, length)
)
}
return createComponent(MobileStateContext.Provider, {
value: [
{ index, isOpen, isAnimating, isScrollLocked },
{
setIndex,
incIndex: () => {
updateIndex(1)
},
decIndex: () => {
updateIndex(-1)
},
setIsOpen,
setIsAnimating,
setIsScrollLocked
}
],
get children() {
return props.children
}
})
}
export function useMobileState(): MobileStateContextType {
const context = useContext(MobileStateContext)
invariant(context, 'undefined mobile context')
return context
}

View File

@@ -1,136 +0,0 @@
import {
createContext,
createSignal,
useContext,
type Accessor,
type JSX,
type Setter
} from 'solid-js'
import invariant from 'tiny-invariant'
import { decrement, getThresholdSessionIndex, increment } from './utils'
/**
* interfaces and types
*/
export interface ThresholdRelated {
threshold: number
trailLength: number
}
export interface State {
index: number
length: number
threshold: number
trailLength: number
}
export type StateContextType = readonly [
Accessor<State>,
{
readonly setIndex: (index: number) => void
readonly incIndex: () => void
readonly decIndex: () => void
readonly incThreshold: () => void
readonly decThreshold: () => void
}
]
/**
* constants
*/
const thresholds: ThresholdRelated[] = [
{ threshold: 20, trailLength: 20 },
{ threshold: 40, trailLength: 10 },
{ threshold: 80, trailLength: 5 },
{ threshold: 140, trailLength: 5 },
{ threshold: 200, trailLength: 5 }
]
const makeStateContext: (
state: Accessor<State>,
setState: Setter<State>
) => StateContextType = (state: Accessor<State>, setState: Setter<State>) => {
return [
state,
{
setIndex: (index: number) => {
setState((s) => {
return { ...s, index }
})
},
incIndex: () => {
setState((s) => {
return { ...s, index: increment(s.index, s.length) }
})
},
decIndex: () => {
setState((s) => {
return { ...s, index: decrement(s.index, s.length) }
})
},
incThreshold: () => {
setState((s) => {
return { ...s, ...updateThreshold(s.threshold, thresholds, 1) }
})
},
decThreshold: () => {
setState((s) => {
return { ...s, ...updateThreshold(s.threshold, thresholds, -1) }
})
}
}
] as const
}
const StateContext = createContext<StateContextType>()
/**
* helper functions
*/
function updateThreshold(
currentThreshold: number,
thresholds: ThresholdRelated[],
stride: number
): ThresholdRelated {
const i = thresholds.findIndex((t) => t.threshold === currentThreshold) + stride
if (i < 0 || i >= thresholds.length) return thresholds[i - stride]
// storage the index so we can restore it even if we go to another page
sessionStorage.setItem('thresholdsIndex', i.toString())
return thresholds[i]
}
/**
* StateProvider
*/
export function StateProvider(props: {
children?: JSX.Element
length: number
}): JSX.Element {
const defaultState: State = {
index: -1,
// eslint-disable-next-line solid/reactivity
length: props.length,
threshold: thresholds[getThresholdSessionIndex()].threshold,
trailLength: thresholds[getThresholdSessionIndex()].trailLength
}
const [state, setState] = createSignal(defaultState)
// eslint-disable-next-line solid/reactivity
const contextValue = makeStateContext(state, setState)
return (
<StateContext.Provider value={contextValue}>{props.children}</StateContext.Provider>
)
}
/**
* use context
*/
export function useState(): StateContextType {
const uc = useContext(StateContext)
invariant(uc, 'undefined context')
return uc
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
bundled/js/BQuRTE.js Normal file

File diff suppressed because one or more lines are too long

1
bundled/js/Briij_.js Normal file

File diff suppressed because one or more lines are too long

1
bundled/js/BwPjw0.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

1
bundled/js/jjLR4b.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
bundled/js/v3xXn8.js Normal file

File diff suppressed because one or more lines are too long

10
flake.lock generated
View File

@@ -2,12 +2,12 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1762596750,
"narHash": "sha256-rXXuz51Bq7DHBlfIjN7jO8Bu3du5TV+3DSADBX7/9YQ=",
"rev": "b6a8526db03f735b89dd5ff348f53f752e7ddc8e",
"revCount": 891611,
"lastModified": 1769461804,
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
"revCount": 935279,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.891611%2Brev-b6a8526db03f735b89dd5ff348f53f752e7ddc8e/019a684c-ea63-75fd-99cc-3b869954e5f9/source.tar.gz"
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.935279%2Brev-bfc1b8a4574108ceef22f02bafcf6611380c100d/019c02ef-f13d-717e-8527-f1603ec205db/source.tar.gz"
},
"original": {
"type": "tarball",

View File

@@ -39,30 +39,31 @@
},
"homepage": "https://github.com/Sped0n/bridget#readme",
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.0",
"@typescript-eslint/parser": "^8.46.4",
"eslint": "^9.39.1",
"eslint-config-love": "^133.0.0",
"@eslint/js": "^9.39.4",
"@types/node": "^25.5.2",
"@typescript-eslint/parser": "^8.58.0",
"eslint": "^9.39.2",
"eslint-config-love": "^151.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-solid": "^0.14.5",
"npm-run-all": "^4.1.5",
"prettier": "3.6.2",
"prettier": "3.8.1",
"prettier-plugin-go-template": "^0.0.15",
"prettier-plugin-organize-imports": "^4.3.0",
"sass-embedded": "^1.93.3",
"sass-embedded": "^1.99.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.2",
"vite-plugin-solid": "^2.11.10"
"typescript-eslint": "^8.58.1",
"vite": "^8.0.5",
"vite-plugin-solid": "^2.11.12",
"vitefu": "^1.1.3"
},
"dependencies": {
"gsap": "^3.13.0",
"solid-js": "^1.9.10",
"swiper": "^12.0.3",
"gsap": "^3.15.0",
"solid-js": "^1.9.12",
"swiper": "^12.1.3",
"tiny-invariant": "^1.3.3"
}
}

2409
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
node_modules_generated_dir="./node_modules/exampleSite/resources/_gen"
project_generated_dir="./exampleSite/resources/_gen"
node_modules_generated_dir="./node_modules/exampleSite/resources/_gen/images"
project_generated_dir="./exampleSite/resources/_gen/images"
copy_generated_assets_to_project() {
if [ -d "${node_modules_generated_dir}" ]; then
@@ -16,7 +16,8 @@ copy_generated_assets_to_project() {
}
run_site_build() {
pnpm run vite:build && hugo --logLevel info --source=exampleSite --gc --minify
rm -rf bundled
pnpm run build
}
copy_generated_assets_to_node_modules() {

View File

@@ -5,6 +5,7 @@ export default defineConfig({
plugins: [solidPlugin()],
build: {
outDir: './bundled',
cssMinify: 'esbuild',
watch: process.env.DISABLE_WATCH
? null
: {
@@ -19,8 +20,7 @@ export default defineConfig({
format: 'es',
entryFileNames: 'js/[name].js',
chunkFileNames: 'js/[hash:6].js',
assetFileNames: '[ext]/[name].[ext]',
compact: true
assetFileNames: '[ext]/[name].[ext]'
}
}
},