mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-15 10:39:30 -07:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22b81a8e1d | ||
|
|
8432540bde | ||
|
|
bb056d9c4f | ||
|
|
6bf10c103f | ||
|
|
1b1aea5047 | ||
|
|
5132e36e87 | ||
|
|
964c1802d3 | ||
|
|
0af4e20720 | ||
|
|
1f65b08b56 | ||
|
|
9bfaac25f5 | ||
|
|
794b5c0ea6 | ||
|
|
2fd34c2f7f | ||
|
|
44d7da48e3 | ||
|
|
49e9f904e2 | ||
|
|
1a3fade5fc | ||
|
|
4e4c32384a | ||
|
|
88b518b773 | ||
|
|
4eece4684a | ||
|
|
4f9b5ff311 | ||
|
|
c123fd45be | ||
|
|
09506d2d4e | ||
|
|
08350cfb9f | ||
|
|
cf2f36d232 | ||
|
|
480df04e55 | ||
|
|
ae96a07793 | ||
|
|
17ef30c18b | ||
|
|
0717ce1051 | ||
|
|
9fa1b718b8 | ||
|
|
8c6b38bb49 | ||
|
|
80c784262b | ||
|
|
ae1a08eb82 | ||
|
|
ff1a76eef8 | ||
|
|
d9452ca8d2 | ||
|
|
3fef127666 | ||
|
|
ed3b08dce2 | ||
|
|
8ba41fc32c | ||
|
|
0f537630e8 | ||
|
|
3f60289381 | ||
|
|
a3c2310375 | ||
|
|
b9de1b9c70 | ||
|
|
ac5fb33f41 | ||
|
|
ba044a2147 | ||
|
|
b34a85fa92 | ||
|
|
7b0cd6aae7 | ||
|
|
bc04ef37bb | ||
|
|
a5807f7625 | ||
|
|
0c16bab065 | ||
|
|
30a01cfd63 | ||
|
|
1e2f1fadde | ||
|
|
4932953871 | ||
|
|
44f3187540 | ||
|
|
80d5a2071b | ||
|
|
c02099aba9 | ||
|
|
50b0e6a5f4 | ||
|
|
6e066497f6 | ||
|
|
af664924e5 | ||
|
|
cdebc028cb | ||
|
|
6210fdda61 | ||
|
|
58f4ebe137 | ||
|
|
1381190a1a | ||
|
|
99220ec318 | ||
|
|
44b883ea96 | ||
|
|
8fd8ed52fe | ||
|
|
6a75a9bc83 | ||
|
|
96b38e9e60 | ||
|
|
267e79a090 | ||
|
|
b3ca475134 | ||
|
|
0126dfdd7e | ||
|
|
2c8db4abe1 | ||
|
|
4e0f083840 | ||
|
|
b3e3c7b0d4 | ||
|
|
784716658d | ||
|
|
a8975c5324 | ||
|
|
11c6fc4c0c | ||
|
|
1da9215a79 | ||
|
|
e0bf170cf2 | ||
|
|
ebdf2405f0 | ||
|
|
83370e6e67 | ||
|
|
c2956b7d4b | ||
|
|
619dc05e97 | ||
|
|
5394ea20eb | ||
|
|
39fdbb2921 | ||
|
|
8ddc7c840e | ||
|
|
9b08d255f1 | ||
|
|
b751623530 | ||
|
|
54e715dfd5 | ||
|
|
9c073e98e1 | ||
|
|
83363387cd | ||
|
|
d608016829 | ||
|
|
cd47ac8538 | ||
|
|
3c9f19a506 | ||
|
|
454862fb5f | ||
|
|
fe7923e558 | ||
|
|
565ce4a241 | ||
|
|
f2b319caa2 |
@@ -22,6 +22,7 @@
|
||||
"arrow-body-style": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"import/no-cycle": "error",
|
||||
"@typescript-eslint/non-nullable-type-assertion-style": "off",
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -8,5 +8,5 @@ updates:
|
||||
- package-ecosystem: 'npm' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
open-pull-requests-limit: 1000
|
||||
|
||||
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
- name: Get changed files in scope
|
||||
id: changed-files-specific
|
||||
uses: tj-actions/changed-files@v40
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: |
|
||||
package.json
|
||||
@@ -39,6 +39,11 @@ jobs:
|
||||
github.ref == 'refs/heads/main' &&
|
||||
github.event.repository.fork == false
|
||||
steps:
|
||||
- name: Set current date as env variable
|
||||
run: |
|
||||
echo "builddate=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -79,7 +84,10 @@ jobs:
|
||||
|
||||
- name: Push artifacts
|
||||
if: ${{ (github.event_name == 'push' || github.event.pull_request.merged == true) && needs.filter.outputs.any_changed == 'true' }}
|
||||
run: |
|
||||
git config --global user.email "noreply@github.com"
|
||||
git config --global user.name "CI"
|
||||
git diff --quiet || (git add -A && git commit -m "build: update bundled artifacts [skip ci]" && git push)
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
title: "ci: update bundled artifacts"
|
||||
commit-message: "ci: update bundled artifacts"
|
||||
branch: update-artifacts-${{ steps.version.outputs.builddate }}
|
||||
base: main
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -21,4 +21,7 @@ $RECYCLE.BIN/
|
||||
|
||||
# Hugo
|
||||
.hugo_build.lock
|
||||
jsconfig.json
|
||||
jsconfig.json
|
||||
|
||||
# css map
|
||||
*.css.map
|
||||
|
||||
@@ -3,3 +3,4 @@ static
|
||||
exmapleSite
|
||||
*.yaml
|
||||
*.yml
|
||||
single.json
|
||||
|
||||
@@ -17,3 +17,7 @@ a,
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
@font-face {
|
||||
font-family: 'Geist';
|
||||
src: url('/lib/fonts/GeistVF.woff2') format('woff2');
|
||||
font-weight: 90;
|
||||
src:
|
||||
url('/lib/fonts/GeistVF.woff2') format('woff2 supports variations'),
|
||||
url('/lib/fonts/GeistVF.woff2') format('woff2-variations');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -45,3 +47,11 @@
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'FW';
|
||||
src: url('/lib/fonts/fw.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ body {
|
||||
font-family: sans-serif;
|
||||
|
||||
button {
|
||||
font-family: 'Noto Sans', sans-serif;
|
||||
font-family: 'FW';
|
||||
}
|
||||
|
||||
@include min-width('tablet') {
|
||||
@@ -17,15 +17,43 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
body:lang(en, de, es, fr, it) {
|
||||
body:lang(en) {
|
||||
font-family: 'Geist', sans-serif;
|
||||
}
|
||||
|
||||
body:lang(zh-cn, zh-sg) {
|
||||
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-hk, zh-mo, zh-tw) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
.container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
$tablet: map-get($breakpoints, 'tablet') - 1;
|
||||
|
||||
width: 100vw;
|
||||
height: var(--window-height);
|
||||
@media (max-width: $tablet), (hover: none) {
|
||||
.container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
background: white;
|
||||
width: 100vw;
|
||||
height: var(--window-height);
|
||||
|
||||
overscroll-behavior: none;
|
||||
-webkit-overflow-scrolling: none;
|
||||
}
|
||||
|
||||
.disableScroll {
|
||||
pointer-events: none;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
background: white;
|
||||
|
||||
overscroll-behavior: none;
|
||||
-webkit-overflow-scrolling: none;
|
||||
}
|
||||
|
||||
.disableScroll {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,30 @@
|
||||
.galleryInner {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.swiper-slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.loadingText {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.slideContainer {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nav {
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
|
||||
@import '_partial/nav';
|
||||
@import '_partial/article';
|
||||
@import '_partial/container';
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import { scrollable } from './mobile/scroll'
|
||||
import { Watchable } from './globalUtils'
|
||||
|
||||
export let container: HTMLDivElement
|
||||
export const scrollable = new Watchable<boolean>(true)
|
||||
|
||||
export let container: Container
|
||||
|
||||
/**
|
||||
* interfaces
|
||||
*/
|
||||
|
||||
export interface Container extends HTMLDivElement {
|
||||
dataset: {
|
||||
next: string
|
||||
close: string
|
||||
prev: string
|
||||
loading: string
|
||||
}
|
||||
}
|
||||
|
||||
export function initContainer(): void {
|
||||
container = document.getElementsByClassName('container').item(0) as HTMLDivElement
|
||||
container = document.getElementsByClassName('container').item(0) as Container
|
||||
scrollable.addWatcher((o) => {
|
||||
if (o) {
|
||||
container.classList.remove('disableScroll')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { container } from '../container'
|
||||
|
||||
import { active } from './stage'
|
||||
import { active } from './state'
|
||||
|
||||
/**
|
||||
* variables
|
||||
|
||||
@@ -4,6 +4,10 @@ import { initCustomCursor } from './customCursor'
|
||||
import { initStage } from './stage'
|
||||
import { initStageNav } from './stageNav'
|
||||
|
||||
/**
|
||||
* main functions
|
||||
*/
|
||||
|
||||
export function initDesktop(ijs: ImageJSON[]): void {
|
||||
initCustomCursor()
|
||||
initStage(ijs)
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
import { type Power3, type gsap } from 'gsap'
|
||||
|
||||
import { container } from '../container'
|
||||
import { incIndex, isAnimating, navigateVector, state } from '../globalState'
|
||||
import { decrement, increment, loadGsap } from '../globalUtils'
|
||||
import { type ImageJSON } from '../resources'
|
||||
import { incIndex, state } from '../state'
|
||||
import { Watchable, decrement, increment, loadGsap } from '../utils'
|
||||
|
||||
/**
|
||||
* types
|
||||
*/
|
||||
|
||||
export interface HistoryItem {
|
||||
i: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
import { active, cordHist, isLoading, isOpen } from './state'
|
||||
// eslint-disable-next-line sort-imports
|
||||
import { onMutation, type DesktopImage } from './utils'
|
||||
|
||||
/**
|
||||
* variables
|
||||
*/
|
||||
|
||||
let imgs: HTMLImageElement[] = []
|
||||
let imgs: DesktopImage[] = []
|
||||
let last = { x: 0, y: 0 }
|
||||
export const cordHist = new Watchable<HistoryItem[]>([])
|
||||
export const isOpen = new Watchable<boolean>(false)
|
||||
export const isAnimating = new Watchable<boolean>(false)
|
||||
export const active = new Watchable<boolean>(false)
|
||||
|
||||
let _gsap: typeof gsap
|
||||
let _Power3: typeof Power3
|
||||
@@ -35,41 +25,34 @@ let gsapLoaded = false
|
||||
* getter
|
||||
*/
|
||||
|
||||
function getElTrail(): HTMLImageElement[] {
|
||||
return cordHist.get().map((item) => imgs[item.i])
|
||||
function getTrailElsIndex(): number[] {
|
||||
return cordHist.get().map((item) => item.i)
|
||||
}
|
||||
|
||||
function getElTrailCurrent(): HTMLImageElement[] {
|
||||
return getElTrail().slice(-state.get().trailLength)
|
||||
function getTrailCurrentElsIndex(): number[] {
|
||||
return getTrailElsIndex().slice(-state.get().trailLength)
|
||||
}
|
||||
|
||||
function getElTrailInactive(): HTMLImageElement[] {
|
||||
const elTrailCurrent = getElTrailCurrent()
|
||||
return elTrailCurrent.slice(0, elTrailCurrent.length - 1)
|
||||
function getTrailInactiveElsIndex(): number[] {
|
||||
const trailCurrentElsIndex = getTrailCurrentElsIndex()
|
||||
return trailCurrentElsIndex.slice(0, trailCurrentElsIndex.length - 1)
|
||||
}
|
||||
|
||||
function getElCurrent(): HTMLImageElement {
|
||||
const elTrail = getElTrail()
|
||||
return elTrail[elTrail.length - 1]
|
||||
function getCurrentElIndex(): number {
|
||||
const trailElsIndex = getTrailElsIndex()
|
||||
return trailElsIndex[trailElsIndex.length - 1]
|
||||
}
|
||||
|
||||
function getElNextFive(): HTMLImageElement[] {
|
||||
function getPrevElIndex(): number {
|
||||
const c = cordHist.get()
|
||||
const s = state.get()
|
||||
const els = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
els.push(imgs[increment(s.index + i, s.length)])
|
||||
}
|
||||
return els
|
||||
return decrement(c[c.length - 1].i, s.length)
|
||||
}
|
||||
|
||||
function getElPrev(): HTMLImageElement {
|
||||
function getNextElIndex(): number {
|
||||
const c = cordHist.get()
|
||||
const s = state.get()
|
||||
return imgs[increment(s.index, s.length)]
|
||||
}
|
||||
|
||||
function getElNext(): HTMLImageElement {
|
||||
const s = state.get()
|
||||
return imgs[decrement(s.index, s.length)]
|
||||
return increment(c[c.length - 1].i, s.length)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +61,11 @@ function getElNext(): HTMLImageElement {
|
||||
|
||||
// on mouse
|
||||
function onMouse(e: MouseEvent): void {
|
||||
if (isOpen.get() || isAnimating.get() || !gsapLoaded) return
|
||||
if (isOpen.get() || isAnimating.get()) return
|
||||
if (!gsapLoaded) {
|
||||
loadLib()
|
||||
return
|
||||
}
|
||||
const cord = { x: e.clientX, y: e.clientY }
|
||||
const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y)
|
||||
|
||||
@@ -91,15 +78,14 @@ function onMouse(e: MouseEvent): void {
|
||||
}
|
||||
}
|
||||
|
||||
// set image position with gsap
|
||||
// set image position with gsap (in both stage and navigation)
|
||||
function setPositions(): void {
|
||||
const elTrail = getElTrail()
|
||||
if (elTrail.length === 0 || !gsapLoaded) return
|
||||
const trailElsIndex = getTrailElsIndex()
|
||||
if (trailElsIndex.length === 0 || !gsapLoaded) return
|
||||
|
||||
// preload
|
||||
lores(getElNextFive())
|
||||
const elsTrail = getImagesWithIndexArray(trailElsIndex)
|
||||
|
||||
_gsap.set(elTrail, {
|
||||
_gsap.set(elsTrail, {
|
||||
x: (i: number) => cordHist.get()[i].x - window.innerWidth / 2,
|
||||
y: (i: number) => cordHist.get()[i].y - window.innerHeight / 2,
|
||||
opacity: (i: number) =>
|
||||
@@ -109,25 +95,47 @@ function setPositions(): void {
|
||||
})
|
||||
|
||||
if (isOpen.get()) {
|
||||
lores(getElTrail())
|
||||
hires([getElCurrent()])
|
||||
const elc = getImagesWithIndexArray([getCurrentElIndex()])[0]
|
||||
elc.classList.add('hide') // hide image to prevent flash
|
||||
const indexArrayToHires: number[] = []
|
||||
switch (navigateVector.get()) {
|
||||
case 'prev':
|
||||
indexArrayToHires.push(getPrevElIndex())
|
||||
break
|
||||
case 'next':
|
||||
indexArrayToHires.push(getNextElIndex())
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
hires(getImagesWithIndexArray(indexArrayToHires)) // preload
|
||||
setLoaderForImage(elc)
|
||||
_gsap.set(imgs, { opacity: 0 })
|
||||
_gsap.set(getElCurrent(), { opacity: 1, x: 0, y: 0, scale: 1 })
|
||||
_gsap.set(elc, { opacity: 1, x: 0, y: 0, scale: 1 })
|
||||
} else {
|
||||
lores(elsTrail)
|
||||
}
|
||||
}
|
||||
|
||||
// open image into navigation
|
||||
function expandImage(): void {
|
||||
if (isAnimating.get() || !gsapLoaded) return
|
||||
if (isAnimating.get()) return
|
||||
|
||||
isOpen.set(true)
|
||||
isAnimating.set(true)
|
||||
|
||||
hires([getElCurrent(), getElPrev(), getElNext()])
|
||||
const elcIndex = getCurrentElIndex()
|
||||
const elc = getImagesWithIndexArray([elcIndex])[0]
|
||||
// don't hide here because we want a better transition
|
||||
// elc.classList.add('hide')
|
||||
|
||||
hires(getImagesWithIndexArray([elcIndex, getPrevElIndex(), getNextElIndex()]))
|
||||
setLoaderForImage(elc)
|
||||
|
||||
const tl = _gsap.timeline()
|
||||
const trailInactiveEls = getImagesWithIndexArray(getTrailInactiveElsIndex())
|
||||
// move down and hide trail inactive
|
||||
tl.to(getElTrailInactive(), {
|
||||
tl.to(trailInactiveEls, {
|
||||
y: '+=20',
|
||||
ease: _Power3.easeIn,
|
||||
stagger: 0.075,
|
||||
@@ -136,7 +144,7 @@ function expandImage(): void {
|
||||
opacity: 0
|
||||
})
|
||||
// current move to center
|
||||
tl.to(getElCurrent(), {
|
||||
tl.to(elc, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
ease: _Power3.easeInOut,
|
||||
@@ -144,7 +152,7 @@ function expandImage(): void {
|
||||
delay: 0.3
|
||||
})
|
||||
// current expand
|
||||
tl.to(getElCurrent(), {
|
||||
tl.to(elc, {
|
||||
delay: 0.1,
|
||||
scale: 1,
|
||||
ease: _Power3.easeInOut
|
||||
@@ -159,23 +167,27 @@ function expandImage(): void {
|
||||
|
||||
// close navigation and back to stage
|
||||
export function minimizeImage(): void {
|
||||
if (isAnimating.get() || !gsapLoaded) return
|
||||
if (isAnimating.get()) return
|
||||
|
||||
isOpen.set(false)
|
||||
isAnimating.set(true)
|
||||
navigateVector.set('none') // cleanup
|
||||
|
||||
lores([getElCurrent()])
|
||||
lores(getElTrailInactive())
|
||||
lores(
|
||||
getImagesWithIndexArray([...getTrailInactiveElsIndex(), ...[getCurrentElIndex()]])
|
||||
)
|
||||
|
||||
const tl = _gsap.timeline()
|
||||
const elc = getImagesWithIndexArray([getCurrentElIndex()])[0]
|
||||
const elsTrailInactive = getImagesWithIndexArray(getTrailInactiveElsIndex())
|
||||
// shrink current
|
||||
tl.to(getElCurrent(), {
|
||||
tl.to(elc, {
|
||||
scale: 0.6,
|
||||
duration: 0.6,
|
||||
ease: _Power3.easeInOut
|
||||
})
|
||||
// move current to original position
|
||||
tl.to(getElCurrent(), {
|
||||
tl.to(elc, {
|
||||
delay: 0.3,
|
||||
duration: 0.7,
|
||||
ease: _Power3.easeInOut,
|
||||
@@ -183,7 +195,7 @@ export function minimizeImage(): void {
|
||||
y: cordHist.get()[cordHist.get().length - 1].y - window.innerHeight / 2
|
||||
})
|
||||
// show trail inactive
|
||||
tl.to(getElTrailInactive(), {
|
||||
tl.to(elsTrailInactive, {
|
||||
y: '-=20',
|
||||
ease: _Power3.easeOut,
|
||||
stagger: -0.1,
|
||||
@@ -208,14 +220,47 @@ export function initStage(ijs: ImageJSON[]): void {
|
||||
// get stage
|
||||
const stage = document.getElementsByClassName('stage').item(0) as HTMLDivElement
|
||||
// get image elements
|
||||
imgs = Array.from(stage.getElementsByTagName('img'))
|
||||
imgs = Array.from(stage.getElementsByTagName('img')) as DesktopImage[]
|
||||
imgs.forEach((img, i) => {
|
||||
// preload first 5 images on page load
|
||||
if (i < 5) {
|
||||
img.src = img.dataset.loUrl
|
||||
}
|
||||
// lores preloader for rest of the images
|
||||
onMutation(img, (mutations, observer) => {
|
||||
mutations.every((mutation) => {
|
||||
// if open or animating, skip
|
||||
if (isOpen.get() || isAnimating.get()) return true
|
||||
// if mutation is not about style attribute, skip
|
||||
if (mutation.attributeName !== 'style') return true
|
||||
const opacity = parseFloat(img.style.opacity)
|
||||
// if opacity is not 1, skip
|
||||
if (opacity !== 1) return true
|
||||
// preload the i + 5th image
|
||||
if (i + 5 < imgs.length) {
|
||||
imgs[i + 5].src = imgs[i + 5].dataset.loUrl
|
||||
}
|
||||
// disconnect observer and return false to break the loop
|
||||
observer.disconnect()
|
||||
return false
|
||||
})
|
||||
})
|
||||
})
|
||||
// event listeners
|
||||
stage.addEventListener('click', () => {
|
||||
expandImage()
|
||||
})
|
||||
stage.addEventListener('keydown', () => {
|
||||
expandImage()
|
||||
})
|
||||
stage.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
expandImage()
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
stage.addEventListener(
|
||||
'keydown',
|
||||
() => {
|
||||
expandImage()
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
window.addEventListener('mousemove', onMouse, { passive: true })
|
||||
// watchers
|
||||
isOpen.addWatcher((o) => {
|
||||
@@ -227,21 +272,11 @@ export function initStage(ijs: ImageJSON[]): void {
|
||||
cordHist.addWatcher((_) => {
|
||||
setPositions()
|
||||
})
|
||||
// preload
|
||||
lores(getElNextFive())
|
||||
// dynamic import
|
||||
window.addEventListener(
|
||||
'mousemove',
|
||||
() => {
|
||||
loadGsap()
|
||||
.then((g) => {
|
||||
_gsap = g[0]
|
||||
_Power3 = g[1]
|
||||
gsapLoaded = true
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
loadLib()
|
||||
},
|
||||
{ once: true, passive: true }
|
||||
)
|
||||
@@ -257,7 +292,7 @@ function createStage(ijs: ImageJSON[]): void {
|
||||
stage.className = 'stage'
|
||||
// append images to container
|
||||
for (const ij of ijs) {
|
||||
const e = document.createElement('img')
|
||||
const e = document.createElement('img') as DesktopImage
|
||||
e.height = ij.loImgH
|
||||
e.width = ij.loImgW
|
||||
// set data attributes
|
||||
@@ -268,23 +303,66 @@ function createStage(ijs: ImageJSON[]): void {
|
||||
e.dataset.loImgH = ij.loImgH.toString()
|
||||
e.dataset.loImgW = ij.loImgW.toString()
|
||||
e.alt = ij.alt
|
||||
// append
|
||||
stage.append(e)
|
||||
}
|
||||
container.append(stage)
|
||||
}
|
||||
|
||||
function hires(imgs: HTMLImageElement[]): void {
|
||||
function getImagesWithIndexArray(indexArray: number[]): DesktopImage[] {
|
||||
return indexArray.map((i) => imgs[i])
|
||||
}
|
||||
|
||||
function hires(imgs: DesktopImage[]): void {
|
||||
imgs.forEach((img) => {
|
||||
img.src = img.dataset.hiUrl as string
|
||||
img.height = parseInt(img.dataset.hiImgH as string)
|
||||
img.width = parseInt(img.dataset.hiImgW as string)
|
||||
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: HTMLImageElement[]): void {
|
||||
function lores(imgs: DesktopImage[]): void {
|
||||
imgs.forEach((img) => {
|
||||
img.src = img.dataset.loUrl as string
|
||||
img.height = parseInt(img.dataset.loImgH as string)
|
||||
img.width = parseInt(img.dataset.loImgW as string)
|
||||
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 setLoaderForImage(e: HTMLImageElement): void {
|
||||
if (!e.complete) {
|
||||
isLoading.set(true)
|
||||
e.addEventListener(
|
||||
'load',
|
||||
() => {
|
||||
isLoading.set(false)
|
||||
e.classList.remove('hide')
|
||||
},
|
||||
{ once: true, passive: true }
|
||||
)
|
||||
e.addEventListener(
|
||||
'error',
|
||||
() => {
|
||||
isLoading.set(false)
|
||||
},
|
||||
{ once: true, passive: true }
|
||||
)
|
||||
} else {
|
||||
e.classList.remove('hide')
|
||||
isLoading.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
function loadLib(): void {
|
||||
loadGsap()
|
||||
.then((g) => {
|
||||
_gsap = g[0]
|
||||
_Power3 = g[1]
|
||||
gsapLoaded = true
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { container } from '../container'
|
||||
import { decIndex, incIndex, state } from '../state'
|
||||
import { decrement, increment } from '../utils'
|
||||
import { decIndex, incIndex, isAnimating, navigateVector, state } from '../globalState'
|
||||
import { decrement, increment } from '../globalUtils'
|
||||
|
||||
import { setCustomCursor } from './customCursor'
|
||||
import { active, cordHist, isAnimating, isOpen, minimizeImage } from './stage'
|
||||
import { minimizeImage } from './stage'
|
||||
import { active, cordHist, isLoading, isOpen } from './state'
|
||||
|
||||
/**
|
||||
* types
|
||||
@@ -15,12 +16,13 @@ type NavItem = (typeof navItems)[number]
|
||||
* variables
|
||||
*/
|
||||
|
||||
const mainDiv = document.getElementById('main') as HTMLDivElement
|
||||
const navItems = [
|
||||
mainDiv.getAttribute('prevText') as string,
|
||||
mainDiv.getAttribute('closeText') as string,
|
||||
mainDiv.getAttribute('nextText') as string
|
||||
container.dataset.prev,
|
||||
container.dataset.close,
|
||||
container.dataset.next
|
||||
] as const
|
||||
const loadingText = container.dataset.loading + '...'
|
||||
let loadedText = ''
|
||||
|
||||
/**
|
||||
* main functions
|
||||
@@ -56,39 +58,80 @@ function handleKey(e: KeyboardEvent): void {
|
||||
*/
|
||||
|
||||
export function initStageNav(): void {
|
||||
// isLoading
|
||||
isLoading.addWatcher((o) => {
|
||||
if (o) setCustomCursor(loadingText)
|
||||
else setCustomCursor(loadedText)
|
||||
})
|
||||
// navOverlay
|
||||
const navOverlay = document.createElement('div')
|
||||
navOverlay.className = 'navOverlay'
|
||||
for (const navItem of navItems) {
|
||||
for (const [index, navItem] of navItems.entries()) {
|
||||
const overlay = document.createElement('div')
|
||||
overlay.className = 'overlay'
|
||||
overlay.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
handleClick(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'keydown',
|
||||
() => {
|
||||
handleClick(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'mouseover',
|
||||
() => {
|
||||
setCustomCursor(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'focus',
|
||||
() => {
|
||||
setCustomCursor(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
const isClose = index === 1
|
||||
// close
|
||||
if (isClose) {
|
||||
overlay.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
handleCloseClick(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'keydown',
|
||||
() => {
|
||||
handleCloseClick(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'mouseover',
|
||||
() => {
|
||||
handleCloseHover(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'focus',
|
||||
() => {
|
||||
handleCloseHover(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
}
|
||||
// prev and next
|
||||
else {
|
||||
overlay.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
handlePNClick(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'keydown',
|
||||
() => {
|
||||
handlePNClick(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'mouseover',
|
||||
() => {
|
||||
handlePNHover(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
overlay.addEventListener(
|
||||
'focus',
|
||||
() => {
|
||||
handlePNHover(navItem)
|
||||
},
|
||||
{ passive: true }
|
||||
)
|
||||
}
|
||||
navOverlay.append(overlay)
|
||||
}
|
||||
active.addWatcher(() => {
|
||||
@@ -108,6 +151,7 @@ export function initStageNav(): void {
|
||||
|
||||
function nextImage(): void {
|
||||
if (isAnimating.get()) return
|
||||
navigateVector.set('next')
|
||||
cordHist.set(
|
||||
cordHist.get().map((item) => {
|
||||
return { ...item, i: increment(item.i, state.get().length) }
|
||||
@@ -119,6 +163,7 @@ function nextImage(): void {
|
||||
|
||||
function prevImage(): void {
|
||||
if (isAnimating.get()) return
|
||||
navigateVector.set('prev')
|
||||
cordHist.set(
|
||||
cordHist.get().map((item) => {
|
||||
return { ...item, i: decrement(item.i, state.get().length) }
|
||||
@@ -127,3 +172,23 @@ function prevImage(): void {
|
||||
|
||||
decIndex()
|
||||
}
|
||||
|
||||
function handleCloseClick(navItem: NavItem): void {
|
||||
handleClick(navItem)
|
||||
isLoading.set(false)
|
||||
}
|
||||
|
||||
function handleCloseHover(navItem: NavItem): void {
|
||||
loadedText = navItem
|
||||
setCustomCursor(navItem)
|
||||
}
|
||||
|
||||
function handlePNClick(navItem: NavItem): void {
|
||||
if (!isLoading.get()) handleClick(navItem)
|
||||
}
|
||||
|
||||
function handlePNHover(navItem: NavItem): void {
|
||||
loadedText = navItem
|
||||
if (isLoading.get()) setCustomCursor(loadingText)
|
||||
else setCustomCursor(navItem)
|
||||
}
|
||||
|
||||
20
assets/ts/desktop/state.ts
Normal file
20
assets/ts/desktop/state.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Watchable } from '../globalUtils'
|
||||
|
||||
/**
|
||||
* types
|
||||
*/
|
||||
|
||||
export interface HistoryItem {
|
||||
i: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
/**
|
||||
* variables
|
||||
*/
|
||||
|
||||
export const cordHist = new Watchable<HistoryItem[]>([])
|
||||
export const isOpen = new Watchable<boolean>(false)
|
||||
export const active = new Watchable<boolean>(false)
|
||||
export const isLoading = new Watchable<boolean>(false)
|
||||
28
assets/ts/desktop/utils.ts
Normal file
28
assets/ts/desktop/utils.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* interfaces
|
||||
*/
|
||||
|
||||
export interface DesktopImage extends HTMLImageElement {
|
||||
dataset: {
|
||||
hiUrl: string
|
||||
hiImgH: string
|
||||
hiImgW: string
|
||||
loUrl: string
|
||||
loImgH: string
|
||||
loImgW: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* utils
|
||||
*/
|
||||
|
||||
export function onMutation<T extends HTMLElement>(
|
||||
element: T,
|
||||
callback: (arg0: MutationRecord[], arg1: MutationObserver) => void,
|
||||
observeOptions: MutationObserverInit = { attributes: true }
|
||||
): void {
|
||||
new MutationObserver((mutations, observer) => {
|
||||
callback(mutations, observer)
|
||||
}).observe(element, observeOptions)
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import { Watchable, decrement, increment } from './utils'
|
||||
import {
|
||||
Watchable,
|
||||
decrement,
|
||||
getThresholdSessionIndex,
|
||||
increment
|
||||
} from './globalUtils'
|
||||
|
||||
/**
|
||||
* types
|
||||
*/
|
||||
|
||||
export type State = typeof defaultState
|
||||
export type NavVec = 'next' | 'none' | 'prev'
|
||||
|
||||
/**
|
||||
* variables
|
||||
@@ -26,6 +32,8 @@ const defaultState = {
|
||||
}
|
||||
|
||||
export const state = new Watchable<State>(defaultState)
|
||||
export const isAnimating = new Watchable<boolean>(false)
|
||||
export const navigateVector = new Watchable<NavVec>('none')
|
||||
|
||||
/**
|
||||
* main functions
|
||||
@@ -81,9 +89,3 @@ function updateThreshold(state: State, inc: number): State {
|
||||
const newItems = thresholds[i]
|
||||
return { ...state, ...newItems }
|
||||
}
|
||||
|
||||
function getThresholdSessionIndex(): number {
|
||||
const s = sessionStorage.getItem('thresholdsIndex')
|
||||
if (s === null) return 2
|
||||
return parseInt(s)
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { type Power3, type gsap } from 'gsap'
|
||||
import { type Swiper } from 'swiper'
|
||||
|
||||
/**
|
||||
* custom helpers
|
||||
* utils
|
||||
*/
|
||||
|
||||
export function increment(num: number, length: number): number {
|
||||
@@ -17,44 +16,24 @@ export function expand(num: number): string {
|
||||
return ('0000' + num.toString()).slice(-4)
|
||||
}
|
||||
|
||||
export function isMobile(): boolean {
|
||||
return window.matchMedia('(hover: none)').matches
|
||||
}
|
||||
|
||||
export function getRandom(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
export function onVisible<T extends Element>(
|
||||
element: T,
|
||||
callback: (arg0: T) => void
|
||||
): void {
|
||||
new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.intersectionRatio > 0) {
|
||||
callback(element)
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
}).observe(element)
|
||||
}
|
||||
|
||||
export function capitalizeFirstLetter(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
export async function loadGsap(): Promise<[typeof gsap, typeof Power3]> {
|
||||
const g = await import('gsap')
|
||||
return [g.gsap, g.Power3]
|
||||
}
|
||||
|
||||
export async function loadSwiper(): Promise<typeof Swiper> {
|
||||
const s = await import('swiper')
|
||||
return s.Swiper
|
||||
export function getThresholdSessionIndex(): number {
|
||||
const s = sessionStorage.getItem('thresholdsIndex')
|
||||
if (s === null) return 2
|
||||
return parseInt(s)
|
||||
}
|
||||
|
||||
export function removeDuplicates<T>(arr: T[]): T[] {
|
||||
if (arr.length < 2) return arr // optimization
|
||||
return [...new Set(arr)]
|
||||
}
|
||||
|
||||
/**
|
||||
* custom types
|
||||
* custom "reactive" object
|
||||
*/
|
||||
|
||||
export class Watchable<T> {
|
||||
@@ -1,19 +1,33 @@
|
||||
import { initContainer } from './container'
|
||||
import { initState } from './globalState'
|
||||
import { initNav } from './nav'
|
||||
import { initResources } from './resources'
|
||||
import { initState } from './state'
|
||||
import { isMobile } from './utils'
|
||||
|
||||
initContainer()
|
||||
const ijs = initResources()
|
||||
initState(ijs.length)
|
||||
initNav()
|
||||
// this is the main entry point for the app
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
main().catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: it seems firefox and chromnium don't like top layer await
|
||||
// so we are using import then instead
|
||||
if (ijs.length > 0) {
|
||||
/**
|
||||
* main functions
|
||||
*/
|
||||
|
||||
async function main(): Promise<void> {
|
||||
initContainer()
|
||||
const ijs = await initResources()
|
||||
initState(ijs.length)
|
||||
initNav()
|
||||
|
||||
if (ijs.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: it seems firefox and chromnium don't like top layer await
|
||||
// so we are using import then instead
|
||||
if (!isMobile()) {
|
||||
import('./desktop/init')
|
||||
await import('./desktop/init')
|
||||
.then((d) => {
|
||||
d.initDesktop(ijs)
|
||||
})
|
||||
@@ -21,7 +35,7 @@ if (ijs.length > 0) {
|
||||
console.log(e)
|
||||
})
|
||||
} else {
|
||||
import('./mobile/init')
|
||||
await import('./mobile/init')
|
||||
.then((m) => {
|
||||
m.initMobile(ijs)
|
||||
})
|
||||
@@ -30,3 +44,11 @@ if (ijs.length > 0) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hepler
|
||||
*/
|
||||
|
||||
function isMobile(): boolean {
|
||||
return window.matchMedia('(hover: none)').matches
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { container } from '../container'
|
||||
import { setIndex } from '../globalState'
|
||||
import { type ImageJSON } from '../resources'
|
||||
import { setIndex } from '../state'
|
||||
import { getRandom, onVisible } from '../utils'
|
||||
|
||||
import { slideUp } from './gallery'
|
||||
import { mounted } from './mounted'
|
||||
import { mounted } from './state'
|
||||
// eslint-disable-next-line sort-imports
|
||||
import { getRandom, onIntersection, type MobileImage } from './utils'
|
||||
|
||||
/**
|
||||
* variables
|
||||
*/
|
||||
|
||||
export let imgs: HTMLImageElement[] = []
|
||||
export let imgs: MobileImage[] = []
|
||||
|
||||
/**
|
||||
* main functions
|
||||
@@ -40,9 +41,14 @@ export function initCollection(ijs: ImageJSON[]): void {
|
||||
}
|
||||
})
|
||||
// get image elements
|
||||
imgs = Array.from(collection.getElementsByTagName('img'))
|
||||
imgs = Array.from(collection.getElementsByTagName('img')) as MobileImage[]
|
||||
// add event listeners
|
||||
imgs.forEach((img, i) => {
|
||||
// preload first 5 images on page load
|
||||
if (i < 5) {
|
||||
img.src = img.dataset.src
|
||||
}
|
||||
// event listeners
|
||||
img.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
@@ -58,12 +64,18 @@ export function initCollection(ijs: ImageJSON[]): void {
|
||||
{ passive: true }
|
||||
)
|
||||
// preload
|
||||
onVisible(img, () => {
|
||||
for (let _i = 0; _i < 5; _i++) {
|
||||
const n = i + _i
|
||||
if (n < 0 || n > imgs.length - 1) continue
|
||||
imgs[n].src = imgs[n].dataset.src as string
|
||||
}
|
||||
onIntersection(img, (entries, observer) => {
|
||||
entries.every((entry) => {
|
||||
// no intersection, skip
|
||||
if (entry.intersectionRatio <= 0) return true
|
||||
// preload the i + 5th image
|
||||
if (i + 5 < imgs.length) {
|
||||
imgs[i + 5].src = imgs[i + 5].dataset.src
|
||||
}
|
||||
// disconnect observer and return false to break the loop
|
||||
observer.disconnect()
|
||||
return false
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -82,7 +94,7 @@ function createCollection(ijs: ImageJSON[]): void {
|
||||
const x = i !== 0 ? getRandom(-25, 25) : 0
|
||||
const y = i !== 0 ? getRandom(-30, 30) : 0
|
||||
// element
|
||||
const e = document.createElement('img')
|
||||
const e = document.createElement('img') as MobileImage
|
||||
e.dataset.src = ij.loUrl
|
||||
e.height = ij.loImgH
|
||||
e.width = ij.loImgW
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { type Power3, type gsap } from 'gsap'
|
||||
import { type Swiper } from 'swiper'
|
||||
|
||||
import { container } from '../container'
|
||||
import { container, scrollable } from '../container'
|
||||
import { isAnimating, navigateVector, setIndex, state } from '../globalState'
|
||||
import { expand, loadGsap, removeDuplicates } from '../globalUtils'
|
||||
import { type ImageJSON } from '../resources'
|
||||
import { setIndex, state } from '../state'
|
||||
import {
|
||||
Watchable,
|
||||
capitalizeFirstLetter,
|
||||
expand,
|
||||
loadGsap,
|
||||
loadSwiper
|
||||
} from '../utils'
|
||||
|
||||
import { mounted } from './mounted'
|
||||
import { scrollable } from './scroll'
|
||||
import { mounted } from './state'
|
||||
// eslint-disable-next-line sort-imports
|
||||
import { capitalizeFirstLetter, loadSwiper, type MobileImage } from './utils'
|
||||
|
||||
/**
|
||||
* variables
|
||||
@@ -23,11 +18,10 @@ let swiperNode: HTMLDivElement
|
||||
let gallery: HTMLDivElement
|
||||
let curtain: HTMLDivElement
|
||||
let swiper: Swiper
|
||||
const isAnimating = new Watchable<boolean>(false)
|
||||
let lastIndex = -1
|
||||
let indexDispNums: HTMLSpanElement[] = []
|
||||
let galleryImages: HTMLImageElement[] = []
|
||||
let collectionImages: HTMLImageElement[] = []
|
||||
let galleryImages: MobileImage[] = []
|
||||
let collectionImages: MobileImage[] = []
|
||||
|
||||
let _Swiper: typeof Swiper
|
||||
let _gsap: typeof gsap
|
||||
@@ -44,7 +38,7 @@ export function slideUp(): void {
|
||||
isAnimating.set(true)
|
||||
|
||||
// load active image
|
||||
loadImages()
|
||||
galleryLoadImages()
|
||||
|
||||
_gsap.to(curtain, {
|
||||
opacity: 1,
|
||||
@@ -61,11 +55,12 @@ export function slideUp(): void {
|
||||
setTimeout(() => {
|
||||
scrollable.set(false)
|
||||
isAnimating.set(false)
|
||||
}, 1200)
|
||||
}, 1400)
|
||||
}
|
||||
|
||||
function slideDown(): void {
|
||||
scrollable.set(true)
|
||||
if (isAnimating.get()) return
|
||||
isAnimating.set(true)
|
||||
scrollToActive()
|
||||
|
||||
_gsap.to(gallery, {
|
||||
@@ -79,6 +74,11 @@ function slideDown(): void {
|
||||
duration: 1.2,
|
||||
delay: 0.4
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
scrollable.set(true)
|
||||
isAnimating.set(false)
|
||||
}, 1600)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,18 +95,22 @@ export function initGallery(ijs: ImageJSON[]): void {
|
||||
swiperNode = document.getElementsByClassName('galleryInner').item(0) as HTMLDivElement
|
||||
gallery = document.getElementsByClassName('gallery').item(0) as HTMLDivElement
|
||||
curtain = document.getElementsByClassName('curtain').item(0) as HTMLDivElement
|
||||
galleryImages = Array.from(gallery.getElementsByTagName('img'))
|
||||
galleryImages = Array.from(gallery.getElementsByTagName('img')) as MobileImage[]
|
||||
collectionImages = Array.from(
|
||||
document
|
||||
.getElementsByClassName('collection')
|
||||
.item(0)
|
||||
?.getElementsByTagName('img') ?? []
|
||||
)
|
||||
) as MobileImage[]
|
||||
// state watcher
|
||||
state.addWatcher(() => {
|
||||
const s = state.get()
|
||||
// change slide only when index is changed
|
||||
if (s.index === lastIndex) return
|
||||
else if (lastIndex === -1)
|
||||
navigateVector.set('none') // lastIndex before first set
|
||||
else if (s.index < lastIndex) navigateVector.set('prev')
|
||||
else navigateVector.set('next')
|
||||
changeSlide(s.index)
|
||||
updateIndexText()
|
||||
lastIndex = s.index
|
||||
@@ -152,7 +156,7 @@ export function initGallery(ijs: ImageJSON[]): void {
|
||||
*/
|
||||
|
||||
function changeSlide(slide: number): void {
|
||||
loadImages()
|
||||
galleryLoadImages()
|
||||
swiper.slideTo(slide, 0)
|
||||
}
|
||||
|
||||
@@ -175,6 +179,29 @@ function updateIndexText(): void {
|
||||
})
|
||||
}
|
||||
|
||||
function galleryLoadImages(): void {
|
||||
let activeImagesIndex: number[] = []
|
||||
const currentIndex = state.get().index
|
||||
const nextIndex = Math.min(currentIndex + 1, state.get().length - 1)
|
||||
const prevIndex = Math.max(currentIndex - 1, 0)
|
||||
switch (navigateVector.get()) {
|
||||
case 'next':
|
||||
activeImagesIndex = [nextIndex]
|
||||
break
|
||||
case 'prev':
|
||||
activeImagesIndex = [prevIndex]
|
||||
break
|
||||
case 'none':
|
||||
activeImagesIndex = [currentIndex, nextIndex, prevIndex]
|
||||
break
|
||||
}
|
||||
removeDuplicates(activeImagesIndex).forEach((i) => {
|
||||
const e = galleryImages[i]
|
||||
if (e.src === e.dataset.src) return // already loaded
|
||||
e.src = e.dataset.src
|
||||
})
|
||||
}
|
||||
|
||||
function createGallery(ijs: ImageJSON[]): void {
|
||||
/**
|
||||
* gallery
|
||||
@@ -192,18 +219,39 @@ function createGallery(ijs: ImageJSON[]): void {
|
||||
// swiper wrapper
|
||||
const _swiperWrapper = document.createElement('div')
|
||||
_swiperWrapper.className = 'swiper-wrapper'
|
||||
// swiper slide
|
||||
// loading text
|
||||
const loadingText = container.dataset.loading
|
||||
for (const ij of ijs) {
|
||||
// swiper slide
|
||||
const _swiperSlide = document.createElement('div')
|
||||
_swiperSlide.className = 'swiper-slide'
|
||||
// loading indicator
|
||||
const l = document.createElement('div')
|
||||
l.className = 'loadingText'
|
||||
l.innerText = loadingText
|
||||
// img
|
||||
const e = document.createElement('img')
|
||||
const e = document.createElement('img') as MobileImage
|
||||
e.dataset.src = ij.hiUrl
|
||||
e.height = ij.hiImgH
|
||||
e.width = ij.hiImgW
|
||||
e.alt = ij.alt
|
||||
e.classList.add('hide')
|
||||
// load event
|
||||
e.addEventListener(
|
||||
'load',
|
||||
() => {
|
||||
e.classList.remove('hide')
|
||||
l.classList.add('hide')
|
||||
},
|
||||
{ once: true, passive: true }
|
||||
)
|
||||
// parent container
|
||||
const p = document.createElement('div')
|
||||
p.className = 'slideContainer'
|
||||
// append
|
||||
_swiperSlide.append(e)
|
||||
p.append(e)
|
||||
p.append(l)
|
||||
_swiperSlide.append(p)
|
||||
_swiperWrapper.append(_swiperSlide)
|
||||
}
|
||||
// swiper node
|
||||
@@ -220,10 +268,7 @@ function createGallery(ijs: ImageJSON[]): void {
|
||||
)
|
||||
// close
|
||||
const _close = document.createElement('div')
|
||||
const str: string = document
|
||||
.getElementById('main')
|
||||
?.getAttribute('closeText') as string
|
||||
_close.innerText = capitalizeFirstLetter(str)
|
||||
_close.innerText = capitalizeFirstLetter(container.dataset.close)
|
||||
_close.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
@@ -261,16 +306,3 @@ function createGallery(ijs: ImageJSON[]): void {
|
||||
*/
|
||||
container.append(_gallery, _curtain)
|
||||
}
|
||||
|
||||
function loadImages(): void {
|
||||
const activeImages: HTMLImageElement[] = []
|
||||
// load current, next, prev image
|
||||
activeImages.push(galleryImages[swiper.activeIndex])
|
||||
activeImages.push(
|
||||
galleryImages[Math.min(swiper.activeIndex + 1, swiper.slides.length)]
|
||||
)
|
||||
activeImages.push(galleryImages[Math.max(swiper.activeIndex - 1, 0)])
|
||||
for (const e of activeImages) {
|
||||
e.src = e.dataset.src as string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { Watchable } from '../utils'
|
||||
|
||||
export const scrollable = new Watchable<boolean>(true)
|
||||
@@ -1,3 +1,3 @@
|
||||
import { Watchable } from '../utils'
|
||||
import { Watchable } from '../globalUtils'
|
||||
|
||||
export const mounted = new Watchable<boolean>(false)
|
||||
37
assets/ts/mobile/utils.ts
Normal file
37
assets/ts/mobile/utils.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { type Swiper } from 'swiper'
|
||||
|
||||
/**
|
||||
* interfaces
|
||||
*/
|
||||
|
||||
export interface MobileImage extends HTMLImageElement {
|
||||
dataset: {
|
||||
src: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* utils
|
||||
*/
|
||||
|
||||
export function getRandom(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
export function onIntersection<T extends HTMLElement>(
|
||||
element: T,
|
||||
callback: (arg0: IntersectionObserverEntry[], arg1: IntersectionObserver) => void
|
||||
): void {
|
||||
new IntersectionObserver((entries, observer) => {
|
||||
callback(entries, observer)
|
||||
}).observe(element)
|
||||
}
|
||||
|
||||
export function capitalizeFirstLetter(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
export async function loadSwiper(): Promise<typeof Swiper> {
|
||||
const s = await import('swiper')
|
||||
return s.Swiper
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { decThreshold, incThreshold, state } from './state'
|
||||
import { expand } from './utils'
|
||||
import { decThreshold, incThreshold, state } from './globalState'
|
||||
import { expand } from './globalUtils'
|
||||
|
||||
/**
|
||||
* variables
|
||||
@@ -31,37 +31,11 @@ const indexDispNums = Array.from(
|
||||
indexDiv.getElementsByClassName('num')
|
||||
) as HTMLSpanElement[]
|
||||
|
||||
// links div
|
||||
const linksDiv = document.getElementsByClassName('links').item(0) as HTMLDivElement
|
||||
|
||||
// links
|
||||
const links = Array.from(linksDiv.getElementsByClassName('link')) as HTMLAnchorElement[]
|
||||
|
||||
// current link index
|
||||
const currentLinkIndex = document
|
||||
.getElementById('main')
|
||||
?.getAttribute('currentMenuItemIndex') as string
|
||||
|
||||
// set current link
|
||||
for (const [index, link] of links.entries()) {
|
||||
if (index === parseInt(currentLinkIndex)) {
|
||||
// set current link style
|
||||
link.classList.add('current')
|
||||
// set current link title (only if not home)
|
||||
if (index !== 0) document.title = link.innerText + ' | ' + document.title
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* init
|
||||
*/
|
||||
|
||||
export function initNav(): void {
|
||||
const s = state.get()
|
||||
// init threshold text
|
||||
updateThresholdText(expand(s.threshold))
|
||||
// init index text
|
||||
updateIndexText(expand(s.index + 1), expand(s.length))
|
||||
// add watcher for updating nav text
|
||||
state.addWatcher((o) => {
|
||||
updateIndexText(expand(o.index + 1), expand(o.length))
|
||||
|
||||
@@ -10,17 +10,21 @@ export interface ImageJSON {
|
||||
hiImgW: number
|
||||
}
|
||||
|
||||
export function initResources(): ImageJSON[] {
|
||||
const imagesJson = document.getElementById('imagesSource')
|
||||
if (imagesJson === null) {
|
||||
return []
|
||||
}
|
||||
return JSON.parse(imagesJson.textContent as string).sort(
|
||||
(a: ImageJSON, b: ImageJSON) => {
|
||||
export async function initResources(): Promise<ImageJSON[]> {
|
||||
try {
|
||||
const response = await fetch(`${window.location.href}index.json`, {
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
}
|
||||
})
|
||||
const data: ImageJSON[] = await response.json()
|
||||
return data.sort((a: ImageJSON, b: ImageJSON) => {
|
||||
if (a.index < b.index) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
)
|
||||
})
|
||||
} catch (_) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ The minimum required Hugo version can be seen in the [`theme.toml`](https://gith
|
||||
|
||||
## Installation
|
||||
|
||||
### Git
|
||||
### 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:
|
||||
|
||||
@@ -36,9 +36,13 @@ If you are already using Git for your site, you can add the theme as a submodule
|
||||
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 modify the theme, use Git installation instead.
|
||||
> 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:
|
||||
|
||||
@@ -46,7 +50,7 @@ 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 link.
|
||||
# or whatever you like, it doesn’t necessarily have to be a GitHub repo link.
|
||||
hugo mod init blablabla
|
||||
```
|
||||
|
||||
@@ -65,6 +69,10 @@ If you want to upgrade the theme, just run:
|
||||
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.
|
||||
@@ -155,8 +163,12 @@ replacements = "github.com/Sped0n/bridget -> ../.."
|
||||
path = "github.com/Sped0n/bridget"
|
||||
```
|
||||
|
||||
- If you want to <u>modify the theme</u> or you have <u>installation with Git</u>, please keep the `replacements` configuration and change the path after the arrow to the location of your local theme file.
|
||||
- If you have <u>installation with Module</u>, remove the `replacements` configuration.
|
||||
- If you have <u>installation with Git</u>
|
||||
|
||||
- `replacement`: replace the <u>path after the arrow</u>(`../..`) with the location of your local theme file (⚠️⚠️⚠️**relative path only**, example: `themes/bridget`)
|
||||
- `path`: no change
|
||||
|
||||
- If you have <u>installation with Module</u>, **remove the `replacements` configuration**.
|
||||
|
||||
### `markup.toml`
|
||||
|
||||
@@ -164,8 +176,40 @@ path = "github.com/Sped0n/bridget"
|
||||
|
||||
### `params.toml`
|
||||
|
||||
There is a detailed description in the comments.
|
||||
Detailed description in the comments.
|
||||
|
||||
> ⚠️⚠️⚠️
|
||||
>
|
||||
> Only thing that you need to pay **extra attention** is the [`bundled`](https://github.com/Sped0n/bridget/blob/1e2f1fadde9c16989eef1ab771f2ac8463dec5a4/exampleSite/config/_default/params.toml#L6) option, please read the corresponding doc and set it as your need.
|
||||
>
|
||||
> For users who have installation with module, please always set this option to `true`, unless you know what you are doing.
|
||||
>
|
||||
> Or you might get the error related to `node_modules/swiper/swiper.scss`.
|
||||
|
||||
### `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**.
|
||||
|
||||
### Option 1: _it just works_ way
|
||||
|
||||
> If you want to modify js/ts file, please use option 2.
|
||||
|
||||
1. Use hugo create a site and move the bridget theme into the theme directory.
|
||||
2. Run `npm install` in the <u>bridget theme root dir</u>, not <u>your hugo site root dir</u>.
|
||||
3. After the command is done, copy the `node_modules` dir from <u>bridget theme root dir</u> to <u>your hugo site root dir</u>.
|
||||
4. In <u>your hugo site root dir</u>, write/modify configuration files according to your needs, remember to set `bundled` option to `false`, so hugo will not use prebuilt css file.
|
||||
5. Run `hugo server` in <u>your hugo site root dir</u>, and you are good to go.
|
||||
|
||||
### Option 2: recommended way
|
||||
|
||||
1. Use hugo create a site and move the bridget theme into the theme directory.
|
||||
2. Run `npm install` in the <u>bridget theme root dir</u>, not <u>your hugo site root dir</u>.
|
||||
3. Run `npm run dev` in the <u>bridget theme root dir</u>, we will use content in exampleSite to debug.
|
||||
4. Make your customization.
|
||||
5. After modification, run `npm run build` in the <u>bridget theme root dir</u> to build artifacts.
|
||||
6. In <u>your hugo site root dir</u>, write/modify configuration files according to your needs, remember to set `bundled` option to `true`, so hugo will use the artifacts you built in step 5.
|
||||
7. Run `hugo server` in <u>your hugo site root dir</u>, and you are good to go.
|
||||
|
||||
1
exampleSite/config/_default/outputs.toml
Normal file
1
exampleSite/config/_default/outputs.toml
Normal file
@@ -0,0 +1 @@
|
||||
page = ["HTML", "JSON"]
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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."
|
||||
# use bundled js and css
|
||||
# * if you want to build the js and css from scratch, set this to false and run `npm install` and `npm run build`
|
||||
# * tldr: set this to false if you want to develop and edit the js and css
|
||||
@@ -13,6 +13,10 @@ svgFavicon = "/dot.svg"
|
||||
# fallback png favicon for unsupported browsers
|
||||
svgFaviconFallback = "/dot.png"
|
||||
|
||||
# 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"
|
||||
|
||||
# page config
|
||||
[page]
|
||||
# unified alt text for all images in the page
|
||||
|
||||
@@ -10,16 +10,14 @@ menu:
|
||||
unifiedAlt: ''
|
||||
---
|
||||
|
||||
Bridget is a _minimal_ Hugo theme designed for photographers / visual artists.
|
||||
Bridget is a _minimal_ Hugo theme designed for photographers/visual artists.
|
||||
|
||||
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 designed using pure TypeScript and CSS. However, after website designer <u>[Tyler McRobert](https://tylermcrobert.com)</u> made the source code publicly available, the whole project was modified to mimic the original design. The animations in Bridget are achieved using gsap and swiper. In essence, it is more like a porting of the original design into Hugo.
|
||||
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 raw TypeScript and CSS. 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 components, 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.
|
||||
|
||||
[Github ↗](https://instagram.com/pictures.bridget)
|
||||
|
||||
[Instagram ↗](https://www.instagram.com/sped0n/)
|
||||
[Repo ↗](https://github.com/Sped0n/bridget)
|
||||
|
||||
Original site design by <u>[Tyler McRobert](https://tylermcrobert.com)</u>.
|
||||
|
||||
© {{< year >}} Spedon | Powered by [Hugo](https://gohugo.io)
|
||||
© {{< year >}} <u>[Spedon](https://github.com/Sped0n)</u> | Powered by [Hugo](https://gohugo.io)
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "schließen"
|
||||
other = "schwelle"
|
||||
[404]
|
||||
other = "seite nicht gefunden"
|
||||
[loading]
|
||||
other = "lade"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "close"
|
||||
other = "threshold"
|
||||
[404]
|
||||
other = "page not found"
|
||||
[loading]
|
||||
other = "loading"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "cerrar"
|
||||
other = "umbral"
|
||||
[404]
|
||||
other = "página no encontrada"
|
||||
[loading]
|
||||
other = "cargando"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "fermer"
|
||||
other = "seuil"
|
||||
[404]
|
||||
other = "page non trouvée"
|
||||
[loading]
|
||||
other = "chargement"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "chiudi"
|
||||
other = "soglia"
|
||||
[404]
|
||||
other = "pagina non trovata"
|
||||
[loading]
|
||||
other = "caricamento"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "閉じる"
|
||||
other = "しきい値"
|
||||
[404]
|
||||
other = "ページが見つかりません"
|
||||
[loading]
|
||||
other = "読み込み中"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "닫기"
|
||||
other = "임계값"
|
||||
[404]
|
||||
other = "페이지를 찾을 수 없습니다"
|
||||
[loading]
|
||||
other = "로딩중"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "关闭"
|
||||
other = "阈值"
|
||||
[404]
|
||||
other = "页面不存在"
|
||||
[loading]
|
||||
other = "加载中"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "關閉"
|
||||
other = "閾值"
|
||||
[404]
|
||||
other = "找不到頁面"
|
||||
[loading]
|
||||
other = "載入中"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "關閉"
|
||||
other = "閾值"
|
||||
[404]
|
||||
other = "找不到頁面"
|
||||
[loading]
|
||||
other = "載入中"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "关闭"
|
||||
other = "阈值"
|
||||
[404]
|
||||
other = "页面不存在"
|
||||
[loading]
|
||||
other = "加载中"
|
||||
|
||||
@@ -8,3 +8,5 @@ other = "關閉"
|
||||
other = "閾值"
|
||||
[404]
|
||||
other = "找不到頁面"
|
||||
[loading]
|
||||
other = "載入中"
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
{{- define "main" -}}
|
||||
{{- $params := .Scratch.Get "params" -}}
|
||||
{{- $currentPage := . -}}
|
||||
{{- with partial "function/currentMenuItem.html" . -}}
|
||||
{{- partial "resources/imageJSON.html" (dict "Path" .DirName "Page" $currentPage) -}}
|
||||
<script>document.getElementById("main").setAttribute("currentMenuItemIndex", "{{- (sub .ID 1) -}}")</script>
|
||||
{{- end -}}
|
||||
<div class="container">
|
||||
{{- partial "nav.html" . -}}
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{ site.Title }}</title>
|
||||
{{- partial "head/link.html" -}}
|
||||
{{- partial "head/meta.html" -}}
|
||||
{{- partial "head/seo.html" -}}
|
||||
{{- partial "head/favicon.html" -}}
|
||||
{{- partial "head/link.html" . -}}
|
||||
{{- partial "head/meta.html" . -}}
|
||||
{{- partial "head/seo.html" . -}}
|
||||
{{- partial "head/favicon.html" . -}}
|
||||
</head>
|
||||
<body lang="{{- site.LanguageCode -}}">
|
||||
<div id="main">
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
{{- define "main" -}}
|
||||
{{- $params := .Scratch.Get "params" -}}
|
||||
{{- $currentPage := . -}}
|
||||
{{- with partial "function/currentMenuItem.html" . -}}
|
||||
{{- partial "resources/imageJSON.html" (dict "Path" .DirName "Page" $currentPage) -}}
|
||||
<script>
|
||||
document.getElementById("main").setAttribute("currentMenuItemIndex", "{{- (sub .ID 1) -}}")
|
||||
document.getElementById("main").setAttribute("nextText", "{{- i18n "next" -}}")
|
||||
document.getElementById("main").setAttribute("prevText", "{{- i18n "prev" -}}")
|
||||
document.getElementById("main").setAttribute("closeText", "{{- i18n "close" -}}")
|
||||
</script>
|
||||
{{- end -}}
|
||||
<div class="container">
|
||||
<div
|
||||
class="container"
|
||||
data-next="{{- i18n "next" -}}"
|
||||
data-prev="{{- i18n "prev" -}}"
|
||||
data-close="{{- i18n "close" -}}"
|
||||
data-loading="{{- i18n "loading" -}}"
|
||||
>
|
||||
{{- partial "nav.html" . -}}
|
||||
{{- with .Content -}}
|
||||
<article class="info">
|
||||
{{- . -}}
|
||||
</article>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<article class="info">
|
||||
{{ .Content }}
|
||||
</article>
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{{- $Page := .Page -}}
|
||||
{{- $context := . -}}
|
||||
{{- $params := .Page.Params | merge .Site.Params.Page -}}
|
||||
|
||||
{{- $gallery := site.GetPage .Path -}}
|
||||
{{- with $gallery.Resources.ByType "image" -}}
|
||||
{{- with partial "function/getImageSlice.html" . -}}
|
||||
{{- $index := len . -}}
|
||||
{{- $Page.Scratch.Add "img" slice -}}
|
||||
{{- $context.Scratch.Add "img" slice -}}
|
||||
{{- range sort . "Name" "desc" -}}
|
||||
{{- $image := . -}}
|
||||
{{- $index = sub $index 1 -}}
|
||||
@@ -12,9 +11,9 @@
|
||||
{{- with $params.unifiedAlt -}}
|
||||
{{- $alt = . -}}
|
||||
{{- end -}}
|
||||
{{- $lores := .Resize "800x webp Lanczos q60" -}}
|
||||
{{- $hires := .Resize "2500x webp Lanczos q75" -}}
|
||||
{{- $Page.Scratch.Add "img" (dict
|
||||
{{- $lores := .Resize (site.Params.loResOpt | default "700x webp Lanczos q60") -}}
|
||||
{{- $hires := .Resize (site.Params.hiResOpt | default "2000x webp Lanczos q75") -}}
|
||||
{{- $context.Scratch.Add "img" (dict
|
||||
"index" (int $index)
|
||||
"alt" (string $alt)
|
||||
"loUrl" (string $lores.RelPermalink)
|
||||
@@ -26,5 +25,7 @@
|
||||
)
|
||||
-}}
|
||||
{{- end -}}
|
||||
<script id="imagesSource" type="application/json">{{ $Page.Scratch.Get "img" | jsonify | safeJS }}</script>
|
||||
{{ $context.Scratch.Get "img" | jsonify }}
|
||||
{{- else -}}
|
||||
[]
|
||||
{{- end -}}
|
||||
@@ -1,15 +1,17 @@
|
||||
{{- $currentPage := . -}}
|
||||
|
||||
{{- $dirName := "" -}}
|
||||
{{- $id := -1 -}}
|
||||
{{- $identifier := "" -}}
|
||||
{{- $title := "" -}}
|
||||
{{- $weight := -1 -}}
|
||||
|
||||
{{- range site.Menus.main -}}
|
||||
{{ $menu_item_url := .URL | relLangURL }}
|
||||
{{ $page_url:= $currentPage.RelPermalink | relLangURL }}
|
||||
{{ if eq $menu_item_url $page_url }}
|
||||
{{- $dirName = .Identifier -}}
|
||||
{{- $id = .Weight -}}
|
||||
{{- $identifier = .Identifier -}}
|
||||
{{- $title = .Title -}}
|
||||
{{- $weight = .Weight -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- return (dict "DirName" $dirName "ID" $id) -}}
|
||||
{{- return (dict "Identifier" $identifier "Title" $title "Weight" $weight) -}}
|
||||
|
||||
10
layouts/partials/function/getImageSlice.html
Normal file
10
layouts/partials/function/getImageSlice.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{- $context := . -}}
|
||||
{{- $Path := "" -}}
|
||||
{{- $params := .Page.Params | merge .Site.Params.Page -}}
|
||||
|
||||
{{- with partial "function/currentMenuItem.html" . -}}
|
||||
{{- $Path = .Identifier -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $gallery := site.GetPage $Path -}}
|
||||
{{- return $gallery.Resources.ByType "image" -}}
|
||||
@@ -8,7 +8,7 @@
|
||||
{{- partial "plugin/style.html" $style -}}
|
||||
|
||||
{{/* main style */}}
|
||||
{{- if site.Params.bundled -}}
|
||||
{{- if (site.Params.bundled | default true) -}}
|
||||
{{- $style := dict "Link" "/bundled/css/style.min.css" "Defer" true -}}
|
||||
{{- partial "plugin/style.html" $style -}}
|
||||
{{- else -}}
|
||||
@@ -18,15 +18,26 @@
|
||||
{{- partial "plugin/style.html" $style -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* fuck safari */}}
|
||||
<script>
|
||||
function z() {
|
||||
const r = document.querySelector(':root')
|
||||
r.style.setProperty('--window-height', `${window.innerHeight}px`)
|
||||
}
|
||||
z()
|
||||
window.addEventListener('resize', z, { passive: true })
|
||||
</script>
|
||||
|
||||
{{/* main js */}}
|
||||
{{- $script := dict "Link" "/bundled/js/main.js" "Defer" true "Esm" true -}}
|
||||
{{- partial "plugin/script.html" $script -}}
|
||||
|
||||
{{/* fonts */}}
|
||||
<link rel="preload" href="/lib/fonts/NotoSans-Regular.woff2" as="font" crossorigin />
|
||||
<link rel="preload" href="/lib/fonts/fw.woff2" as="font" crossorigin />
|
||||
{{- if (partial "function/langCode.html" (slice "en" "de" "fr" "es" "it")) -}}
|
||||
<link rel="preload" href="/lib/fonts/GeistVF.woff2" as="font" crossorigin />
|
||||
{{- else if (partial "function/langCode.html" (slice "zh-cn" "zh-sg")) -}}
|
||||
<link rel="preload" href="/lib/fonts/NotoSans-Regular.woff2" as="font" crossorigin />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/lib/fonts/NotoSansCJKsc-Regular.woff2"
|
||||
@@ -34,6 +45,7 @@
|
||||
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"
|
||||
@@ -41,6 +53,7 @@
|
||||
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"
|
||||
@@ -48,6 +61,7 @@
|
||||
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"
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
<meta name="Description" content="{{- site.Params.description -}}" />
|
||||
<meta
|
||||
name="application-name"
|
||||
content="{{- .Site.Params.app.title | default site.Title -}}"
|
||||
/>
|
||||
<meta
|
||||
name="apple-mobile-web-app-title"
|
||||
content="{{- .Site.Params.app.title | default site.Title -}}"
|
||||
/>
|
||||
{{/* Title */}}
|
||||
{{- $page_title := "" -}}
|
||||
{{- with partial "function/currentMenuItem.html" . -}}
|
||||
{{ $page_title = printf "%s" site.Title | printf "%s%s" " | " | printf "%s%s" .Title | printf "%s" }}
|
||||
{{- end -}}
|
||||
<title>{{ $page_title }}</title>
|
||||
|
||||
{{/* Basic */}}
|
||||
<meta name="Description" content="{{ site.Params.description }}" />
|
||||
<meta name="application-name" content="{{ $page_title }}" />
|
||||
<meta name="apple-mobile-web-app-title" content="{{ $page_title }}" />
|
||||
|
||||
{{/* Opengraph */}}
|
||||
<meta property="og:title" content="{{ $page_title }}" />
|
||||
<meta name="twitter:title" content="{{ $page_title }}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="{{ .Permalink }}" />
|
||||
<meta property="og:description" content="{{ site.Params.description }}" />
|
||||
<meta name="twitter:description" content="{{ site.Params.description }}" />
|
||||
|
||||
{{/* Generator */}}
|
||||
{{ hugo.Generator }}
|
||||
|
||||
@@ -4,19 +4,39 @@
|
||||
</div>
|
||||
<div class="links">
|
||||
{{- $index := 0 -}}
|
||||
{{- $currentIndex := -1 -}}
|
||||
{{- with partial "function/currentMenuItem.html" . -}}
|
||||
{{- $currentIndex = sub .Weight 1 -}}
|
||||
{{- end -}}
|
||||
{{- $menus := .Site.Menus.main -}}
|
||||
{{- $len := len $menus }}
|
||||
{{- range $menus -}}
|
||||
{{- $url := .URL | relURL -}}
|
||||
{{- if eq (add $index 1) $len -}}
|
||||
<a href="{{- .URL | relURL -}}" class="link">{{- .Title -}}</a>
|
||||
<a
|
||||
href="{{- .URL | relURL -}}"
|
||||
class="link{{- if eq $index $currentIndex -}}
|
||||
{{- printf " current" -}}
|
||||
{{- end -}}"
|
||||
>{{- .Title -}}</a
|
||||
>
|
||||
{{- else -}}
|
||||
<a href="{{- .URL | relURL -}}" class="link">{{- .Title -}}</a>, 
|
||||
<a
|
||||
href="{{- .URL | relURL -}}"
|
||||
class="link{{- if eq $index $currentIndex -}}
|
||||
{{- printf " current" -}}
|
||||
{{- end -}}"
|
||||
>{{- .Title -}}</a
|
||||
>, 
|
||||
{{- end -}}
|
||||
{{- $index = add $index 1 -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
<div class="threshold">
|
||||
{{- $length := 0 -}}
|
||||
{{- with partial "function/getImageSlice.html" . -}}
|
||||
{{- $length = len . -}}
|
||||
{{- end -}}
|
||||
<span>{{- i18n "threshold" | strings.FirstUpper -}}:</span>
|
||||
<span>
|
||||
<button class="dec">-</button>
|
||||
@@ -26,10 +46,43 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="index">
|
||||
<span class="num"></span><span class="num"></span><span class="num"></span
|
||||
><span class="num"></span>
|
||||
<span class="num">0</span><span class="num">0</span><span class="num">0</span
|
||||
><span class="num">0</span>
|
||||
<span>/</span>
|
||||
<span class="num"></span><span class="num"></span><span class="num"></span
|
||||
><span class="num"></span>
|
||||
<span class="num">{{- math.Floor (div $length 1000) -}}</span
|
||||
><span class="num">{{- mod (math.Floor (div $length 100)) 10 -}}</span
|
||||
><span class="num">{{- mod (math.Floor (div $length 10)) 10 -}}</span
|
||||
><span class="num">{{- mod $length 10 -}}</span>
|
||||
</div>
|
||||
</nav>
|
||||
{{- /* for threshold */ -}}
|
||||
<script>
|
||||
i = 2
|
||||
const s = sessionStorage.getItem('thresholdsIndex')
|
||||
if (s !== null) {
|
||||
i = parseInt(s)
|
||||
}
|
||||
var t = ''
|
||||
switch (i) {
|
||||
case 0:
|
||||
t = '0020'
|
||||
break
|
||||
case 1:
|
||||
t = '0040'
|
||||
break
|
||||
case 2:
|
||||
t = '0080'
|
||||
break
|
||||
case 3:
|
||||
t = '0140'
|
||||
break
|
||||
case 4:
|
||||
t = '0200'
|
||||
break
|
||||
}
|
||||
Array.from(
|
||||
document.getElementsByClassName('threshold').item(0).getElementsByClassName('num')
|
||||
).forEach((e, i) => {
|
||||
e.innerText = t[i]
|
||||
})
|
||||
</script>
|
||||
|
||||
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bridget",
|
||||
"version": "0.0.1",
|
||||
"version": "v1.0.0",
|
||||
"description": "bridget theme source file",
|
||||
"packageManager": "pnpm@8.10.2",
|
||||
"private": true,
|
||||
@@ -9,7 +9,7 @@
|
||||
"lint": "eslint . --fix && prettier --write .",
|
||||
"lint:check": "eslint . && prettier . --check",
|
||||
"dev": "run-p rollup:dev hugo:dev",
|
||||
"build": "rm -f ./static/bundled/js/* && run-s rollup:build hugo:build && yes | cp -rf ./exampleSite/public/css/* ./static/bundled/css",
|
||||
"build": "rm -f ./static/bundled/js/* && run-s rollup:build hugo:build && yes | cp -rf ./exampleSite/public/css/*.css ./static/bundled/css",
|
||||
"server": "run-p rollup:server hugo:server",
|
||||
"rollup:build": "rollup -c --environment BUILD:production",
|
||||
"rollup:server": "rollup -c --watch --environment BUILD:production",
|
||||
@@ -37,29 +37,29 @@
|
||||
},
|
||||
"homepage": "https://github.com/Sped0n/bridget#readme",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.9.0",
|
||||
"@typescript-eslint/parser": "^6.9.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-config-standard-with-typescript": "^39.1.1",
|
||||
"eslint-config-standard-with-typescript": "^43.0.1",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-n": "^16.2.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-n": "^16.6.2",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "3.0.3",
|
||||
"prettier": "3.2.4",
|
||||
"prettier-plugin-go-template": "^0.0.15",
|
||||
"prettier-plugin-organize-imports": "^3.2.3",
|
||||
"rollup": "^4.3.0",
|
||||
"typescript": "^5.2.2"
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"gsap": "^3.12.2",
|
||||
"swiper": "^11.0.3"
|
||||
"gsap": "^3.12.5",
|
||||
"swiper": "^11.0.5",
|
||||
"rollup": "^4.9.6",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
846
pnpm-lock.yaml
generated
846
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
2
static/bundled/css/style.min.css
vendored
2
static/bundled/css/style.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/bundled/js/EY5BO_.js
Normal file
1
static/bundled/js/EY5BO_.js
Normal file
File diff suppressed because one or more lines are too long
1
static/bundled/js/Ej2jsy.js
Normal file
1
static/bundled/js/Ej2jsy.js
Normal file
@@ -0,0 +1 @@
|
||||
import{W as e,s as t,n,f as a,l as s,g as c,h as o,r as i,c as r,i as d}from"./main.js";const l=new e(!1);function m(e,t){return Math.floor(Math.random()*(t-e+1))+e}let p,u,g,h,v,E,y,f=-1,N=[],w=[],x=[],L=!1;function B(){d.get()||(d.set(!0),x[t.get().index].scrollIntoView({block:"center",behavior:"auto"}),E.to(u,{y:"100%",ease:y.easeInOut,duration:1}),E.to(g,{opacity:0,duration:1.2,delay:.4}),setTimeout((()=>{a.set(!0),d.set(!1)}),1600))}function I(){let e=[];const a=t.get().index,s=Math.min(a+1,t.get().length-1),c=Math.max(a-1,0);switch(n.get()){case"next":e=[s];break;case"prev":e=[c];break;case"none":e=[a,s,c]}i(e).forEach((e=>{const t=w[e];t.src!==t.dataset.src&&(t.src=t.dataset.src)}))}let T=[];function C(e){c(e),!d.get()&&L&&(d.set(!0),I(),E.to(g,{opacity:1,duration:1}),E.to(u,{y:0,ease:y.easeInOut,duration:1,delay:.4}),setTimeout((()=>{a.set(!1),d.set(!1)}),1400))}function b(e){(function(e){!function(e){const t=document.createElement("div");t.className="collection";for(const[n,a]of e.entries()){const e=0!==n?m(-25,25):0,s=0!==n?m(-30,30):0,c=document.createElement("img");c.dataset.src=a.loUrl,c.height=a.loImgH,c.width=a.loImgW,c.alt=a.alt,c.style.transform=`translate3d(${e}%, ${s-50}%, 0)`,t.append(c)}r.append(t)}(e);const t=document.getElementsByClassName("collection").item(0);l.addWatcher((e=>{e?t.classList.remove("hidden"):t.classList.add("hidden")})),T=Array.from(t.getElementsByTagName("img")),T.forEach(((e,t)=>{var n,a;t<5&&(e.src=e.dataset.src),e.addEventListener("click",(()=>{C(t)}),{passive:!0}),e.addEventListener("keydown",(()=>{C(t)}),{passive:!0}),n=e,a=(e,n)=>{e.every((e=>e.intersectionRatio<=0||(t+5<T.length&&(T[t+5].src=T[t+5].dataset.src),n.disconnect(),!1)))},new IntersectionObserver(((e,t)=>{a(e,t)})).observe(n)}))})(e),function(e){!function(e){const t=document.createElement("div");t.className="swiper-wrapper";const n=r.dataset.loading;for(const a of e){const e=document.createElement("div");e.className="swiper-slide";const s=document.createElement("div");s.className="loadingText",s.innerText=n;const c=document.createElement("img");c.dataset.src=a.hiUrl,c.height=a.hiImgH,c.width=a.hiImgW,c.alt=a.alt,c.classList.add("hide"),c.addEventListener("load",(()=>{c.classList.remove("hide"),s.classList.add("hide")}),{once:!0,passive:!0});const o=document.createElement("div");o.className="slideContainer",o.append(c),o.append(s),e.append(o),t.append(e)}const a=document.createElement("div");a.className="galleryInner",a.append(t);const s=document.createElement("div");s.insertAdjacentHTML("afterbegin",'<span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>\n <span>/</span>\n <span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>');const c=document.createElement("div");var o;c.innerText=(o=r.dataset.close).charAt(0).toUpperCase()+o.slice(1),c.addEventListener("click",(()=>{B()}),{passive:!0}),c.addEventListener("keydown",(()=>{B()}),{passive:!0});const i=document.createElement("div");i.className="nav",i.append(s,c);const d=document.createElement("div");d.className="gallery",d.append(a),d.append(i);const l=document.createElement("div");l.className="curtain",r.append(d,l)}(e),N=Array.from(document.getElementsByClassName("nav").item(0)?.getElementsByClassName("num")??[]),p=document.getElementsByClassName("galleryInner").item(0),u=document.getElementsByClassName("gallery").item(0),g=document.getElementsByClassName("curtain").item(0),w=Array.from(u.getElementsByTagName("img")),x=Array.from(document.getElementsByClassName("collection").item(0)?.getElementsByTagName("img")??[]),t.addWatcher((()=>{const e=t.get();var a;e.index!==f&&(-1===f?n.set("none"):e.index<f?n.set("prev"):n.set("next"),a=e.index,I(),h.slideTo(a,0),function(){const e=o(t.get().index+1),n=o(t.get().length);N.forEach(((t,a)=>{t.innerText=a<4?e[a]:n[a-4]}))}(),f=e.index)})),l.addWatcher((e=>{e&&a.set(!0)})),window.addEventListener("touchstart",(()=>{s().then((e=>{E=e[0],y=e[1]})).catch((e=>{console.log(e)})),async function(){return(await import("./f6JTi3.js")).Swiper}().then((e=>{v=e,h=new v(p,{spaceBetween:20}),h.on("slideChange",(({realIndex:e})=>{c(e)}))})).catch((e=>{console.log(e)})),L=!0}),{once:!0,passive:!0}),l.set(!0)}(e)}export{b as initMobile};
|
||||
1
static/bundled/js/LbzzjK.js
Normal file
1
static/bundled/js/LbzzjK.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/bundled/js/f6JTi3.js
Normal file
1
static/bundled/js/f6JTi3.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{W as e,s as t,e as n,l as a,f as s,g as c,h as o,j as l,c as m,o as i,k as d}from"./main.js";const r=new e(!1);let p,u,g,h;const E=new e(!1);let y,v,f,N=-1,w=[],x=[],I=[],B=!1;function T(){n.set(!0),I[t.get().index].scrollIntoView({block:"center",behavior:"auto"}),v.to(u,{y:"100%",ease:f.easeInOut,duration:1}),v.to(g,{opacity:0,duration:1.2,delay:.4})}function C(){const e=[];e.push(x[h.activeIndex]),e.push(x[Math.min(h.activeIndex+1,h.slides.length)]),e.push(x[Math.max(h.activeIndex-1,0)]);for(const t of e)t.src=t.dataset.src}let L=[];function k(e){c(e),!E.get()&&B&&(E.set(!0),C(),v.to(g,{opacity:1,duration:1}),v.to(u,{y:0,ease:f.easeInOut,duration:1,delay:.4}),setTimeout((()=>{n.set(!1),E.set(!1)}),1200))}function A(e){(function(e){!function(e){const t=document.createElement("div");t.className="collection";for(const[n,a]of e.entries()){const e=0!==n?d(-25,25):0,s=0!==n?d(-30,30):0,c=document.createElement("img");c.dataset.src=a.loUrl,c.height=a.loImgH,c.width=a.loImgW,c.alt=a.alt,c.style.transform=`translate3d(${e}%, ${s-50}%, 0)`,t.append(c)}m.append(t)}(e);const t=document.getElementsByClassName("collection").item(0);r.addWatcher((e=>{e?t.classList.remove("hidden"):t.classList.add("hidden")})),L=Array.from(t.getElementsByTagName("img")),L.forEach(((e,t)=>{e.addEventListener("click",(()=>{k(t)}),{passive:!0}),e.addEventListener("keydown",(()=>{k(t)}),{passive:!0}),i(e,(()=>{for(let e=0;e<5;e++){const n=t+e;n<0||n>L.length-1||(L[n].src=L[n].dataset.src)}}))}))})(e),function(e){!function(e){const t=document.createElement("div");t.className="swiper-wrapper";for(const n of e){const e=document.createElement("div");e.className="swiper-slide";const a=document.createElement("img");a.dataset.src=n.hiUrl,a.height=n.hiImgH,a.width=n.hiImgW,a.alt=n.alt,e.append(a),t.append(e)}const n=document.createElement("div");n.className="galleryInner",n.append(t);const a=document.createElement("div");a.insertAdjacentHTML("afterbegin",'<span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>\n <span>/</span>\n <span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>');const s=document.createElement("div"),c=document.getElementById("main")?.getAttribute("closeText");s.innerText=l(c),s.addEventListener("click",(()=>{T()}),{passive:!0}),s.addEventListener("keydown",(()=>{T()}),{passive:!0});const o=document.createElement("div");o.className="nav",o.append(a,s);const i=document.createElement("div");i.className="gallery",i.append(n),i.append(o);const d=document.createElement("div");d.className="curtain",m.append(i,d)}(e),w=Array.from(document.getElementsByClassName("nav").item(0)?.getElementsByClassName("num")??[]),p=document.getElementsByClassName("galleryInner").item(0),u=document.getElementsByClassName("gallery").item(0),g=document.getElementsByClassName("curtain").item(0),x=Array.from(u.getElementsByTagName("img")),I=Array.from(document.getElementsByClassName("collection").item(0)?.getElementsByTagName("img")??[]),t.addWatcher((()=>{const e=t.get();var n;e.index!==N&&(n=e.index,C(),h.slideTo(n,0),function(){const e=o(t.get().index+1),n=o(t.get().length);w.forEach(((t,a)=>{t.innerText=a<4?e[a]:n[a-4]}))}(),N=e.index)})),r.addWatcher((e=>{e&&n.set(!0)})),window.addEventListener("touchstart",(()=>{a().then((e=>{v=e[0],f=e[1]})).catch((e=>{console.log(e)})),s().then((e=>{y=e,h=new y(p,{spaceBetween:20}),h.on("slideChange",(({realIndex:e})=>{c(e)}))})).catch((e=>{console.log(e)})),B=!0}),{once:!0,passive:!0}),r.set(!0)}(e)}export{A as initMobile};
|
||||
@@ -1 +1 @@
|
||||
function t(t,e){return(t+1)%e}function e(t,e){return(t+e-1)%e}function n(t){return("0000"+t.toString()).slice(-4)}function s(t,e){return Math.floor(Math.random()*(e-t+1))+t}function o(t,e){new IntersectionObserver(((n,s)=>{n.forEach((n=>{n.intersectionRatio>0&&(e(t),s.disconnect())}))})).observe(t)}function r(t){return t.charAt(0).toUpperCase()+t.slice(1)}async function i(){const t=await import("./-BhIIs.js");return[t.gsap,t.Power3]}async function a(){return(await import("./ep7_9p.js")).Swiper}class c{constructor(t){this.obj=t,this.watchers=[]}get(){return this.obj}set(t){this.obj=t,this.watchers.forEach((t=>{t(this.obj)}))}addWatcher(t){this.watchers.push(t)}}const l=new c(!0);let h;const d=[{threshold:20,trailLength:20},{threshold:40,trailLength:10},{threshold:80,trailLength:5},{threshold:140,trailLength:5},{threshold:200,trailLength:5}],m=new c({index:-1,length:0,threshold:d[E()].threshold,trailLength:d[E()].trailLength});function u(t){const e=m.get();e.index=t,m.set(e)}function g(){const e=m.get();e.index=t(e.index,e.length),m.set(e)}function f(){const t=m.get();t.index=e(t.index,t.length),m.set(t)}function x(t,e){const n=d.findIndex((e=>t.threshold===e.threshold))+e;if(n<0||n>=d.length)return t;sessionStorage.setItem("thresholdsIndex",n.toString());const s=d[n];return{...t,...s}}function E(){const t=sessionStorage.getItem("thresholdsIndex");return null===t?2:parseInt(t)}const p=document.getElementsByClassName("threshold").item(0),y=Array.from(p.getElementsByClassName("num")),w=p.getElementsByClassName("dec").item(0),I=p.getElementsByClassName("inc").item(0),L=document.getElementsByClassName("index").item(0),b=Array.from(L.getElementsByClassName("num")),B=document.getElementsByClassName("links").item(0),C=Array.from(B.getElementsByClassName("link")),N=document.getElementById("main")?.getAttribute("currentMenuItemIndex");for(const[t,e]of C.entries())t===parseInt(N)&&(e.classList.add("current"),0!==t&&(document.title=e.innerText+" | "+document.title));function j(t){y.forEach(((e,n)=>{e.innerText=t[n]}))}function S(t,e){b.forEach(((n,s)=>{n.innerText=s<4?t[s]:e[s-4]}))}h=document.getElementsByClassName("container").item(0),l.addWatcher((t=>{t?h.classList.remove("disableScroll"):h.classList.add("disableScroll")}));const v=function(){const t=document.getElementById("imagesSource");return null===t?[]:JSON.parse(t.textContent).sort(((t,e)=>t.index<e.index?-1:1))}();!function(t){const e=m.get();e.length=t,x(e,0),m.set(e)}(v.length),function(){const t=m.get();j(n(t.threshold)),S(n(t.index+1),n(t.length)),m.addWatcher((t=>{S(n(t.index+1),n(t.length)),j(n(t.threshold))})),w.addEventListener("click",(()=>{!function(){let t=m.get();t=x(t,-1),m.set(t)}()}),{passive:!0}),I.addEventListener("click",(()=>{!function(){let t=m.get();t=x(t,1),m.set(t)}()}),{passive:!0})}(),v.length>0&&(window.matchMedia("(hover: none)").matches?import("./fRwBqI.js").then((t=>{t.initMobile(v)})).catch((t=>{console.log(t)})):import("./wWaJYt.js").then((t=>{t.initDesktop(v)})).catch((t=>{console.log(t)})));export{c as W,g as a,f as b,h as c,e as d,l as e,a as f,u as g,n as h,t as i,r as j,s as k,i as l,o,m as s};
|
||||
function t(t,e){return(t+1)%e}function e(t,e){return(t+e-1)%e}function n(t){return("0000"+t.toString()).slice(-4)}async function s(){const t=await import("./EY5BO_.js");return[t.gsap,t.Power3]}function o(){const t=sessionStorage.getItem("thresholdsIndex");return null===t?2:parseInt(t)}function a(t){return t.length<2?t:[...new Set(t)]}class i{constructor(t){this.obj=t,this.watchers=[]}get(){return this.obj}set(t){this.obj=t,this.watchers.forEach((t=>{t(this.obj)}))}addWatcher(t){this.watchers.push(t)}}const r=new i(!0);let c;const h=[{threshold:20,trailLength:20},{threshold:40,trailLength:10},{threshold:80,trailLength:5},{threshold:140,trailLength:5},{threshold:200,trailLength:5}],l=new i({index:-1,length:0,threshold:h[o()].threshold,trailLength:h[o()].trailLength}),d=new i(!1),g=new i("none");function m(t){const e=l.get();e.index=t,l.set(e)}function u(){const e=l.get();e.index=t(e.index,e.length),l.set(e)}function f(){const t=l.get();t.index=e(t.index,t.length),l.set(t)}function w(t,e){const n=h.findIndex((e=>t.threshold===e.threshold))+e;if(n<0||n>=h.length)return t;sessionStorage.setItem("thresholdsIndex",n.toString());const s=h[n];return{...t,...s}}const x=document.getElementsByClassName("threshold").item(0),p=Array.from(x.getElementsByClassName("num")),y=x.getElementsByClassName("dec").item(0),E=x.getElementsByClassName("inc").item(0),L=document.getElementsByClassName("index").item(0),j=Array.from(L.getElementsByClassName("num"));document.addEventListener("DOMContentLoaded",(()=>{(async function(){c=document.getElementsByClassName("container").item(0),r.addWatcher((t=>{t?c.classList.remove("disableScroll"):c.classList.add("disableScroll")}));const t=await async function(){try{const t=await fetch(`${window.location.href}index.json`,{headers:{Accept:"application/json"}});return(await t.json()).sort(((t,e)=>t.index<e.index?-1:1))}catch(t){return[]}}();(function(t){const e=l.get();e.length=t,w(e,0),l.set(e)})(t.length),l.addWatcher((t=>{var e,s,o;e=n(t.index+1),s=n(t.length),j.forEach(((t,n)=>{t.innerText=n<4?e[n]:s[n-4]})),o=n(t.threshold),p.forEach(((t,e)=>{t.innerText=o[e]}))})),y.addEventListener("click",(()=>{!function(){let t=l.get();t=w(t,-1),l.set(t)}()}),{passive:!0}),E.addEventListener("click",(()=>{!function(){let t=l.get();t=w(t,1),l.set(t)}()}),{passive:!0}),0!==t.length&&(window.matchMedia("(hover: none)").matches?await import("./Ej2jsy.js").then((e=>{e.initMobile(t)})).catch((t=>{console.log(t)})):await import("./LbzzjK.js").then((e=>{e.initDesktop(t)})).catch((t=>{console.log(t)})))})().catch((t=>{console.log(t)}))}));export{i as W,u as a,t as b,c,e as d,f as e,r as f,m as g,n as h,d as i,s as l,g as n,a as r,l as s};
|
||||
@@ -1 +0,0 @@
|
||||
import{l as e,c as t,W as n,s as a,i as s,a as i,d as o,b as c}from"./main.js";let r=[],d={x:0,y:0};const g=new n([]),l=new n(!1),m=new n(!1),u=new n(!1);let h,p,f=!1;function v(){return g.get().map((e=>r[e.i]))}function y(){const e=v().slice(-a.get().trailLength);return e.slice(0,e.length-1)}function w(){const e=v();return e[e.length-1]}function I(){const e=a.get(),t=[];for(let n=0;n<5;n++)t.push(r[s(e.index+n,e.length)]);return t}function x(){const e=a.get();return r[s(e.index,e.length)]}function E(){const e=a.get();return r[o(e.index,e.length)]}function L(e){if(l.get()||m.get()||!f)return;const t={x:e.clientX,y:e.clientY};if(Math.hypot(t.x-d.x,t.y-d.y)>a.get().threshold){d=t,i();const e={i:a.get().index,...t};g.set([...g.get(),e].slice(-a.get().length))}}function W(){if(m.get()||!f)return;l.set(!0),m.set(!0),k([w(),x(),E()]);const e=h.timeline();e.to(y(),{y:"+=20",ease:p.easeIn,stagger:.075,duration:.3,delay:.1,opacity:0}),e.to(w(),{x:0,y:0,ease:p.easeInOut,duration:.7,delay:.3}),e.to(w(),{delay:.1,scale:1,ease:p.easeInOut}),e.then((()=>{m.set(!1)})).catch((e=>{console.log(e)}))}function H(){if(m.get()||!f)return;l.set(!1),m.set(!0),N([w()]),N(y());const e=h.timeline();e.to(w(),{scale:.6,duration:.6,ease:p.easeInOut}),e.to(w(),{delay:.3,duration:.7,ease:p.easeInOut,x:g.get()[g.get().length-1].x-window.innerWidth/2,y:g.get()[g.get().length-1].y-window.innerHeight/2}),e.to(y(),{y:"-=20",ease:p.easeOut,stagger:-.1,duration:.3,opacity:1}),e.then((()=>{m.set(!1)})).catch((e=>{console.log(e)}))}function k(e){e.forEach((e=>{e.src=e.dataset.hiUrl,e.height=parseInt(e.dataset.hiImgH),e.width=parseInt(e.dataset.hiImgW)}))}function N(e){e.forEach((e=>{e.src=e.dataset.loUrl,e.height=parseInt(e.dataset.loImgH),e.width=parseInt(e.dataset.loImgW)}))}const b=document.createElement("div"),A=document.createElement("div");function O(e){const t=e.clientX,n=e.clientY;b.style.transform=`translate3d(${t}px, ${n}px, 0)`}function U(e){A.innerText=e}const T=document.getElementById("main"),S=[T.getAttribute("prevText"),T.getAttribute("closeText"),T.getAttribute("nextText")];function B(e){e===S[0]?$():e===S[1]?H():Y()}function X(e){if(!l.get()&&!m.get())switch(e.key){case"ArrowLeft":$();break;case"Escape":H();break;case"ArrowRight":Y()}}function Y(){m.get()||(g.set(g.get().map((e=>({...e,i:s(e.i,a.get().length)})))),i())}function $(){m.get()||(g.set(g.get().map((e=>({...e,i:o(e.i,a.get().length)})))),c())}function j(n){b.className="cursor",A.className="cursorInner",b.append(A),t.append(b),window.addEventListener("mousemove",O,{passive:!0}),u.addWatcher((e=>{e?b.classList.add("active"):b.classList.remove("active")})),function(n){!function(e){const n=document.createElement("div");n.className="stage";for(const t of e){const e=document.createElement("img");e.height=t.loImgH,e.width=t.loImgW,e.dataset.hiUrl=t.hiUrl,e.dataset.hiImgH=t.hiImgH.toString(),e.dataset.hiImgW=t.hiImgW.toString(),e.dataset.loUrl=t.loUrl,e.dataset.loImgH=t.loImgH.toString(),e.dataset.loImgW=t.loImgW.toString(),e.alt=t.alt,n.append(e)}t.append(n)}(n);const s=document.getElementsByClassName("stage").item(0);r=Array.from(s.getElementsByTagName("img")),s.addEventListener("click",(()=>{W()})),s.addEventListener("keydown",(()=>{W()})),window.addEventListener("mousemove",L,{passive:!0}),l.addWatcher((e=>{u.set(e&&!m.get())})),m.addWatcher((e=>{u.set(l.get()&&!e)})),g.addWatcher((e=>{!function(){const e=v();0!==e.length&&f&&(N(I()),h.set(e,{x:e=>g.get()[e].x-window.innerWidth/2,y:e=>g.get()[e].y-window.innerHeight/2,opacity:e=>e+1+a.get().trailLength<=g.get().length?0:1,zIndex:e=>e,scale:.6}),l.get()&&(N(v()),k([w()]),h.set(r,{opacity:0}),h.set(w(),{opacity:1,x:0,y:0,scale:1})))}()})),N(I()),window.addEventListener("mousemove",(()=>{e().then((e=>{h=e[0],p=e[1],f=!0})).catch((e=>{console.log(e)}))}),{once:!0,passive:!0})}(n),function(){const e=document.createElement("div");e.className="navOverlay";for(const t of S){const n=document.createElement("div");n.className="overlay",n.addEventListener("click",(()=>{B(t)}),{passive:!0}),n.addEventListener("keydown",(()=>{B(t)}),{passive:!0}),n.addEventListener("mouseover",(()=>{U(t)}),{passive:!0}),n.addEventListener("focus",(()=>{U(t)}),{passive:!0}),e.append(n)}u.addWatcher((()=>{u.get()?e.classList.add("active"):e.classList.remove("active")})),t.append(e),window.addEventListener("keydown",X,{passive:!0})}()}export{j as initDesktop};
|
||||
Binary file not shown.
BIN
static/lib/fonts/fw.woff2
Normal file
BIN
static/lib/fonts/fw.woff2
Normal file
Binary file not shown.
@@ -7,7 +7,7 @@ licenselink = "https://github.com/Sped0n/bridget/blob/main/LICENSE"
|
||||
description = "Bridget is a minimal Hugo theme designed for photographers/visual artists."
|
||||
homepage = "https://github.com/Sped0n/bridget"
|
||||
demosite = "https://bridget-demo.sped0nwen.com"
|
||||
tags = ["photography", "visual art", "minimal", "portfolio", "responsive"]
|
||||
tags = ["gallery", "minimal", "portfolio", "responsive"]
|
||||
features = ["SEO optimized", "TypeScript", "multi-language support"]
|
||||
|
||||
[author]
|
||||
|
||||
Reference in New Issue
Block a user