Fixed chapter selection on last chapter
This commit is contained in:
@@ -3,14 +3,16 @@ import type { Component } from 'solid-js';
|
|||||||
import Controls from './components/Controls';
|
import Controls from './components/Controls';
|
||||||
import Hero from './components/Hero'
|
import Hero from './components/Hero'
|
||||||
import Projects from './components/Projects/Projects'
|
import Projects from './components/Projects/Projects'
|
||||||
|
import ContactForm from './components/ContactForm'
|
||||||
|
|
||||||
const App: Component = () => {
|
const App: Component = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div class='App'>
|
||||||
<Controls/>
|
<Controls/>
|
||||||
<Hero/>
|
<Hero/>
|
||||||
<Projects/>
|
<Projects/>
|
||||||
</>
|
<ContactForm/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
24
src/components/ContactForm.module.css
Normal file
24
src/components/ContactForm.module.css
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.ContactForm {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--gap-md);
|
||||||
|
margin-left: var(--gap-xl);
|
||||||
|
margin-right: var(--gap-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ContactForm > * {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: var(--gap-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1080px) {
|
||||||
|
.ContactForm {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/components/ContactForm.tsx
Normal file
51
src/components/ContactForm.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useI18n } from "@solid-primitives/i18n";
|
||||||
|
import { createViewportObserver } from '@solid-primitives/intersection-observer';
|
||||||
|
|
||||||
|
import { useStore } from '../store/index';
|
||||||
|
import type { Store } from '../store/index';
|
||||||
|
import { scrollHereWhenSelected } from "../utlis/scroll";
|
||||||
|
import styles from './ContactForm.module.css';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [t] = useI18n();
|
||||||
|
const [state, setters] = useStore() as Store;
|
||||||
|
const [observer] = createViewportObserver({threshold: 0.9});
|
||||||
|
const chapterName = 'mail';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
use:observer={(event) => {
|
||||||
|
if (event.isIntersecting) {
|
||||||
|
if (!state.scrolling()) {
|
||||||
|
setters.setVisibleChapter(chapterName);
|
||||||
|
} else if (state.visibleChapter() === chapterName) {
|
||||||
|
setters.setScrolling(false)
|
||||||
|
}
|
||||||
|
// Сраный костыль
|
||||||
|
} else if (state.visibleChapter() === chapterName) {
|
||||||
|
setters.setVisibleChapter('projects');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={(element) => scrollHereWhenSelected(element, [state, setters], chapterName)}
|
||||||
|
class={styles.ContactForm}
|
||||||
|
action="https://send.pageclip.co/ipMETNW8CCV8ka21myU6D22bvnOAV0Ag"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
placeholder={t('name')}
|
||||||
|
></input>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
name="message"
|
||||||
|
placeholder="Write something.."
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Submit"
|
||||||
|
></input>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
transition: width .6s, height .6s, transform .6s;
|
transition: width .6s, height .6s, transform .6s;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
transform: translate(-50%, 50%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
|
|||||||
@@ -9,31 +9,31 @@ import gridIcon from '../assets/icons/grid.svg'
|
|||||||
import mailIcon from '../assets/icons/mail.svg'
|
import mailIcon from '../assets/icons/mail.svg'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [store, {setVisibleChapter}] = useStore() as Store;
|
const [state, {setVisibleChapter, setScrolling}] = useStore() as Store;
|
||||||
const [selected, setSelected] = createSignal('home');
|
const [selected, setSelected] = createSignal('home');
|
||||||
const [blobby, setBlobby] = createSignal(['home']);
|
const [blobby, setBlobby] = createSignal(['home']);
|
||||||
const chapters = [
|
const chapters = [
|
||||||
{ name: 'home', icon: homeIcon },
|
{ name: 'home', icon: homeIcon },
|
||||||
{ name: 'projects', icon: gridIcon },
|
{ name: 'projects', icon: gridIcon },
|
||||||
{ name: 'whatever', icon: mailIcon },
|
{ name: 'mail', icon: mailIcon },
|
||||||
]
|
]
|
||||||
|
|
||||||
const selectChapter = (chapterName: string) => {
|
const selectChapter = (chapterName: string) => {
|
||||||
if (chapterName !== selected()) {
|
if (chapterName !== selected()) {
|
||||||
setSelected(chapterName)
|
setSelected(chapterName);
|
||||||
setVisibleChapter(chapterName)
|
setVisibleChapter(chapterName);
|
||||||
setBlobby(b => [chapterName, ...b])
|
setBlobby(b => [chapterName, ...b]);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setBlobby(b => [chapterName])
|
setBlobby(b => [chapterName]);
|
||||||
}, 500)
|
}, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect((prev) => {
|
createEffect((prev) => {
|
||||||
if (prev !== store.visibleChapter()) {
|
if (prev !== state.visibleChapter()) {
|
||||||
selectChapter(store.visibleChapter());
|
selectChapter(state.visibleChapter());
|
||||||
}
|
}
|
||||||
return store.visibleChapter();
|
return state.visibleChapter();
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -54,6 +54,7 @@ export default () => {
|
|||||||
<For each={chapters}>{(chapter) =>
|
<For each={chapters}>{(chapter) =>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setScrolling(true);
|
||||||
selectChapter(chapter.name)
|
selectChapter(chapter.name)
|
||||||
}}
|
}}
|
||||||
class={styles.control}
|
class={styles.control}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.Hero {
|
.Hero {
|
||||||
|
position: relative;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -23,6 +24,19 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blur {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
backdrop-filter: blur(50px);
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 2.5s;
|
||||||
|
transition-delay: .5s;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes gradient {
|
@keyframes gradient {
|
||||||
0% {
|
0% {
|
||||||
background-position: 0% 50%;
|
background-position: 0% 50%;
|
||||||
|
|||||||
@@ -9,17 +9,22 @@ import styles from './Hero.module.css';
|
|||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [t] = useI18n();
|
const [t] = useI18n();
|
||||||
const [observer] = createViewportObserver({threshold: 0.9});
|
const [observer] = createViewportObserver({threshold: 0.8});
|
||||||
const [store, { setVisibleChapter }] = useStore() as Store;
|
const [state, setters] = useStore() as Store;
|
||||||
|
const chapterName = 'home'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
use:observer={(event) => {
|
use:observer={(event) => {
|
||||||
if (event.isIntersecting) {
|
if (event.isIntersecting) {
|
||||||
setVisibleChapter('home');
|
if (!state.scrolling()) {
|
||||||
}}
|
setters.setVisibleChapter(chapterName);
|
||||||
}
|
} else if (state.visibleChapter() === chapterName) {
|
||||||
ref={(element) => scrollHereWhenSelected(element, store, 'home')}
|
setters.setScrolling(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={(element) => scrollHereWhenSelected(element, [state, setters], chapterName)}
|
||||||
class={styles.Hero}
|
class={styles.Hero}
|
||||||
>
|
>
|
||||||
<h1 class={styles.gradientText}>
|
<h1 class={styles.gradientText}>
|
||||||
@@ -30,6 +35,13 @@ export default () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Links />
|
<Links />
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={styles.blur}
|
||||||
|
ref={(element) => {
|
||||||
|
setTimeout(() => element.style.opacity = '0')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
.Projects {
|
.Projects {
|
||||||
padding: var(--gap-md);
|
padding: var(--gap-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: var(--gap-lg);
|
gap: var(--gap-lg);
|
||||||
margin-left: 130px;
|
margin-left: var(--gap-xl);
|
||||||
margin-right: 130px;
|
margin-right: var(--gap-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1080px) {
|
||||||
|
.Projects {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,17 +10,23 @@ import Project from './Project';
|
|||||||
import type { Project as ProjectType } from './projectList';
|
import type { Project as ProjectType } from './projectList';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [store, { setVisibleChapter }] = useStore() as Store;
|
const [state, setters] = useStore() as Store;
|
||||||
const [observer] = createViewportObserver({threshold: 0.5});
|
const [observer] = createViewportObserver({threshold: 0.1});
|
||||||
|
const chapterName = 'projects'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
use:observer={(event) => {
|
use:observer={(event) => {
|
||||||
if (event.isIntersecting) {
|
if (event.isIntersecting) {
|
||||||
setVisibleChapter('projects');
|
console.log(chapterName);
|
||||||
}}
|
if (!state.scrolling()) {
|
||||||
}
|
setters.setVisibleChapter(chapterName);
|
||||||
ref={(element) => scrollHereWhenSelected(element, store, 'projects')}
|
} else if (state.visibleChapter() === chapterName) {
|
||||||
|
setters.setScrolling(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={(element) => scrollHereWhenSelected(element, [state, setters], chapterName)}
|
||||||
class={styles.Projects}
|
class={styles.Projects}
|
||||||
>
|
>
|
||||||
<For each={projects}>{(project: ProjectType) =>
|
<For each={projects}>{(project: ProjectType) =>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
--gap-sm: 18px;
|
--gap-sm: 18px;
|
||||||
--gap-md: 36px;
|
--gap-md: 36px;
|
||||||
--gap-lg: 72px;
|
--gap-lg: 72px;
|
||||||
|
--gap-xl: 144px;
|
||||||
|
|
||||||
--radius-md: 12px;
|
--radius-md: 12px;
|
||||||
|
|
||||||
@@ -19,6 +20,10 @@
|
|||||||
--font-md: normal normal 400 18px var(--ff-default);
|
--font-md: normal normal 400 18px var(--ff-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.App {
|
||||||
|
padding-bottom: var(--gap-xl);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@@ -41,3 +46,12 @@ code {
|
|||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
background: var(--clr-bg-secondary);
|
||||||
|
border: none;
|
||||||
|
padding: var(--gap-sm);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--clr-text);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@
|
|||||||
"warframe_desc": "A service that monitors prices of items on warframe.market and calculates profitable gaps between them.",
|
"warframe_desc": "A service that monitors prices of items on warframe.market and calculates profitable gaps between them.",
|
||||||
"worktime_desc": "A PWA that I use daily to track my time spent working. Full offline support.",
|
"worktime_desc": "A PWA that I use daily to track my time spent working. Full offline support.",
|
||||||
"studybuddy_desc": "A PWA for splitting into groups or taking topics for an assignment.",
|
"studybuddy_desc": "A PWA for splitting into groups or taking topics for an assignment.",
|
||||||
"vkmute_desc": "A Browser extension that allows you to mute people in group chats on VK.com."
|
"vkmute_desc": "A Browser extension that allows you to mute people in group chats on VK.com.",
|
||||||
|
"name": "Your name"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,30 @@ import { createSignal, createContext, useContext } from "solid-js";
|
|||||||
import type { Accessor, Setter } from 'solid-js';
|
import type { Accessor, Setter } from 'solid-js';
|
||||||
|
|
||||||
export type Store = [
|
export type Store = [
|
||||||
{ visibleChapter: Accessor<string> },
|
{
|
||||||
{ setVisibleChapter: Setter<string> }
|
visibleChapter: Accessor<string>,
|
||||||
|
scrolling: Accessor<boolean>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
setVisibleChapter: Setter<string>,
|
||||||
|
setScrolling: Setter<boolean>,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const StoreContext = createContext<Store>();
|
const StoreContext = createContext<Store>();
|
||||||
|
|
||||||
export function StoreProvider(props: any) {
|
export function StoreProvider(props: any) {
|
||||||
const [visibleChapter, setVisibleChapter] = createSignal('home');
|
const [visibleChapter, setVisibleChapter] = createSignal('home');
|
||||||
|
const [scrolling, setScrolling] = createSignal(false);
|
||||||
|
|
||||||
const store: Store = [
|
const store: Store = [
|
||||||
{
|
{
|
||||||
visibleChapter
|
visibleChapter,
|
||||||
|
scrolling,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
setVisibleChapter
|
setVisibleChapter,
|
||||||
|
setScrolling
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { createEffect } from "solid-js";
|
import { createEffect } from "solid-js";
|
||||||
|
|
||||||
export const scrollHereWhenSelected = (element: HTMLElement, store, chapter) => {
|
import type { Store } from '../store/index';
|
||||||
|
|
||||||
|
export const scrollHereWhenSelected = (element: HTMLElement, store: Store, chapter: string) => {
|
||||||
|
const [state, setters] = store;
|
||||||
return createEffect((prev) => {
|
return createEffect((prev) => {
|
||||||
if (prev !== store.visibleChapter() && store.visibleChapter() === chapter) {
|
if (prev !== state.visibleChapter() && state.visibleChapter() === chapter && state.scrolling()) {
|
||||||
element.scrollIntoView({behavior: "smooth"})
|
element.scrollIntoView({behavior: "smooth"});
|
||||||
|
setters.setScrolling(true);
|
||||||
}
|
}
|
||||||
return store.visibleChapter();
|
return state.visibleChapter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user