mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-17 03:29:31 -07:00
Compare commits
240 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
758a2d1a62 | ||
|
|
ec1df7f070 | ||
|
|
f09988f32d | ||
|
|
e16aaca42b | ||
|
|
80442eb569 | ||
|
|
9039e04b38 | ||
|
|
56304e09f1 | ||
|
|
0cbaacbc0e | ||
|
|
f78449adb9 | ||
|
|
4d55bca248 | ||
|
|
ad998ba153 | ||
|
|
bd95ab861b | ||
|
|
919489c7e9 | ||
|
|
efa72bb763 | ||
|
|
90f79113c7 | ||
|
|
b5d0754c45 | ||
|
|
8a751b7437 | ||
|
|
212dca53e8 | ||
|
|
989a7f4951 | ||
|
|
f25b71a858 | ||
|
|
1c386386f3 | ||
|
|
797b59a38a | ||
|
|
dbbc063353 | ||
|
|
f7e87c3c15 | ||
|
|
1242146140 | ||
|
|
a8c1f948db | ||
|
|
dc30a8cd16 | ||
|
|
71c7b02c1d | ||
|
|
6cfcfb272a | ||
|
|
c239112cb2 | ||
|
|
637391bb34 | ||
|
|
fe38e1289a | ||
|
|
08bd9a76ef | ||
|
|
14f481b2c0 | ||
|
|
546791e90b | ||
|
|
136c2303f9 | ||
|
|
b13f1bf454 | ||
|
|
313a6f294a | ||
|
|
2804be174d | ||
|
|
fec976ad9e | ||
|
|
582433874c | ||
|
|
037cdbd679 | ||
|
|
50ff7f62bb | ||
|
|
3fe8477646 | ||
|
|
64b43597a4 | ||
|
|
94603999d7 | ||
|
|
c3c42e4aca | ||
|
|
38f4895233 | ||
|
|
636fea3496 | ||
|
|
c15b8a3e46 | ||
|
|
0cd891d16b | ||
|
|
4670c00157 | ||
|
|
032a75f581 | ||
|
|
5e40e9041e | ||
|
|
9c7731b8e5 | ||
|
|
d59475fbdc | ||
|
|
27c3b754ac | ||
|
|
1359adee59 | ||
|
|
b8d7193fb9 | ||
|
|
a06027401b | ||
|
|
5501d10cd0 | ||
|
|
31e20f752a | ||
|
|
fd300d8104 | ||
|
|
e91a7c6633 | ||
|
|
321e4b618a | ||
|
|
f630b85669 | ||
|
|
a431d74e8c | ||
|
|
975e084ea8 | ||
|
|
771b9b34ab | ||
|
|
1e5155c49e | ||
|
|
44750037f3 | ||
|
|
9af0090644 | ||
|
|
b05eec64cb | ||
|
|
284cfa4e84 | ||
|
|
11ea5c101b | ||
|
|
d4b9a4588a | ||
|
|
4ed332314d | ||
|
|
82ab5b996f | ||
|
|
81c14d9f2b | ||
|
|
7cf5f9ad2d | ||
|
|
0f69ec5f96 | ||
|
|
e6a9cacfef | ||
|
|
b24e401a28 | ||
|
|
ca9fef6c2d | ||
|
|
2835d46a57 | ||
|
|
3779bc7ce6 | ||
|
|
de16c6b443 | ||
|
|
d536303f8e | ||
|
|
76d420c2c9 | ||
|
|
c448648127 | ||
|
|
7e0e6244b0 | ||
|
|
c76835d474 | ||
|
|
39c13e242d | ||
|
|
61c86692ee | ||
|
|
cd63e9fec5 | ||
|
|
8bc8fded81 | ||
|
|
8bc54835e5 | ||
|
|
bd632b0ca4 | ||
|
|
b550dcd236 | ||
|
|
1b067c26d0 | ||
|
|
665522c8d1 | ||
|
|
e67865b82b | ||
|
|
6440556242 | ||
|
|
59227fb265 | ||
|
|
d291aab64f | ||
|
|
c290595a35 | ||
|
|
a6a576246f | ||
|
|
93629a4e6b | ||
|
|
a909afee97 | ||
|
|
9c15a367ea | ||
|
|
73ee16c6fb | ||
|
|
91b0314c5d | ||
|
|
d1a1dba210 | ||
|
|
110ff665e7 | ||
|
|
b39d563e77 | ||
|
|
0e74655820 | ||
|
|
8926caed69 | ||
|
|
19f54640f9 | ||
|
|
56b87d6393 | ||
|
|
75d8310953 | ||
|
|
a9f164f2af | ||
|
|
7773f184aa | ||
|
|
bc501934ae | ||
|
|
44b619e49b | ||
|
|
024d013219 | ||
|
|
268159e7d2 | ||
|
|
2e7131a5a5 | ||
|
|
1de3926c49 | ||
|
|
4b1f529589 | ||
|
|
8b3b5cd77a | ||
|
|
3a0025ebd1 | ||
|
|
283f386371 | ||
|
|
4c91cd269e | ||
|
|
304abf3b65 | ||
|
|
99a2866d4a | ||
|
|
ba46f52d55 | ||
|
|
48eaa9d95b | ||
|
|
6be1a924de | ||
|
|
ba0def753e | ||
|
|
c9410c9644 | ||
|
|
1d1f893c92 | ||
|
|
7d4bed3ba6 | ||
|
|
72b830def9 | ||
|
|
6b2c1de9d8 | ||
|
|
ae899035ae | ||
|
|
36721f8bf6 | ||
|
|
2dcbb5e5d5 | ||
|
|
07aa48f2db | ||
|
|
099513500c | ||
|
|
f0da771dca | ||
|
|
dfd4abdf2a | ||
|
|
6f8ce6bbd8 | ||
|
|
543a08d472 | ||
|
|
05599ef190 | ||
|
|
95bd4d1c28 | ||
|
|
835cd6f343 | ||
|
|
479164bc83 | ||
|
|
9ea37b8a3f | ||
|
|
5df519e55f | ||
|
|
860428d03b | ||
|
|
c1ad92fbeb | ||
|
|
78f657618e | ||
|
|
1fe7095898 | ||
|
|
0471af5085 | ||
|
|
543d630535 | ||
|
|
b7ed5a2834 | ||
|
|
5ba7d77c07 | ||
|
|
af82026d1a | ||
|
|
b5ef661e1c | ||
|
|
883eec1a3b | ||
|
|
b027cd03cf | ||
|
|
aa34979bb4 | ||
|
|
08bbfaa3ba | ||
|
|
b8c6ce2b04 | ||
|
|
27083c0336 | ||
|
|
082458b2aa | ||
|
|
1c05eb2633 | ||
|
|
9016cfb035 | ||
|
|
d9b4100d17 | ||
|
|
082f5f4961 | ||
|
|
9d91becbd5 | ||
|
|
2e51b7eb89 | ||
|
|
7dd8c2242c | ||
|
|
2a10e4944e | ||
|
|
7893586d24 | ||
|
|
5a51f83654 | ||
|
|
e4d5ac4389 | ||
|
|
ea3d58760b | ||
|
|
4812cdb191 | ||
|
|
b93b8d3ac6 | ||
|
|
a7bc6b2df5 | ||
|
|
1cfbc8ac28 | ||
|
|
c4cea2648e | ||
|
|
26bdddc5ff | ||
|
|
437bbf17e0 | ||
|
|
2a715327f6 | ||
|
|
c111de15b1 | ||
|
|
1b9826f582 | ||
|
|
6a3ce498a9 | ||
|
|
8d48e6347e | ||
|
|
4599a5dfc2 | ||
|
|
7536288baa | ||
|
|
e12c32388b | ||
|
|
4198a5fa90 | ||
|
|
089e9b285a | ||
|
|
60e19fed00 | ||
|
|
626433e67d | ||
|
|
cb5080ce41 | ||
|
|
e2f8317669 | ||
|
|
5d9e32f62b | ||
|
|
b96ecd6042 | ||
|
|
5b7ec62106 | ||
|
|
3ca4a0d803 | ||
|
|
a8d8802d9f | ||
|
|
28782217f1 | ||
|
|
cfcda29524 | ||
|
|
fb498971c7 | ||
|
|
9dbb3cb624 | ||
|
|
129f26dd54 | ||
|
|
d1f9b843c3 | ||
|
|
a7b5ec45ed | ||
|
|
5d82276734 | ||
|
|
b0c4fa8ea7 | ||
|
|
a6f983de5d | ||
|
|
3bc232638a | ||
|
|
67944df12f | ||
|
|
e82fe6cab2 | ||
|
|
819df6b2ed | ||
|
|
dfef87ca55 | ||
|
|
477b6d748a | ||
|
|
31a59c5e9e | ||
|
|
e8cdd12151 | ||
|
|
a8bc17ca12 | ||
|
|
4d04fe1945 | ||
|
|
bafd2aa3b3 | ||
|
|
acf50d10d7 | ||
|
|
30a6a3bd23 | ||
|
|
d808782afd | ||
|
|
5327d7c585 | ||
|
|
d7e7fc68ba |
@@ -1,3 +0,0 @@
|
|||||||
node_modules
|
|
||||||
static
|
|
||||||
exampleSite
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"standard-with-typescript",
|
|
||||||
"prettier",
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:solid/typescript"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["prettier", "@typescript-eslint", "solid"],
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"project": "./tsconfig.json",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"prettier/prettier": "error",
|
|
||||||
"arrow-body-style": "off",
|
|
||||||
"prefer-arrow-callback": "off",
|
|
||||||
"import/no-cycle": "error",
|
|
||||||
"sort-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignoreCase": false,
|
|
||||||
"ignoreDeclarationSort": true,
|
|
||||||
"ignoreMemberSort": true,
|
|
||||||
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
|
|
||||||
"allowSeparatedGroups": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"import/no-unresolved": "error",
|
|
||||||
"import/order": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"external",
|
|
||||||
"internal",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index",
|
|
||||||
"unknown"
|
|
||||||
],
|
|
||||||
"newlines-between": "always",
|
|
||||||
"alphabetize": {
|
|
||||||
"order": "asc",
|
|
||||||
"caseInsensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"import/resolver": {
|
|
||||||
"typescript": {
|
|
||||||
"project": "./tsconfig.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
98
.github/workflows/build.yml
vendored
98
.github/workflows/build.yml
vendored
@@ -12,80 +12,72 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
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@v41
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
package.json
|
|
||||||
assets/**
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Build
|
name: Build (Hugo ${{ matrix.hugo-label }})
|
||||||
needs: [filter]
|
if: github.event.repository.fork == false
|
||||||
if: |
|
strategy:
|
||||||
github.ref == 'refs/heads/main' &&
|
matrix:
|
||||||
github.event.repository.fork == false
|
hugo-version: ['latest', '0.114.0']
|
||||||
|
include:
|
||||||
|
- hugo-version: latest
|
||||||
|
hugo-label: Latest
|
||||||
|
- hugo-version: '0.114.0'
|
||||||
|
hugo-label: 'v0.114.0'
|
||||||
steps:
|
steps:
|
||||||
- name: Set current date as env variable
|
|
||||||
run: |
|
|
||||||
echo "builddate=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
id: version
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
- name: Setup Hugo
|
- name: Setup Mise
|
||||||
uses: peaceiris/actions-hugo@v2.6.0
|
uses: jdx/mise-action@v3
|
||||||
with:
|
with:
|
||||||
hugo-version: '0.114.0'
|
install_args: node@latest pnpm@10 hugo-extended@${{ matrix.hugo-version }}
|
||||||
extended: true
|
tool_versions: |
|
||||||
|
node latest
|
||||||
|
pnpm 10
|
||||||
|
hugo-extended ${{ matrix.hugo-version }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Get pnpm store path
|
||||||
uses: pnpm/action-setup@v3
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
id: pnpm-cache
|
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
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
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-
|
restore-keys: |
|
||||||
|
pnpm-store-
|
||||||
|
|
||||||
- name: Setup hugo cache
|
- name: Setup Hugo cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ./exampleSite/resources
|
path: exampleSite/resources/_gen
|
||||||
key: ${{ runner.os }}-hugo-${{ hashFiles('./exampleSite/resources') }}
|
key: hugo-${{ matrix.hugo-version }}-${{ hashFiles('./exampleSite/**/*.jpg') }}
|
||||||
restore-keys: ${{ runner.os }}-hugo-
|
restore-keys: |
|
||||||
|
hugo-${{ matrix.hugo-version }}-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install project dependencies
|
||||||
run: pnpm install
|
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
|
- name: Build
|
||||||
run: pnpm run build
|
run: 'pnpm run build'
|
||||||
|
|
||||||
- name: Push artifacts
|
- name: Push artifacts
|
||||||
if: ${{ (github.event_name == 'push' || github.event.pull_request.merged == true) && needs.filter.outputs.any_changed == 'true' }}
|
if: >
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
matrix.hugo-version == 'latest' &&
|
||||||
|
(github.event_name == 'push' || github.event.pull_request.merged == true)
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v7
|
||||||
with:
|
with:
|
||||||
|
file_pattern: 'bundled/**/*.js bundled/**/*.css'
|
||||||
commit_message: 'ci: update bundled artifacts [skip ci]'
|
commit_message: 'ci: update bundled artifacts [skip ci]'
|
||||||
|
|||||||
8
.github/workflows/codeql.yml
vendored
8
.github/workflows/codeql.yml
vendored
@@ -46,11 +46,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# 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).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ 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
|
# 📚 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
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
57
.github/workflows/eslint.yml
vendored
57
.github/workflows/eslint.yml
vendored
@@ -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 }}
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v3
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
|
|
||||||
- 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'
|
|
||||||
58
.github/workflows/lint.yml
vendored
Normal file
58
.github/workflows/lint.yml
vendored
Normal 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@v3
|
||||||
|
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]'
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
# Hugo default output directory
|
# Hugo default output directory
|
||||||
public/
|
public/
|
||||||
/exampleSite/resources/
|
exampleSite/resources/
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
build/
|
build/
|
||||||
@@ -25,3 +25,6 @@ jsconfig.json
|
|||||||
|
|
||||||
# css map
|
# css map
|
||||||
*.css.map
|
*.css.map
|
||||||
|
|
||||||
|
# dummmy file
|
||||||
|
bundled/js/critical.js
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
node_modules
|
node_modules/
|
||||||
static
|
static/
|
||||||
exmapleSite
|
exmapleSite/
|
||||||
single.json
|
single.json
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
bundled/
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"useTabs": false,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"printWidth": 88,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"semi": false,
|
|
||||||
"plugins": ["prettier-plugin-go-template", "prettier-plugin-organize-imports"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.html"],
|
|
||||||
"options": {
|
|
||||||
"parser": "go-template"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement at
|
||||||
hi@sped0nwen.com.
|
hi@sped0n.com.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -2,44 +2,38 @@
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
Bridget is a minimal [Hugo](https://gohugo.io) theme for photographers/visual artists, powered by Solid.js.
|
Bridget is a minimal [Hugo](https://gohugo.io) theme for photographers/visual artists, based on https://github.com/tylermcrobert/bridget-pictures-www.
|
||||||
|
|
||||||
Based on the https://github.com/tylermcrobert/bridget-pictures-www.
|
Here is a [live demo](https://bridget-demo.sped0n.com).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## [Demo Site](https://bridget-demo.sped0nwen.com)
|
> [!NOTE]
|
||||||
|
> This repository is currently in **maintaince mode** for two reasons:
|
||||||
To see this theme in action, here is a live [demo site](https://bridget-demo.sped0nwen.com) which is rendered with **Bridget** theme.
|
>
|
||||||
|
> 1. I want to keep this theme minimal.
|
||||||
|
> 2. My bandwith after work is limited.
|
||||||
|
>
|
||||||
|
> BUT, bug fixes will be addressed (including related issues and PRs), and they are the **number one priority** for this project.
|
||||||
|
>
|
||||||
|
> Please understand that feature request might **NOT** be addressed or may take a long time to be implemented.
|
||||||
|
>
|
||||||
|
> Anyway, forks are welcomed, and I'm looking forward to seeing what you can do with the theme.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Head to this [documentation](https://github.com/Sped0n/bridget/blob/main/doc/getStarted.md) for a complete guidance to get started with the Bridget theme.
|
Head to this [documentation](https://github.com/Sped0n/bridget/blob/main/docs.md) for a complete guidance to get started with the theme.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Blazingly fast**: 100/100 on both desktop and mobile in [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights)
|
- **Blazingly fast**: 100/100 on both desktop and mobile in [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights)
|
||||||
- Powered by **[Solid.js](https://www.solidjs.com)**, a declarative, efficient, and flexible JavaScript library for building user interfaces
|
- Powered by **[SolidJS](https://www.solidjs.com)**, a declarative, efficient, and flexible JavaScript library for building user interfaces
|
||||||
- JS **dynamic loading** (powered by ESM)
|
- JS **dynamic loading**
|
||||||
- Image **Preloading**/**Lazy loading**
|
- Image **preloading** + **lazy loading**
|
||||||
- **Dynamic resolution** based on view mode
|
- **Dynamic resolution** based on view mode
|
||||||
- Multiple **analytics** services supported
|
- Multiple **analytics** services supported
|
||||||
- Search engine **verification** supported (Google, Bind, Yandex and Baidu)
|
- Search engine **verification** supported (Google, Bind, Yandex and Baidu)
|
||||||
|
|
||||||
## Multilingual and i18n
|
|
||||||
|
|
||||||
Bridget supports the following languages:
|
|
||||||
|
|
||||||
- English
|
|
||||||
- Simplified Chinese
|
|
||||||
- Traditional Chinese
|
|
||||||
- Japanese
|
|
||||||
- Korean
|
|
||||||
- Deutsch
|
|
||||||
- Spanish
|
|
||||||
- Italian
|
|
||||||
- [Contribute with a new language](https://github.com/Sped0n/bridget/pulls)
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- https://github.com/tylermcrobert/bridget-pictures-www
|
- https://github.com/tylermcrobert/bridget-pictures-www
|
||||||
|
|||||||
@@ -1,48 +1,10 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Geist';
|
font-family: 'Geist';
|
||||||
src:
|
src:
|
||||||
url('/lib/fonts/GeistVF.woff2') format('woff2 supports variations'),
|
url(/* @vite-ignore */'{{- "lib/fonts/GeistVF.woff2" | absURL -}}')
|
||||||
url('/lib/fonts/GeistVF.woff2') format('woff2-variations');
|
format('woff2 supports variations'),
|
||||||
font-weight: 400;
|
url(/* @vite-ignore */'{{- "lib/fonts/GeistVF.woff2" | absURL -}}')
|
||||||
font-style: normal;
|
format('woff2-variations');
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Noto Sans CJK SC';
|
|
||||||
src: url('/lib/fonts/NotoSansCJKsc-Regular.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Noto Sans CJK TC';
|
|
||||||
src: url('/lib/fonts/NotoSansCJKtc-Regular.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Noto Sans CJK JP';
|
|
||||||
src: url('/lib/fonts/NotoSansCJKjp-Regular.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Noto Sans CJK KR';
|
|
||||||
src: url('/lib/fonts/NotoSansCJKkr-Regular.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Noto Sans';
|
|
||||||
src: url('/lib/fonts/NotoSans-Regular.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@@ -50,7 +12,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'FW';
|
font-family: 'FW';
|
||||||
src: url('/lib/fonts/fw.woff2') format('woff2');
|
src: url(/* @vite-ignore */'{{- "lib/fonts/fw.woff2" | absURL -}}') format('woff2');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
|||||||
4
assets/scss/_core/_foundation.scss
Normal file
4
assets/scss/_core/_foundation.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@forward 'reset';
|
||||||
|
@forward 'font';
|
||||||
|
@forward 'typography';
|
||||||
|
@forward 'mixins';
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@use 'sass:map';
|
||||||
|
|
||||||
$breakpoints: (
|
$breakpoints: (
|
||||||
'mobile': 375px,
|
'mobile': 375px,
|
||||||
'tablet': 768px,
|
'tablet': 768px,
|
||||||
@@ -8,8 +10,8 @@ $breakpoints: (
|
|||||||
// Breakpoints
|
// Breakpoints
|
||||||
|
|
||||||
@mixin min-width($breakpoint) {
|
@mixin min-width($breakpoint) {
|
||||||
@if map-has-key($breakpoints, $breakpoint) {
|
@if map.has-key($breakpoints, $breakpoint) {
|
||||||
@media (min-width: map-get($breakpoints, $breakpoint)) {
|
@media (min-width: map.get($breakpoints, $breakpoint)) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
@@ -18,8 +20,8 @@ $breakpoints: (
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin max-width($breakpoint) {
|
@mixin max-width($breakpoint) {
|
||||||
@if map-has-key($breakpoints, $breakpoint) {
|
@if map.has-key($breakpoints, $breakpoint) {
|
||||||
@media (max-width: (map-get($breakpoints, $breakpoint) - 1px)) {
|
@media (max-width: (map.get($breakpoints, $breakpoint) - 1px)) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
@import 'mixins';
|
@use 'mixins' as *;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: sans-serif;
|
font-family: 'Geist', sans-serif;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
font-family: 'FW';
|
font-family: 'FW', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include min-width('tablet') {
|
@include min-width('tablet') {
|
||||||
@@ -16,51 +16,3 @@ body {
|
|||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body:lang(en) {
|
|
||||||
font-family: 'Geist', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(de) {
|
|
||||||
font-family: 'Geist', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(es) {
|
|
||||||
font-family: 'Geist', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(fr) {
|
|
||||||
font-family: 'Geist', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(it) {
|
|
||||||
font-family: 'Geist', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(zh-cn) {
|
|
||||||
font-family: 'Noto Sans', 'Noto Sans CJK SC', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(zh-sg) {
|
|
||||||
font-family: 'Noto Sans', 'Noto Sans CJK SC', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(zh-hk) {
|
|
||||||
font-family: 'Noto Sans', 'Noto Sans CJK TC', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(zh-mo) {
|
|
||||||
font-family: 'Noto Sans', 'Noto Sans CJK TC', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(zh-tw) {
|
|
||||||
font-family: 'Noto Sans', 'Noto Sans CJK TC', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(ja) {
|
|
||||||
font-family: 'Noto Sans', 'Noto Sans CJK JP', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body:lang(ko) {
|
|
||||||
font-family: 'Noto Sans', 'Noto Sans CJK KR', sans-serif;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
@use 'sass:map';
|
||||||
|
|
||||||
|
@use '_core/mixins' as *;
|
||||||
|
|
||||||
|
$tablet: map.get($breakpoints, 'tablet') - 1;
|
||||||
|
|
||||||
article {
|
article {
|
||||||
padding: var(--space-standard);
|
padding: var(--space-standard);
|
||||||
max-width: 25em;
|
max-width: 25em;
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20vh;
|
gap: 20vh;
|
||||||
|
|
||||||
padding-top: 50vh;
|
padding-top: calc(var(--window-height) * 0.4);
|
||||||
margin-top: calc(var(--nav-height) * -1);
|
margin-top: calc(var(--nav-height) * -1);
|
||||||
|
|
||||||
img {
|
img {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 50vh;
|
top: calc(var(--window-height) * 0.4);
|
||||||
|
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
height: 20vh;
|
height: 20vh;
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 20vh;
|
margin-bottom: calc(var(--window-height) * 0.35);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
$tablet: map-get($breakpoints, 'tablet') - 1;
|
@use 'sass:map';
|
||||||
|
|
||||||
|
@use '_core/mixins' as *;
|
||||||
|
|
||||||
|
$tablet: map.get($breakpoints, 'tablet') - 1;
|
||||||
|
|
||||||
@media (max-width: $tablet), (hover: none) {
|
@media (max-width: $tablet), (hover: none) {
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: var(--nav-height);
|
top: var(--nav-height);
|
||||||
z-index: var(--z-nav-gallery);
|
z-index: var(--z-nav-gallery);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -25,8 +26,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
max-height: calc(var(--window-height) - 2 * var(--nav-height));
|
||||||
height: 100%;
|
max-width: 100%;
|
||||||
|
width: auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +51,21 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
$tablet: map-get($breakpoints, 'tablet') - 1;
|
@use 'sass:map';
|
||||||
|
|
||||||
|
@use '_core/mixins' as *;
|
||||||
|
|
||||||
|
$tablet: map.get($breakpoints, 'tablet') - 1;
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
@import '_core/mixins';
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--window-height: 100vh;
|
--window-height: 100vh;
|
||||||
--nav-height: 2rem;
|
--nav-height: 2rem;
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
@charset "utf-8";
|
@charset "utf-8";
|
||||||
|
|
||||||
@import '_core/reset';
|
@use '_core/foundation';
|
||||||
@import '_core/font';
|
@use '_variables';
|
||||||
@import '_core/typography';
|
@use '_core/base';
|
||||||
@import '_core/mixins';
|
|
||||||
@import '_variables';
|
|
||||||
@import '_core/base';
|
|
||||||
|
|
||||||
@import '_partial/nav';
|
@use '_partial/nav';
|
||||||
@import '_partial/article';
|
@use '_partial/article';
|
||||||
@import '_partial/container';
|
@use '_partial/container';
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
@charset "utf-8";
|
@charset "utf-8";
|
||||||
|
|
||||||
@import '_partial/customCursor';
|
@use '_partial/customCursor';
|
||||||
@import '_partial/stage';
|
@use '_partial/stage';
|
||||||
@import '_partial/stageNav';
|
@use '_partial/stageNav';
|
||||||
|
@use '_partial/collection';
|
||||||
|
@use '_partial/gallery';
|
||||||
|
|
||||||
@import '_partial/collection';
|
@use '../../node_modules/swiper/swiper.css';
|
||||||
@import '_partial/gallery';
|
|
||||||
|
|
||||||
@import 'node_modules/swiper/swiper.scss';
|
|
||||||
|
|||||||
91
assets/ts/configState.tsx
Normal file
91
assets/ts/configState.tsx
Normal 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
|
||||||
|
}
|
||||||
2
assets/ts/critical.ts
Normal file
2
assets/ts/critical.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// this is a dummy file to trick vite to generate a critical.css file
|
||||||
|
import '../scss/critical.scss'
|
||||||
@@ -4,7 +4,6 @@ export default function CustomCursor(props: {
|
|||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
active: Accessor<boolean>
|
active: Accessor<boolean>
|
||||||
cursorText: Accessor<string>
|
cursorText: Accessor<string>
|
||||||
isOpen: Accessor<boolean>
|
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
// types
|
// types
|
||||||
interface XY {
|
interface XY {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
// eslint-disable-next-line sort-imports
|
import { Show, createMemo, type JSX } from 'solid-js'
|
||||||
import { Show, createMemo, createSignal, type JSX } from 'solid-js'
|
|
||||||
|
|
||||||
import type { ImageJSON } from '../resources'
|
import { useImageState } from '../imageState'
|
||||||
import type { Vector } from '../utils'
|
|
||||||
|
|
||||||
import CustomCursor from './customCursor'
|
import CustomCursor from './customCursor'
|
||||||
import Nav from './nav'
|
import Nav from './nav'
|
||||||
import Stage from './stage'
|
import Stage from './stage'
|
||||||
import StageNav from './stageNav'
|
import StageNav from './stageNav'
|
||||||
|
import { useDesktopState } from './state'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* interfaces and types
|
* interfaces and types
|
||||||
@@ -24,65 +23,36 @@ export interface DesktopImage extends HTMLImageElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistoryItem {
|
|
||||||
i: number
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* components
|
* components
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function Desktop(props: {
|
export default function Desktop(props: {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
ijs: ImageJSON[]
|
|
||||||
prevText: string
|
prevText: string
|
||||||
closeText: string
|
closeText: string
|
||||||
nextText: string
|
nextText: string
|
||||||
loadingText: string
|
loadingText: string
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [cordHist, setCordHist] = createSignal<HistoryItem[]>([])
|
const imageState = useImageState()
|
||||||
const [isLoading, setIsLoading] = createSignal(false)
|
const [desktop] = useDesktopState()
|
||||||
const [isOpen, setIsOpen] = createSignal(false)
|
|
||||||
const [isAnimating, setIsAnimating] = createSignal(false)
|
|
||||||
const [hoverText, setHoverText] = createSignal('')
|
|
||||||
const [navVector, setNavVector] = createSignal<Vector>('none')
|
|
||||||
|
|
||||||
const active = createMemo(() => isOpen() && !isAnimating())
|
const active = createMemo(() => desktop.isOpen() && !desktop.isAnimating())
|
||||||
const cursorText = createMemo(() => (isLoading() ? props.loadingText : hoverText()))
|
const cursorText = createMemo(() =>
|
||||||
|
desktop.isLoading() ? props.loadingText : desktop.hoverText()
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Nav />
|
<Nav />
|
||||||
<Show when={props.ijs.length > 0}>
|
<Show when={imageState().length > 0}>
|
||||||
<Stage
|
<Stage />
|
||||||
ijs={props.ijs}
|
<Show when={desktop.isOpen()}>
|
||||||
setIsLoading={setIsLoading}
|
<CustomCursor cursorText={cursorText} active={active} />
|
||||||
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} />
|
|
||||||
<StageNav
|
<StageNav
|
||||||
prevText={props.prevText}
|
prevText={props.prevText}
|
||||||
closeText={props.closeText}
|
closeText={props.closeText}
|
||||||
nextText={props.nextText}
|
nextText={props.nextText}
|
||||||
loadingText={props.loadingText}
|
|
||||||
active={active}
|
|
||||||
isAnimating={isAnimating}
|
|
||||||
setCordHist={setCordHist}
|
|
||||||
isOpen={isOpen}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
setHoverText={setHoverText}
|
|
||||||
navVector={navVector}
|
|
||||||
setNavVector={setNavVector}
|
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -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'
|
import { expand } from '../utils'
|
||||||
|
|
||||||
/**
|
import { useDesktopState } from './state'
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function Nav(): null {
|
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(() => {
|
const imageState = useImageState()
|
||||||
updateIndexText(expand(state().index + 1), expand(state().length))
|
const [config, { incThreshold, decThreshold }] = useConfigState()
|
||||||
updateThresholdText(expand(state().threshold))
|
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
|
createEffect(() => {
|
||||||
incButton.onclick = incThreshold
|
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
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,409 +1,167 @@
|
|||||||
import { type gsap } from 'gsap'
|
import { type gsap } from 'gsap'
|
||||||
import {
|
import { For, createEffect, on, onMount, type JSX } from 'solid-js'
|
||||||
For,
|
|
||||||
createEffect,
|
|
||||||
on,
|
|
||||||
onMount,
|
|
||||||
type Accessor,
|
|
||||||
type JSX,
|
|
||||||
type Setter
|
|
||||||
} from 'solid-js'
|
|
||||||
|
|
||||||
import type { ImageJSON } from '../resources'
|
import { useConfigState } from '../configState'
|
||||||
import { useState, type State } from '../state'
|
import { useImageState } from '../imageState'
|
||||||
import { decrement, increment, loadGsap, type Vector } from '../utils'
|
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'
|
||||||
|
|
||||||
/**
|
export default function Stage(): JSX.Element {
|
||||||
* 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
|
|
||||||
let _gsap: typeof gsap
|
let _gsap: typeof gsap
|
||||||
|
let gsapPromise: Promise<void> | undefined
|
||||||
|
|
||||||
// eslint-disable-next-line solid/reactivity
|
const imageState = useImageState()
|
||||||
const imgs: DesktopImage[] = Array<DesktopImage>(props.ijs.length)
|
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 last = { x: 0, y: 0 }
|
||||||
|
|
||||||
let abortController: AbortController | undefined
|
let abortController: AbortController | undefined
|
||||||
|
|
||||||
// states
|
|
||||||
let gsapLoaded = false
|
let gsapLoaded = false
|
||||||
|
|
||||||
const [state, { incIndex }] = useState()
|
|
||||||
const stateLength = state().length
|
|
||||||
|
|
||||||
let mounted = false
|
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) => {
|
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 cord = { x: e.clientX, y: e.clientY }
|
||||||
const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y)
|
const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y)
|
||||||
|
|
||||||
if (travelDist > state().threshold) {
|
if (travelDist > config().threshold) {
|
||||||
last = cord
|
const nextIndex = increment(desktop.index(), length)
|
||||||
incIndex()
|
|
||||||
|
|
||||||
const _state = state()
|
last = cord
|
||||||
const newHist = { i: _state.index, ...cord }
|
setIndex(nextIndex)
|
||||||
props.setCordHist((prev) => [...prev, newHist].slice(-stateLength))
|
setCordHist((prev) => [...prev, { i: nextIndex, ...cord }].slice(-length))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick: () => void = () => {
|
const onClick: () => Promise<void> = async () => {
|
||||||
!props.isAnimating() && props.setIsOpen(true)
|
if (!gsapLoaded) {
|
||||||
|
await ensureGsapReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desktop.isAnimating() || !gsapLoaded) return
|
||||||
|
if (desktop.index() < 0 || desktop.cordHist().length === 0) return
|
||||||
|
setIsOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setPosition: () => void = () => {
|
const setPosition: () => void = () => {
|
||||||
if (!mounted) return
|
syncStagePosition({
|
||||||
if (imgs.length === 0) return
|
gsap: _gsap,
|
||||||
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(
|
|
||||||
imgs,
|
imgs,
|
||||||
getTrailInactiveElsIndex(_cordHist, _state)
|
cordHist: desktop.cordHist(),
|
||||||
)
|
trailLength: config().trailLength,
|
||||||
// move down and hide trail inactive
|
length: imageState().length,
|
||||||
tl.to(trailInactiveEls, {
|
isOpen: desktop.isOpen(),
|
||||||
y: '+=20',
|
navVector: desktop.navVector(),
|
||||||
ease: 'power3.in',
|
mounted,
|
||||||
stagger: 0.075,
|
setIsLoading
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const minimizeImage: () => Promise<
|
const expandImage: () => Promise<void> = async () => {
|
||||||
gsap.core.Omit<gsap.core.Timeline, 'then'>
|
|
||||||
> = async () => {
|
|
||||||
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
|
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
|
||||||
|
|
||||||
props.setIsAnimating(true)
|
await expandStage({
|
||||||
props.setNavVector('none') // cleanup
|
gsap: _gsap,
|
||||||
|
imgs,
|
||||||
const _cordHist = props.cordHist()
|
cordHist: desktop.cordHist(),
|
||||||
const _state = state()
|
trailLength: config().trailLength,
|
||||||
|
length: imageState().length,
|
||||||
const elcIndex = getCurrentElIndex(_cordHist)
|
mounted,
|
||||||
const elsTrailInactiveIndexes = getTrailInactiveElsIndex(_cordHist, _state)
|
setIsLoading,
|
||||||
|
setIsAnimating
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLoaderForHiresImage(img: DesktopImage): void {
|
const minimizeImage: () => Promise<void> = async () => {
|
||||||
if (!mounted || !gsapLoaded) return
|
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
|
||||||
if (!img.complete) {
|
|
||||||
props.setIsLoading(true)
|
setNavVector('none')
|
||||||
// abort controller for cleanup
|
|
||||||
const controller = new AbortController()
|
await minimizeStage({
|
||||||
const abortSignal = controller.signal
|
gsap: _gsap,
|
||||||
// event listeners
|
imgs,
|
||||||
img.addEventListener(
|
cordHist: desktop.cordHist(),
|
||||||
'load',
|
trailLength: config().trailLength,
|
||||||
() => {
|
mounted,
|
||||||
_gsap
|
setIsAnimating
|
||||||
.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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// preload logic
|
|
||||||
imgs.forEach((img, i) => {
|
imgs.forEach((img, i) => {
|
||||||
// preload first 5 images on page load
|
|
||||||
if (i < 5) {
|
if (i < 5) {
|
||||||
img.src = img.dataset.loUrl
|
img.src = img.dataset.loUrl
|
||||||
}
|
}
|
||||||
// lores preloader for rest of the images
|
|
||||||
// eslint-disable-next-line solid/reactivity
|
|
||||||
onMutation(img, (mutation) => {
|
onMutation(img, (mutation) => {
|
||||||
// if open or animating, hold
|
if (desktop.isOpen() || desktop.isAnimating()) return false
|
||||||
if (props.isOpen() || props.isAnimating()) return false
|
|
||||||
// if mutation is not about style attribute, hold
|
|
||||||
if (mutation.attributeName !== 'style') return false
|
if (mutation.attributeName !== 'style') return false
|
||||||
|
|
||||||
const opacity = parseFloat(img.style.opacity)
|
const opacity = parseFloat(img.style.opacity)
|
||||||
// if opacity is not 1, hold
|
|
||||||
if (opacity !== 1) return false
|
if (opacity !== 1) return false
|
||||||
// preload the i + 5th image, if it exists
|
|
||||||
if (i + 5 < imgs.length) {
|
if (i + 5 < imgs.length) {
|
||||||
imgs[i + 5].src = imgs[i + 5].dataset.loUrl
|
imgs[i + 5].src = imgs[i + 5].dataset.loUrl
|
||||||
}
|
}
|
||||||
// triggered
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// load gsap on mousemove
|
|
||||||
window.addEventListener(
|
window.addEventListener('pointermove', () => void ensureGsapReady(), {
|
||||||
'mousemove',
|
passive: true,
|
||||||
() => {
|
once: true
|
||||||
loadGsap()
|
})
|
||||||
.then((g) => {
|
window.addEventListener('pointerdown', () => void ensureGsapReady(), {
|
||||||
_gsap = g
|
passive: true,
|
||||||
gsapLoaded = true
|
once: true
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
window.addEventListener('click', () => void ensureGsapReady(), {
|
||||||
console.log(e)
|
passive: true,
|
||||||
})
|
once: true
|
||||||
},
|
})
|
||||||
{ passive: true, once: true }
|
|
||||||
)
|
|
||||||
// event listeners
|
|
||||||
abortController = new AbortController()
|
abortController = new AbortController()
|
||||||
const abortSignal = abortController.signal
|
const abortSignal = abortController.signal
|
||||||
window.addEventListener('mousemove', onMouse, {
|
window.addEventListener('mousemove', onMouse, {
|
||||||
passive: true,
|
passive: true,
|
||||||
signal: abortSignal
|
signal: abortSignal
|
||||||
})
|
})
|
||||||
// mounted
|
|
||||||
mounted = true
|
mounted = true
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => props.cordHist(),
|
() => desktop.cordHist(),
|
||||||
() => {
|
() => {
|
||||||
setPosition()
|
setPosition()
|
||||||
},
|
},
|
||||||
@@ -413,37 +171,38 @@ export default function Stage(props: {
|
|||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => props.isOpen(),
|
desktop.isOpen,
|
||||||
async () => {
|
async (isOpen) => {
|
||||||
if (props.isAnimating()) return
|
if (desktop.isAnimating()) return
|
||||||
if (props.isOpen()) {
|
|
||||||
// expand image
|
if (isOpen) {
|
||||||
|
if (desktop.index() < 0 || desktop.cordHist().length === 0) {
|
||||||
|
setIsOpen(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await expandImage()
|
await expandImage()
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
void 0
|
setIsOpen(false)
|
||||||
|
setIsAnimating(false)
|
||||||
|
setIsLoading(false)
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line solid/reactivity
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// abort controller for cleanup
|
|
||||||
abortController?.abort()
|
abortController?.abort()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// minimize image
|
|
||||||
await minimizeImage()
|
await minimizeImage()
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
void 0
|
void 0
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line solid/reactivity
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// event listeners and its abort controller
|
|
||||||
abortController = new AbortController()
|
abortController = new AbortController()
|
||||||
const abortSignal = abortController.signal
|
const abortSignal = abortController.signal
|
||||||
window.addEventListener('mousemove', onMouse, {
|
window.addEventListener('mousemove', onMouse, {
|
||||||
passive: true,
|
passive: true,
|
||||||
signal: abortSignal
|
signal: abortSignal
|
||||||
})
|
})
|
||||||
// cleanup isLoading
|
setIsLoading(false)
|
||||||
props.setIsLoading(false)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -454,7 +213,7 @@ export default function Stage(props: {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="stage" onClick={onClick} onKeyDown={onClick}>
|
<div class="stage" onClick={onClick} onKeyDown={onClick}>
|
||||||
<For each={props.ijs}>
|
<For each={imageState().images}>
|
||||||
{(ij, i) => (
|
{(ij, i) => (
|
||||||
<img
|
<img
|
||||||
ref={imgs[i()]}
|
ref={imgs[i()]}
|
||||||
|
|||||||
263
assets/ts/desktop/stageAnimations.ts
Normal file
263
assets/ts/desktop/stageAnimations.ts
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,78 +1,93 @@
|
|||||||
import { For, 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 { useImageState } from '../imageState'
|
||||||
import { decrement, increment, type Vector } from '../utils'
|
import { decrement, increment } from '../utils'
|
||||||
|
|
||||||
import type { HistoryItem } from './layout'
|
import { useDesktopState } from './state'
|
||||||
|
|
||||||
export default function StageNav(props: {
|
export default function StageNav(props: {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
prevText: string
|
prevText: string
|
||||||
closeText: string
|
closeText: string
|
||||||
nextText: 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 {
|
}): JSX.Element {
|
||||||
// types
|
// types
|
||||||
type NavItem = (typeof navItems)[number]
|
type NavItem = (typeof navItems)[number]
|
||||||
|
|
||||||
// variables
|
// variables
|
||||||
|
let controller: AbortController | undefined
|
||||||
// eslint-disable-next-line solid/reactivity
|
// eslint-disable-next-line solid/reactivity
|
||||||
const navItems = [props.prevText, props.closeText, props.nextText] as const
|
const navItems = [props.prevText, props.closeText, props.nextText] as const
|
||||||
|
|
||||||
// states
|
// 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 = () => {
|
const prevImage: () => void = () => {
|
||||||
props.setNavVector('prev')
|
setNavVector('prev')
|
||||||
props.setCordHist((c) =>
|
setCordHist((c) =>
|
||||||
c.map((item) => {
|
c.map((item) => {
|
||||||
return { ...item, i: decrement(item.i, stateLength) }
|
return { ...item, i: decrement(item.i, imageState().length) }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
decIndex()
|
decIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeImage: () => void = () => {
|
const closeImage: () => void = () => {
|
||||||
props.setIsOpen(false)
|
setIsOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextImage: () => void = () => {
|
const nextImage: () => void = () => {
|
||||||
props.setNavVector('next')
|
setNavVector('next')
|
||||||
props.setCordHist((c) =>
|
setCordHist((c) =>
|
||||||
c.map((item) => {
|
c.map((item) => {
|
||||||
return { ...item, i: increment(item.i, stateLength) }
|
return { ...item, i: increment(item.i, imageState().length) }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
incIndex()
|
incIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick: (item: NavItem) => void = (item) => {
|
const handleClick: (item: NavItem) => void = (item) => {
|
||||||
if (!props.isOpen() || props.isAnimating()) return
|
if (!desktop.isOpen() || desktop.isAnimating()) return
|
||||||
if (item === navItems[0]) prevImage()
|
if (item === navItems[0]) prevImage()
|
||||||
else if (item === navItems[1]) closeImage()
|
else if (item === navItems[1]) closeImage()
|
||||||
else nextImage()
|
else nextImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKey: (e: KeyboardEvent) => void = (e) => {
|
const handleKey: (e: KeyboardEvent) => void = (e) => {
|
||||||
if (!props.isOpen() || props.isAnimating()) return
|
if (!desktop.isOpen() || desktop.isAnimating()) return
|
||||||
if (e.key === 'ArrowLeft') prevImage()
|
if (e.key === 'ArrowLeft') prevImage()
|
||||||
else if (e.key === 'Escape') closeImage()
|
else if (e.key === 'Escape') closeImage()
|
||||||
else if (e.key === 'ArrowRight') nextImage()
|
else if (e.key === 'ArrowRight') nextImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="navOverlay" classList={{ active: props.active() }}>
|
<div class="navOverlay" classList={{ active: active() }}>
|
||||||
<For each={navItems}>
|
<For each={navItems}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<div
|
<div
|
||||||
@@ -80,9 +95,9 @@ export default function StageNav(props: {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleClick(item)
|
handleClick(item)
|
||||||
}}
|
}}
|
||||||
onKeyDown={handleKey}
|
onFocus={() => setHoverText(item)}
|
||||||
onFocus={() => props.setHoverText(item)}
|
onMouseOver={() => setHoverText(item)}
|
||||||
onMouseOver={() => props.setHoverText(item)}
|
tabIndex="-1"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
67
assets/ts/desktop/stageUtils.ts
Normal file
67
assets/ts/desktop/stageUtils.ts
Normal 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)
|
||||||
|
}
|
||||||
96
assets/ts/desktop/state.ts
Normal file
96
assets/ts/desktop/state.ts
Normal 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
41
assets/ts/imageState.tsx
Normal 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
|
||||||
|
}
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
import {
|
import { Match, Show, Switch, createResource, lazy, type JSX } from 'solid-js'
|
||||||
Match,
|
|
||||||
Show,
|
|
||||||
Switch,
|
|
||||||
createEffect,
|
|
||||||
createResource,
|
|
||||||
createSignal,
|
|
||||||
lazy,
|
|
||||||
type JSX
|
|
||||||
} from 'solid-js'
|
|
||||||
import { render } from 'solid-js/web'
|
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 { getImageJSON } from './resources'
|
||||||
import { StateProvider } from './state'
|
|
||||||
|
|
||||||
import '../scss/style.scss'
|
import '../scss/style.scss'
|
||||||
|
|
||||||
@@ -35,46 +29,60 @@ const container = document.getElementsByClassName('container')[0] as Container
|
|||||||
const Desktop = lazy(async () => await import('./desktop/layout'))
|
const Desktop = lazy(async () => await import('./desktop/layout'))
|
||||||
const Mobile = lazy(async () => await import('./mobile/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 {
|
function Main(): JSX.Element {
|
||||||
// variables
|
// variables
|
||||||
const [ijs] = createResource(getImageJSON)
|
const [ijs] = createResource(getImageJSON)
|
||||||
const isMobile = window.matchMedia('(hover: none)').matches
|
const ua = window.navigator.userAgent.toLowerCase()
|
||||||
|
const hasTouchInput = 'ontouchstart' in window || window.navigator.maxTouchPoints > 0
|
||||||
// states
|
const hasTouchLayout =
|
||||||
const [scrollable, setScollable] = createSignal(true)
|
window.matchMedia('(pointer: coarse)').matches ||
|
||||||
|
window.matchMedia('(hover: none)').matches
|
||||||
createEffect(() => {
|
const isMobileUA = /android|iphone|ipad|ipod|mobile/.test(ua)
|
||||||
if (scrollable()) {
|
const isWindowsDesktop = /windows nt/.test(ua)
|
||||||
container.classList.remove('disableScroll')
|
const isMobile = isMobileUA || (hasTouchInput && hasTouchLayout && !isWindowsDesktop)
|
||||||
} else {
|
|
||||||
container.classList.add('disableScroll')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={ijs.state === 'ready'}>
|
<Show when={ijs.state === 'ready'}>
|
||||||
<StateProvider length={ijs()?.length ?? 0}>
|
<ImageStateProvider images={ijs() ?? []}>
|
||||||
<Switch fallback={<div>Error</div>}>
|
<ConfigStateProvider>
|
||||||
<Match when={isMobile}>
|
<AppContent
|
||||||
<Mobile
|
isMobile={isMobile}
|
||||||
ijs={ijs() ?? []}
|
prevText={container.dataset.prev}
|
||||||
closeText={container.dataset.close}
|
closeText={container.dataset.close}
|
||||||
loadingText={container.dataset.loading}
|
nextText={container.dataset.next}
|
||||||
setScrollable={setScollable}
|
loadingText={container.dataset.loading}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</ConfigStateProvider>
|
||||||
<Match when={!isMobile}>
|
</ImageStateProvider>
|
||||||
<Desktop
|
|
||||||
ijs={ijs() ?? []}
|
|
||||||
prevText={container.dataset.prev}
|
|
||||||
closeText={container.dataset.close}
|
|
||||||
nextText={container.dataset.next}
|
|
||||||
loadingText={container.dataset.loading}
|
|
||||||
/>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</StateProvider>
|
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
import {
|
import { For, createEffect, on, onMount, type JSX } from 'solid-js'
|
||||||
For,
|
|
||||||
createEffect,
|
|
||||||
on,
|
|
||||||
onMount,
|
|
||||||
type Accessor,
|
|
||||||
type JSX,
|
|
||||||
type Setter
|
|
||||||
} from 'solid-js'
|
|
||||||
|
|
||||||
import type { ImageJSON } from '../resources'
|
import { useImageState } from '../imageState'
|
||||||
import { useState } from '../state'
|
|
||||||
|
|
||||||
import type { MobileImage } from './layout'
|
import type { MobileImage } from './layout'
|
||||||
|
import { useMobileState } from './state'
|
||||||
|
|
||||||
function getRandom(min: number, max: number): number {
|
function getRandom(min: number, max: number): number {
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
@@ -31,29 +23,26 @@ function onIntersection<T extends HTMLElement>(
|
|||||||
}).observe(element)
|
}).observe(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Collection(props: {
|
export default function Collection(): JSX.Element {
|
||||||
children?: JSX.Element
|
|
||||||
ijs: ImageJSON[]
|
|
||||||
isAnimating: Accessor<boolean>
|
|
||||||
isOpen: Accessor<boolean>
|
|
||||||
setIsOpen: Setter<boolean>
|
|
||||||
}): JSX.Element {
|
|
||||||
// variables
|
// variables
|
||||||
// eslint-disable-next-line solid/reactivity
|
const imageState = useImageState()
|
||||||
const imgs: MobileImage[] = Array<MobileImage>(props.ijs.length)
|
const imgs: MobileImage[] = Array<MobileImage>(imageState().length)
|
||||||
|
|
||||||
// states
|
// states
|
||||||
const [state, { setIndex }] = useState()
|
const [mobile, { setIndex, setIsOpen }] = useMobileState()
|
||||||
|
|
||||||
// helper functions
|
// helper functions
|
||||||
const handleClick: (i: number) => void = (i) => {
|
const handleClick: (i: number) => void = (i) => {
|
||||||
if (props.isAnimating()) return
|
if (mobile.isAnimating()) return
|
||||||
setIndex(i)
|
setIndex(i)
|
||||||
props.setIsOpen(true)
|
setIsOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollToActive: () => void = () => {
|
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
|
// effects
|
||||||
@@ -94,11 +83,9 @@ export default function Collection(props: {
|
|||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
|
mobile.isOpen,
|
||||||
() => {
|
() => {
|
||||||
props.isOpen()
|
if (!mobile.isOpen()) scrollToActive() // scroll to active when closed
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (!props.isOpen()) scrollToActive() // scroll to active when closed
|
|
||||||
},
|
},
|
||||||
{ defer: true }
|
{ defer: true }
|
||||||
)
|
)
|
||||||
@@ -107,7 +94,7 @@ export default function Collection(props: {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="collection">
|
<div class="collection">
|
||||||
<For each={props.ijs}>
|
<For each={imageState().images}>
|
||||||
{(ij, i) => (
|
{(ij, i) => (
|
||||||
<img
|
<img
|
||||||
ref={imgs[i()]}
|
ref={imgs[i()]}
|
||||||
@@ -116,7 +103,7 @@ export default function Collection(props: {
|
|||||||
data-src={ij.loUrl}
|
data-src={ij.loUrl}
|
||||||
alt={ij.alt}
|
alt={ij.alt}
|
||||||
style={{
|
style={{
|
||||||
transform: `translate3d(${i() !== 0 ? getRandom(-25, 25) : 0}%, ${i() !== 0 ? getRandom(-30, 30) : 0}%, 0)`
|
transform: `translate3d(${i() !== 0 ? getRandom(-25, 25) : 0}%, ${i() !== 0 ? getRandom(-35, 35) : 0}%, 0)`
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleClick(i())
|
handleClick(i())
|
||||||
|
|||||||
@@ -1,209 +1,170 @@
|
|||||||
import { type gsap } from 'gsap'
|
import { type gsap } from 'gsap'
|
||||||
import {
|
import {
|
||||||
createEffect,
|
createEffect,
|
||||||
|
createMemo,
|
||||||
createSignal,
|
createSignal,
|
||||||
For,
|
For,
|
||||||
on,
|
on,
|
||||||
onMount,
|
onMount,
|
||||||
Show,
|
untrack,
|
||||||
type Accessor,
|
type JSX
|
||||||
type JSX,
|
|
||||||
type Setter
|
|
||||||
} from 'solid-js'
|
} from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
import { type Swiper } from 'swiper'
|
import { type Swiper } from 'swiper'
|
||||||
import invariant from 'tiny-invariant'
|
import invariant from 'tiny-invariant'
|
||||||
|
|
||||||
import { type ImageJSON } from '../resources'
|
import { useImageState } from '../imageState'
|
||||||
import { useState } from '../state'
|
import { loadGsap, removeDuplicates, type Vector } from '../utils'
|
||||||
import { loadGsap, type Vector } from '../utils'
|
|
||||||
|
|
||||||
import GalleryImage from './galleryImage'
|
import GalleryImage from './galleryImage'
|
||||||
import GalleryNav, { capitalizeFirstLetter } from './galleryNav'
|
import GalleryNav, { capitalizeFirstLetter } from './galleryNav'
|
||||||
|
import { closeGallery, openGallery } from './galleryTransitions'
|
||||||
function removeDuplicates<T>(arr: T[]): T[] {
|
import { getActiveImageIndexes, loadSwiper } from './galleryUtils'
|
||||||
if (arr.length < 2) return arr // optimization
|
import { useMobileState } from './state'
|
||||||
return [...new Set(arr)]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSwiper(): Promise<typeof Swiper> {
|
|
||||||
const s = await import('swiper')
|
|
||||||
return s.Swiper
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Gallery(props: {
|
export default function Gallery(props: {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
ijs: ImageJSON[]
|
|
||||||
closeText: string
|
closeText: string
|
||||||
loadingText: string
|
loadingText: string
|
||||||
isAnimating: Accessor<boolean>
|
|
||||||
setIsAnimating: Setter<boolean>
|
|
||||||
isOpen: Accessor<boolean>
|
|
||||||
setIsOpen: Setter<boolean>
|
|
||||||
setScrollable: Setter<boolean>
|
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
// variables
|
|
||||||
let _gsap: typeof gsap
|
let _gsap: typeof gsap
|
||||||
let _swiper: Swiper
|
let _swiper: Swiper | undefined
|
||||||
|
let initPromise: Promise<void> | undefined
|
||||||
|
|
||||||
let curtain: HTMLDivElement | undefined
|
let curtain: HTMLDivElement | undefined
|
||||||
let gallery: HTMLDivElement | undefined
|
let gallery: HTMLDivElement | undefined
|
||||||
let galleryInner: HTMLDivElement | undefined
|
let galleryInner: HTMLDivElement | undefined
|
||||||
|
|
||||||
// eslint-disable-next-line solid/reactivity
|
const imageState = useImageState()
|
||||||
const _loadingText = capitalizeFirstLetter(props.loadingText)
|
const [mobile, { setIndex, setIsAnimating, setIsScrollLocked }] = useMobileState()
|
||||||
|
|
||||||
|
const loadingText = createMemo(() => capitalizeFirstLetter(props.loadingText))
|
||||||
|
|
||||||
// states
|
|
||||||
let lastIndex = -1
|
let lastIndex = -1
|
||||||
let mounted = false
|
let mounted = false
|
||||||
let navigateVector: Vector = 'none'
|
let navigateVector: Vector = 'none'
|
||||||
|
|
||||||
const [state, { setIndex }] = useState()
|
|
||||||
const [libLoaded, setLibLoaded] = createSignal(false)
|
const [libLoaded, setLibLoaded] = createSignal(false)
|
||||||
// eslint-disable-next-line solid/reactivity
|
const [swiperReady, setSwiperReady] = createSignal(false)
|
||||||
const [loads, setLoads] = createStore(Array<boolean>(props.ijs.length).fill(false))
|
const [loads, setLoads] = createStore(Array<boolean>(imageState().length).fill(false))
|
||||||
|
|
||||||
// helper functions
|
|
||||||
const slideUp: () => void = () => {
|
const slideUp: () => void = () => {
|
||||||
// isAnimating is prechecked in isOpen effect
|
|
||||||
if (!libLoaded() || !mounted) return
|
if (!libLoaded() || !mounted) return
|
||||||
props.setIsAnimating(true)
|
|
||||||
|
|
||||||
invariant(curtain, 'curtain is not defined')
|
invariant(curtain, 'curtain is not defined')
|
||||||
invariant(gallery, 'gallery is not defined')
|
invariant(gallery, 'gallery is not defined')
|
||||||
|
|
||||||
_gsap.to(curtain, {
|
openGallery({
|
||||||
opacity: 1,
|
gsap: _gsap,
|
||||||
duration: 1
|
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 = () => {
|
const slideDown: () => void = () => {
|
||||||
// isAnimating is prechecked in isOpen effect
|
|
||||||
props.setIsAnimating(true)
|
|
||||||
|
|
||||||
invariant(gallery, 'curtain is not defined')
|
invariant(gallery, 'curtain is not defined')
|
||||||
invariant(curtain, 'gallery is not defined')
|
invariant(curtain, 'gallery is not defined')
|
||||||
|
|
||||||
_gsap.to(gallery, {
|
closeGallery({
|
||||||
y: '100%',
|
gsap: _gsap,
|
||||||
ease: 'power3.inOut',
|
curtain,
|
||||||
duration: 1
|
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 = () => {
|
const galleryLoadImages: () => void = () => {
|
||||||
let activeImagesIndex: number[] = []
|
const currentIndex = mobile.index()
|
||||||
const _state = state()
|
|
||||||
const currentIndex = _state.index
|
setLoads(
|
||||||
const nextIndex = Math.min(currentIndex + 1, _state.length - 1)
|
removeDuplicates(
|
||||||
const prevIndex = Math.max(currentIndex - 1, 0)
|
getActiveImageIndexes(currentIndex, imageState().length, navigateVector)
|
||||||
switch (navigateVector) {
|
),
|
||||||
case 'next':
|
true
|
||||||
activeImagesIndex = [nextIndex]
|
)
|
||||||
break
|
|
||||||
case 'prev':
|
|
||||||
activeImagesIndex = [prevIndex]
|
|
||||||
break
|
|
||||||
case 'none':
|
|
||||||
activeImagesIndex = [currentIndex, nextIndex, prevIndex]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
setLoads(removeDuplicates(activeImagesIndex), true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeSlide: (slide: number) => void = (slide) => {
|
const changeSlide: (slide: number) => void = (slide) => {
|
||||||
// we are already in the gallery, don't need to
|
if (!swiperReady() || _swiper === undefined) return
|
||||||
// check mounted or libLoaded
|
|
||||||
galleryLoadImages()
|
galleryLoadImages()
|
||||||
_swiper.slideTo(slide, 0)
|
_swiper.slideTo(slide, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// effects
|
const ensureGalleryReady: () => Promise<void> = async () => {
|
||||||
onMount(() => {
|
if (initPromise !== undefined) return await initPromise
|
||||||
window.addEventListener(
|
|
||||||
'touchstart',
|
initPromise = (async () => {
|
||||||
() => {
|
try {
|
||||||
loadGsap()
|
const [g, S] = await Promise.all([loadGsap(), loadSwiper()])
|
||||||
.then((g) => {
|
|
||||||
_gsap = g
|
_gsap = g
|
||||||
})
|
|
||||||
.catch((e) => {
|
invariant(galleryInner, 'galleryInner is not defined')
|
||||||
console.log(e)
|
_swiper = new S(galleryInner, { spaceBetween: 20 })
|
||||||
})
|
_swiper.on('slideChange', ({ realIndex }) => {
|
||||||
loadSwiper()
|
setIndex(realIndex)
|
||||||
.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)
|
|
||||||
})
|
|
||||||
setLibLoaded(true)
|
setLibLoaded(true)
|
||||||
},
|
setSwiperReady(true)
|
||||||
{ once: true, passive: 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
|
mounted = true
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => {
|
() => [swiperReady(), mobile.index()] as const,
|
||||||
state()
|
([ready, index]) => {
|
||||||
},
|
if (!ready || index < 0) return
|
||||||
() => {
|
if (index === lastIndex) return
|
||||||
const i = state().index
|
if (lastIndex === -1) navigateVector = 'none'
|
||||||
if (i === lastIndex)
|
else if (index < lastIndex) navigateVector = 'prev'
|
||||||
return // change slide only when index is changed
|
else if (index > lastIndex) navigateVector = 'next'
|
||||||
else if (lastIndex === -1)
|
else navigateVector = 'none'
|
||||||
navigateVector = 'none' // lastIndex before set
|
changeSlide(index)
|
||||||
else if (i < lastIndex)
|
lastIndex = index
|
||||||
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
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => {
|
() => mobile.isOpen(),
|
||||||
props.isOpen()
|
async (isOpen) => {
|
||||||
},
|
if (isOpen && !swiperReady()) {
|
||||||
() => {
|
await ensureGalleryReady()
|
||||||
if (props.isAnimating()) return
|
}
|
||||||
if (props.isOpen()) slideUp()
|
|
||||||
|
if (!libLoaded() || !swiperReady()) return
|
||||||
|
if (mobile.isAnimating()) return
|
||||||
|
if (isOpen) slideUp()
|
||||||
else slideDown()
|
else slideDown()
|
||||||
},
|
},
|
||||||
{ defer: true }
|
{ defer: true }
|
||||||
@@ -215,26 +176,16 @@ export default function Gallery(props: {
|
|||||||
<div ref={gallery} class="gallery">
|
<div ref={gallery} class="gallery">
|
||||||
<div ref={galleryInner} class="galleryInner">
|
<div ref={galleryInner} class="galleryInner">
|
||||||
<div class="swiper-wrapper">
|
<div class="swiper-wrapper">
|
||||||
<Show when={libLoaded()}>
|
<For each={imageState().images}>
|
||||||
<For each={props.ijs}>
|
{(ij, i) => (
|
||||||
{(ij, i) => (
|
<div class="swiper-slide">
|
||||||
<div class="swiper-slide">
|
<GalleryImage load={loads[i()]} ij={ij} loadingText={loadingText()} />
|
||||||
<GalleryImage
|
</div>
|
||||||
load={loads[i()]}
|
)}
|
||||||
ij={ij}
|
</For>
|
||||||
loadingText={_loadingText}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GalleryNav
|
<GalleryNav closeText={props.closeText} />
|
||||||
closeText={props.closeText}
|
|
||||||
isAnimating={props.isAnimating}
|
|
||||||
setIsOpen={props.setIsOpen}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div ref={curtain} class="curtain" />
|
<div ref={curtain} class="curtain" />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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 invariant from 'tiny-invariant'
|
||||||
|
|
||||||
import type { ImageJSON } from '../resources'
|
import type { ImageJSON } from '../resources'
|
||||||
import { useState } from '../state'
|
|
||||||
import { loadGsap } from '../utils'
|
import { loadGsap } from '../utils'
|
||||||
|
|
||||||
|
import { useMobileState } from './state'
|
||||||
|
|
||||||
export default function GalleryImage(props: {
|
export default function GalleryImage(props: {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
load: boolean
|
load: boolean
|
||||||
@@ -14,40 +16,83 @@ export default function GalleryImage(props: {
|
|||||||
let img: HTMLImageElement | undefined
|
let img: HTMLImageElement | undefined
|
||||||
let loadingDiv: HTMLDivElement | 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(() => {
|
onMount(() => {
|
||||||
loadGsap()
|
gsapPromise = loadGsap()
|
||||||
.then((g) => {
|
.then((g) => {
|
||||||
_gsap = g
|
_gsap = g
|
||||||
|
return g
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
throw e
|
||||||
})
|
})
|
||||||
|
|
||||||
img?.addEventListener(
|
img?.addEventListener(
|
||||||
'load',
|
'load',
|
||||||
() => {
|
() => {
|
||||||
invariant(img, 'ref must be defined')
|
void revealImage()
|
||||||
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' })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ once: true, passive: true }
|
{ 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="slideContainer">
|
<div class="slideContainer">
|
||||||
|
|||||||
@@ -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 { expand } from '../utils'
|
||||||
|
|
||||||
|
import { useMobileState } from './state'
|
||||||
|
|
||||||
export function capitalizeFirstLetter(str: string): string {
|
export function capitalizeFirstLetter(str: string): string {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
}
|
}
|
||||||
@@ -10,17 +12,16 @@ export function capitalizeFirstLetter(str: string): string {
|
|||||||
export default function GalleryNav(props: {
|
export default function GalleryNav(props: {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
closeText: string
|
closeText: string
|
||||||
isAnimating: Accessor<boolean>
|
|
||||||
setIsOpen: Setter<boolean>
|
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
// states
|
// states
|
||||||
const [state] = useState()
|
const imageState = useImageState()
|
||||||
const indexValue = createMemo(() => expand(state().index + 1))
|
const [mobile, { setIsOpen }] = useMobileState()
|
||||||
const indexLength = createMemo(() => expand(state().length))
|
const indexValue = createMemo(() => expand(mobile.index() + 1))
|
||||||
|
const indexLength = createMemo(() => expand(imageState().length))
|
||||||
|
|
||||||
const onClick: () => void = () => {
|
const onClick: () => void = () => {
|
||||||
if (props.isAnimating()) return
|
if (mobile.isAnimating()) return
|
||||||
props.setIsOpen(false)
|
setIsOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -37,7 +38,14 @@ export default function GalleryNav(props: {
|
|||||||
<span class="num">{indexLength()[2]}</span>
|
<span class="num">{indexLength()[2]}</span>
|
||||||
<span class="num">{indexLength()[3]}</span>
|
<span class="num">{indexLength()[3]}</span>
|
||||||
</div>
|
</div>
|
||||||
<div onClick={onClick} onKeyDown={onClick}>
|
<div
|
||||||
|
class="navClose"
|
||||||
|
onClick={onClick}
|
||||||
|
onTouchEnd={onClick}
|
||||||
|
onKeyDown={onClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
>
|
||||||
{capitalizeFirstLetter(props.closeText)}
|
{capitalizeFirstLetter(props.closeText)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
64
assets/ts/mobile/galleryTransitions.ts
Normal file
64
assets/ts/mobile/galleryTransitions.ts
Normal 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)
|
||||||
|
}
|
||||||
26
assets/ts/mobile/galleryUtils.ts
Normal file
26
assets/ts/mobile/galleryUtils.ts
Normal 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 Collection from './collection'
|
||||||
import Gallery from './gallery'
|
import Gallery from './gallery'
|
||||||
|
import { useMobileState } from './state'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* interfaces
|
* interfaces
|
||||||
@@ -18,34 +19,33 @@ export interface MobileImage extends HTMLImageElement {
|
|||||||
|
|
||||||
export default function Mobile(props: {
|
export default function Mobile(props: {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
ijs: ImageJSON[]
|
|
||||||
closeText: string
|
closeText: string
|
||||||
loadingText: string
|
loadingText: string
|
||||||
setScrollable: Setter<boolean>
|
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
// states
|
const imageState = useImageState()
|
||||||
const [isOpen, setIsOpen] = createSignal(false)
|
const [mobile] = useMobileState()
|
||||||
const [isAnimating, setIsAnimating] = createSignal(false)
|
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={props.ijs.length > 0}>
|
<Show when={imageState().length > 0}>
|
||||||
<Collection
|
<Collection />
|
||||||
ijs={props.ijs}
|
<Gallery closeText={props.closeText} loadingText={props.loadingText} />
|
||||||
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>
|
</Show>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
78
assets/ts/mobile/state.ts
Normal file
78
assets/ts/mobile/state.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -14,8 +14,16 @@ export async function getImageJSON(): Promise<ImageJSON[]> {
|
|||||||
if (document.title.split(' | ')[0] === '404') {
|
if (document.title.split(' | ')[0] === '404') {
|
||||||
return [] // no images on 404 page
|
return [] // no images on 404 page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ogUrlMetaTag = document.querySelector(
|
||||||
|
'meta[property="og:url"]'
|
||||||
|
) as HTMLMetaElement | null
|
||||||
|
const indexJsonUrl = ogUrlMetaTag?.content
|
||||||
|
? new URL('index.json', ogUrlMetaTag.content).href
|
||||||
|
: new URL('index.json', window.location.href).href
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${window.location.href}index.json`, {
|
const response = await fetch(indexJsonUrl, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json'
|
Accept: 'application/json'
|
||||||
}
|
}
|
||||||
@@ -27,7 +35,8 @@ export async function getImageJSON(): Promise<ImageJSON[]> {
|
|||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
})
|
})
|
||||||
} catch (_) {
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
1
bundled/css/critical.css
Normal file
1
bundled/css/critical.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*:where(:not(html,iframe,canvas,img,svg,video,audio):not(svg *,symbol *)){all:unset;display:revert}*,*:before,*:after{box-sizing:border-box}html{-moz-text-size-adjust:none;-webkit-text-size-adjust:none;text-size-adjust:none}a,button{cursor:revert}ol,ul,menu,summary{list-style:none}img{max-inline-size:100%;max-block-size:100%}table{border-collapse:collapse}input,textarea{-webkit-user-select:auto}textarea{white-space:revert}meter{-webkit-appearance:revert;appearance:revert}:where(pre){all:revert;box-sizing:border-box}::placeholder{color:unset}:where([hidden]){display:none}:where([contenteditable]:not([contenteditable=false])){-moz-user-modify:read-write;-webkit-user-modify:read-write;overflow-wrap:break-word;-webkit-line-break:after-white-space;-webkit-user-select:auto}:where([draggable=true]){-webkit-user-drag:element}:where(dialog:modal){all:revert;box-sizing:border-box}@font-face{font-family:Geist;src:url('{{- "lib/fonts/GeistVF.woff2" | absURL -}}') format("woff2 supports variations"),url('{{- "lib/fonts/GeistVF.woff2" | absURL -}}') format("woff2-variations");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:FW;src:url('{{- "lib/fonts/fw.woff2" | absURL -}}') format("woff2");font-weight:400;font-style:normal;font-display:swap}body{line-height:1.2;font-size:16px;font-family:Geist,sans-serif}body button{font-family:FW,sans-serif}@media(min-width:768px){body{font-size:18px}}@media(min-width:1024px){body{font-size:19px}}:root{--window-height: 100vh;--nav-height: 2rem;--space-standard: .625rem;--z-curtain: 200;--z-nav-gallery: 500;--z-cursor: 600;--z-nav: 800}*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{-webkit-user-select:none;user-select:none;background:#fff}html,body{overscroll-behavior-y:none}a,button{cursor:pointer}nav{display:flex;justify-content:space-between;align-items:center;width:100%;height:var(--nav-height);padding:0 var(--space-standard);position:fixed;bottom:0;background:#fff;z-index:var(--z-nav);pointer-events:all}.num{width:.625em;display:inline-block;text-align:center}.current{font-style:italic;text-decoration:underline}@media(max-width:767px),(hover:none){nav{top:0}.index,.threshold{display:none}}article{padding:var(--space-standard);max-width:25em}article p{margin-bottom:1em}article u{text-decoration:underline}article>h1{font-size:1.6em}article>h2{font-size:1.5em}article>h3{font-size:1.375em}article>h4{font-size:1.25em}article>h5{font-size:1.125em}article h1,article h2,article h3,article h4,article h5,article h6{font-weight:700;margin:1.2rem 0}@media(max-width:767px),(hover:none){article{margin-top:var(--nav-height)}}@media(max-width:767px),(hover:none){.container{position:fixed;top:0;z-index:0;width:100vw;height:var(--window-height);overflow-y:scroll;overflow-x:hidden;background:#fff;overscroll-behavior:none;-webkit-overflow-scrolling:none}.disableScroll{pointer-events:none}}
|
||||||
1
bundled/css/main.css
Normal file
1
bundled/css/main.css
Normal file
File diff suppressed because one or more lines are too long
1
bundled/js/CO8Cxe.js
Normal file
1
bundled/js/CO8Cxe.js
Normal file
File diff suppressed because one or more lines are too long
1
bundled/js/DaqdZh.js
Normal file
1
bundled/js/DaqdZh.js
Normal file
File diff suppressed because one or more lines are too long
1
bundled/js/fZjYgW.js
Normal file
1
bundled/js/fZjYgW.js
Normal file
File diff suppressed because one or more lines are too long
1
bundled/js/h6I38a.js
Normal file
1
bundled/js/h6I38a.js
Normal file
File diff suppressed because one or more lines are too long
2
bundled/js/main.js
Normal file
2
bundled/js/main.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["js/fZjYgW.js","js/h6I38a.js","js/zA1TQP.js"])))=>i.map(i=>d[i]);
|
||||||
|
import{B as e,C as t,D as n,E as r,M as i,P as a,S as o,_ as s,a as c,j as l,k as u,l as d,n as f,o as p,p as m,r as h,t as g,u as _,w as v}from"./h6I38a.js";var y=[{threshold:20,trailLength:20},{threshold:40,trailLength:10},{threshold:80,trailLength:5},{threshold:140,trailLength:5},{threshold:200,trailLength:5}],b=n();function x(){let e=c();return e<0||e>=y.length?2:e}function S(e){let[t,n]=i(x()),a=u(()=>{let e=y[t()];return{thresholdIndex:t(),threshold:e.threshold,trailLength:e.trailLength}}),o=e=>{let r=t()+e;r<0||r>=y.length||(sessionStorage.setItem(`thresholdsIndex`,r.toString()),n(r))};return r(b.Provider,{value:[a,{incThreshold:()=>{o(1)},decThreshold:()=>{o(-1)}}],get children(){return e.children}})}function C(){let t=e(b);return _(t,`undefined config context`),t}var w=n();function T(e){let t=f(),[n,a]=i(-1),[o,s]=i([]),[c,l]=i(``),[u,d]=i(!1),[m,g]=i(!1),[_,v]=i(!1),[y,b]=i(`none`),x=e=>{let n=t().length;n<=0||a(t=>e===1?p(t,n):h(t,n))};return r(w.Provider,{value:[{index:n,cordHist:o,hoverText:c,isOpen:u,isAnimating:m,isLoading:_,navVector:y},{setIndex:a,incIndex:()=>{x(1)},decIndex:()=>{x(-1)},setCordHist:s,setHoverText:l,setIsOpen:d,setIsAnimating:g,setIsLoading:v,setNavVector:b}],get children(){return e.children}})}function E(){let t=e(w);return _(t,`undefined desktop context`),t}var D=n();function O(e){let t=f(),[n,a]=i(-1),[o,s]=i(!1),[c,l]=i(!1),[u,d]=i(!1),m=e=>{let n=t().length;n<=0||a(t=>e===1?p(t,n):h(t,n))};return r(D.Provider,{value:[{index:n,isOpen:o,isAnimating:c,isScrollLocked:u},{setIndex:a,incIndex:()=>{m(1)},decIndex:()=>{m(-1)},setIsOpen:s,setIsAnimating:l,setIsScrollLocked:d}],get children(){return e.children}})}function k(){let t=e(D);return _(t,`undefined mobile context`),t}async function A(){if(document.title.split(` | `)[0]===`404`)return[];let e=document.querySelector(`meta[property="og:url"]`),t=e?.content?new URL(`index.json`,e.content).href:new URL(`index.json`,window.location.href).href;try{return(await(await fetch(t,{headers:{Accept:`application/json`}})).json()).sort((e,t)=>e.index<t.index?-1:1)}catch(e){return console.error(e),[]}}var j=s(`<div>Error`),M=document.getElementsByClassName(`container`)[0],N=a(async()=>await d(()=>import(`./fZjYgW.js`),__vite__mapDeps([0,1]))),P=a(async()=>await d(()=>import(`./zA1TQP.js`),__vite__mapDeps([2,1])));function F(e){return r(v,{get fallback(){return j()},get children(){return[r(o,{get when(){return e.isMobile},get children(){return r(O,{get children(){return r(P,{get closeText(){return e.closeText},get loadingText(){return e.loadingText}})}})}}),r(o,{get when(){return!e.isMobile},get children(){return r(T,{get children(){return r(N,{get prevText(){return e.prevText},get closeText(){return e.closeText},get nextText(){return e.nextText},get loadingText(){return e.loadingText}})}})}})]}})}function I(){let[e]=l(A),n=window.navigator.userAgent.toLowerCase(),i=`ontouchstart`in window||window.navigator.maxTouchPoints>0,a=window.matchMedia(`(pointer: coarse)`).matches||window.matchMedia(`(hover: none)`).matches,o=/android|iphone|ipad|ipod|mobile/.test(n),s=/windows nt/.test(n),c=o||i&&a&&!s;return r(t,{get when(){return e.state===`ready`},get children(){return r(g,{get images(){return e()??[]},get children(){return r(S,{get children(){return r(F,{isMobile:c,get prevText(){return M.dataset.prev},get closeText(){return M.dataset.close},get nextText(){return M.dataset.next},get loadingText(){return M.dataset.loading}})}})}})}})}m(()=>r(I,{}),M);export{E as n,C as r,k as t};
|
||||||
1
bundled/js/zA1TQP.js
Normal file
1
bundled/js/zA1TQP.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,197 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
The files in `exampleSite` is just a simple example. Now, we will introduce it based on the same structure.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
Before you start, make sure you have installed Hugo **extended version**. For more information, see [Hugo's documentation](https://gohugo.io/getting-started/installing/).
|
|
||||||
|
|
||||||
Once you have installed Hugo, you can check the version by running the following command:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
hugo version
|
|
||||||
```
|
|
||||||
|
|
||||||
Which should output something like this (the version number may be different), notice the `extended` keyword:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
hugo v0.120.3-a4892a07b41b7b3f1f143140ee4ec0a9a5cf3970+extended darwin/arm64 BuildDate=2023-11-01T17:57:00Z VendorInfo=brew
|
|
||||||
```
|
|
||||||
|
|
||||||
The minimum required Hugo version can be seen in the [`theme.toml`](https://github.com/Sped0n/bridget/blob/main/theme.toml#L19).
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Git (for adavanced user)
|
|
||||||
|
|
||||||
On the main branch, you can find the theme's latest source code. To use the latest version, you can clone the repository to `themes/bridget` by running the following command in the root directory of your Hugo site:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://github.com/Sped0n/bridget themes/bridget
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are already using Git for your site, you can add the theme as a submodule by running the following command in the root directory of your Hugo site:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git submodule add https://github.com/Sped0n/bridget themes/bridget
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️⚠️⚠️
|
|
||||||
>
|
|
||||||
> Please refer to the config section for the following content.
|
|
||||||
|
|
||||||
### Module (recommended)
|
|
||||||
|
|
||||||
> If you want to have some customizations, use Git installation instead.
|
|
||||||
|
|
||||||
This theme is also available as a [Hugo module](https://gohugo.io/hugo-modules/). Run the following command in the root directory of your Hugo site:
|
|
||||||
|
|
||||||
First turn your site into a Hugo module (in case you haven't done it yet):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
hugo mod init github.com/me/my-new-site
|
|
||||||
# or whatever you like, it doesn’t necessarily have to be a GitHub repo link.
|
|
||||||
hugo mod init blablabla
|
|
||||||
```
|
|
||||||
|
|
||||||
Then import the theme as a dependency adding the following line to the `module` section of your site's configuration file.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# config/_default/hugo.toml
|
|
||||||
[module]
|
|
||||||
[[module.imports]]
|
|
||||||
path = "github.com/Sped0n/bridget/v2"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to upgrade the theme, just run:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
hugo mod get -u
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️⚠️⚠️
|
|
||||||
>
|
|
||||||
> Please refer to the config section for the following content.
|
|
||||||
|
|
||||||
## Content Management
|
|
||||||
|
|
||||||
The content is where the pictures/text is stored, while the static refers to the website icons.
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── content
|
|
||||||
│ ├── Erwitt
|
|
||||||
│ │ ├── 1.jpg
|
|
||||||
│ │ ├── ***
|
|
||||||
│ │ └── index.md
|
|
||||||
│ ├── Gruyaert
|
|
||||||
│ │ ├── 1.jpg
|
|
||||||
│ │ ├── ***
|
|
||||||
│ │ └── index.md
|
|
||||||
│ ├── Info
|
|
||||||
│ │ └── index.md
|
|
||||||
│ └── Webb
|
|
||||||
│ ├── 1.jpg
|
|
||||||
│ ├── ***
|
|
||||||
│ └── index.md
|
|
||||||
└── static
|
|
||||||
├── dot.png
|
|
||||||
└── dot.svg
|
|
||||||
```
|
|
||||||
|
|
||||||
In each index.md file, there is a configuration file like this:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
type: _default
|
|
||||||
layout: single
|
|
||||||
url: /erwitt/
|
|
||||||
menu:
|
|
||||||
main:
|
|
||||||
weight: 3
|
|
||||||
identifier: Erwitt
|
|
||||||
title: Erwitt
|
|
||||||
unifiedAlt: '© Elliott Erwitt'
|
|
||||||
_build:
|
|
||||||
publishResources: false
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
- keep the `type` and `layout` **untouched**;
|
|
||||||
|
|
||||||
- `url` is the href link to this page, in this case, you can visit this page with `blabla.com/erwitt`;
|
|
||||||
|
|
||||||
- `main` is the entry to `menu`;
|
|
||||||
|
|
||||||
- `weight` determines the position of this link in the navigation bar, with the first one being 1, the second one being 2, and so on;
|
|
||||||
|
|
||||||
- `identifier` should be the **same** as the name of the **upper-level directory**;
|
|
||||||
|
|
||||||
- `title` refers to the text that appears on the navigation bar;
|
|
||||||
|
|
||||||
- `unifiedAlt` is **optional**, If you left it empty, the alt attribute of the image will default to its file name; if it is set, the alt attributes of all images will be unified to the value you have set;
|
|
||||||
|
|
||||||
- `publishResources` is **optional but recommended**, setting it to false will hide unprocessed images in the `public` directory. By default, Hugo’s value for this option is true, which can potentially result in source image leakage.
|
|
||||||
|
|
||||||
- If this is a **showcase** page, simply place the images in the same directory as index.md.
|
|
||||||
|
|
||||||
- If this is an **information** page, you can continue writing the information you want to display in index.md.
|
|
||||||
|
|
||||||
> However, please note that the CSS for the information page **only provides simple styling for text**. If you have any requirements beyond text and the browser rendering does not meet your expectations, please modify [`_article.scss`](https://github.com/Sped0n/bridget/blob/main/assets/scss/_partial/_article.scss).
|
|
||||||
|
|
||||||
As for the **website icon**, place the files in static and then go to config part for further reading.
|
|
||||||
|
|
||||||
## Config
|
|
||||||
|
|
||||||
You can simply copy this to the root directory of your site with minor modifications, and you’ll be ready to proceed.
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
└── config
|
|
||||||
└── _default
|
|
||||||
├── hugo.toml
|
|
||||||
├── markup.toml
|
|
||||||
├── params.toml
|
|
||||||
└── sitemap.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
### `hugo.toml`
|
|
||||||
|
|
||||||
We will focus on introducing the part about `theme as module`, detailed comments are provided for other options, so we won’t repeat them here.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# theme as module
|
|
||||||
[module]
|
|
||||||
replacements = "github.com/Sped0n/bridget/v2 -> ../.."
|
|
||||||
[[module.imports]]
|
|
||||||
path = "github.com/Sped0n/bridget/v2"
|
|
||||||
```
|
|
||||||
|
|
||||||
- If you have _installation with Git_
|
|
||||||
|
|
||||||
- `replacement`: replace the _path after the arrow_(`../..`) with the location of your local theme file (⚠️⚠️⚠️**relative path only**, example: `themes/bridget`)
|
|
||||||
- `path`: no change
|
|
||||||
|
|
||||||
- If you have _installation with Module_, **remove the `replacements` configuration**.
|
|
||||||
|
|
||||||
### `markup.toml`
|
|
||||||
|
|
||||||
**DO NOT TOUCH THIS**
|
|
||||||
|
|
||||||
### `params.toml`
|
|
||||||
|
|
||||||
Detailed description in the comments.
|
|
||||||
|
|
||||||
### `sitemap.toml`
|
|
||||||
|
|
||||||
https://gohugo.io/templates/sitemap-template/#configuration
|
|
||||||
|
|
||||||
## Customization (AKA for developer)
|
|
||||||
|
|
||||||
> Before heading to this section, please make sure you have **installation with Git**.
|
|
||||||
>
|
|
||||||
> You can use any package manager you want (npm/pnpm/yarn/bun).
|
|
||||||
|
|
||||||
- run `pnpm install` to install neceessary dependencies.
|
|
||||||
- run `pnpm run dev` to host a dev server.
|
|
||||||
- when you’re ready, run `pnpm run build` to update artifacts.
|
|
||||||
307
docs.md
Normal file
307
docs.md
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
### Contents
|
||||||
|
|
||||||
|
- [Prequisites](#prequisites)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Hugo Modules (Recommended)](#hugo-modules-recommended)
|
||||||
|
- [Git Repository (For Customizations)](#git-repository-for-customizations)
|
||||||
|
- [Content Management](#content-management)
|
||||||
|
- [`index.md`](#indexmd)
|
||||||
|
- [Front Matter](#front-matter)
|
||||||
|
- [Markdown Content](#markdown-content)
|
||||||
|
- [Favicon](#favicon)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [`hugo.toml`](#hugotoml)
|
||||||
|
- [`markup.toml`](#markuptoml)
|
||||||
|
- [`outputs.toml`](#outputstoml)
|
||||||
|
- [`params.toml`](#paramstoml)
|
||||||
|
- [`sitemap.toml`](#sitemaptoml)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Customizations](#customizations)
|
||||||
|
- [Change Font](#change-font)
|
||||||
|
- [Add a Custom Analytic Script](#add-a-custom-analytic-script)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prequisites
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
- [Hugo (extended)](https://gohugo.io/installation/), minimum required version can be seen in the [`theme.toml`](https://github.com/Sped0n/bridget/blob/main/theme.toml#L19)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ hugo version
|
||||||
|
hugo v0.152.2+extended+withdeploy darwin/arm64 BuildDate=unknown VendorInfo=nixpkgs
|
||||||
|
```
|
||||||
|
|
||||||
|
- [pnpm](https://pnpm.io/installation) and [Node.js](https://nodejs.org/en/download), please note that these two are only needed for customizations or development.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ pnpm --version && node --version
|
||||||
|
10.20.0
|
||||||
|
v22.20.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
### Hugo Modules (Recommended)
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Checkout https://gohugo.io/hugo-modules/use-modules/#prerequisite before using Hugo Modules.
|
||||||
|
|
||||||
|
First turn your site into a Hugo module (in case you haven't done it yet):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hugo mod init github.com/me/my-new-site
|
||||||
|
# or whatever you like, it doesn’t need to be a valid GitHub repo link.
|
||||||
|
hugo mod init blablabla
|
||||||
|
```
|
||||||
|
|
||||||
|
Then import the theme as a dependency adding the following line to the `module` section of your site's configuration file.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# config/_default/hugo.toml
|
||||||
|
[module]
|
||||||
|
[[module.imports]]
|
||||||
|
path = "github.com/Sped0n/bridget/v2"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to upgrade the theme, just run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
hugo mod get -u
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git Repository (For Customizations)
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
First clone the repository into your `themes` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# latest version (main branch, might be unstable)
|
||||||
|
git clone https://github.com/Sped0n/bridget themes/bridget
|
||||||
|
|
||||||
|
# and you can checkout to a specific stable version, see https://github.com/Sped0n/bridget/releases
|
||||||
|
cd themes/bridget
|
||||||
|
git checkout v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are already using Git for your site, you can add the theme as a submodule by running the following command in the root directory of your Hugo site:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule add https://github.com/Sped0n/bridget themes/bridget
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content Management
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
The content is where the pictures/text is stored, while the static refers to the website icons.
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── content
|
||||||
|
│ ├── Erwitt
|
||||||
|
│ │ ├── 1.jpg
|
||||||
|
│ │ ├── ***
|
||||||
|
│ │ └── index.md
|
||||||
|
│ ├── Gruyaert
|
||||||
|
│ │ ├── 1.jpg
|
||||||
|
│ │ ├── ***
|
||||||
|
│ │ └── index.md
|
||||||
|
│ ├── Info
|
||||||
|
│ │ └── index.md
|
||||||
|
│ └── Webb
|
||||||
|
│ ├── 1.jpg
|
||||||
|
│ ├── ***
|
||||||
|
│ └── index.md
|
||||||
|
└── static
|
||||||
|
├── dot.png
|
||||||
|
└── dot.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
### `index.md`
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
#### Front Matter
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
Inside each index.md file, there is a front matter like this:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
type: _default # just copy
|
||||||
|
layout: single # just copy
|
||||||
|
url: /erwitt/
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
weight: 3
|
||||||
|
identifier: Erwitt
|
||||||
|
title: Erwitt
|
||||||
|
unifiedAlt: '© Elliott Erwitt'
|
||||||
|
build:
|
||||||
|
publishResources: false # just copy
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
- `url` is the href link to this page, in this case, you can visit this page with `blabla.com/erwitt`;
|
||||||
|
- `main` is the entry to `menu`;
|
||||||
|
- `weight` determines the position of this link in the navigation bar, with the first one being 1, the second one being 2, and so on;
|
||||||
|
- `identifier` should be the **same** as the name of the **upper-level directory**;
|
||||||
|
- `title` refers to the text that appears on the navigation bar;
|
||||||
|
- `unifiedAlt` is **optional**, If you left it empty, the alt attribute of the image will default to its file name; if it is set, the alt attributes of all images will be unified to the value you have set;
|
||||||
|
|
||||||
|
#### Markdown Content
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
- If this is a **showcase** page:
|
||||||
|
- No need to write anything in index.md.
|
||||||
|
- Place the images in the same directory as `index.md`.
|
||||||
|
- If this is an **information** page:
|
||||||
|
- You can write anything in index.md, and it will be rendered as HTML.
|
||||||
|
- However, please note that the CSS for the information page **only provides simple styling for text**. If you have any requirements beyond text and the browser rendering does not meet your expectations, please modify [`_article.scss`](https://github.com/Sped0n/bridget/blob/main/assets/scss/_partial/_article.scss).
|
||||||
|
|
||||||
|
### Favicon
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
As for the **website icon**, place the files under `static` directory and then go to [config](#configuration) part for further reading.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
You can simply copy `exampleSite/config` to the root directory, with some minor modifications and you should be good to go.
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
└── config
|
||||||
|
└── _default
|
||||||
|
├── hugo.toml
|
||||||
|
├── markup.toml
|
||||||
|
├── outputs.toml
|
||||||
|
├── params.toml
|
||||||
|
└── sitemap.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
### `hugo.toml`
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
First, what you need to modify is the `baseURL` and `title`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# timeout
|
||||||
|
timeout = "1200s"
|
||||||
|
# your website url
|
||||||
|
baseURL = 'https://bridget-demo.sped0n.com' # <-- MODIFY ME
|
||||||
|
# website title
|
||||||
|
title = 'Bridget' # <-- MODIFY ME
|
||||||
|
# don't touch this
|
||||||
|
disableKinds = ["section", "taxonomy", "term", "home"]
|
||||||
|
# robots.txt
|
||||||
|
enableRobotsTXT = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Depend on which [installation](#installation) method you choose, you need to modify the `module` section:
|
||||||
|
|
||||||
|
- If you use [Hugo Modules](#hugo-modules-recommended):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[module]
|
||||||
|
[[module.imports]]
|
||||||
|
path = "github.com/Sped0n/bridget/v2"
|
||||||
|
```
|
||||||
|
|
||||||
|
- If you use [Git Repository](#git-repository-for-customizations):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[module]
|
||||||
|
# This is the relative path to hugo theme directory([official doc](https://gohugo.io/hugo-modules/configuration/#module-configuration-top-level))**.
|
||||||
|
replacements = "github.com/Sped0n/bridget/v2 -> ../.."
|
||||||
|
[[module.imports]]
|
||||||
|
path = "github.com/Sped0n/bridget/v2"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `markup.toml`
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
**Just copy it.**
|
||||||
|
|
||||||
|
### `outputs.toml`
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
**Just copy it.**
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
### `params.toml`
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
Detailed description in the comments.
|
||||||
|
|
||||||
|
### `sitemap.toml`
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
https://gohugo.io/templates/sitemap-template/#configuration
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
Bridget will work as a normal Hugo theme (if you don't have needs to customize), https://gohugo.io/getting-started/usage/ is a great start.
|
||||||
|
|
||||||
|
For further reading, you can refer to the `scripts` field of `package.json`.
|
||||||
|
|
||||||
|
## Customizations
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Please make sure you have [installation with Git](#git-repository-for-customizations).
|
||||||
|
>
|
||||||
|
> If you want to try some changes on the `exampleSite`, below are some commands you might need:
|
||||||
|
>
|
||||||
|
> - `pnpm install` to install dependencies.
|
||||||
|
> - `pnpm run dev` to start a dev server (`http://localhost:1313`).
|
||||||
|
> - `pnpm run build` to update artifacts.
|
||||||
|
|
||||||
|
### Change Font
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
These are the places you need to focus on:
|
||||||
|
|
||||||
|
- `assets/scss/_core/_font.scss` (`@font-face`)
|
||||||
|
- `assets/scss/_core/_typography.scss` (`body.font-family`)
|
||||||
|
- `layouts/partials/head/link.html` (`preload`)
|
||||||
|
- `static/lib/fonts/GeistVF.woff2` (font file itself)
|
||||||
|
|
||||||
|
### Add a Custom Analytic Script
|
||||||
|
|
||||||
|
_[Contents](#contents)_
|
||||||
|
|
||||||
|
Go to `layouts/_default/baseof.html`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="{{- site.LanguageCode -}}">
|
||||||
|
<head>
|
||||||
|
/* ---------- INSERT HERE ---------- */
|
||||||
|
</head>
|
||||||
|
<body lang="{{- site.LanguageCode -}}">
|
||||||
|
<div class="analytics">/* ---------- OR HERE ---------- */</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
79
eslint.config.mjs
Normal file
79
eslint.config.mjs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import tsParser from '@typescript-eslint/parser'
|
||||||
|
import love from 'eslint-config-love'
|
||||||
|
import importPlugin from 'eslint-plugin-import'
|
||||||
|
import prettier from 'eslint-plugin-prettier/recommended'
|
||||||
|
import solid from 'eslint-plugin-solid/configs/recommended'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
importPlugin.flatConfigs.recommended,
|
||||||
|
solid,
|
||||||
|
globalIgnores(['node_modules/', 'static/', 'exampleSite/', '*.mjs', 'bundled/']),
|
||||||
|
{
|
||||||
|
...love,
|
||||||
|
...prettier,
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: {
|
||||||
|
project: './tsconfig.json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'error',
|
||||||
|
'arrow-body-style': 'off',
|
||||||
|
'prefer-arrow-callback': 'off',
|
||||||
|
'import/no-cycle': 'error',
|
||||||
|
|
||||||
|
'sort-imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
ignoreCase: false,
|
||||||
|
ignoreDeclarationSort: true,
|
||||||
|
ignoreMemberSort: true,
|
||||||
|
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
|
||||||
|
allowSeparatedGroups: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
'import/no-unresolved': 'error',
|
||||||
|
|
||||||
|
'import/order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
'builtin',
|
||||||
|
'external',
|
||||||
|
'internal',
|
||||||
|
'parent',
|
||||||
|
'sibling',
|
||||||
|
'index',
|
||||||
|
'unknown'
|
||||||
|
],
|
||||||
|
'newlines-between': 'always',
|
||||||
|
|
||||||
|
alphabetize: {
|
||||||
|
order: 'asc',
|
||||||
|
caseInsensitive: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
@@ -1,27 +1,16 @@
|
|||||||
# timeout
|
# timeout
|
||||||
timeout = "1200s"
|
timeout = "1200s"
|
||||||
# your website url
|
# your website url
|
||||||
baseURL = 'https://www.example.com/'
|
baseURL = 'https://bridget-demo.sped0n.com'
|
||||||
# website title
|
# website title
|
||||||
title = 'Bridget'
|
title = 'Bridget'
|
||||||
# don't touch this
|
# don't touch this
|
||||||
disableKinds = ["section", "taxonomy", "term", "home"]
|
disableKinds = ["section", "taxonomy", "term", "home"]
|
||||||
# robots.txt
|
# robots.txt
|
||||||
enableRobotsTXT = true
|
enableRobotsTXT = true
|
||||||
# available options
|
|
||||||
# * en (powered by Geist)
|
|
||||||
# * de (powered by Geist)
|
|
||||||
# * es (powered by Geist)
|
|
||||||
# * fr (powered by Geist)
|
|
||||||
# * it (powered by Geist)
|
|
||||||
# * zh-sg zh-cn (powered by Noto Sans SC)
|
|
||||||
# * zh-hk zh-tw zh-mo (powered by Noto Sans TC)
|
|
||||||
# * ja (powered by Noto Sans JP)
|
|
||||||
# * ko (powered by Noto Sans KR)
|
|
||||||
defaultContentLanguage = 'en'
|
|
||||||
|
|
||||||
# theme as module
|
# theme as module
|
||||||
[module]
|
[module]
|
||||||
replacements = "github.com/Sped0n/bridget/v2 -> ../.." # deploy with local dir WARN: delete this line if you want to deploy with git
|
replacements = "github.com/Sped0n/bridget/v2 -> ../.." # deploy with local dir (relative to hugo site theme dir) WARN: delete this line if you want to deploy with git
|
||||||
[[module.imports]]
|
[[module.imports]]
|
||||||
path = "github.com/Sped0n/bridget/v2" # deploy with git (recommended) WARN: you should also set `bundled` to true in params.toml !!!
|
path = "github.com/Sped0n/bridget/v2" # deploy with git (recommended)
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
|
# --- REQUIRED -----------------------------------------------------------------
|
||||||
|
|
||||||
# description of the site (will be placed in meta)
|
# description of the site (will be placed in meta)
|
||||||
description = "Bridget is a minimal Hugo theme designed for photographers/visual artists."
|
description = "Bridget is a minimal Hugo theme designed for photographers/visual artists."
|
||||||
|
|
||||||
|
# resize options for dynamic resolution, please refer to https://gohugo.io/content-management/image-processing/#image-processing-options
|
||||||
|
loResOpt = "800x webp Lanczos q60"
|
||||||
|
hiResOpt = "2500x webp Lanczos q75"
|
||||||
|
|
||||||
|
# labels (text shown on the UI)
|
||||||
|
[labels]
|
||||||
|
next = "next"
|
||||||
|
prev = "prev"
|
||||||
|
close = "close"
|
||||||
|
threshold = "Threshold"
|
||||||
|
error = "page not found"
|
||||||
|
loading = "loading..."
|
||||||
|
|
||||||
|
# --- OPTIONAL -----------------------------------------------------------------
|
||||||
|
|
||||||
# whether to use favicon resource links
|
# whether to use favicon resource links
|
||||||
# generate these with https://realfavicongenerator.net
|
# generate these with https://realfavicongenerator.net
|
||||||
favicon = true
|
favicon = true
|
||||||
@@ -9,16 +26,7 @@ svgFavicon = "/dot.svg"
|
|||||||
# fallback png favicon for unsupported browsers
|
# fallback png favicon for unsupported browsers
|
||||||
svgFaviconFallback = "/dot.png"
|
svgFaviconFallback = "/dot.png"
|
||||||
|
|
||||||
# resize options for dynamic resolution, please refer to https://gohugo.io/content-management/image-processing/#image-processing-options
|
# site verification code for Google/Bing/Yandex/Pinterest/Baidu
|
||||||
loResOpt = "800x webp Lanczos q60"
|
|
||||||
hiResOpt = "2500x webp Lanczos q75"
|
|
||||||
|
|
||||||
# page config
|
|
||||||
[page]
|
|
||||||
# unified alt text for all images in the page
|
|
||||||
unifiedAlt = ''
|
|
||||||
|
|
||||||
# Site verification code for Google/Bing/Yandex/Pinterest/Baidu
|
|
||||||
[verification]
|
[verification]
|
||||||
google = ""
|
google = ""
|
||||||
bing = ""
|
bing = ""
|
||||||
@@ -28,7 +36,7 @@ baidu = ""
|
|||||||
so = ""
|
so = ""
|
||||||
sogou = ""
|
sogou = ""
|
||||||
|
|
||||||
# Analytics config
|
# analytics config
|
||||||
[analytics]
|
[analytics]
|
||||||
enable = true
|
enable = true
|
||||||
# Google Analytics
|
# Google Analytics
|
||||||
@@ -46,10 +54,10 @@ server = ""
|
|||||||
id = ""
|
id = ""
|
||||||
# Umami Analytics
|
# Umami Analytics
|
||||||
[analytics.umami]
|
[analytics.umami]
|
||||||
data_website_id = "44a4a42d-ec8e-44c9-a38c-7533929e9845"
|
data_website_id = "942d4c0d-ebd0-4da7-936a-bd278af32e5e"
|
||||||
src = "https://umami.sped0nwen.com/script.js"
|
src = "https://umami.sped0n.com/script.js"
|
||||||
data_host_url = ""
|
data_host_url = ""
|
||||||
data_domains = "bridget-demo.sped0nwen.com"
|
data_domains = "bridget-demo.sped0n.com"
|
||||||
# Plausible Analytics
|
# Plausible Analytics
|
||||||
[analytics.plausible]
|
[analytics.plausible]
|
||||||
data_domain = ""
|
data_domain = ""
|
||||||
@@ -61,8 +69,8 @@ token = ""
|
|||||||
[analytics.splitbee]
|
[analytics.splitbee]
|
||||||
enable = false
|
enable = false
|
||||||
# no cookie mode
|
# no cookie mode
|
||||||
No_cookie = true
|
no_cookie = true
|
||||||
# respect the do not track setting of the browser
|
# respect the do not track setting of the browser
|
||||||
Do_not_track = true
|
do_not_track = true
|
||||||
# token(optional), more info on https://splitbee.io/docs/embed-the-script
|
# token(optional), more info on https://splitbee.io/docs/embed-the-script
|
||||||
data_token = ""
|
data_token = ""
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ menu:
|
|||||||
identifier: Erwitt
|
identifier: Erwitt
|
||||||
title: Erwitt
|
title: Erwitt
|
||||||
unifiedAlt: '© Elliott Erwitt'
|
unifiedAlt: '© Elliott Erwitt'
|
||||||
_build:
|
build:
|
||||||
publishResources: false
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ menu:
|
|||||||
identifier: Gruyaert
|
identifier: Gruyaert
|
||||||
title: Gruyaert
|
title: Gruyaert
|
||||||
unifiedAlt: '© Harry Gruyaert'
|
unifiedAlt: '© Harry Gruyaert'
|
||||||
_build:
|
build:
|
||||||
publishResources: false
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ menu:
|
|||||||
identifier: Info
|
identifier: Info
|
||||||
title: Info
|
title: Info
|
||||||
unifiedAlt: ''
|
unifiedAlt: ''
|
||||||
_build:
|
build:
|
||||||
publishResources: false
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|
||||||
Bridget is a _minimal_ Hugo theme designed for photographers/visual artists.
|
Bridget is a _minimal_ Hugo theme designed for photographers/visual artists, powered by <u>[SolidJS](https://www.solidjs.com)</u>.
|
||||||
|
|
||||||
The inspiration for this theme came from a video by <u>[Hyperlexed](https://www.youtube.com/@Hyperplexed)</u>, which can be found <u>[here](https://www.youtube.com/watch?v=Jt3A2lNN2aE)</u>. Initially, it was developed using no third-party dependencies. However, after website designer <u>[Tyler McRobert](https://tylermcrobert.com)</u> made the source code publicly available, I realized that I have invented many unnecessary wheels, and this project was modified to porting the original design to hugo while focusing on _performance_.
|
The inspiration for this theme came from a video by <u>[Hyperlexed](https://www.youtube.com/@Hyperplexed)</u>, which can be found <u>[here](https://www.youtube.com/watch?v=Jt3A2lNN2aE)</u>. Initially, it was developed using no third-party dependencies. However, after website designer <u>[Tyler McRobert](https://tylermcrobert.com)</u> made the source code publicly available, I realized that I have invented many unnecessary wheels, and this project was modified to porting the original design to Hugo while focusing on _performance_.
|
||||||
|
|
||||||
Once again, great shout out to <u>[Tyler McRobert](https://tylermcrobert.com)</u> for his inspiration to this project.
|
Once again, great shout out to <u>[Tyler McRobert](https://tylermcrobert.com)</u> for his inspiration to this project.
|
||||||
|
|
||||||
@@ -22,4 +22,4 @@ Once again, great shout out to <u>[Tyler McRobert](https://tylermcrobert.com)</u
|
|||||||
|
|
||||||
Original site design by <u>[Tyler McRobert](https://tylermcrobert.com)</u>.
|
Original site design by <u>[Tyler McRobert](https://tylermcrobert.com)</u>.
|
||||||
|
|
||||||
© {{< year >}} <u>[Spedon](https://github.com/Sped0n)</u> | Powered by [Hugo](https://gohugo.io)
|
© {{< year >}} <u>[Spedon](https://github.com/Sped0n)</u> | Built with Hugo
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ menu:
|
|||||||
identifier: Webb
|
identifier: Webb
|
||||||
title: Webb
|
title: Webb
|
||||||
unifiedAlt: '© Alex Webb'
|
unifiedAlt: '© Alex Webb'
|
||||||
_build:
|
build:
|
||||||
publishResources: false
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|||||||
25
flake.lock
generated
Normal file
25
flake.lock
generated
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769461804,
|
||||||
|
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
|
||||||
|
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
|
||||||
|
"revCount": 935279,
|
||||||
|
"type": "tarball",
|
||||||
|
"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",
|
||||||
|
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
37
flake.nix
Normal file
37
flake.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
description = "bridget";
|
||||||
|
inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1";
|
||||||
|
outputs =
|
||||||
|
{ self, ... }@inputs:
|
||||||
|
let
|
||||||
|
supportedSystems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
forEachSupportedSystem =
|
||||||
|
f:
|
||||||
|
inputs.nixpkgs.lib.genAttrs supportedSystems (
|
||||||
|
system:
|
||||||
|
f {
|
||||||
|
pkgs = import inputs.nixpkgs { inherit system; };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells = forEachSupportedSystem (
|
||||||
|
{ pkgs }:
|
||||||
|
{
|
||||||
|
default = pkgs.mkShellNoCC {
|
||||||
|
packages = with pkgs; [
|
||||||
|
nodejs
|
||||||
|
nodePackages.pnpm
|
||||||
|
hugo
|
||||||
|
go
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
18
hugo.toml
Normal file
18
hugo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[[module.mounts]]
|
||||||
|
source = 'archetypes'
|
||||||
|
target = 'archetypes'
|
||||||
|
[[module.mounts]]
|
||||||
|
source = 'assets'
|
||||||
|
target = 'assets'
|
||||||
|
[[module.mounts]]
|
||||||
|
source = 'layouts'
|
||||||
|
target = 'layouts'
|
||||||
|
[[module.mounts]]
|
||||||
|
source = 'static'
|
||||||
|
target = 'static'
|
||||||
|
[[module.mounts]]
|
||||||
|
source = "bundled"
|
||||||
|
target = "assets/bundled"
|
||||||
|
[[module.mounts]]
|
||||||
|
source = "bundled"
|
||||||
|
target = "static/bundled"
|
||||||
12
i18n/de.toml
12
i18n/de.toml
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "nächste"
|
|
||||||
[prev]
|
|
||||||
other = "vorher"
|
|
||||||
[close]
|
|
||||||
other = "schließen"
|
|
||||||
[threshold]
|
|
||||||
other = "schwelle"
|
|
||||||
[404]
|
|
||||||
other = "seite nicht gefunden"
|
|
||||||
[loading]
|
|
||||||
other = "lade..."
|
|
||||||
12
i18n/en.toml
12
i18n/en.toml
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "next"
|
|
||||||
[prev]
|
|
||||||
other = "prev"
|
|
||||||
[close]
|
|
||||||
other = "close"
|
|
||||||
[threshold]
|
|
||||||
other = "threshold"
|
|
||||||
[404]
|
|
||||||
other = "page not found"
|
|
||||||
[loading]
|
|
||||||
other = "loading..."
|
|
||||||
12
i18n/es.toml
12
i18n/es.toml
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "siguiente"
|
|
||||||
[prev]
|
|
||||||
other = "previo"
|
|
||||||
[close]
|
|
||||||
other = "cerrar"
|
|
||||||
[threshold]
|
|
||||||
other = "umbral"
|
|
||||||
[404]
|
|
||||||
other = "página no encontrada"
|
|
||||||
[loading]
|
|
||||||
other = "cargando..."
|
|
||||||
12
i18n/fr.toml
12
i18n/fr.toml
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "suivant"
|
|
||||||
[prev]
|
|
||||||
other = "précédent"
|
|
||||||
[close]
|
|
||||||
other = "fermer"
|
|
||||||
[threshold]
|
|
||||||
other = "seuil"
|
|
||||||
[404]
|
|
||||||
other = "page non trouvée"
|
|
||||||
[loading]
|
|
||||||
other = "chargement..."
|
|
||||||
12
i18n/it.toml
12
i18n/it.toml
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "prossimo"
|
|
||||||
[prev]
|
|
||||||
other = "precedente"
|
|
||||||
[close]
|
|
||||||
other = "chiudi"
|
|
||||||
[threshold]
|
|
||||||
other = "soglia"
|
|
||||||
[404]
|
|
||||||
other = "pagina non trovata"
|
|
||||||
[loading]
|
|
||||||
other = "caricamento..."
|
|
||||||
12
i18n/ja.toml
12
i18n/ja.toml
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "進む"
|
|
||||||
[prev]
|
|
||||||
other = "後退"
|
|
||||||
[close]
|
|
||||||
other = "閉じる"
|
|
||||||
[threshold]
|
|
||||||
other = "しきい値"
|
|
||||||
[404]
|
|
||||||
other = "ページが見つかりません"
|
|
||||||
[loading]
|
|
||||||
other = "読み込み中..."
|
|
||||||
12
i18n/ko.toml
12
i18n/ko.toml
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "전진"
|
|
||||||
[prev]
|
|
||||||
other = "물러나세요"
|
|
||||||
[close]
|
|
||||||
other = "닫기"
|
|
||||||
[threshold]
|
|
||||||
other = "임계값"
|
|
||||||
[404]
|
|
||||||
other = "페이지를 찾을 수 없습니다"
|
|
||||||
[loading]
|
|
||||||
other = "로딩중..."
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "前进"
|
|
||||||
[prev]
|
|
||||||
other = "后退"
|
|
||||||
[close]
|
|
||||||
other = "关闭"
|
|
||||||
[threshold]
|
|
||||||
other = "阈值"
|
|
||||||
[404]
|
|
||||||
other = "页面不存在"
|
|
||||||
[loading]
|
|
||||||
other = "加载中..."
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "前進"
|
|
||||||
[prev]
|
|
||||||
other = "後退"
|
|
||||||
[close]
|
|
||||||
other = "關閉"
|
|
||||||
[threshold]
|
|
||||||
other = "閾值"
|
|
||||||
[404]
|
|
||||||
other = "找不到頁面"
|
|
||||||
[loading]
|
|
||||||
other = "載入中..."
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "前進"
|
|
||||||
[prev]
|
|
||||||
other = "後退"
|
|
||||||
[close]
|
|
||||||
other = "關閉"
|
|
||||||
[threshold]
|
|
||||||
other = "閾值"
|
|
||||||
[404]
|
|
||||||
other = "找不到頁面"
|
|
||||||
[loading]
|
|
||||||
other = "載入中..."
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "前进"
|
|
||||||
[prev]
|
|
||||||
other = "后退"
|
|
||||||
[close]
|
|
||||||
other = "关闭"
|
|
||||||
[threshold]
|
|
||||||
other = "阈值"
|
|
||||||
[404]
|
|
||||||
other = "页面不存在"
|
|
||||||
[loading]
|
|
||||||
other = "加载中..."
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[next]
|
|
||||||
other = "前進"
|
|
||||||
[prev]
|
|
||||||
other = "後退"
|
|
||||||
[close]
|
|
||||||
other = "關閉"
|
|
||||||
[threshold]
|
|
||||||
other = "閾值"
|
|
||||||
[404]
|
|
||||||
other = "找不到頁面"
|
|
||||||
[loading]
|
|
||||||
other = "載入中..."
|
|
||||||
@@ -2,9 +2,15 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
{{- partial "nav.html" . -}}
|
{{- partial "nav.html" . -}}
|
||||||
<article>
|
<article>
|
||||||
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
<p class="error">
|
||||||
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
⛝ <u>404</u> {{- site.Params.labels.error -}} ⛝
|
||||||
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
</p>
|
||||||
|
<p class="error">
|
||||||
|
⛝ <u>404</u> {{- site.Params.labels.error -}} ⛝
|
||||||
|
</p>
|
||||||
|
<p class="error">
|
||||||
|
⛝ <u>404</u> {{- site.Params.labels.error -}} ⛝
|
||||||
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{{- define "main" -}}
|
{{- define "main" -}}
|
||||||
<div
|
<div
|
||||||
class="container"
|
class="container"
|
||||||
data-next="{{- i18n "next" -}}"
|
data-next="{{- site.Params.labels.next -}}"
|
||||||
data-prev="{{- i18n "prev" -}}"
|
data-prev="{{- site.Params.labels.prev -}}"
|
||||||
data-close="{{- i18n "close" -}}"
|
data-close="{{- site.Params.labels.close -}}"
|
||||||
data-loading="{{- i18n "loading" -}}"
|
data-loading="{{- site.Params.labels.loading -}}"
|
||||||
>
|
>
|
||||||
{{- with .Content -}}
|
{{- with .Content -}}
|
||||||
<article>
|
<article>
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
{{- $weight := -1 -}}
|
{{- $weight := -1 -}}
|
||||||
|
|
||||||
{{- range site.Menus.main -}}
|
{{- range site.Menus.main -}}
|
||||||
{{ $menu_item_url := .URL | relLangURL }}
|
{{- $menu_item_url := .URL | relLangURL -}}
|
||||||
{{ $page_url:= $currentPage.RelPermalink | relLangURL }}
|
{{- $page_url:= $currentPage.RelPermalink | relLangURL -}}
|
||||||
{{ if eq $menu_item_url $page_url }}
|
{{- if eq $menu_item_url $page_url -}}
|
||||||
{{- $identifier = .Identifier -}}
|
{{- $identifier = .Identifier -}}
|
||||||
{{- $title = .Title -}}
|
{{- $title = .Title -}}
|
||||||
{{- $weight = .Weight -}}
|
{{- $weight = .Weight -}}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{{- $res := false -}}
|
|
||||||
|
|
||||||
{{- range . -}}
|
|
||||||
{{- if eq site.LanguageCode . -}}
|
|
||||||
{{- $res = true -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{- return $res -}}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{{- if site.Params.favicon -}}
|
{{- if site.Params.favicon -}}
|
||||||
{{- with site.Params.svgFavicon -}}
|
{{- with site.Params.svgFavicon -}}
|
||||||
<link rel="icon" type="image/svg+xml" href="{{ . }}" />
|
<link rel="icon" type="image/svg+xml" href="{{- . -}}" />
|
||||||
{{- with site.Params.svgFaviconFallback -}}
|
{{- with site.Params.svgFaviconFallback -}}
|
||||||
<link rel="icon" type="image/png" href="{{ . }}" />
|
<link rel="icon" type="image/png" href="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{{/* fingerprint */}}
|
{{- /* fingerprint */ -}}
|
||||||
{{- $fingerprint := .Scratch.Get "fingerprint" | default "" -}}
|
{{- $fingerprint := .Scratch.Get "fingerprint" | default "" -}}
|
||||||
|
|
||||||
{{/* critical style */}}
|
{{- /* critical style */ -}}
|
||||||
{{- $style := dict "Source" "scss/critical.scss" "Fingerprint" $fingerprint -}}
|
{{- $style := dict "Source" "bundled/css/critical.css" "Fingerprint" $fingerprint -}}
|
||||||
{{- $options := dict "enableSourceMap" true "includePaths" (slice "node_modules") -}}
|
{{- $options := dict "enableSourceMap" false -}}
|
||||||
{{- $style = dict "Context" . "ToCSS" $options "Inline" true | merge $style -}}
|
{{- $style = dict "Context" . "ToCSS" $options "Inline" true "Template" true | merge $style -}}
|
||||||
{{- partial "plugin/style.html" $style -}}
|
{{- partial "plugin/style.html" $style -}}
|
||||||
|
|
||||||
{{- $style := dict "Link" "/bundled/css/main.css" "Defer" true -}}
|
{{- $style := dict "Link" ("bundled/css/main.css" | absURL) "Defer" true -}}
|
||||||
{{- partial "plugin/style.html" $style -}}
|
{{- partial "plugin/style.html" $style -}}
|
||||||
|
|
||||||
{{/* fuck safari */}}
|
{{- /* fuck safari */ -}}
|
||||||
<script>
|
<script>
|
||||||
function z() {
|
function z() {
|
||||||
const r = document.querySelector(':root')
|
const r = document.querySelector(':root')
|
||||||
@@ -20,44 +20,20 @@
|
|||||||
window.addEventListener('resize', z, { passive: true })
|
window.addEventListener('resize', z, { passive: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{/* main js */}}
|
{{- /* main js */ -}}
|
||||||
{{- $script := dict "Link" "/bundled/js/main.js" "Defer" true "Esm" true -}}
|
{{- $script := dict "Link" ("bundled/js/main.js" | absURL) "Defer" true "Esm" true -}}
|
||||||
{{- partial "plugin/script.html" $script -}}
|
{{- partial "plugin/script.html" $script -}}
|
||||||
|
|
||||||
{{/* fonts */}}
|
{{- /* fonts */ -}}
|
||||||
<link rel="preload" href="/lib/fonts/fw.woff2" as="font" crossorigin />
|
<link
|
||||||
{{- if (partial "function/langCode.html" (slice "en" "de" "fr" "es" "it")) -}}
|
rel="preload"
|
||||||
<link rel="preload" href="/lib/fonts/GeistVF.woff2" as="font" crossorigin />
|
href="{{- "lib/fonts/fw.woff2" | absURL -}}"
|
||||||
{{- else if (partial "function/langCode.html" (slice "zh-cn" "zh-sg")) -}}
|
as="font"
|
||||||
<link rel="preload" href="/lib/fonts/NotoSans-Regular.woff2" as="font" crossorigin />
|
crossorigin
|
||||||
<link
|
/>
|
||||||
rel="preload"
|
<link
|
||||||
href="/lib/fonts/NotoSansCJKsc-Regular.woff2"
|
rel="preload"
|
||||||
as="font"
|
href="{{- "lib/fonts/GeistVF.woff2" | absURL -}}"
|
||||||
crossorigin
|
as="font"
|
||||||
/>
|
crossorigin
|
||||||
{{- else if (partial "function/langCode.html" (slice "zh-tw" "zh-hk" "zh-mo")) -}}
|
/>
|
||||||
<link rel="preload" href="/lib/fonts/NotoSans-Regular.woff2" as="font" crossorigin />
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/lib/fonts/NotoSansCJKtc-Regular.woff2"
|
|
||||||
as="font"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
{{- else if (partial "function/langCode.html" (slice "ja")) -}}
|
|
||||||
<link rel="preload" href="/lib/fonts/NotoSans-Regular.woff2" as="font" crossorigin />
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/lib/fonts/NotoSansCJKjp-Regular.woff2"
|
|
||||||
as="font"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
{{- else if (partial "function/langCode.html" (slice "ko")) -}}
|
|
||||||
<link rel="preload" href="/lib/fonts/NotoSans-Regular.woff2" as="font" crossorigin />
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/lib/fonts/NotoSansCJKkr-Regular.woff2"
|
|
||||||
as="font"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
{{- end -}}
|
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
{{/* Title */}}
|
{{- /* Title */ -}}
|
||||||
{{- $page_title := "" -}}
|
{{- $page_title := "" -}}
|
||||||
{{- with partial "function/currentMenuItem.html" . -}}
|
{{- with partial "function/currentMenuItem.html" . -}}
|
||||||
{{ $page_title = printf "%s" site.Title | printf "%s%s" " | " | printf "%s%s" .Title | printf "%s" }}
|
{{- $page_title = printf "%s" site.Title | printf "%s%s" " | " | printf "%s%s" .Title | printf "%s" -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<title>{{ $page_title }}</title>
|
<title>{{- $page_title -}}</title>
|
||||||
|
|
||||||
{{/* Basic */}}
|
{{- /* Basic */ -}}
|
||||||
<meta name="Description" content="{{ site.Params.description }}" />
|
<meta name="Description" content="{{- site.Params.description -}}" />
|
||||||
<meta name="application-name" content="{{ $page_title }}" />
|
<meta name="application-name" content="{{- $page_title -}}" />
|
||||||
<meta name="apple-mobile-web-app-title" content="{{ $page_title }}" />
|
<meta name="apple-mobile-web-app-title" content="{{- $page_title -}}" />
|
||||||
|
|
||||||
{{/* Opengraph */}}
|
{{- /* Opengraph */ -}}
|
||||||
<meta property="og:title" content="{{ $page_title }}" />
|
<meta property="og:title" content="{{- $page_title -}}" />
|
||||||
<meta name="twitter:title" content="{{ $page_title }}" />
|
<meta name="twitter:title" content="{{- $page_title -}}" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="{{ .Permalink }}" />
|
<meta property="og:url" content="{{- .Permalink -}}" />
|
||||||
<meta property="og:description" content="{{ site.Params.description }}" />
|
<meta property="og:description" content="{{- site.Params.description -}}" />
|
||||||
<meta name="twitter:description" content="{{ site.Params.description }}" />
|
<meta name="twitter:description" content="{{- site.Params.description -}}" />
|
||||||
|
|
||||||
{{/* Generator */}}
|
{{- /* Generator */ -}}
|
||||||
{{ hugo.Generator }}
|
{{- hugo.Generator -}}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
{{- with site.Params.verification.google -}}
|
{{- with site.Params.verification.google -}}
|
||||||
<meta name="google-site-verification" content="{{ . }}" />
|
<meta name="google-site-verification" content="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- with site.Params.verification.bing -}}
|
{{- with site.Params.verification.bing -}}
|
||||||
<meta name="msvalidate.01" content="{{ . }}" />
|
<meta name="msvalidate.01" content="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- with site.Params.verification.yandex -}}
|
{{- with site.Params.verification.yandex -}}
|
||||||
<meta name="yandex-verification" content="{{ . }}" />
|
<meta name="yandex-verification" content="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- with site.Params.verification.pinterest -}}
|
{{- with site.Params.verification.pinterest -}}
|
||||||
<meta name="p:domain_verify" content="{{ . }}" />
|
<meta name="p:domain_verify" content="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- with site.Params.verification.baidu -}}
|
{{- with site.Params.verification.baidu -}}
|
||||||
<meta name="baidu-site-verification" content="{{ . }}" />
|
<meta name="baidu-site-verification" content="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- with site.Params.verification.sogou -}}
|
{{- with site.Params.verification.sogou -}}
|
||||||
<meta name="sogou_site_verification" content="{{ . }}" />
|
<meta name="sogou_site_verification" content="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- with site.Params.verification.so -}}
|
{{- with site.Params.verification.so -}}
|
||||||
<meta name="360-site-verification" content="{{ . }}" />
|
<meta name="360-site-verification" content="{{- . -}}" />
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<div class="navArtist">
|
<div class="navArtist">
|
||||||
<a href="/">{{ site.Title }}</a>
|
<a href="/">{{- site.Title -}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
{{- $index := 0 -}}
|
{{- $index := 0 -}}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
{{- $currentIndex = sub .Weight 1 -}}
|
{{- $currentIndex = sub .Weight 1 -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- $menus := .Site.Menus.main -}}
|
{{- $menus := .Site.Menus.main -}}
|
||||||
{{- $len := len $menus }}
|
{{- $len := len $menus -}}
|
||||||
{{- range $menus -}}
|
{{- range $menus -}}
|
||||||
{{- $url := .URL | relURL -}}
|
{{- $url := .URL | relURL -}}
|
||||||
{{- if eq (add $index 1) $len -}}
|
{{- if eq (add $index 1) $len -}}
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
{{- with partial "function/getImageSlice.html" . -}}
|
{{- with partial "function/getImageSlice.html" . -}}
|
||||||
{{- $length = len . -}}
|
{{- $length = len . -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<span>{{- i18n "threshold" | strings.FirstUpper -}}:</span>
|
<span>{{- site.Params.labels.threshold -}}:</span>
|
||||||
<span>
|
<span>
|
||||||
<button class="dec">-</button>
|
<button class="dec">-</button>
|
||||||
<span class="num"></span><span class="num"></span><span class="num"></span
|
<span class="num"></span><span class="num"></span><span class="num"></span
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{- with $analytics.google.id -}}
|
{{- with $analytics.google.id -}}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());
|
window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());
|
||||||
gtag('config', '{{ . }}'{{ if $analytics.google.anonymizeIP }}, { 'anonymize_ip': true }{{ end }});
|
gtag('config', '{{- . -}}'{{- if $analytics.google.anonymizeIP -}}, { 'anonymize_ip': true }{{- end -}});
|
||||||
</script> {{- printf "https://www.googletagmanager.com/gtag/js?id=%v" . | dict "Async" true "Source" | partial "plugin/script.html" -}}
|
</script> {{- printf "https://www.googletagmanager.com/gtag/js?id=%v" . | dict "Async" true "Source" | partial "plugin/script.html" -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
{{- with $analytics.fathom.id -}}
|
{{- with $analytics.fathom.id -}}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.fathom=window.fathom||function(){(fathom.q=fathom.q||[]).push(arguments);};
|
window.fathom=window.fathom||function(){(fathom.q=fathom.q||[]).push(arguments);};
|
||||||
fathom('set', 'siteId', '{{ . }}');
|
fathom('set', 'siteId', '{{- . -}}');
|
||||||
fathom('trackPageview');
|
fathom('trackPageview');
|
||||||
</script> {{- dict "Source" ($analytics.fathom.server | default "cdn.usefathom.com" | printf "https://%v/tracker.js") "Async" true "Attr" "id=fathom-script" | partial "plugin/script.html" -}}
|
</script> {{- dict "Source" ($analytics.fathom.server | default "cdn.usefathom.com" | printf "https://%v/tracker.js") "Async" true "Attr" "id=fathom-script" | partial "plugin/script.html" -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
var _hmt = _hmt || [];
|
var _hmt = _hmt || [];
|
||||||
(function() {
|
(function() {
|
||||||
var hm = document.createElement("script");
|
var hm = document.createElement("script");
|
||||||
hm.src = "https://hm.baidu.com/hm.js?{{ . }}";
|
hm.src = "https://hm.baidu.com/hm.js?{{- . -}}";
|
||||||
var s = document.getElementsByTagName("script")[0];
|
var s = document.getElementsByTagName("script")[0];
|
||||||
s.parentNode.insertBefore(hm, s);
|
s.parentNode.insertBefore(hm, s);
|
||||||
})();
|
})();
|
||||||
@@ -36,10 +36,10 @@
|
|||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
defer
|
defer
|
||||||
data-website-id="{{ . }}"
|
data-website-id="{{- . -}}"
|
||||||
src="{{ $analytics.umami.src }}"
|
src="{{- $analytics.umami.src -}}"
|
||||||
{{ with $analytics.umami.data_host_url }}data-host-url="{{ . }}"{{ end }}
|
{{- with $analytics.umami.data_host_url -}}data-host-url="{{- . -}}"{{- end -}}
|
||||||
{{ with $analytics.umami.data_domains }}data-domains="{{ . }}"{{ end }}
|
{{- with $analytics.umami.data_domains -}}data-domains="{{- . -}}"{{- end -}}
|
||||||
></script>
|
></script>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
defer
|
defer
|
||||||
data-domain="{{ . }}"
|
data-domain="{{- . -}}"
|
||||||
src="{{ $analytics.plausible.src }}"
|
src="{{- $analytics.plausible.src -}}"
|
||||||
></script>
|
></script>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
@@ -58,17 +58,17 @@
|
|||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||||
data-cf-beacon='{"token": "{{ $analytics.cloudflare.token }}"}'
|
data-cf-beacon='{"token": "{{- $analytics.cloudflare.token -}}"}'
|
||||||
></script>
|
></script>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{- /* Splitbee Analytics */ -}}
|
{{- /* Splitbee Analytics */ -}}
|
||||||
{{- if $analytics.splitbee.enable -}}
|
{{- if $analytics.splitbee.enable -}}
|
||||||
{{- $attr := "" -}}
|
{{- $attr := "" -}}
|
||||||
{{- if $analytics.splitbee.Do_not_track -}}
|
{{- if $analytics.splitbee.do_not_track -}}
|
||||||
{{- $attr = printf `%v data-respect-dnt` $attr -}}
|
{{- $attr = printf `%v data-respect-dnt` $attr -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- if $analytics.splitbee.No_cookie -}}
|
{{- if $analytics.splitbee.no_cookie -}}
|
||||||
{{- $attr = printf `%v data-no-cookie` $attr -}}
|
{{- $attr = printf `%v data-no-cookie` $attr -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- with $analytics.splitbee.data_token -}}
|
{{- with $analytics.splitbee.data_token -}}
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
{{ with $attr }}{{ . | safeHTMLAttr }}{{ end }}
|
{{- with $attr -}}{{- . | safeHTMLAttr -}}{{- end -}}
|
||||||
src="https://cdn.splitbee.io/sb.js"
|
src="https://cdn.splitbee.io/sb.js"
|
||||||
></script>
|
></script>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
{{- $resource = resources.FromString $.Path . -}}
|
{{- $resource = resources.FromString $.Path . -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- if $resource -}}
|
{{- if $resource -}}
|
||||||
{{- with .Template -}}
|
|
||||||
{{- $resource = $resource | resources.ExecuteAsTemplate . $.Context -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- with .ToCSS -}}
|
{{- with .ToCSS -}}
|
||||||
{{- $options := . | merge (dict "outputStyle" "compressed") -}}
|
{{- $options := . | merge (dict "outputStyle" "compressed") -}}
|
||||||
{{- $resource = $resource | toCSS $options -}}
|
{{- $resource = $resource | toCSS $options -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
{{- with .Template -}}
|
||||||
|
{{- $resource = $resource | resources.ExecuteAsTemplate . $.Context -}}
|
||||||
|
{{- end -}}
|
||||||
{{- if or .Minify .Inline -}}
|
{{- if or .Minify .Inline -}}
|
||||||
{{- $resource = $resource | minify -}}
|
{{- $resource = $resource | minify -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
@@ -37,26 +37,26 @@
|
|||||||
rel="preload"
|
rel="preload"
|
||||||
as="style"
|
as="style"
|
||||||
onload="this.onload=null;this.rel='stylesheet'"
|
onload="this.onload=null;this.rel='stylesheet'"
|
||||||
href="{{ $href }}"
|
href="{{- $href -}}"
|
||||||
{{ if .Crossorigin }}crossorigin="anonymous"{{ end }}{{ with $integrity }}
|
{{- if .Crossorigin -}}crossorigin="anonymous"{{- end -}}{{- with $integrity -}}
|
||||||
integrity="{{ . }}"
|
integrity="{{- . -}}"
|
||||||
{{ end }}{{ with .Attr }}{{ . | safeHTMLAttr }}{{ end }}
|
{{- end -}}{{- with .Attr -}}{{- . | safeHTMLAttr -}}{{- end -}}
|
||||||
/>
|
/>
|
||||||
<noscript
|
<noscript
|
||||||
><link
|
><link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="{{ $href }}"
|
href="{{- $href -}}"
|
||||||
{{ if .Crossorigin }}crossorigin="anonymous"{{ end }}{{ with $integrity }}
|
{{- if .Crossorigin -}}crossorigin="anonymous"{{- end -}}{{- with $integrity -}}
|
||||||
integrity="{{ . }}"
|
integrity="{{- . -}}"
|
||||||
{{ end }}{{ with .Attr }}{{ . | safeHTMLAttr }}{{ end }}
|
{{- end -}}{{- with .Attr -}}{{- . | safeHTMLAttr -}}{{- end -}}
|
||||||
/></noscript>
|
/></noscript>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="{{ $href }}"
|
href="{{- $href -}}"
|
||||||
{{ if .Crossorigin }}crossorigin="anonymous"{{ end }}{{ with $integrity }}
|
{{- if .Crossorigin -}}crossorigin="anonymous"{{- end -}}{{- with $integrity -}}
|
||||||
integrity="{{ . }}"
|
integrity="{{- . -}}"
|
||||||
{{ end }}{{ with .Attr }}{{ . | safeHTMLAttr }}{{ end }}
|
{{- end -}}{{- with .Attr -}}{{- . | safeHTMLAttr -}}{{- end -}}
|
||||||
/>
|
/>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@@ -58,4 +58,4 @@ Disallow: /
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
|
|
||||||
Sitemap: {{ "/sitemap.xml" | absURL }}
|
Sitemap: {{- "/sitemap.xml" | absURL -}}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{{ now.Year }}
|
{{- now.Year -}}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
{{- $weeks := div (sub now.Unix .Lastmod.Unix) 604800 -}}
|
{{- $weeks := div (sub now.Unix .Lastmod.Unix) 604800 -}}
|
||||||
{{- $priority := sub 1 (div $weeks 10.0 ) -}}
|
{{- $priority := sub 1 (div $weeks 10.0 ) -}}
|
||||||
{{- if ge .Sitemap.Priority $priority -}}
|
{{- if ge .Sitemap.Priority $priority -}}
|
||||||
<priority>{{ .Sitemap.Priority }}</priority>
|
<priority>{{- .Sitemap.Priority -}}</priority>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<priority>{{ $priority }}</priority>
|
<priority>{{- $priority -}}</priority>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
@@ -32,14 +32,14 @@
|
|||||||
{{- range .Translations -}}
|
{{- range .Translations -}}
|
||||||
<xhtml:link
|
<xhtml:link
|
||||||
rel="alternate"
|
rel="alternate"
|
||||||
hreflang="{{ .Lang }}"
|
hreflang="{{- .Lang -}}"
|
||||||
href="{{ .Permalink }}"
|
href="{{- .Permalink -}}"
|
||||||
/>
|
/>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<xhtml:link
|
<xhtml:link
|
||||||
rel="alternate"
|
rel="alternate"
|
||||||
hreflang="{{ .Lang }}"
|
hreflang="{{- .Lang -}}"
|
||||||
href="{{ .Permalink }}"
|
href="{{- .Permalink -}}"
|
||||||
/>
|
/>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</url>
|
</url>
|
||||||
|
|||||||
59
package.json
59
package.json
@@ -3,23 +3,23 @@
|
|||||||
"version": "v1.0.0",
|
"version": "v1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "bridget theme source file",
|
"description": "bridget theme source file",
|
||||||
"packageManager": "pnpm@8.10.2",
|
"packageManager": "pnpm@10.20.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vite": "vite build --no-watch",
|
"vite": "DISABLE_WATCH=1 vite build",
|
||||||
"lint": "eslint . --fix && prettier --write .",
|
"lint": "eslint . --fix && prettier --write .",
|
||||||
"lint:check": "eslint . && prettier . --check",
|
"lint:check": "eslint . && prettier . --check",
|
||||||
"dev": "run-p vite:dev hugo:dev",
|
"dev": "run-p vite:dev hugo:dev",
|
||||||
"build": "run-s vite:build hugo:build",
|
"build": "run-s vite:build hugo:build",
|
||||||
"server": "run-p vite:server hugo:server",
|
"server": "run-p vite:server hugo:server",
|
||||||
"vite:build": "vite build --no-watch --minify terser",
|
"vite:build": "DISABLE_WATCH=1 vite build",
|
||||||
"vite:server": "vite build --minify terser",
|
"vite:server": "vite build",
|
||||||
"vite:dev": "vite build --mode development --minify false",
|
"vite:dev": "vite build --mode development --minify false",
|
||||||
"hugo:build": "hugo --logLevel info --source=exampleSite --gc",
|
"hugo:build": "hugo --logLevel info --source=exampleSite --minify",
|
||||||
"hugo:preview": "hugo --logLevel info --source=exampleSite -D --gc",
|
"hugo:preview": "hugo --logLevel info --source=exampleSite -D",
|
||||||
"hugo:dev": "hugo server --source=exampleSite --gc -D --disableFastRender --watch --logLevel info",
|
"hugo:dev": "hugo server --source=exampleSite -D --disableFastRender --watch --logLevel info",
|
||||||
"hugo:server": "hugo server --source=exampleSite --gc --disableFastRender -e production --watch --logLevel info"
|
"hugo:server": "hugo server --source=exampleSite --disableFastRender -e production --watch --logLevel info"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -39,32 +39,31 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/Sped0n/bridget#readme",
|
"homepage": "https://github.com/Sped0n/bridget#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@eslint/js": "^9.39.4",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@types/node": "^25.5.0",
|
||||||
"eslint": "^8.57.0",
|
"@typescript-eslint/parser": "^8.57.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint": "^9.39.2",
|
||||||
"eslint-config-standard": "^17.1.0",
|
"eslint-config-love": "^151.0.0",
|
||||||
"eslint-config-standard-with-typescript": "^43.0.1",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^4.4.4",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-n": "^16.6.2",
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-solid": "^0.14.5",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
|
||||||
"eslint-plugin-solid": "^0.13.1",
|
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.8.1",
|
||||||
"prettier-plugin-go-template": "^0.0.15",
|
"prettier-plugin-go-template": "^0.0.15",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^4.3.0",
|
||||||
"sass": "^1.71.1",
|
"sass-embedded": "^1.98.0",
|
||||||
"terser": "^5.29.2",
|
"typescript": "^5.9.3",
|
||||||
"typescript": "^5.4.2",
|
"typescript-eslint": "^8.53.1",
|
||||||
"vite": "^5.1.6",
|
"vite": "^8.0.1",
|
||||||
"vite-plugin-solid": "^2.10.2"
|
"vite-plugin-solid": "^2.11.11",
|
||||||
|
"vitefu": "^1.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.14.2",
|
||||||
"solid-js": "^1.8.15",
|
"solid-js": "^1.9.11",
|
||||||
"swiper": "^11.0.7",
|
"swiper": "^12.1.2",
|
||||||
"tiny-invariant": "^1.3.3"
|
"tiny-invariant": "^1.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6507
pnpm-lock.yaml
generated
6507
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
23
prettier.config.mjs
Normal file
23
prettier.config.mjs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @see https://prettier.io/docs/configuration
|
||||||
|
* @type {import("prettier").Config}
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
printWidth: 88,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'none',
|
||||||
|
bracketSpacing: true,
|
||||||
|
semi: false,
|
||||||
|
plugins: ['prettier-plugin-go-template', 'prettier-plugin-organize-imports'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.html'],
|
||||||
|
options: {
|
||||||
|
parser: 'go-template'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
export default config
|
||||||
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
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user