mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-14 10:09:31 -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,
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
user-select: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
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 @@
|
||||
// ==============================
|
||||
// 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;
|
||||
}
|
||||
--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";
|
||||
|
||||
@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";
|
||||
@import '_partial/customCursor';
|
||||
@import '_partial/nav';
|
||||
@import '_partial/stage';
|
||||
@import '_partial/stageNav';
|
||||
|
||||
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 { 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()
|
||||
|
||||
@@ -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 {
|
||||
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<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
export class Watchable<T> {
|
||||
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<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">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
{{ partial "head.html" . }}
|
||||
<title>{{ .Title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
{{ partial "header.html" . }}
|
||||
</header>
|
||||
<div id="main">
|
||||
{{ $sourcePath := "images" }}
|
||||
{{ $gallery := site.GetPage $sourcePath }}
|
||||
{{ with $gallery.Resources.ByType "image" }}
|
||||
{{ $index := len . }}
|
||||
{{ $.Scratch.Add "img" slice }}
|
||||
{{ range . }}
|
||||
{{ $index = sub $index 1}}
|
||||
{{ $colors := .Colors }}
|
||||
{{ $pColor := index $colors 0 }}
|
||||
{{ $sColor := "#ccc" }}
|
||||
{{ if gt (len $colors) 1 }}
|
||||
{{ $sColor = index $colors 1 }}
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{{ partial "head.html" . }}
|
||||
<title>{{ .Title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
{{ partial "header.html" . }}
|
||||
</header>
|
||||
<div id="main">
|
||||
{{ $sourcePath := "images" }}
|
||||
{{ $gallery := site.GetPage $sourcePath }}
|
||||
{{ with $gallery.Resources.ByType "image" }}
|
||||
{{ $index := len . }}
|
||||
{{ $.Scratch.Add "img" slice }}
|
||||
{{ range . }}
|
||||
{{ $index = sub $index 1 }}
|
||||
{{ $colors := .Colors }}
|
||||
{{ $pColor := index $colors 0 }}
|
||||
{{ $sColor := "#ccc" }}
|
||||
{{ if gt (len $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 }}
|
||||
{{ $resize := .Resize "x2000 webp Lanczos q70" }}
|
||||
{{ $.Scratch.Add "img" (dict
|
||||
"index" (string $index)
|
||||
"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>
|
||||
</div>
|
||||
{{ partial "nav.html" . }}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
{{- $options := dict "targetPath" "css/style.min.css" "enableSourceMap" true -}}
|
||||
{{- $style = dict "Context" . "ToCSS" $options | merge $style -}}
|
||||
{{- partial "plugin/style.html" $style -}}
|
||||
{{- $esBuildOpts := dict "minify" hugo.IsProduction -}}
|
||||
|
||||
{{ $script := resources.Get "ts/main.ts" | js.Build }}
|
||||
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
||||
{{- $script := resources.Get "ts/main.ts" | js.Build $esBuildOpts -}}
|
||||
<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-promise": "^6.1.1",
|
||||
"prettier": "3.0.3",
|
||||
"prettier-plugin-go-template": "^0.0.15",
|
||||
"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,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Recommended"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user