From 4387abe52f88fa29604f3fd6e974e2dd537ede58 Mon Sep 17 00:00:00 2001
From: Spedon <70063177+Sped0n@users.noreply.github.com>
Date: Sun, 29 Oct 2023 12:39:56 +0800
Subject: [PATCH] Gsap (#120)
* feat: add gsap to deps
* chore: migrate to pnpm
* refractor: change var name
* chore(.idea): remove unnecessary files and configurations
Remove the following files and configurations from the .idea directory:
- .gitignore: Remove default ignored files.
- bridget.iml: Remove module file.
- codeStyles/Project.xml: Remove project code style configuration.
- codeStyles/codeStyleConfig.xml: Remove project code style configuration.
- inspectionProfiles/Project_Default.xml: Remove project inspection profile.
- jsLibraryMappings.xml: Remove JavaScript library mappings.
- jsLinters/eslint.xml: Remove ESLint configuration.
- modules.xml: Remove project module configuration.
- vcs.xml: Remove VCS directory mapping.
- watcherTasks.xml: Remove project tasks options.
* chore(.prettierrc.json): update Prettier configuration to include support for go-template files and improve code formatting settings
The Prettier configuration file (.prettierrc.json) has been updated with the following changes:
- `useTabs` is set to `false` to use spaces for indentation
- `tabWidth` is set to `2` to specify the number of spaces for each indentation level
- `printWidth` is set to `88` to limit the line length to 88 characters
- `singleQuote` is set to `true` to use single quotes for strings
- `trailingComma` is set to `none` to remove trailing commas in arrays and objects
- `bracketSpacing` is set to `true` to add spaces inside brackets
- `semi` is set to `false` to remove semicolons at the end of statements
- `plugins` is added to include the "prettier-plugin-go-template" plugin for go-template files
- `overrides` is added to specify the parser as "go-template" for files with the ".html" extension
* chore(base.scss): improve font rendering by adding font smoothing properties to all elements
feat(base.scss): add user-select: none to body to disable text selection
feat(base.scss): add overscroll-behavior-y: none to html and body to disable vertical scrolling on overscroll
feat(base.scss): add cursor: pointer to anchor tags and buttons for better user experience
feat(font.scss): add font-face declaration for HelveticaNow font
refactor(media.scss): remove unused file
feat(mixins.scss): add min-width and max-width mixins for responsive design
feat(reset.scss): add the new CSS reset version 1.8.4
feat(reset.scss): remove all styles from User-Agent-Stylesheet except for the display property
feat(reset.scss): set box-sizing: border-box for all elements
feat(reset.scss): revert cursor style for anchor tags and buttons
feat(reset.scss): remove list styles (bullets/numbers) from ol, ul, and menu
feat(reset.scss): set max-inline-size and max-block-size to 100% for images
feat(reset.scss): set border-collapse: collapse for tables
feat(reset.scss): set -webkit-user-select: auto for input and textarea to fix Safari issue
feat(reset.scss): revert white-space property for textarea on Safari
feat(reset.scss): set -webkit-appearance: revert for meter element
feat(reset.scss): revert all styles for preformatted text
feat(reset.scss): unset color for input placeholder
feat(reset.scss): remove default dot sign for lists
feat(reset.scss): set display: none for elements with hidden attribute
feat(reset.scss): revert styles for contenteditable elements
feat(reset.scss): set -webkit-user-drag: element for draggable elements
feat(reset.scss): revert native behavior for modal dialogs
feat(typography.scss): set line-height, font-size, and font-family for body
feat(typography.scss): increase font-size for tablet and laptop breakpoints
* feat: add custom cursor styles
Add a new file `_customCursor.scss` to the `assets/css/_partial` directory. This file contains styles for a custom cursor. The `.cursor` class is used to position the cursor and set its appearance. The `.active` class is used to display the cursor. The `.cursorInner` class is used to position the inner content of the cursor.
---
refactor: remove unused footer styles
Delete the file `_footer.scss` from the `assets/css/_partial` directory. This file contains styles for the footer section of the page. The styles are no longer used and can be safely removed.
---
refactor: remove unused image styles
Delete the files `_imagesDesktop.scss` and `_imagesMobile.scss` from the `assets/css/_partial` directory. These files contain styles for displaying images on desktop and mobile devices. The styles are no longer used and can be safely removed.
---
feat: add navigation styles
Add a new file `_nav.scss` to the `assets/css/_partial` directory. This file contains styles for the navigation bar. The styles define the layout and appearance of the navigation bar, including its position, background color, and alignment of its contents. The styles also include media queries to adjust the layout for smaller screens.
---
refactor: remove unused overlay styles
Delete the file `_overlay.scss` from the `assets/css/_partial` directory. This file contains styles for an overlay element. The styles are no longer used and can be safely removed.
---
feat: add stage styles
Add a new file `_stage.scss` to the `assets/css/_partial` directory. This file contains styles for the stage element, which is used to display images. The styles define the position and size of the stage, as well as the appearance of the images within it.
---
feat: add stage navigation overlay styles
Add a new file `_stageNav.scss` to the `assets/css/_partial` directory. This file contains styles for the stage navigation overlay. The styles define the position and size of the overlay, as well as its appearance and behavior. The overlay is used for navigation within the stage.
* chore(variables.scss): update variable names and values for better readability and consistency
chore(style.scss): reorganize import statements for better organization and readability
* fix(main.ts): import correct functions from utils module
feat(stage.ts): implement stage navigation functionality
feat(stageNav.ts): implement stage navigation overlay functionality
feat(state.ts): implement state management for index and threshold
feat(utils.ts): add utility functions for increment and decrement
* fix(index.html): fix doctype declaration to use lowercase 'doctype' for HTML5 compliance
fix(index.html): fix meta tag indentation for better readability
fix(index.html): fix indentation of head and body tags for better readability
fix(index.html): fix indentation of header, main, and footer sections for better readability
fix(index.html): fix indentation of script tag for better readability
fix(index.html): fix indentation of closing div tag for better readability
fix(index.html): fix indentation of closing body and html tags for better readability
feat(index.html): add partial for navigation bar to improve website navigation
fix(head.html): fix indentation of esBuildOpts variable for better readability
fix(head.html): fix indentation of script tag for better readability
feat(nav.html): add navigation bar partial to improve website navigation
* refactor(stage.ts): change cordHist, isOpen, isAnimating, and active variables to instances of the watchable class to improve semantics and encapsulation
* refactor(customCursor.ts): rename addActiveCallback to active.addWatcher for better readability and consistency
refactor(stageNav.ts): rename getIsAnimating to isAnimating.get for better readability and consistency
refactor(stageNav.ts): rename addActiveCallback to active.addWatcher for better readability and consistency
feat(stageNav.ts): add check for isOpen.get() and isAnimating.get() before handling key events to prevent unwanted actions
* chore(package.json): add prettier-plugin-go-template as a dev dependency to enable formatting of Go templates with Prettier
* chore: add helvetica now font
* chore(tsconfig.json): add "moduleResolution" property with value "node" to improve module resolution in the project configuration
* refactor(stage.ts): rename class 'watchable' to 'Watchable' for consistency and clarity
feat(utils.ts): add Watchable class to provide a generic watchable object with getter, setter, and watcher functionality
---
.idea/.gitignore | 5 -
.idea/bridget.iml | 12 -
.idea/codeStyles/Project.xml | 93 -
.idea/codeStyles/codeStyleConfig.xml | 5 -
.idea/inspectionProfiles/Project_Default.xml | 6 -
.idea/jsLibraryMappings.xml | 6 -
.idea/jsLinters/eslint.xml | 6 -
.idea/modules.xml | 8 -
.idea/vcs.xml | 6 -
.idea/watcherTasks.xml | 4 -
.prettierrc.json | 23 +-
assets/css/_core/_base.scss | 28 +-
assets/css/_core/_font.scss | 6 +
assets/css/_core/_media.scss | 0
assets/css/_core/_mixins.scss | 28 +
assets/css/_core/_reset.scss | 103 +
assets/css/_core/_typography.scss | 14 +
assets/css/_partial/_customCursor.scss | 21 +
assets/css/_partial/_footer.scss | 75 -
assets/css/_partial/_imagesDesktop.scss | 52 -
assets/css/_partial/_imagesMobile.scss | 16 -
assets/css/_partial/_nav.scss | 44 +
assets/css/_partial/_overlay.scss | 15 -
assets/css/_partial/_stage.scss | 22 +
assets/css/_partial/_stageNav.scss | 21 +
assets/css/_variables.scss | 22 +-
assets/css/style.scss | 22 +-
assets/ts/customCursor.ts | 38 +
assets/ts/dataFetch.ts | 14 -
assets/ts/desktop.ts | 165 --
assets/ts/elemGen.ts | 55 -
assets/ts/imageCache.ts | 18 -
assets/ts/indexDisp.ts | 17 -
assets/ts/main.ts | 39 +-
assets/ts/mobile.ts | 24 -
assets/ts/nav.ts | 59 +
assets/ts/overlay.ts | 204 --
assets/ts/resources.ts | 21 +
assets/ts/stage.ts | 184 ++
assets/ts/stageNav.ts | 87 +
assets/ts/state.ts | 68 +
assets/ts/thresholdCtl.ts | 51 -
assets/ts/utils.ts | 148 +-
layouts/index.html | 79 +-
layouts/partials/head.html | 5 +-
layouts/partials/nav.html | 27 +
package.json | 4 +
pnpm-lock.yaml | 2088 ++++++++++++++++++
static/fonts/HelveticaNowText-Regular.woff | Bin 0 -> 51704 bytes
tsconfig.json | 5 +-
50 files changed, 2963 insertions(+), 1100 deletions(-)
delete mode 100644 .idea/.gitignore
delete mode 100644 .idea/bridget.iml
delete mode 100644 .idea/codeStyles/Project.xml
delete mode 100644 .idea/codeStyles/codeStyleConfig.xml
delete mode 100644 .idea/inspectionProfiles/Project_Default.xml
delete mode 100644 .idea/jsLibraryMappings.xml
delete mode 100644 .idea/jsLinters/eslint.xml
delete mode 100644 .idea/modules.xml
delete mode 100644 .idea/vcs.xml
delete mode 100644 .idea/watcherTasks.xml
create mode 100644 assets/css/_core/_font.scss
delete mode 100644 assets/css/_core/_media.scss
create mode 100644 assets/css/_core/_mixins.scss
create mode 100644 assets/css/_core/_reset.scss
create mode 100644 assets/css/_core/_typography.scss
create mode 100644 assets/css/_partial/_customCursor.scss
delete mode 100644 assets/css/_partial/_footer.scss
delete mode 100644 assets/css/_partial/_imagesDesktop.scss
delete mode 100644 assets/css/_partial/_imagesMobile.scss
create mode 100644 assets/css/_partial/_nav.scss
delete mode 100644 assets/css/_partial/_overlay.scss
create mode 100644 assets/css/_partial/_stage.scss
create mode 100644 assets/css/_partial/_stageNav.scss
create mode 100644 assets/ts/customCursor.ts
delete mode 100644 assets/ts/dataFetch.ts
delete mode 100644 assets/ts/desktop.ts
delete mode 100644 assets/ts/elemGen.ts
delete mode 100644 assets/ts/imageCache.ts
delete mode 100644 assets/ts/indexDisp.ts
delete mode 100644 assets/ts/mobile.ts
create mode 100644 assets/ts/nav.ts
delete mode 100644 assets/ts/overlay.ts
create mode 100644 assets/ts/resources.ts
create mode 100644 assets/ts/stage.ts
create mode 100644 assets/ts/stageNav.ts
create mode 100644 assets/ts/state.ts
delete mode 100644 assets/ts/thresholdCtl.ts
create mode 100644 layouts/partials/nav.html
create mode 100644 pnpm-lock.yaml
create mode 100644 static/fonts/HelveticaNowText-Regular.woff
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index b58b603..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/bridget.iml b/.idea/bridget.iml
deleted file mode 100644
index 0c8867d..0000000
--- a/.idea/bridget.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 70eea87..0000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 03d9549..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
deleted file mode 100644
index d23208f..0000000
--- a/.idea/jsLibraryMappings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml
deleted file mode 100644
index b269b71..0000000
--- a/.idea/jsLinters/eslint.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index a6ba3d7..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml
deleted file mode 100644
index fb0d65a..0000000
--- a/.idea/watcherTasks.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.prettierrc.json b/.prettierrc.json
index 4107696..263da8b 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -1,9 +1,18 @@
{
-"useTabs": false,
-"tabWidth": 2,
-"printWidth": 88,
-"singleQuote": true,
-"trailingComma": "none",
-"bracketSpacing": true,
-"semi": false
+ "useTabs": false,
+ "tabWidth": 2,
+ "printWidth": 88,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "bracketSpacing": true,
+ "semi": false,
+ "plugins": ["prettier-plugin-go-template"],
+ "overrides": [
+ {
+ "files": ["*.html"],
+ "options": {
+ "parser": "go-template"
+ }
+ }
+ ]
}
diff --git a/assets/css/_core/_base.scss b/assets/css/_core/_base.scss
index d65d850..865a8c5 100644
--- a/assets/css/_core/_base.scss
+++ b/assets/css/_core/_base.scss
@@ -1,14 +1,20 @@
-html {
- font-family: $global-font-family;
- overflow: hidden;
- position: relative;
- scroll-behavior: smooth;
+* {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
body {
- background-color: white;
- height: 100vh;
- margin: 0;
- overflow: hidden;
- line-height: 1.5;
-}
\ No newline at end of file
+ user-select: none;
+ background: white;
+}
+
+html,
+body {
+ overscroll-behavior-y: none;
+}
+
+a,
+button {
+ cursor: pointer;
+}
+
diff --git a/assets/css/_core/_font.scss b/assets/css/_core/_font.scss
new file mode 100644
index 0000000..397038e
--- /dev/null
+++ b/assets/css/_core/_font.scss
@@ -0,0 +1,6 @@
+@font-face {
+ font-family: HelveticaNow;
+ src: url('/fonts/HelveticaNowText-Regular.woff') format('woff');
+ font-weight: 400;
+ font-style: normal;
+}
diff --git a/assets/css/_core/_media.scss b/assets/css/_core/_media.scss
deleted file mode 100644
index e69de29..0000000
diff --git a/assets/css/_core/_mixins.scss b/assets/css/_core/_mixins.scss
new file mode 100644
index 0000000..d818128
--- /dev/null
+++ b/assets/css/_core/_mixins.scss
@@ -0,0 +1,28 @@
+$breakpoints: (
+ 'mobile': 375px,
+ 'tablet': 768px,
+ 'laptop': 1024px,
+ 'desktop': 1440px
+) !default;
+
+// Breakpoints
+
+@mixin min-width($breakpoint) {
+ @if map-has-key($breakpoints, $breakpoint) {
+ @media (min-width: map-get($breakpoints, $breakpoint)) {
+ @content;
+ }
+ } @else {
+ @error "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " + "Available breakpoints are: #{map-keys($breakpoints)}.";
+ }
+}
+
+@mixin max-width($breakpoint) {
+ @if map-has-key($breakpoints, $breakpoint) {
+ @media (max-width: (map-get($breakpoints, $breakpoint) - 1px)) {
+ @content;
+ }
+ } @else {
+ @error "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " + "Available breakpoints are: #{map-keys($breakpoints)}.";
+ }
+}
diff --git a/assets/css/_core/_reset.scss b/assets/css/_core/_reset.scss
new file mode 100644
index 0000000..0de3854
--- /dev/null
+++ b/assets/css/_core/_reset.scss
@@ -0,0 +1,103 @@
+/***
+ The new CSS reset - version 1.8.4 (last updated 14.2.2023)
+ GitHub page: https://github.com/elad2412/the-new-css-reset
+***/
+
+/*
+ Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
+ - The "symbol *" part is to solve Firefox SVG sprite bug
+ */
+*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
+ all: unset;
+ display: revert;
+}
+
+/* Preferred box-sizing value */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+/* Reapply the pointer cursor for anchor tags */
+a,
+button {
+ cursor: revert;
+}
+
+/* Remove list styles (bullets/numbers) */
+ol,
+ul,
+menu {
+ list-style: none;
+}
+
+/* For images to not be able to exceed their container */
+img {
+ max-inline-size: 100%;
+ max-block-size: 100%;
+}
+
+/* removes spacing between cells in tables */
+table {
+ border-collapse: collapse;
+}
+
+/* Safari - solving issue when using user-select:none on the
text input doesn't working */
+input,
+textarea {
+ -webkit-user-select: auto;
+}
+
+/* revert the 'white-space' property for textarea elements on Safari */
+textarea {
+ white-space: revert;
+}
+
+/* minimum style to allow to style meter element */
+meter {
+ -webkit-appearance: revert;
+ appearance: revert;
+}
+
+/* preformatted text - use only for this feature */
+:where(pre) {
+ all: revert;
+}
+
+/* reset default text opacity of input placeholder */
+::placeholder {
+ color: unset;
+}
+
+/* remove default dot (•) sign */
+::marker {
+ content: initial;
+}
+
+/* fix the feature of 'hidden' attribute.
+ display:revert; revert to element instead of attribute */
+:where([hidden]) {
+ display: none;
+}
+
+/* revert for bug in Chromium browsers
+ - fix for the content editable attribute will work properly.
+ - webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
+: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;
+}
+
+/* apply back the draggable feature - exist only in Chromium and Safari */
+:where([draggable='true']) {
+ -webkit-user-drag: element;
+}
+
+/* Revert Modal native behavior */
+:where(dialog:modal) {
+ all: revert;
+}
diff --git a/assets/css/_core/_typography.scss b/assets/css/_core/_typography.scss
new file mode 100644
index 0000000..00d5d55
--- /dev/null
+++ b/assets/css/_core/_typography.scss
@@ -0,0 +1,14 @@
+@import 'mixins';
+
+body {
+ line-height: 1.2;
+ font-size: 16px;
+ font-family: HelveticaNow, helvetica, arial, sans-serif;
+
+ @include min-width('tablet') {
+ font-size: 18px;
+ }
+ @include min-width('laptop') {
+ font-size: 19px;
+ }
+}
diff --git a/assets/css/_partial/_customCursor.scss b/assets/css/_partial/_customCursor.scss
new file mode 100644
index 0000000..591c8f5
--- /dev/null
+++ b/assets/css/_partial/_customCursor.scss
@@ -0,0 +1,21 @@
+.cursor {
+ position: fixed;
+ z-index: var(--z-cursor);
+ top: 0;
+ left: 0;
+
+ display: none;
+ cursor: none;
+ pointer-events: none;
+
+ color: white;
+ mix-blend-mode: difference;
+}
+
+.active {
+ display: block;
+}
+
+.cursorInner {
+ transform: translate3d(-50%, -50%, 0);
+}
diff --git a/assets/css/_partial/_footer.scss b/assets/css/_partial/_footer.scss
deleted file mode 100644
index 62823fb..0000000
--- a/assets/css/_partial/_footer.scss
+++ /dev/null
@@ -1,75 +0,0 @@
-footer {
- max-height: fit-content;
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 22;
- background-color: #fff;
- height: 38px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 20px;
- line-height: 1.5;
- padding: 4px 9px;
- float: none;
-
- .footer_name {
- }
-
- .footer_categoryWrapper {
- .footer_category {
- cursor: pointer;
- }
-
- .selected {
- text-decoration: underline;
- font-style: italic;
- }
- }
-
- .footer_threshold {
- button {
- border: none;
- background-color: transparent;
- margin: 0;
- font-size: 20px;
- padding: 0 4px;
- cursor: pointer;
- }
-
- .thid{
- display: inline-block;
- text-align: center;
- width: 48px;
- }
- }
-
- .footer_imageIndex {
- margin: 0;
-
- .ftid {
- display: inline-block;
- text-align: center;
- width: 7px;
- }
- }
-}
-
-@media only screen and (max-width: 768px) {
- footer {
- top: 0;
- padding: 3px 8px;
- font-size: 17px;
- height: 31px;
-
- .footer_threshold {
- display: none;
- }
-
- .footer_imageIndex {
- display: none;
- }
- }
-}
\ No newline at end of file
diff --git a/assets/css/_partial/_imagesDesktop.scss b/assets/css/_partial/_imagesDesktop.scss
deleted file mode 100644
index a3e3a86..0000000
--- a/assets/css/_partial/_imagesDesktop.scss
+++ /dev/null
@@ -1,52 +0,0 @@
-.imagesDesktop {
-
- img {
- position: absolute;
- top: 0;
- left: 0;
- object-fit: contain;
- max-height: 50vmin;
- max-width: 100vw;
-
- &[data-status='null'] {
- opacity: 1;
- }
-
- &[data-status='top'] {
- opacity: 1;
- max-height: calc(100vh - var(--footer-height));;
- transition-property: transform, max-height;
- transition-timing-function: ease-in-out;
- transition-duration: 0.5s, 0.5s;
- }
-
- &[data-status='trail'] {
- opacity: 0;
- margin-top: 40px;
- transition-property: opacity, margin-top;
- transition-timing-function: ease-out;
- transition-duration: 0.2s;
- }
-
- &[data-status='resumeTop'] {
- opacity: 1;
- max-height: 50vmin;
- transition-property: max-height, transform;
- transition-timing-function: ease-in-out;
- transition-duration: 0.7s, 0.5s;
- }
-
- &[data-status='resume'] {
- opacity: 1;
- margin-top: 0;
- transition-property: opacity, margin-top;
- transition-timing-function: ease-out;
- transition-duration: 0.2s;
- }
-
- &[data-status='overlay'] {
- opacity: 1;
- max-height: calc(100vh - var(--footer-height));
- }
- }
-}
\ No newline at end of file
diff --git a/assets/css/_partial/_imagesMobile.scss b/assets/css/_partial/_imagesMobile.scss
deleted file mode 100644
index ea94e08..0000000
--- a/assets/css/_partial/_imagesMobile.scss
+++ /dev/null
@@ -1,16 +0,0 @@
-.imagesMobile {
- height: 100vh;
- overflow: scroll;
- position: relative;
-
- img {
- height: 20vh;
- width: 60vw;
- object-fit: contain;
- position: sticky;
- top: 50vh;
- margin-left: 0;
- margin-right: 0;
- margin-bottom: 20vh;
- }
-}
\ No newline at end of file
diff --git a/assets/css/_partial/_nav.scss b/assets/css/_partial/_nav.scss
new file mode 100644
index 0000000..6b4d1c3
--- /dev/null
+++ b/assets/css/_partial/_nav.scss
@@ -0,0 +1,44 @@
+@import '../_core/mixins';
+$tablet: map-get($breakpoints, 'tablet') - 1;
+
+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: white;
+
+ z-index: var(--z-nav);
+
+ // Maintain functionality while container is locked
+ pointer-events: all;
+}
+
+.num {
+ width: 0.625em;
+ display: inline-block;
+ text-align: center;
+}
+
+.current {
+ font-style: italic;
+ text-decoration: underline;
+}
+
+@media (max-width: $tablet), (hover: none) {
+ nav {
+ top: 0;
+ position: sticky;
+ }
+
+ .index,
+ .threshold {
+ display: none;
+ }
+}
diff --git a/assets/css/_partial/_overlay.scss b/assets/css/_partial/_overlay.scss
deleted file mode 100644
index 9c55b08..0000000
--- a/assets/css/_partial/_overlay.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.overlay_cursor {
- position: fixed;
- top: 0;
- left: 0;
- mix-blend-mode: difference;
- font-size: 19px;
- box-sizing: border-box;
- cursor: none;
- user-select: none;
-
- .cursor_innerText {
- color: white;
- transform: translate3d(-50%, -50%, 0);
- }
-}
\ No newline at end of file
diff --git a/assets/css/_partial/_stage.scss b/assets/css/_partial/_stage.scss
new file mode 100644
index 0000000..ef53850
--- /dev/null
+++ b/assets/css/_partial/_stage.scss
@@ -0,0 +1,22 @@
+.stage {
+ position: relative;
+ overflow: hidden;
+ width: 100vw;
+ height: calc(var(--window-height) - var(--nav-height));
+
+ cursor: pointer;
+
+ img {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ width: 100vw;
+ height: var(--window-height);
+ object-fit: contain;
+
+ transform: scale(0.6);
+ opacity: 0;
+ pointer-events: none;
+ }
+}
diff --git a/assets/css/_partial/_stageNav.scss b/assets/css/_partial/_stageNav.scss
new file mode 100644
index 0000000..c347049
--- /dev/null
+++ b/assets/css/_partial/_stageNav.scss
@@ -0,0 +1,21 @@
+.navOverlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: var(--z-nav-gallery);
+
+ width: 100vw;
+ height: calc(var(--window-height) - var(--nav-height));
+
+ display: flex;
+ cursor: none;
+
+ &:not(.active) {
+ pointer-events: none;
+ display: none;
+ }
+
+ .overlay {
+ flex: 1;
+ }
+}
diff --git a/assets/css/_variables.scss b/assets/css/_variables.scss
index 193d082..b126326 100644
--- a/assets/css/_variables.scss
+++ b/assets/css/_variables.scss
@@ -1,14 +1,12 @@
-// ==============================
-// Variables
-// ==============================
-
-// ========== Global ========== //
-// Font and Line Height
-$global-font-family: system-ui, -apple-system, BlinkMacSystemFont, PingFang SC, Microsoft YaHei UI, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, Helvetica, Arial, sans-serif !default;
-$global-font-size: 16px;
-$global-font-weight: 400;
-$global-line-height: 1.5rem;
+@import '_core/mixins';
:root {
- --footer-height: 38px;
-}
\ No newline at end of file
+ --window-height: 100vh;
+ --nav-height: 2rem;
+ --space-standard: 0.625rem;
+
+ --z-curtain: 200;
+ --z-nav-gallery: 500;
+ --z-cursor: 600;
+ --z-nav: 800;
+}
diff --git a/assets/css/style.scss b/assets/css/style.scss
index 7df3db5..96c8842 100644
--- a/assets/css/style.scss
+++ b/assets/css/style.scss
@@ -1,15 +1,13 @@
@charset "utf-8";
-@import "_variables";
+@import '_core/reset';
+@import '_core/font';
+@import '_core/typography';
+@import '_core/mixins';
+@import '_variables';
+@import '_core/base';
-@import "_core/base";
-
-@import "_core/media";
-
-@import "_partial/imagesDesktop";
-
-@import "_partial/imagesMobile";
-
-@import "_partial/footer";
-
-@import "_partial/overlay";
\ No newline at end of file
+@import '_partial/customCursor';
+@import '_partial/nav';
+@import '_partial/stage';
+@import '_partial/stageNav';
diff --git a/assets/ts/customCursor.ts b/assets/ts/customCursor.ts
new file mode 100644
index 0000000..030e1a2
--- /dev/null
+++ b/assets/ts/customCursor.ts
@@ -0,0 +1,38 @@
+import { active } from './stage'
+
+let cursor: HTMLDivElement
+
+// create cursor
+cursor = document.createElement('div')
+cursor.className = 'cursor'
+cursor.classList.add('active')
+// create cursor inner
+const cursorInner = document.createElement('div')
+cursorInner.className = 'cursorInner'
+// append cursor inner to cursor
+cursor.append(cursorInner)
+
+function onMouse(e: MouseEvent) {
+ const x = e.clientX
+ const y = e.clientY
+ cursor.style.transform = `translate3d(${x}px, ${y}px, 0)`
+}
+
+export function initCustomCursor(): void {
+ // append cursor to main
+ document.getElementById('main')!.append(cursor)
+ // bind mousemove event to window
+ window.addEventListener('mousemove', onMouse)
+ // add active callback
+ active.addWatcher(() => {
+ if (active.get()) {
+ cursor.classList.add('active')
+ } else {
+ cursor.classList.remove('active')
+ }
+ })
+}
+
+export function setCustomCursor(text: string): void {
+ cursorInner.innerText = text
+}
diff --git a/assets/ts/dataFetch.ts b/assets/ts/dataFetch.ts
deleted file mode 100644
index 07db65d..0000000
--- a/assets/ts/dataFetch.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { type ImageData } from './utils'
-
-// fetch images info from JSON
-const imageArrayElement = document.getElementById('images_array') as HTMLScriptElement
-const rawImageArray = imageArrayElement.textContent as string
-export const imagesArray: ImageData[] = JSON.parse(rawImageArray).sort(
- (a: ImageData, b: ImageData) => {
- if (a.index < b.index) {
- return -1
- }
- return 1
- }
-)
-export const imagesArrayLen: number = imagesArray.length
diff --git a/assets/ts/desktop.ts b/assets/ts/desktop.ts
deleted file mode 100644
index dba830e..0000000
--- a/assets/ts/desktop.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import { overlayEnable } from './overlay'
-import {
- calcImageIndex,
- center,
- delay,
- mouseToTransform,
- pushIndex,
- type position,
- hideImage
-} from './utils'
-import { thresholdIndex, thresholdSensitivityArray } from './thresholdCtl'
-import { imgIndexSpanUpdate } from './indexDisp'
-import { imagesArrayLen } from './dataFetch'
-import { imagesDivNodes as images } from './elemGen'
-
-// global index for "activated"
-export let globalIndex: number = 0
-// last position set as "activated"
-let last: position = { x: 0, y: 0 }
-export let trailingImageIndexes: number[] = []
-// only used in overlay disable, for storing positions temporarily
-export let transformCache: string[] = []
-// abort controller for enter overlay event listener
-let EnterOverlayClickAbCtl = new AbortController()
-// stack depth of images array
-export let stackDepth: number = 5
-export let lastStackDepth: number = 5
-
-export const addEnterOverlayEL = (e: HTMLImageElement): void => {
- EnterOverlayClickAbCtl.abort()
- EnterOverlayClickAbCtl = new AbortController()
- e.addEventListener(
- 'click',
- () => {
- void enterOverlay()
- },
- {
- passive: true,
- once: true,
- signal: EnterOverlayClickAbCtl.signal
- }
- )
-}
-
-// activate top image
-const activate = (index: number, mouseX: number, mouseY: number): void => {
- addEnterOverlayEL(images[index])
- if (stackDepth !== lastStackDepth) {
- trailingImageIndexes.push(index)
- refreshStack()
- lastStackDepth = stackDepth
- }
- const indexesNum: number = pushIndex(
- index,
- trailingImageIndexes,
- stackDepth,
- images,
- imagesArrayLen
- )
- // set img position
- images[index].style.transform = mouseToTransform(mouseX, mouseY, true, true)
- images[index].dataset.status = 'null'
- // reset z index
- for (let i = indexesNum; i > 0; i--) {
- images[trailingImageIndexes[i - 1]].style.zIndex = `${i}`
- }
- images[index].style.visibility = 'visible'
- last = { x: mouseX, y: mouseY }
-}
-
-// Compare the current mouse position with the last activated position
-const distanceFromLast = (x: number, y: number): number => {
- return Math.hypot(x - last.x, y - last.y)
-}
-
-// handle mouse move
-export const handleOnMove = (e: MouseEvent): void => {
- // meet threshold
- if (
- distanceFromLast(e.clientX, e.clientY) >
- window.innerWidth / thresholdSensitivityArray[thresholdIndex]
- ) {
- // calculate the actual index
- const imageIndex = calcImageIndex(globalIndex, imagesArrayLen)
- // show top image and change index
- activate(imageIndex, e.clientX, e.clientY)
- imgIndexSpanUpdate(imageIndex + 1, imagesArrayLen)
- // self increment
- globalIndexInc()
- }
-}
-
-async function enterOverlay(): Promise {
- // stop images animation
- window.removeEventListener('mousemove', handleOnMove)
- // get index array length
- const indexesNum: number = trailingImageIndexes.length
- for (let i = 0; i < indexesNum; i++) {
- // create image element
- const e: HTMLImageElement = images[trailingImageIndexes[i]]
- // cache images' position
- transformCache.push(e.style.transform)
- // set style for the images
- if (i === indexesNum - 1) {
- e.style.transitionDelay = `${0.1 * i + 0.2}s, ${0.1 * i + 0.2 + 0.5}s`
- e.dataset.status = 'top'
- center(e)
- } else {
- e.style.transitionDelay = `${0.1 * i}s`
- e.dataset.status = 'trail'
- }
- }
- // sleep
- await delay(stackDepth * 100 + 100 + 1000)
- // post process
- for (let i = 0; i < indexesNum; i++) {
- images[trailingImageIndexes[i]].style.transitionDelay = ''
- if (i === indexesNum - 1) {
- images[trailingImageIndexes[i]].dataset.status = 'overlay'
- } else {
- images[trailingImageIndexes[i]].style.visibility = 'hidden'
- }
- }
- // Offset previous self increment of global index (by handleOnMove)
- globalIndexDec()
- // overlay init
- overlayEnable()
-}
-
-// initialization
-export const trackMouseInit = (): void => {
- window.addEventListener('mousemove', handleOnMove)
-}
-
-export const globalIndexDec = (): void => {
- globalIndex--
-}
-
-export const globalIndexInc = (): void => {
- globalIndex++
-}
-
-export const emptyTransformCache = (): void => {
- transformCache = []
-}
-
-export const emptyTrailingImageIndexes = (): void => {
- trailingImageIndexes = []
-}
-
-export const setStackDepth = (newStackDepth: number): void => {
- if (stackDepth !== newStackDepth) {
- lastStackDepth = stackDepth
- stackDepth = newStackDepth
- }
-}
-
-export const refreshStack = (): void => {
- const l: number = trailingImageIndexes.length
- if (stackDepth < lastStackDepth && l > stackDepth) {
- const times: number = l - stackDepth
- for (let i = 0; i < times; i++)
- hideImage(images[trailingImageIndexes.shift() as number])
- }
-}
diff --git a/assets/ts/elemGen.ts b/assets/ts/elemGen.ts
deleted file mode 100644
index 65963f9..0000000
--- a/assets/ts/elemGen.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { imagesArray, imagesArrayLen } from './dataFetch'
-import { createImgElement } from './utils'
-
-// get components of overlay
-export let overlayCursor: HTMLDivElement
-export let cursorInnerContent: HTMLDivElement
-export let imagesDivNodes: NodeListOf
-
-const mainDiv = document.getElementById('main') as HTMLDivElement
-
-const passDesktopElements = (): void => {
- overlayCursor = document
- .getElementsByClassName('overlay_cursor')
- .item(0) as HTMLDivElement
- cursorInnerContent = document
- .getElementsByClassName('cursor_innerText')
- .item(0) as HTMLDivElement
- imagesDivNodes = document.getElementsByClassName('imagesDesktop')[0]
- .childNodes as NodeListOf
-}
-
-const passMobileElements = (): void => {
- imagesDivNodes = document.getElementsByClassName('imagesMobile')[0]
- .childNodes as NodeListOf
-}
-
-const createCursorDiv = (): HTMLDivElement => {
- const cursorDiv: HTMLDivElement = document.createElement('div')
- cursorDiv.className = 'overlay_cursor'
- const innerTextDiv: HTMLDivElement = document.createElement('div')
- innerTextDiv.className = 'cursor_innerText'
- cursorDiv.appendChild(innerTextDiv)
- return cursorDiv
-}
-
-export const createDesktopElements = (): void => {
- mainDiv.appendChild(createCursorDiv())
- const imagesDiv: HTMLDivElement = document.createElement('div')
- imagesDiv.className = 'imagesDesktop'
- for (let i = 0; i < imagesArrayLen; i++) {
- imagesDiv.appendChild(createImgElement(imagesArray[i]))
- }
- mainDiv.appendChild(imagesDiv)
- passDesktopElements()
-}
-
-export const createMobileElements = (): void => {
- const imagesDiv: HTMLDivElement = document.createElement('div')
- imagesDiv.className = 'imagesMobile'
- for (let i = 0; i < imagesArrayLen; i++) {
- imagesDiv.appendChild(createImgElement(imagesArray[i]))
- }
- mainDiv.appendChild(imagesDiv)
- passMobileElements()
-}
diff --git a/assets/ts/imageCache.ts b/assets/ts/imageCache.ts
deleted file mode 100644
index feaa88d..0000000
--- a/assets/ts/imageCache.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { imagesArray, imagesArrayLen } from './dataFetch'
-import { preloadImage, calcImageIndex } from './utils'
-
-let lastIndex: number = 0
-
-export const preloader = (index: number): void => {
- if (lastIndex === index) {
- for (let i: number = -2; i <= 1; i++)
- preloadImage(imagesArray[calcImageIndex(index + i, imagesArrayLen)].url)
- } else if (lastIndex > index) {
- for (let i: number = 1; i <= 3; i++)
- preloadImage(imagesArray[calcImageIndex(index - i, imagesArrayLen)].url)
- } else {
- for (let i: number = 1; i <= 3; i++)
- preloadImage(imagesArray[calcImageIndex(index + i, imagesArrayLen)].url)
- }
- lastIndex = index
-}
diff --git a/assets/ts/indexDisp.ts b/assets/ts/indexDisp.ts
deleted file mode 100644
index e772230..0000000
--- a/assets/ts/indexDisp.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { duper } from './utils'
-
-// update index of displaying image
-export const imgIndexSpanUpdate = (numOne: number, numTwo: number): void => {
- // footer index number display module
- const footerIndexDisp = document.getElementsByClassName('ftid')
- const numOneString: string = duper(numOne)
- const numTwoString: string = duper(numTwo)
- for (let i: number = 0; i <= 7; i++) {
- const footerIndex = footerIndexDisp[i] as HTMLSpanElement
- if (i > 3) {
- footerIndex.innerText = numTwoString[i - 4]
- } else {
- footerIndex.innerText = numOneString[i]
- }
- }
-}
diff --git a/assets/ts/main.ts b/assets/ts/main.ts
index e1e3b1b..2d58579 100644
--- a/assets/ts/main.ts
+++ b/assets/ts/main.ts
@@ -1,28 +1,13 @@
-import { createDesktopElements, createMobileElements } from './elemGen'
-import { imgIndexSpanUpdate } from './indexDisp'
-import { trackMouseInit } from './desktop'
-import { thresholdCtlInit } from './thresholdCtl'
-import { imagesArrayLen } from './dataFetch'
-import { vwRefreshInit } from './overlay'
-import { preloader } from './imageCache'
-import { getDeviceType } from './utils'
-import { renderImages } from './mobile'
+import { initResources } from './resources'
+import { initState } from './state'
+import { initCustomCursor } from './customCursor'
+import { initNav } from './nav'
+import { initStage } from './stage'
+import { initStageNav } from './stageNav'
-const desktopInit = (): void => {
- createDesktopElements()
- preloader(0)
- vwRefreshInit()
- imgIndexSpanUpdate(0, imagesArrayLen)
- thresholdCtlInit()
- trackMouseInit()
-}
-
-const mobileInit = (): void => {
- createMobileElements()
- vwRefreshInit()
- imgIndexSpanUpdate(0, imagesArrayLen)
- renderImages()
- console.log('mobile')
-}
-
-getDeviceType().desktop ? mobileInit() : desktopInit()
+initCustomCursor()
+const ijs = initResources()
+initState(ijs.length)
+initStage(ijs)
+initStageNav()
+initNav()
diff --git a/assets/ts/mobile.ts b/assets/ts/mobile.ts
deleted file mode 100644
index 8f66752..0000000
--- a/assets/ts/mobile.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { imagesDivNodes as images } from './elemGen'
-import { imagesArrayLen } from './dataFetch'
-
-export const renderImages = (): void => {
- images.forEach((img: HTMLImageElement, idx: number): void => {
- const randomX: number = Math.floor(Math.random() * 35) + 2
- let randomY: number
-
- // random Y calculation
- if (idx === 0) {
- randomY = 68
- } else if (idx === 1) {
- randomY = 44
- } else if (idx === imagesArrayLen - 1) {
- randomY = 100
- } else {
- randomY = Math.floor(Math.random() * 51) + 2
- }
-
- img.style.transform = `translate(${randomX}vw, -${randomY}%)`
- img.style.marginTop = `${idx === 1 ? 70 : 0}vh`
- img.style.visibility = 'visible'
- })
-}
diff --git a/assets/ts/nav.ts b/assets/ts/nav.ts
new file mode 100644
index 0000000..6a8b235
--- /dev/null
+++ b/assets/ts/nav.ts
@@ -0,0 +1,59 @@
+import { getState, incThreshold, decThreshold } from './state'
+import { expand } from './utils'
+
+// threshold div
+const thresholdDiv = document
+ .getElementsByClassName('threshold')
+ .item(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[]
+
+export function initNav() {
+ // init threshold text
+ updateThresholdText()
+ // init index text
+ updateIndexText()
+ // event listeners
+ decButton.addEventListener('click', () => decThreshold())
+ incButton.addEventListener('click', () => incThreshold())
+}
+
+// helper
+
+export function updateThresholdText(): void {
+ const thresholdValue: string = expand(getState().threshold)
+ thresholdDispNums.forEach((e: HTMLSpanElement, i: number) => {
+ e.innerText = thresholdValue[i]
+ })
+}
+
+export function updateIndexText(): void {
+ const indexValue: string = expand(getState().index + 1)
+ const indexLength: string = expand(getState().length)
+ indexDispNums.forEach((e: HTMLSpanElement, i: number) => {
+ if (i < 4) {
+ e.innerText = indexValue[i]
+ } else {
+ e.innerText = indexLength[i - 4]
+ }
+ })
+}
diff --git a/assets/ts/overlay.ts b/assets/ts/overlay.ts
deleted file mode 100644
index 08e883f..0000000
--- a/assets/ts/overlay.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import { delay, center, calcImageIndex, mouseToTransform, pushIndex } from './utils'
-import {
- handleOnMove,
- globalIndex,
- globalIndexDec,
- globalIndexInc,
- trailingImageIndexes,
- transformCache,
- emptyTransformCache,
- emptyTrailingImageIndexes,
- stackDepth,
- addEnterOverlayEL
-} from './desktop'
-import { imagesArrayLen } from './dataFetch'
-import { imgIndexSpanUpdate } from './indexDisp'
-import { overlayCursor, cursorInnerContent, imagesDivNodes as images } from './elemGen'
-
-let oneThird: number = Math.round(window.innerWidth / 3)
-
-// set cursor text
-const setCursorText = (text: string): void => {
- cursorInnerContent.innerText = text
-}
-
-// overlay cursor event handler
-const setTextPos = (e: MouseEvent): void => {
- overlayCursor.style.transform = mouseToTransform(e.clientX, e.clientY, false, true)
-}
-
-// disable listeners
-const disableListener = (): void => {
- window.removeEventListener('mousemove', handleOverlayMouseMove)
- overlayCursor.removeEventListener('click', handleOverlayClick)
-}
-
-// enable overlay
-export const overlayEnable = (): void => {
- // show the overlay components
- overlayCursor.style.zIndex = '21'
- // set overlay event listeners
- setListener()
-}
-
-// disable overlay
-export const overlayDisable = (): void => {
- // hide the overlay components
- overlayCursor.style.zIndex = '-1'
- // set overlay cursor text content to none
- setCursorText('')
- // disable overlay event listeners
- disableListener()
-}
-
-// handle close click
-async function handleCloseClick(): Promise {
- // disable overlay
- overlayDisable()
- // get length of indexes and empty indexes array
- const indexesNum = trailingImageIndexes.length
- emptyTrailingImageIndexes()
- // prepare animation
- for (let i: number = 0; i < indexesNum; i++) {
- // get element from index and store the index
- const index: number = calcImageIndex(globalIndex - i, imagesArrayLen)
- const e: HTMLImageElement = images[index]
- trailingImageIndexes.unshift(index)
- // set z index for the image element
- e.style.zIndex = `${indexesNum - i - 1}`
- // set different style for trailing and top image
- if (i === 0) {
- // set position
- e.style.transform = transformCache[indexesNum - i - 1]
- // set transition delay
- e.style.transitionDelay = '0s, 0.7s'
- // set status for css
- e.dataset.status = 'resumeTop'
- } else {
- // set position
- e.style.transform = transformCache[indexesNum - i - 1]
- // set transition delay
- e.style.transitionDelay = `${1.2 + 0.1 * i - 0.1}s`
- // set status for css
- e.dataset.status = 'resume'
- }
- // style process complete, show the image
- e.style.visibility = 'visible'
- }
- // halt the function while animation is running
- await delay(1200 + stackDepth * 100 + 100)
- // add back enter overlay event listener to top image
- addEnterOverlayEL(images[calcImageIndex(globalIndex, imagesArrayLen)])
- // clear unused status and transition delay
- for (let i: number = 0; i < indexesNum; i++) {
- const index: number = calcImageIndex(globalIndex - i, imagesArrayLen)
- images[index].dataset.status = 'null'
- images[index].style.transitionDelay = ''
- }
- // Add back previous self increment of global index (by handleOnMove)
- globalIndexInc()
- // add back mousemove event listener
- window.addEventListener('mousemove', handleOnMove, { passive: true })
- // empty the position array cache
- emptyTransformCache()
-}
-
-const handleSideClick = (CLD: boolean): void => {
- // get last displayed image's index
- const imgIndex: number = calcImageIndex(globalIndex, imagesArrayLen)
- // change global index and get current displayed image's index
- CLD ? globalIndexInc() : globalIndexDec()
- const currImgIndex: number = calcImageIndex(globalIndex, imagesArrayLen)
- // store current displayed image's index
- CLD
- ? pushIndex(
- currImgIndex,
- trailingImageIndexes,
- stackDepth,
- images,
- imagesArrayLen,
- false,
- false
- )
- : pushIndex(
- currImgIndex,
- trailingImageIndexes,
- stackDepth,
- images,
- imagesArrayLen,
- true,
- false
- )
- // hide last displayed image
- images[imgIndex].style.visibility = 'hidden'
- images[imgIndex].dataset.status = 'trail'
- // process the image going to display
- center(images[currImgIndex])
- images[currImgIndex].dataset.status = 'overlay'
- // process complete, show the image
- images[currImgIndex].style.visibility = 'visible'
- // change index display
- imgIndexSpanUpdate(currImgIndex + 1, imagesArrayLen)
-}
-
-// change text and position of overlay cursor
-const handleOverlayMouseMove = (e: MouseEvent): void => {
- // set text position
- setTextPos(e)
- // set text content
- if (e.clientX < oneThird) {
- setCursorText('PREV')
- overlayCursor.dataset.status = 'PREV'
- } else if (e.clientX < oneThird * 2) {
- setCursorText('CLOSE')
- overlayCursor.dataset.status = 'CLOSE'
- } else {
- setCursorText('NEXT')
- overlayCursor.dataset.status = 'NEXT'
- }
-}
-
-const handleOverlayClick = (): void => {
- switch (overlayCursor.dataset.status) {
- case 'PREV':
- handleSideClick(false)
- break
- case 'CLOSE':
- void handleCloseClick()
- break
- case 'NEXT':
- handleSideClick(true)
- break
- }
-}
-
-// set event listener
-const setListener = (): void => {
- // add mouse move event listener (for overlay text cursor)
- window.addEventListener('mousemove', handleOverlayMouseMove, { passive: true })
- // add close/prev/next click event listener
- overlayCursor.addEventListener('click', handleOverlayClick, { passive: true })
-}
-
-export const vwRefreshInit = (): void => {
- window.addEventListener(
- 'resize',
- () => {
- // refresh value of one third
- oneThird = Math.round(window.innerWidth / 3)
- // reset footer height
- const r = document.querySelector(':root') as HTMLStyleElement
- if (window.innerWidth > 768) {
- r.style.setProperty('--footer-height', '38px')
- } else {
- r.style.setProperty('--footer-height', '31px')
- }
- // recenter image (only in overlay)
- if (
- images[calcImageIndex(globalIndex, imagesArrayLen)].dataset.status === 'overlay'
- )
- center(images[calcImageIndex(globalIndex, imagesArrayLen)])
- },
- { passive: true }
- )
-}
diff --git a/assets/ts/resources.ts b/assets/ts/resources.ts
new file mode 100644
index 0000000..4eb0b06
--- /dev/null
+++ b/assets/ts/resources.ts
@@ -0,0 +1,21 @@
+// data structure for images info
+export interface ImageJSON {
+ index: number
+ url: string
+ imgH: number
+ imgW: number
+ pColor: string
+ sColor: string
+}
+
+export function initResources(): ImageJSON[] {
+ const imagesJson = document.getElementById('imagesSource') as HTMLScriptElement
+ return JSON.parse(imagesJson.textContent as string).sort(
+ (a: ImageJSON, b: ImageJSON) => {
+ if (a.index < b.index) {
+ return -1
+ }
+ return 1
+ }
+ )
+}
diff --git a/assets/ts/stage.ts b/assets/ts/stage.ts
new file mode 100644
index 0000000..8b22caf
--- /dev/null
+++ b/assets/ts/stage.ts
@@ -0,0 +1,184 @@
+import { incIndex, getState } from './state'
+import { gsap, Power3 } from 'gsap'
+import { ImageJSON } from './resources'
+import { Watchable } from './utils'
+
+// types
+
+export type HistoryItem = { i: number; x: number; y: number }
+
+// variables
+
+let imgs: HTMLImageElement[] = []
+let last = { x: 0, y: 0 }
+export const cordHist = new Watchable([])
+export const isOpen = new Watchable(false)
+export const isAnimating = new Watchable(false)
+export const active = new Watchable(false)
+
+// getter
+
+function getElTrail(): HTMLImageElement[] {
+ return cordHist.get().map((item) => imgs[item.i])
+}
+
+function getElTrailCurrent(): HTMLImageElement[] {
+ return getElTrail().slice(-getState().trailLength)
+}
+
+function getElTrailInactive(): HTMLImageElement[] {
+ const elTrailCurrent = getElTrailCurrent()
+ return elTrailCurrent.slice(0, elTrailCurrent.length - 1)
+}
+
+function getElCurrent(): HTMLImageElement {
+ const elTrail = getElTrail()
+ return elTrail[elTrail.length - 1]
+}
+
+// main functions
+
+// on mouse
+function onMouse(e: MouseEvent): void {
+ if (isOpen.get() || isAnimating.get()) return
+ const cord = { x: e.clientX, y: e.clientY }
+ const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y)
+
+ if (travelDist > getState().threshold) {
+ last = cord
+ incIndex()
+
+ const newHist = { i: getState().index, ...cord }
+ cordHist.set([...cordHist.get(), newHist].slice(-getState().length))
+ }
+}
+
+// set image position with gsap
+function setPositions(): void {
+ const elTrail = getElTrail()
+ if (!elTrail.length) return
+
+ gsap.set(elTrail, {
+ x: (i: number) => cordHist.get()[i].x - window.innerWidth / 2,
+ y: (i: number) => cordHist.get()[i].y - window.innerHeight / 2,
+ opacity: (i: number) =>
+ i + 1 + getState().trailLength <= cordHist.get().length ? 0 : 1,
+ zIndex: (i: number) => i,
+ scale: 0.6
+ })
+
+ if (isOpen.get()) {
+ gsap.set(imgs, { opacity: 0 })
+ gsap.set(getElCurrent(), { opacity: 1, x: 0, y: 0, scale: 1 })
+ }
+}
+
+// open image into navigation
+function expandImage(): void {
+ if (isAnimating.get()) return
+
+ isOpen.set(true)
+ isAnimating.set(true)
+
+ const tl = gsap.timeline()
+ // move down and hide trail inactive
+ tl.to(getElTrailInactive(), {
+ y: '+=20',
+ ease: Power3.easeIn,
+ stagger: 0.075,
+ duration: 0.3,
+ delay: 0.1,
+ opacity: 0
+ })
+ // current move to center
+ tl.to(getElCurrent(), {
+ x: 0,
+ y: 0,
+ ease: Power3.easeInOut,
+ duration: 0.7,
+ delay: 0.3
+ })
+ // current expand
+ tl.to(getElCurrent(), {
+ delay: 0.1,
+ scale: 1,
+ ease: Power3.easeInOut
+ })
+ // finished
+ tl.then(() => {
+ isAnimating.set(false)
+ })
+}
+
+// close navigation and back to stage
+export function minimizeImage(): void {
+ if (isAnimating.get()) return
+
+ isOpen.set(false)
+ isAnimating.set(true)
+
+ const tl = gsap.timeline()
+ // shrink current
+ tl.to(getElCurrent(), {
+ scale: 0.6,
+ duration: 0.6,
+ ease: Power3.easeInOut
+ })
+ // move current to original position
+ tl.to(getElCurrent(), {
+ delay: 0.3,
+ duration: 0.7,
+ ease: Power3.easeInOut,
+ x: cordHist.get()[cordHist.get().length - 1].x - window.innerWidth / 2,
+ y: cordHist.get()[cordHist.get().length - 1].y - window.innerHeight / 2
+ })
+ // show trail inactive
+ tl.to(getElTrailInactive(), {
+ y: '-=20',
+ ease: Power3.easeOut,
+ stagger: -0.1,
+ duration: 0.3,
+ opacity: 1
+ })
+ // finished
+ tl.then(() => {
+ isAnimating.set(false)
+ })
+}
+
+// init
+
+export function initStage(ijs: ImageJSON[]): void {
+ // create stage element
+ createStage(ijs)
+ // get stage
+ const stage = document.getElementsByClassName('stage').item(0) as HTMLDivElement
+ // get image elements
+ imgs = Array.from(stage.getElementsByTagName('img'))
+ // event listeners
+ stage.addEventListener('click', () => expandImage())
+ stage.addEventListener('keydown', () => expandImage())
+ window.addEventListener('mousemove', onMouse)
+ // watchers
+ isOpen.addWatcher(() => active.set(isOpen.get() && !isAnimating.get()))
+ isAnimating.addWatcher(() => active.set(isOpen.get() && !isAnimating.get()))
+ cordHist.addWatcher(() => setPositions())
+}
+
+// hepler
+
+function createStage(ijs: ImageJSON[]): void {
+ // create container for images
+ const stage: HTMLDivElement = document.createElement('div')
+ stage.className = 'stage'
+ // append images to container
+ for (let ij of ijs) {
+ const e = document.createElement('img')
+ e.src = ij.url
+ e.height = ij.imgH
+ e.width = ij.imgW
+ e.alt = 'image'
+ stage.append(e)
+ }
+ document.getElementById('main')!.append(stage)
+}
diff --git a/assets/ts/stageNav.ts b/assets/ts/stageNav.ts
new file mode 100644
index 0000000..f37009c
--- /dev/null
+++ b/assets/ts/stageNav.ts
@@ -0,0 +1,87 @@
+import { setCustomCursor } from './customCursor'
+import { decIndex, incIndex, getState } from './state'
+import { increment, decrement } from './utils'
+import { cordHist, isOpen, isAnimating, active, minimizeImage } from './stage'
+
+type NavItem = (typeof navItems)[number]
+const navItems = ['prev', 'close', 'next'] as const
+
+// main functions
+
+function handleClick(type: NavItem) {
+ switch (type) {
+ case 'prev':
+ prevImage()
+ break
+ case 'close':
+ minimizeImage()
+ break
+ case 'next':
+ nextImage()
+ break
+ }
+}
+
+function handleKey(e: KeyboardEvent) {
+ if (isOpen.get() || isAnimating.get()) return
+ switch (e.key) {
+ case 'ArrowLeft':
+ prevImage()
+ break
+ case 'Escape':
+ minimizeImage()
+ break
+ case 'ArrowRight':
+ nextImage()
+ break
+ }
+}
+
+// init
+
+export function initStageNav() {
+ const navOverlay = document.createElement('div')
+ navOverlay.className = 'navOverlay'
+ for (let navItem of navItems) {
+ const overlay = document.createElement('div')
+ overlay.className = 'overlay'
+ overlay.addEventListener('click', () => handleClick(navItem))
+ overlay.addEventListener('keydown', () => handleClick(navItem))
+ overlay.addEventListener('mouseover', () => setCustomCursor(navItem))
+ overlay.addEventListener('focus', () => setCustomCursor(navItem))
+ navOverlay.append(overlay)
+ }
+ active.addWatcher(() => {
+ if (active.get()) {
+ navOverlay.classList.add('active')
+ } else {
+ navOverlay.classList.remove('active')
+ }
+ })
+ document.getElementById('main')!.append(navOverlay)
+ window.addEventListener('keydown', handleKey)
+}
+
+// hepler
+
+function nextImage() {
+ if (isAnimating.get()) return
+ cordHist.set(
+ cordHist.get().map((item) => {
+ return { ...item, i: increment(item.i, getState().length) }
+ })
+ )
+
+ incIndex()
+}
+
+function prevImage() {
+ if (isAnimating.get()) return
+ cordHist.set(
+ cordHist.get().map((item) => {
+ return { ...item, i: decrement(item.i, getState().length) }
+ })
+ )
+
+ decIndex()
+}
diff --git a/assets/ts/state.ts b/assets/ts/state.ts
new file mode 100644
index 0000000..4aebfe9
--- /dev/null
+++ b/assets/ts/state.ts
@@ -0,0 +1,68 @@
+import { increment, decrement } from './utils'
+import { updateIndexText, updateThresholdText } from './nav'
+
+const thresholds = [
+ { threshold: 20, trailLength: 20 },
+ { threshold: 40, trailLength: 10 },
+ { threshold: 80, trailLength: 5 },
+ { threshold: 140, trailLength: 5 },
+ { threshold: 200, trailLength: 5 }
+]
+
+const defaultState = {
+ index: -1,
+ length: 0,
+ threshold: thresholds[2].threshold,
+ trailLength: thresholds[2].trailLength
+}
+
+export type State = typeof defaultState
+
+let state = defaultState
+
+export function getState(): State {
+ // return a copy of state
+ return Object.create(
+ Object.getPrototypeOf(state),
+ Object.getOwnPropertyDescriptors(state)
+ )
+}
+
+export function initState(length: number): void {
+ state.length = length
+}
+
+export function setIndex(index: number): void {
+ state.index = index
+ updateIndexText()
+}
+
+export function incIndex(): void {
+ state.index = increment(state.index, state.length)
+ updateIndexText()
+}
+
+export function decIndex(): void {
+ state.index = decrement(state.index, state.length)
+ updateIndexText()
+}
+
+export function incThreshold(): void {
+ state = updateThreshold(state, 1)
+ updateThresholdText()
+}
+
+export function decThreshold(): void {
+ state = updateThreshold(state, -1)
+ updateThresholdText()
+}
+
+// helper
+
+function updateThreshold(state: State, inc: number): State {
+ const i = thresholds.findIndex((t) => state.threshold === t.threshold)
+ const newItems = thresholds[i + inc]
+ // out of range
+ if (!newItems) return state
+ return { ...state, ...newItems }
+}
diff --git a/assets/ts/thresholdCtl.ts b/assets/ts/thresholdCtl.ts
deleted file mode 100644
index 30f2a21..0000000
--- a/assets/ts/thresholdCtl.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { duper } from './utils'
-import { setStackDepth } from './desktop'
-
-// get threshold display element
-const thresholdDisp = document.getElementsByClassName('thid').item(0) as HTMLSpanElement
-
-// threshold data
-const threshold: number[] = [0, 40, 80, 120, 160, 200]
-export const thresholdSensitivityArray: number[] = [100, 40, 18, 14, 9, 5]
-export let thresholdIndex: number = 2
-
-export const stackDepthArray: number[] = [15, 8, 5, 5, 5, 5]
-
-// update inner text of threshold display element
-const thresholdUpdate = (): void => {
- thresholdDisp.innerText = duper(threshold[thresholdIndex])
-}
-
-const stackDepthUpdate = (): void => {
- setStackDepth(stackDepthArray[thresholdIndex])
-}
-
-// threshold control initialization
-export const thresholdCtlInit = (): void => {
- thresholdUpdate()
- const dec = document.getElementById('thresholdDec') as HTMLButtonElement
- dec.addEventListener(
- 'click',
- function () {
- if (thresholdIndex > 0) {
- thresholdIndex--
- thresholdUpdate()
- stackDepthUpdate()
- }
- },
- { passive: true }
- )
-
- const inc = document.getElementById('thresholdInc') as HTMLButtonElement
- inc.addEventListener(
- 'click',
- function () {
- if (thresholdIndex < 5) {
- thresholdIndex++
- thresholdUpdate()
- stackDepthUpdate()
- }
- },
- { passive: true }
- )
-}
diff --git a/assets/ts/utils.ts b/assets/ts/utils.ts
index 04da9fa..e2e21e7 100644
--- a/assets/ts/utils.ts
+++ b/assets/ts/utils.ts
@@ -1,145 +1,33 @@
-export interface ImageData {
- index: string
- url: string
- imgH: string
- imgW: string
- pColor: string
- sColor: string
+export function increment(num: number, length: number): number {
+ return (num + 1) % length
}
-export interface position {
- x: number
- y: number
+export function decrement(num: number, length: number): number {
+ return (num + length - 1) % length
}
-export interface deviceType {
- mobile: boolean
- tablet: boolean
- desktop: boolean
-}
-
-// 0 to 0001, 25 to 0025
-export const duper = (num: number): string => {
+export function expand(num: number): string {
return ('0000' + num.toString()).slice(-4)
}
-export const mouseToTransform = (
- x: number,
- y: number,
- centerCorrection: boolean = true,
- accelerate: boolean = false
-): string => {
- return `translate${accelerate ? '3d' : ''}(${
- centerCorrection ? `calc(${x}px - 50%)` : `${x}px`
- }, ${centerCorrection ? `calc(${y}px - 50%)` : `${y}px`}${accelerate ? ', 0' : ''})`
+export function isMobile(): boolean {
+ return window.matchMedia('(hover: none)').matches
}
-// eslint-disable-next-line @typescript-eslint/promise-function-async
-export function delay(ms: number): Promise {
- return new Promise((resolve) => setTimeout(resolve, ms))
-}
+export class Watchable {
+ constructor(private obj: T) {}
+ private watchers: (() => void)[] = []
-// remove all event listeners from a node
-export const removeAllEventListeners = (e: Node): Node => {
- return e.cloneNode(true)
-}
-
-// center top div
-export const center = (e: HTMLElement): void => {
- const x: number = window.innerWidth / 2
- let y: number
- if (window.innerWidth > 768) {
- y = (window.innerHeight - 38) / 2
- } else {
- y = (window.innerHeight - 31) / 2 + 31
+ get(): T {
+ return this.obj
}
- e.style.transform = mouseToTransform(x, y)
-}
-export const createImgElement = (input: ImageData): HTMLImageElement => {
- const img = document.createElement('img')
- img.setAttribute('src', input.url)
- img.setAttribute('alt', '')
- img.setAttribute('height', input.imgH)
- img.setAttribute('width', input.imgW)
- img.style.visibility = 'hidden'
- img.dataset.status = 'trail'
- // img.style.backgroundImage = `linear-gradient(15deg, ${input.pColor}, ${input.sColor})`
- return img
-}
+ set(e: T): void {
+ this.obj = e
+ this.watchers.forEach((watcher) => watcher())
+ }
-export const calcImageIndex = (index: number, imgCounts: number): number => {
- if (index >= 0) {
- return index % imgCounts
- } else {
- return (imgCounts + (index % imgCounts)) % imgCounts
+ addWatcher(watcher: () => void): void {
+ this.watchers.push(watcher)
}
}
-
-export const preloadImage = (src: string): void => {
- const cache = new Image()
- cache.src = src
-}
-
-export const getDeviceType = (): deviceType => {
- const ua: string = navigator.userAgent
- const result: deviceType = { mobile: false, tablet: false, desktop: false }
- if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
- result.mobile = true
- result.tablet = true
- } else if (
- /Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(
- ua
- )
- ) {
- result.mobile = true
- } else result.desktop = true
- return result
-}
-
-export const hideImage = (e: HTMLImageElement): void => {
- e.style.visibility = 'hidden'
- e.dataset.status = 'trail'
-}
-
-export const pushIndex = (
- index: number,
- indexesArray: number[],
- stackDepth: number,
- imagesArray: NodeListOf,
- imagesArrayLen: number,
- invertFlag: boolean = false,
- autoHideFlag: boolean = true
-): number => {
- let indexesNum: number = indexesArray.length
- // create variable overflow to store the tail index
- let overflow: number
- if (!invertFlag) {
- // if stack is full, push the tail index out and hide the image
- if (indexesNum === stackDepth) {
- // insert
- indexesArray.push(index)
- // pop out
- overflow = indexesArray.shift() as number
- // auto hide tail image
- if (autoHideFlag) hideImage(imagesArray[overflow])
- } else {
- indexesArray.push(index)
- indexesNum += 1
- }
- } else {
- // if stack is full, push the tail index out and hide the image
- if (indexesNum === stackDepth) {
- // insert
- indexesArray.unshift(calcImageIndex(index - stackDepth + 1, imagesArrayLen))
- // pop out
- overflow = indexesArray.pop() as number
- // auto hide tail image
- if (autoHideFlag) hideImage(imagesArray[overflow])
- } else {
- indexesArray.unshift(calcImageIndex(index - indexesNum + 1, imagesArrayLen))
- indexesNum += 1
- }
- }
- return indexesNum
-}
diff --git a/layouts/index.html b/layouts/index.html
index edb54b2..9fe8cfc 100644
--- a/layouts/index.html
+++ b/layouts/index.html
@@ -1,43 +1,42 @@
-
+
-
-
-
- {{ partial "head.html" . }}
- {{ .Title }}
-
-
-
- {{ partial "header.html" . }}
-
-