mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-18 20:19:30 -07:00
#
# <type><package>: <subject> {needed}
#
# <body> {optional}
#
# <footer> {optional}
#
# example:
# feat(login): implementation login api function
#
# finished login module and integration with server login api
#
# <Type>
# feat: new feature
# fix: bug fix
# docs: docs only changes
# style: style changes
# refactor: feature refactor
# perf: performance optimize
# test: test related changes
# build: build related changes
# ci: ci related changes
# chore: changes not related to src or test files
# revert: reverts a previous commit
#
# <Subject>
# describe all major changes briefly
#
# <Body>
# detailed info on major changes
#
This commit is contained in:
5
.idea/.gitignore
generated
vendored
5
.idea/.gitignore
generated
vendored
@@ -1,5 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
12
.idea/bridget.iml
generated
12
.idea/bridget.iml
generated
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
93
.idea/codeStyles/Project.xml
generated
93
.idea/codeStyles/Project.xml
generated
@@ -1,93 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<HTMLCodeStyleSettings>
|
|
||||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
|
||||||
<option name="HTML_QUOTE_STYLE" value="Single" />
|
|
||||||
<option name="HTML_ENFORCE_QUOTES" value="true" />
|
|
||||||
</HTMLCodeStyleSettings>
|
|
||||||
<JSCodeStyleSettings version="0">
|
|
||||||
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
|
||||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
|
||||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
|
||||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
|
||||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
|
||||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
|
||||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
|
||||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
|
||||||
</JSCodeStyleSettings>
|
|
||||||
<TypeScriptCodeStyleSettings version="0">
|
|
||||||
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
|
||||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
|
||||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
|
||||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
|
||||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
|
||||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
|
||||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
|
||||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
|
||||||
</TypeScriptCodeStyleSettings>
|
|
||||||
<VueCodeStyleSettings>
|
|
||||||
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
|
||||||
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
|
||||||
</VueCodeStyleSettings>
|
|
||||||
<codeStyleSettings language="CSS">
|
|
||||||
<indentOptions>
|
|
||||||
<option name="INDENT_SIZE" value="2" />
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
|
||||||
<option name="TAB_SIZE" value="2" />
|
|
||||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="HTML">
|
|
||||||
<option name="SOFT_MARGINS" value="88" />
|
|
||||||
<indentOptions>
|
|
||||||
<option name="INDENT_SIZE" value="2" />
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
||||||
<option name="TAB_SIZE" value="2" />
|
|
||||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="JavaScript">
|
|
||||||
<option name="SOFT_MARGINS" value="88" />
|
|
||||||
<indentOptions>
|
|
||||||
<option name="INDENT_SIZE" value="2" />
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
||||||
<option name="TAB_SIZE" value="2" />
|
|
||||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="LESS">
|
|
||||||
<indentOptions>
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
|
||||||
<option name="TAB_SIZE" value="2" />
|
|
||||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="SASS">
|
|
||||||
<indentOptions>
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
|
||||||
<option name="TAB_SIZE" value="2" />
|
|
||||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="SCSS">
|
|
||||||
<indentOptions>
|
|
||||||
<option name="TAB_SIZE" value="2" />
|
|
||||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="TypeScript">
|
|
||||||
<option name="SOFT_MARGINS" value="88" />
|
|
||||||
<indentOptions>
|
|
||||||
<option name="INDENT_SIZE" value="2" />
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
||||||
<option name="TAB_SIZE" value="2" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="Vue">
|
|
||||||
<option name="SOFT_MARGINS" value="88" />
|
|
||||||
<indentOptions>
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
||||||
</indentOptions>
|
|
||||||
</codeStyleSettings>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
6
.idea/jsLibraryMappings.xml
generated
6
.idea/jsLibraryMappings.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="JavaScriptLibraryMappings">
|
|
||||||
<includedPredefinedLibrary name="Node.js Core" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/jsLinters/eslint.xml
generated
6
.idea/jsLinters/eslint.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="EslintConfiguration">
|
|
||||||
<custom-configuration-file used="true" path="$PROJECT_DIR$/.eslintrc.json" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/bridget.iml" filepath="$PROJECT_DIR$/.idea/bridget.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
4
.idea/watcherTasks.xml
generated
4
.idea/watcherTasks.xml
generated
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectTasksOptions" suppressed-tasks="SCSS" />
|
|
||||||
</project>
|
|
||||||
@@ -1,9 +1,18 @@
|
|||||||
{
|
{
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"printWidth": 88,
|
"printWidth": 88,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
"semi": false
|
"semi": false,
|
||||||
|
"plugins": ["prettier-plugin-go-template"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"options": {
|
||||||
|
"parser": "go-template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
html {
|
* {
|
||||||
font-family: $global-font-family;
|
-webkit-font-smoothing: antialiased;
|
||||||
overflow: hidden;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
position: relative;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: white;
|
user-select: none;
|
||||||
height: 100vh;
|
background: white;
|
||||||
margin: 0;
|
}
|
||||||
overflow: hidden;
|
|
||||||
line-height: 1.5;
|
html,
|
||||||
}
|
body {
|
||||||
|
overscroll-behavior-y: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
6
assets/css/_core/_font.scss
Normal file
6
assets/css/_core/_font.scss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: HelveticaNow;
|
||||||
|
src: url('/fonts/HelveticaNowText-Regular.woff') format('woff');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
28
assets/css/_core/_mixins.scss
Normal file
28
assets/css/_core/_mixins.scss
Normal file
@@ -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)}.";
|
||||||
|
}
|
||||||
|
}
|
||||||
103
assets/css/_core/_reset.scss
Normal file
103
assets/css/_core/_reset.scss
Normal file
@@ -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 <body> 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;
|
||||||
|
}
|
||||||
14
assets/css/_core/_typography.scss
Normal file
14
assets/css/_core/_typography.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
assets/css/_partial/_customCursor.scss
Normal file
21
assets/css/_partial/_customCursor.scss
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
44
assets/css/_partial/_nav.scss
Normal file
44
assets/css/_partial/_nav.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
assets/css/_partial/_stage.scss
Normal file
22
assets/css/_partial/_stage.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
assets/css/_partial/_stageNav.scss
Normal file
21
assets/css/_partial/_stageNav.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
// ==============================
|
@import '_core/mixins';
|
||||||
// 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;
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--footer-height: 38px;
|
--window-height: 100vh;
|
||||||
}
|
--nav-height: 2rem;
|
||||||
|
--space-standard: 0.625rem;
|
||||||
|
|
||||||
|
--z-curtain: 200;
|
||||||
|
--z-nav-gallery: 500;
|
||||||
|
--z-cursor: 600;
|
||||||
|
--z-nav: 800;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
@charset "utf-8";
|
@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 '_partial/customCursor';
|
||||||
|
@import '_partial/nav';
|
||||||
@import "_core/media";
|
@import '_partial/stage';
|
||||||
|
@import '_partial/stageNav';
|
||||||
@import "_partial/imagesDesktop";
|
|
||||||
|
|
||||||
@import "_partial/imagesMobile";
|
|
||||||
|
|
||||||
@import "_partial/footer";
|
|
||||||
|
|
||||||
@import "_partial/overlay";
|
|
||||||
|
|||||||
38
assets/ts/customCursor.ts
Normal file
38
assets/ts/customCursor.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
@@ -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<void> {
|
|
||||||
// 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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<HTMLImageElement>
|
|
||||||
|
|
||||||
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<HTMLImageElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
const passMobileElements = (): void => {
|
|
||||||
imagesDivNodes = document.getElementsByClassName('imagesMobile')[0]
|
|
||||||
.childNodes as NodeListOf<HTMLImageElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,13 @@
|
|||||||
import { createDesktopElements, createMobileElements } from './elemGen'
|
import { initResources } from './resources'
|
||||||
import { imgIndexSpanUpdate } from './indexDisp'
|
import { initState } from './state'
|
||||||
import { trackMouseInit } from './desktop'
|
import { initCustomCursor } from './customCursor'
|
||||||
import { thresholdCtlInit } from './thresholdCtl'
|
import { initNav } from './nav'
|
||||||
import { imagesArrayLen } from './dataFetch'
|
import { initStage } from './stage'
|
||||||
import { vwRefreshInit } from './overlay'
|
import { initStageNav } from './stageNav'
|
||||||
import { preloader } from './imageCache'
|
|
||||||
import { getDeviceType } from './utils'
|
|
||||||
import { renderImages } from './mobile'
|
|
||||||
|
|
||||||
const desktopInit = (): void => {
|
initCustomCursor()
|
||||||
createDesktopElements()
|
const ijs = initResources()
|
||||||
preloader(0)
|
initState(ijs.length)
|
||||||
vwRefreshInit()
|
initStage(ijs)
|
||||||
imgIndexSpanUpdate(0, imagesArrayLen)
|
initStageNav()
|
||||||
thresholdCtlInit()
|
initNav()
|
||||||
trackMouseInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const mobileInit = (): void => {
|
|
||||||
createMobileElements()
|
|
||||||
vwRefreshInit()
|
|
||||||
imgIndexSpanUpdate(0, imagesArrayLen)
|
|
||||||
renderImages()
|
|
||||||
console.log('mobile')
|
|
||||||
}
|
|
||||||
|
|
||||||
getDeviceType().desktop ? mobileInit() : desktopInit()
|
|
||||||
|
|||||||
@@ -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'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
59
assets/ts/nav.ts
Normal file
59
assets/ts/nav.ts
Normal file
@@ -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]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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<void> {
|
|
||||||
// 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 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
21
assets/ts/resources.ts
Normal file
21
assets/ts/resources.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
184
assets/ts/stage.ts
Normal file
184
assets/ts/stage.ts
Normal file
@@ -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<HistoryItem[]>([])
|
||||||
|
export const isOpen = new Watchable<boolean>(false)
|
||||||
|
export const isAnimating = new Watchable<boolean>(false)
|
||||||
|
export const active = new Watchable<boolean>(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)
|
||||||
|
}
|
||||||
87
assets/ts/stageNav.ts
Normal file
87
assets/ts/stageNav.ts
Normal file
@@ -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()
|
||||||
|
}
|
||||||
68
assets/ts/state.ts
Normal file
68
assets/ts/state.ts
Normal file
@@ -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 }
|
||||||
|
}
|
||||||
@@ -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 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,145 +1,33 @@
|
|||||||
export interface ImageData {
|
export function increment(num: number, length: number): number {
|
||||||
index: string
|
return (num + 1) % length
|
||||||
url: string
|
|
||||||
imgH: string
|
|
||||||
imgW: string
|
|
||||||
pColor: string
|
|
||||||
sColor: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface position {
|
export function decrement(num: number, length: number): number {
|
||||||
x: number
|
return (num + length - 1) % length
|
||||||
y: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface deviceType {
|
export function expand(num: number): string {
|
||||||
mobile: boolean
|
|
||||||
tablet: boolean
|
|
||||||
desktop: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0 to 0001, 25 to 0025
|
|
||||||
export const duper = (num: number): string => {
|
|
||||||
return ('0000' + num.toString()).slice(-4)
|
return ('0000' + num.toString()).slice(-4)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mouseToTransform = (
|
export function isMobile(): boolean {
|
||||||
x: number,
|
return window.matchMedia('(hover: none)').matches
|
||||||
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' : ''})`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
export class Watchable<T> {
|
||||||
export function delay(ms: number): Promise<void> {
|
constructor(private obj: T) {}
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
private watchers: (() => void)[] = []
|
||||||
}
|
|
||||||
|
|
||||||
// remove all event listeners from a node
|
get(): T {
|
||||||
export const removeAllEventListeners = (e: Node): Node => {
|
return this.obj
|
||||||
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
|
|
||||||
}
|
}
|
||||||
e.style.transform = mouseToTransform(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createImgElement = (input: ImageData): HTMLImageElement => {
|
set(e: T): void {
|
||||||
const img = document.createElement('img')
|
this.obj = e
|
||||||
img.setAttribute('src', input.url)
|
this.watchers.forEach((watcher) => watcher())
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
export const calcImageIndex = (index: number, imgCounts: number): number => {
|
addWatcher(watcher: () => void): void {
|
||||||
if (index >= 0) {
|
this.watchers.push(watcher)
|
||||||
return index % imgCounts
|
|
||||||
} else {
|
|
||||||
return (imgCounts + (index % imgCounts)) % imgCounts
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<HTMLImageElement>,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,43 +1,42 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
{{ partial "head.html" . }}
|
{{ partial "head.html" . }}
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
{{ partial "header.html" . }}
|
{{ partial "header.html" . }}
|
||||||
</header>
|
</header>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
{{ $sourcePath := "images" }}
|
{{ $sourcePath := "images" }}
|
||||||
{{ $gallery := site.GetPage $sourcePath }}
|
{{ $gallery := site.GetPage $sourcePath }}
|
||||||
{{ with $gallery.Resources.ByType "image" }}
|
{{ with $gallery.Resources.ByType "image" }}
|
||||||
{{ $index := len . }}
|
{{ $index := len . }}
|
||||||
{{ $.Scratch.Add "img" slice }}
|
{{ $.Scratch.Add "img" slice }}
|
||||||
{{ range . }}
|
{{ range . }}
|
||||||
{{ $index = sub $index 1}}
|
{{ $index = sub $index 1 }}
|
||||||
{{ $colors := .Colors }}
|
{{ $colors := .Colors }}
|
||||||
{{ $pColor := index $colors 0 }}
|
{{ $pColor := index $colors 0 }}
|
||||||
{{ $sColor := "#ccc" }}
|
{{ $sColor := "#ccc" }}
|
||||||
{{ if gt (len $colors) 1 }}
|
{{ if gt (len $colors) 1 }}
|
||||||
{{ $sColor = index $colors 1 }}
|
{{ $sColor = index $colors 1 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $resize := .Resize "x2000 webp Lanczos q70" }}
|
||||||
|
{{ $.Scratch.Add "img" (dict
|
||||||
|
"index" (int $index)
|
||||||
|
"url" (string .RelPermalink)
|
||||||
|
"imgH" (int .Height)
|
||||||
|
"imgW" (int .Width)
|
||||||
|
"pColor" (string $pColor)
|
||||||
|
"sColor" (string $sColor))
|
||||||
|
}}
|
||||||
|
{{ end }}
|
||||||
|
<script id="imagesSource" type="application/json">{{ $.Scratch.Get "img" | jsonify | safeJS }}</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ $resize := .Resize "x2000 webp Lanczos q70" }}
|
</div>
|
||||||
{{ $.Scratch.Add "img" (dict
|
{{ partial "nav.html" . }}
|
||||||
"index" (string $index)
|
</body>
|
||||||
"url" (string .RelPermalink)
|
|
||||||
"imgH" (string .Height)
|
|
||||||
"imgW" (string .Width)
|
|
||||||
"pColor" (string $pColor)
|
|
||||||
"sColor" (string $sColor)) }}
|
|
||||||
{{ end }}
|
|
||||||
<script id="images_array" type="application/json">{{ $.Scratch.Get "img" | jsonify | safeJS }}</script>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
{{ partial "footer.html" . }}
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
{{- $options := dict "targetPath" "css/style.min.css" "enableSourceMap" true -}}
|
{{- $options := dict "targetPath" "css/style.min.css" "enableSourceMap" true -}}
|
||||||
{{- $style = dict "Context" . "ToCSS" $options | merge $style -}}
|
{{- $style = dict "Context" . "ToCSS" $options | merge $style -}}
|
||||||
{{- partial "plugin/style.html" $style -}}
|
{{- partial "plugin/style.html" $style -}}
|
||||||
|
{{- $esBuildOpts := dict "minify" hugo.IsProduction -}}
|
||||||
|
|
||||||
{{ $script := resources.Get "ts/main.ts" | js.Build }}
|
{{- $script := resources.Get "ts/main.ts" | js.Build $esBuildOpts -}}
|
||||||
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
||||||
|
|||||||
27
layouts/partials/nav.html
Normal file
27
layouts/partials/nav.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<nav>
|
||||||
|
<div class="navArtist">
|
||||||
|
<a href="/">Bridget Baker</a>
|
||||||
|
</div>
|
||||||
|
<div class="links">
|
||||||
|
<span class="link">Featured</span>
|
||||||
|
<span class="link">iPhone</span>
|
||||||
|
<span class="link">Film</span>
|
||||||
|
<span class="link">Info</span>
|
||||||
|
</div>
|
||||||
|
<div class="threshold">
|
||||||
|
<span>Threshold:</span>
|
||||||
|
<span>
|
||||||
|
<button class="dec">-</button>
|
||||||
|
<span class="num"></span><span class="num"></span><span class="num"></span
|
||||||
|
><span class="num"></span>
|
||||||
|
<button class="inc">+</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="index">
|
||||||
|
<span class="num"></span><span class="num"></span><span class="num"></span
|
||||||
|
><span class="num"></span>
|
||||||
|
<span>/</span>
|
||||||
|
<span class="num"></span><span class="num"></span><span class="num"></span
|
||||||
|
><span class="num"></span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
@@ -37,6 +37,10 @@
|
|||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
"prettier": "3.0.3",
|
"prettier": "3.0.3",
|
||||||
|
"prettier-plugin-go-template": "^0.0.15",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"gsap": "^3.12.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2088
pnpm-lock.yaml
generated
Normal file
2088
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
static/fonts/HelveticaNowText-Regular.woff
Normal file
BIN
static/fonts/HelveticaNowText-Regular.woff
Normal file
Binary file not shown.
@@ -8,8 +8,9 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
},
|
},
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"display": "Recommended"
|
"display": "Recommended"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user