diff --git a/README.md b/README.md index 302783df5..51d3fee07 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # KateVR landing page (HARD) -**⚠️ Attention: this is a hard landing page for development. We highly recommend choosing this landing page only if you didn't have a lot of troubles with previous homework tasks and if you sent all homework in time (didn't miss deadlines).** +**⚠️ Attention: this is a hard landing page for development. We highly recommend choosing this landing page only if you didn't have a lot of troubles with previous homework tasks and if you sent all homework in time (didn't miss deadlines).** Implement landing page according to [Figma design](https://www.figma.com/file/Blpg4iapsI7fRqJeSp6DvK/KatVR?node-id=1%3A370) - Use BEM and SCSS @@ -25,7 +25,7 @@ Implement landing page according to [Figma design](https://www.figma.com/file/Bl 11. `git push origin develop` - to send you code for PR. 12. Create a Pull Request (PR) from your branch `develop` to branch `master` of original repo. 13. Replace `` with your Github username in the - [DEMO LINK](https://.github.io/layout_KateVR/). + [DEMO LINK](https://RuslanV23.github.io/layout_KateVR/). 14. Copy `DEMO LINK` to the PR description. > To update you PR repeat steps 7-11. diff --git a/index.html b/index.html index d339e6856..6da66ab71 100644 --- a/index.html +++ b/index.html @@ -1,19 +1,2021 @@ - + - Title + KateVR + + + + + - -

Hello Mate Academy

- + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

+ THE NEW START OF +
+ VR LOCOMOTION +

+ +

+ Discover the most comprehensive VR Locomotion system, and unlock + infinite motion in any games on any platforms! +

+ +

1200$

+ + + + +
+
+
+
+
+

+ MORE THAN + GAMING! +

+

+ This also made for people who are interested in... +

+
+ +
+
+
+

EDUCATION

+

+ Create aducational simulations, trainings and much more with + unlimited virtual space and minimum physical space +

+
+ +
+
+

REAL ESTATE

+

+ Desighn architectural projects in a deeply realistic environment + allowing visitors to freely walk around, and feel their vibe +

+
+ +
+
+

FITNESS

+

+ Combine business with pleasure, and discover countless ways to + stay fit while playing your favorite VR Games! +

+
+ +
+
+

SOCIAL INTERACTIONS

+

+ Hang out with your friends in the virtual world when you can’t + meet space requirements +

+
+
+
+
+
+

+ ABOUT + PRODUCT +

+
+
+
+ about image 1 +
+
+ about image 2 +
+
+ about image 3 +
+
+ about image 4 +
+
+ about image 5 +
+
+
+
+
+ + +
+
+
+
+ +

+ KAT loco is a foot-based VR locomotion system that gives complete + physical control over lower-body actions, allowing you to freely + walk, run, and carry out just any other movement in virtual reality. +

+ +
+
+
+

Hello,

+

+ NICE TO MEET + YOU! +

+

+ KAT VR is an independent company dedicated to the research, + development, and sales of VR Locomotion products and solutions. + Founded in 2013, we have quickly grown to become one of the + world’s leading professional suppliers of VR games’ & simulations’ + equipment +

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ TECH + SPECS +

+
+ +
+
+
+ + + + +
+
+ +
+
+ + + + +
+
+
+
+
+ +
+
+

+ SENSOR +
+

+

+ Weight: 35g/1.23oz each +
+ Dimension: 50mm/1.97in +
+ 24mm/0.94in +
+ Light: LED lights +

+
+
+

+ BATTERRIES +
+

+

+ Type: Lthium-lon polymer batteries +
+ Capacity: 370mAh +
+ Battery life: 10h of continuous use 150 hours on stand by +
+ Charging: Fast charging - 1 hour +
+ Charging voltage and current: 5V = 0.5A +
+

+
+
+

+ SENSOR +
+

+

+ Weight: 35g/1.23oz each +
+ Dimension: 50mm/1.97in +
+ 24mm/0.94in +
+ Light: LED lights +

+
+
+
+
+
+

+ WHY KAT + LOCO? +

+
+
+
+
+

UNIVERSALLY COMPATIBLE

+

+ KAT Loco offers universal compatibility across all major VR + headsets and platforms allowing you to play any VR game with + support for Free Locomotion +

+
+
+
+

VR/PC CONTROL PANEL

+

+ Our Multifunctional Software allows for quick access to KAT Loco’s + control panel both from a computer desktop, and directly from your + VR headset. +

+
+
+
+

WIRELESS SENSORS

+

+ What makes it even more advanced, KAT Loco is entirely wireless, + and comes with a complete system of algorithms, super durable + batteries and more! +

+
+
+
+
+
+

Have any questions?

+ +

+ GET IN + TOUCH +

+

+ Our manager will reply you within 15 minutes +

+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ + + + + + +
+
+
+
+ +
+ + + +
+

+ HOW CAN WE + HELP + YOU? +

+

+ Welcome to our help center. Our goal is to make the process of + getting acquainted and purchase as easy and pleasant as possible. + Need help with your KAT VR loco product? Many product questions + can be resolved by reviewing our Product Support + + FAQs + + . +
+
+ Please also feel free to + + Contact Us + + directly should you need anything further. We’re happy to help. +

+ + + + +
+
+
+
+ +
+
+
+ +
+

+ FREQUENTLY ASKED +
+ QUESTIONS +

+ +
+
+ + What makes KAT loco different from other systems? + +
+

+ Despite being approached from many different angles, all + traditional VR Locomotion systems including roomscale, + teleportation, and free locomotion failed to provide an + optimal solution capable of integrating high immersion with + convenience. KAT loco does all of that without repeating + their flaws. Our system offers a complete and versatile + solution for an affordable price what makes it a perfect + choice for home-use. It is also 100% user friendly, + wireless, universally compatible, and more! It even offers + decoupled head and body directions! +

+

+ Last updated: Wed, June 12 2019 7:07 PM UTC +03:00 +

+
+
+ +
+ + Will it decrease my motion sickness? + +
+

+ Yes, using this device can significantly reduce motion + sickness by resolving the sensory conflict that typically + occurs in VR. When your eyes perceive movement in the + virtual world while your body remains still, your brain + experiences a disconnect that causes nausea; by + synchronizing your physical steps with your in-game + character's movement, this device aligns your vestibular + system with your visual input. This active physical + engagement helps your brain process virtual locomotion as + natural movement, making your VR experience much more + comfortable and immersive. +

+

+ Last updated: Wed, June 12 2019 7:07 PM UTC +03:00 +

+
+
+ +
+ + Which VR headsets is it compatible with? + +
+

+ Our device is designed with universal compatibility in mind + and works seamlessly with most major VR headsets on the + market. It fully supports standalone systems like Meta Quest + 2 and 3, as well as PC-powered setups such as Valve Index, + HP Reverb G2, and the HTC Vive series. Additionally, it is + compatible with PlayStation VR (PSVR 1 & 2) via a dedicated + adapter, ensuring that whether you are gaming on a console, + PC, or a mobile headset, you can enjoy a fully immersive + walking experience. +

+

+ Last updated: Wed, June 12 2019 7:07 PM UTC +03:00 +

+
+
+ +
+ + Is it compatible with Oculus Quest? + +
+

+ Absolutely. Our device offers full native support for both + Oculus Quest (Meta Quest 1 & 2) and the latest Meta Quest 3. + It connects effortlessly via Bluetooth, allowing you to + enjoy a completely wireless walking experience without the + need for a PC. Whether you are playing standalone Quest + games or using Quest Link/Air Link to play SteamVR titles, + our system synchronizes perfectly with your movement to + provide maximum immersion and freedom. +

+

+ Last updated: Wed, June 12 2019 7:07 PM UTC +03:00 +

+
+
+ + More + +
+
+
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+
+ +
+
+
+ Place Order + Pay + Complete +
+
+
+
+
+
+

Quantity

+ +
+
+

Price

+

+ 1200$ +

+
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ + +
+ +
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+

Country*

+ +
+
+

City*

+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+ +
+

Thank you for your order!

+

Your order has been placed and is being processed. You will recive an email with the order details

+ Back to homepage +
+
+ + + diff --git a/src/images/ABOUT US 2.svg b/src/images/ABOUT US 2.svg new file mode 100644 index 000000000..a3f368f3e --- /dev/null +++ b/src/images/ABOUT US 2.svg @@ -0,0 +1,52 @@ + + diff --git a/src/images/about-product/about-image-1.png b/src/images/about-product/about-image-1.png new file mode 100644 index 000000000..4d204adb2 Binary files /dev/null and b/src/images/about-product/about-image-1.png differ diff --git a/src/images/about-product/about-image-2.png b/src/images/about-product/about-image-2.png new file mode 100644 index 000000000..036693824 Binary files /dev/null and b/src/images/about-product/about-image-2.png differ diff --git a/src/images/about-product/about-image-3.png b/src/images/about-product/about-image-3.png new file mode 100644 index 000000000..4158e9fcc Binary files /dev/null and b/src/images/about-product/about-image-3.png differ diff --git a/src/images/about-product/about-image-4.png b/src/images/about-product/about-image-4.png new file mode 100644 index 000000000..9c5832e92 Binary files /dev/null and b/src/images/about-product/about-image-4.png differ diff --git a/src/images/about-product/about-image-5.png b/src/images/about-product/about-image-5.png new file mode 100644 index 000000000..645b5ad5a Binary files /dev/null and b/src/images/about-product/about-image-5.png differ diff --git a/src/images/bg/1-mob.png b/src/images/bg/1-mob.png new file mode 100644 index 000000000..ed42e588c Binary files /dev/null and b/src/images/bg/1-mob.png differ diff --git a/src/images/bg/1.png b/src/images/bg/1.png new file mode 100644 index 000000000..12fdaff10 Binary files /dev/null and b/src/images/bg/1.png differ diff --git a/src/images/bg/2-mob.png b/src/images/bg/2-mob.png new file mode 100644 index 000000000..db2db1b73 Binary files /dev/null and b/src/images/bg/2-mob.png differ diff --git a/src/images/bg/2.png b/src/images/bg/2.png new file mode 100644 index 000000000..831f77043 Binary files /dev/null and b/src/images/bg/2.png differ diff --git a/src/images/bg/3-mob.png b/src/images/bg/3-mob.png new file mode 100644 index 000000000..ca32381f2 Binary files /dev/null and b/src/images/bg/3-mob.png differ diff --git a/src/images/bg/3.png b/src/images/bg/3.png new file mode 100644 index 000000000..528252ce1 Binary files /dev/null and b/src/images/bg/3.png differ diff --git a/src/images/circle.svg b/src/images/circle.svg new file mode 100644 index 000000000..bdc93b124 --- /dev/null +++ b/src/images/circle.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/images/favicon/favicon.svg b/src/images/favicon/favicon.svg new file mode 100644 index 000000000..a42594520 --- /dev/null +++ b/src/images/favicon/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/images/half-circle.svg b/src/images/half-circle.svg new file mode 100644 index 000000000..0ef503912 --- /dev/null +++ b/src/images/half-circle.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/images/icon/arrow-bottom.svg b/src/images/icon/arrow-bottom.svg new file mode 100644 index 000000000..801d56619 --- /dev/null +++ b/src/images/icon/arrow-bottom.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/icon/arrow-left.svg b/src/images/icon/arrow-left.svg new file mode 100644 index 000000000..1a2fbd8ac --- /dev/null +++ b/src/images/icon/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/icon/dropdown-arrow.png b/src/images/icon/dropdown-arrow.png new file mode 100644 index 000000000..fb380c430 Binary files /dev/null and b/src/images/icon/dropdown-arrow.png differ diff --git a/src/images/icon/menu.svg b/src/images/icon/menu.svg new file mode 100644 index 000000000..a35c129fb --- /dev/null +++ b/src/images/icon/menu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/images/icon/socials/facebook.svg b/src/images/icon/socials/facebook.svg new file mode 100644 index 000000000..c5ce92014 --- /dev/null +++ b/src/images/icon/socials/facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/icon/socials/reddit.svg b/src/images/icon/socials/reddit.svg new file mode 100644 index 000000000..2ec597384 --- /dev/null +++ b/src/images/icon/socials/reddit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/icon/socials/twitter.svg b/src/images/icon/socials/twitter.svg new file mode 100644 index 000000000..cd292e387 --- /dev/null +++ b/src/images/icon/socials/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/icon/socials/youtube.svg b/src/images/icon/socials/youtube.svg new file mode 100644 index 000000000..bd0387c5c --- /dev/null +++ b/src/images/icon/socials/youtube.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/images/logo/logo.svg b/src/images/logo/logo.svg new file mode 100644 index 000000000..b3435d9f7 --- /dev/null +++ b/src/images/logo/logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/images/more-than-gaming/1.svg b/src/images/more-than-gaming/1.svg new file mode 100644 index 000000000..b5b684cb4 --- /dev/null +++ b/src/images/more-than-gaming/1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/images/more-than-gaming/2.svg b/src/images/more-than-gaming/2.svg new file mode 100644 index 000000000..0a2bada72 --- /dev/null +++ b/src/images/more-than-gaming/2.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/more-than-gaming/3.svg b/src/images/more-than-gaming/3.svg new file mode 100644 index 000000000..935fb5749 --- /dev/null +++ b/src/images/more-than-gaming/3.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/images/more-than-gaming/4.svg b/src/images/more-than-gaming/4.svg new file mode 100644 index 000000000..09a32f51f --- /dev/null +++ b/src/images/more-than-gaming/4.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/pay/mastercard.svg b/src/images/pay/mastercard.svg new file mode 100644 index 000000000..67e199b82 --- /dev/null +++ b/src/images/pay/mastercard.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/pay/visa.svg b/src/images/pay/visa.svg new file mode 100644 index 000000000..dcba59054 --- /dev/null +++ b/src/images/pay/visa.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/images/tech-specs/button-active.svg b/src/images/tech-specs/button-active.svg new file mode 100644 index 000000000..d563a8307 --- /dev/null +++ b/src/images/tech-specs/button-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/tech-specs/button.svg b/src/images/tech-specs/button.svg new file mode 100644 index 000000000..4a37bb8e5 --- /dev/null +++ b/src/images/tech-specs/button.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/images/tech-specs/ellipse.svg b/src/images/tech-specs/ellipse.svg new file mode 100644 index 000000000..2d162854e --- /dev/null +++ b/src/images/tech-specs/ellipse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/tech-specs/kat-vr.png b/src/images/tech-specs/kat-vr.png new file mode 100644 index 000000000..ad1d21bf9 Binary files /dev/null and b/src/images/tech-specs/kat-vr.png differ diff --git a/src/images/tech-specs/line3.svg b/src/images/tech-specs/line3.svg new file mode 100644 index 000000000..4f8d27be0 --- /dev/null +++ b/src/images/tech-specs/line3.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/triangle.svg b/src/images/triangle.svg new file mode 100644 index 000000000..f3141dc85 --- /dev/null +++ b/src/images/triangle.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/images/why/1.svg b/src/images/why/1.svg new file mode 100644 index 000000000..bdeab25ab --- /dev/null +++ b/src/images/why/1.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/why/2.svg b/src/images/why/2.svg new file mode 100644 index 000000000..a5798b8d0 --- /dev/null +++ b/src/images/why/2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/images/why/3.svg b/src/images/why/3.svg new file mode 100644 index 000000000..67677d440 --- /dev/null +++ b/src/images/why/3.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/scripts/animation-about-us.ts b/src/scripts/animation-about-us.ts new file mode 100644 index 000000000..be917e305 --- /dev/null +++ b/src/scripts/animation-about-us.ts @@ -0,0 +1,69 @@ +import { myUtils } from './myUtils'; + +export const animationAboutUs = () => { + const curve = [ + { pos: -0.1, value: 0 }, + { pos: 0, value: 100 }, + { pos: 0.3, value: 50 }, + { pos: 0.9, value: 0 }, + { pos: 1, value: 0 }, + ]; + + const aboutUsText = document.querySelector( + '.section-about-product__text-vertical-box', + ) as HTMLElement; + + const firstEl = aboutUsText.children[0] as HTMLElement; + + const updateOpacity = (value: number) => { + ([...aboutUsText.children] as HTMLElement[]).forEach((item) => { + // console.log(item.offsetLeft, aboutUsText.offsetLeft); + item.style.opacity = + myUtils.getValueFromCurve( + curve, + myUtils.mapRange( + item.offsetLeft, + aboutUsText.offsetLeft, + aboutUsText.offsetLeft + aboutUsText.parentElement.clientWidth, + 0, + 1, + 'linean', + ), + ) + '%'; + }); + }; + + function animation(endAnimation: () => void) { + let countEndAnim = 0; + + function _animation(currentValue = 0) { + if (getComputedStyle(aboutUsText.parentElement).display === 'none') { + setTimeout(() => _animation(0), 3); + return; + } + myUtils.tween( + currentValue, + currentValue + -firstEl.clientWidth, + 1000, + (value: number) => { + firstEl.style.marginLeft = Math.round(value) + 'px'; + updateOpacity(value); + }, + (value) => { + countEndAnim++; + if (countEndAnim === 2) { + countEndAnim = 0; + setTimeout(() => _animation(0), 1000); + return; + } + setTimeout(() => _animation(value), 1000); + }, + ); + } + _animation(); + } + + animation(() => { + firstEl.style.marginLeft = 0 + 'px'; + }); +}; diff --git a/src/scripts/animation-play-button.ts b/src/scripts/animation-play-button.ts new file mode 100644 index 000000000..08fbba4b6 --- /dev/null +++ b/src/scripts/animation-play-button.ts @@ -0,0 +1,120 @@ +export const animationPlayButton = () => { + const lines = document.querySelectorAll( + '.button-play__dots-line', + ) as NodeListOf; + + lines.forEach((line) => { + const elements: HTMLElement[] = []; + + const createLine = (h: number, w: number) => { + const line = document.createElement('div'); + line.style.height = h + 'px'; + line.style.width = w + 'px'; + line.classList.add('button-play__line'); + + return line; + }; + + const firstLine = createLine(2, 30); + + elements.push(firstLine); + + const start = 1; + const end = 9; + const n = 26; + + const step = (end - start) / (n - 1); + + Array.from({ length: 26 }).forEach((item, index) => { + const l = 1 / 25; + + const line = createLine(2 - l * index, 2 - l * index); + + line.style.marginLeft = Math.floor(start + step * index) + 'px'; + + elements.push(line); + }); + + line.append(...elements); + }); + + const buttonPlay = document.querySelectorAll( + '.button-play', + ) as NodeListOf; + + let animationId; + let offsetAnimation = 0; + + let startAnimation = false; + + function animate(lines) { + function anim(time) { + if (!startAnimation) { + offsetAnimation = time; + + lines.forEach((item) => { + item.classList.remove('button-play__dots-line--hover'); + }); + } + + animationId = requestAnimationFrame(anim); + + let indexLine = 0; + + for (const line of lines) { + [...line.children].forEach((item, index) => { + const el = item as HTMLElement; + const left = (el.offsetLeft - line.offsetLeft) / 34; + + const power = (index / 26) * 5; + + let transform = + (Math.sin(left + -(time - offsetAnimation) / 150) - 1) * power; + + transform = transform * Math.min((time - offsetAnimation) / 520, 1); + + transform = indexLine % 2 === 1 ? -transform : transform; + + el.style.transform = `translateY(${transform}px)`; + }); + + indexLine += 1; + } + + startAnimation = true; + } + + requestAnimationFrame(anim); + } + + buttonPlay.forEach((button) => { + button.addEventListener('pointerenter', () => { + const linesButton = button.querySelectorAll( + '.button-play__dots-line', + ) as NodeListOf; + + animationId = animate(linesButton); + }); + }); + buttonPlay.forEach((button) => { + button.addEventListener('pointerleave', () => { + const linesButton = button.querySelectorAll( + '.button-play__dots-line', + ) as NodeListOf; + + cancelAnimationFrame(animationId); + startAnimation = false; + linesButton.forEach((item) => { + item.classList.add('button-play__dots-line--hover'); + }); + + for (const line of linesButton) { + [...line.children].forEach((item, index) => { + const el = item as HTMLElement; + + el.style.transform = `translateY(0px)`; + }); + } + }); + }); +}; diff --git a/src/scripts/animation-slider.ts b/src/scripts/animation-slider.ts new file mode 100644 index 000000000..e39f2dd99 --- /dev/null +++ b/src/scripts/animation-slider.ts @@ -0,0 +1,373 @@ +import { myUtils } from "./myUtils"; + +export const animationSlider = () => { + const slider = (exSwiper: HTMLElement, exPagination: HTMLElement) => { + const wrappers = exSwiper.querySelectorAll( + '.ex-wrapper', + ) as NodeListOf; + + const effect = exSwiper.querySelector('.ex-effect') as HTMLElement; + + const maxFrame = wrappers.length; + + const curve = [ + { pos: 0, value: 0 }, + { pos: 0.2, value: 0.8 }, + { pos: 0.5, value: 1 }, + { pos: 0.8, value: 0.8 }, + { pos: 1, value: 0 }, + ]; + const mod = (n, m) => ((n % m) + m) % m; + + let widthSwiper = exSwiper.clientWidth; + let currentIndex = 0; + + let nextIndex = mod(currentIndex + 1, maxFrame); + let prevIndex = mod(currentIndex - 1, maxFrame); + + const setCurrentIndex = (index) => { + exPagination.style.setProperty('--max-frame', maxFrame.toString()); + exPagination.style.setProperty('--current-frame', index.toString()); + currentIndex = index; + + nextIndex = mod(currentIndex + 1, maxFrame); + prevIndex = mod(currentIndex - 1, maxFrame); + }; + + let animValue = 0; + let stop: Function | null = null; + + const setAnimValue = (value: number) => { + if (value < 0) { + const absValue = Math.abs(value); + + effect.style.setProperty( + '--width', + String(myUtils.getValueFromCurve(curve, absValue)), + ); + effect.style.setProperty('--left', String(absValue)); + + wrappers[nextIndex].style.setProperty('--right', '100%'); + wrappers[nextIndex].style.setProperty('--left', '100%'); + + wrappers[currentIndex].style.setProperty('--right', '0%'); + wrappers[currentIndex].style.setProperty('--left', absValue * 100 + '%'); + wrappers[prevIndex].style.setProperty( + '--right', + (1 - absValue) * 100 + '%', + ); + wrappers[prevIndex].style.setProperty('--left', '0%'); + } else { + effect.style.setProperty( + '--width', + String(myUtils.getValueFromCurve(curve, value)), + ); + effect.style.setProperty('--left', String(1 - value)); + + wrappers[prevIndex].style.setProperty('--right', '100%'); + wrappers[prevIndex].style.setProperty('--left', '100%'); + + wrappers[currentIndex].style.setProperty('--left', '0%'); + wrappers[currentIndex].style.setProperty('--right', value * 100 + '%'); + wrappers[nextIndex].style.setProperty('--left', (1 - value) * 100 + '%'); + wrappers[nextIndex].style.setProperty('--right', '0%'); + } + + animValue = value; + }; + + type AnimEndType = 'auto' | 'next' | 'prev'; + + const animTween = ( + endValue: number, + duration: number, + animEndType: AnimEndType = 'auto', + ) => { + stop = myUtils.tween( + animValue, + endValue, + duration, + (value) => { + setAnimValue(value); + }, + (value) => { + stop = null; + animEnd(value, 200, animEndType); + }, + ); + }; + + const animEnd = ( + value: number, + duration: number, + animEndType: AnimEndType = 'auto', + ) => { + if (animEndType === 'auto') { + if (value === -1) { + wrappers[nextIndex].style.setProperty('--right', '100%'); + wrappers[nextIndex].style.setProperty('--left', '100%'); + + wrappers[currentIndex].style.setProperty('--right', '100%'); + wrappers[currentIndex].style.setProperty('--left', '100%'); + wrappers[prevIndex].style.setProperty('--left', '0%'); + wrappers[prevIndex].style.setProperty('--right', '0%'); + + setCurrentIndex(prevIndex); + + animValue = 0; + + return; + } + + if (value === 1) { + wrappers[prevIndex].style.setProperty('--right', '100%'); + wrappers[prevIndex].style.setProperty('--left', '100%'); + + wrappers[currentIndex].style.setProperty('--right', '100%'); + wrappers[currentIndex].style.setProperty('--left', '100%'); + wrappers[nextIndex].style.setProperty('--left', '0%'); + wrappers[nextIndex].style.setProperty('--right', '0%'); + + setCurrentIndex(nextIndex); + + animValue = 0; + + return; + } + + if (value === 0) { + wrappers[prevIndex].style.setProperty('--right', '100%'); + wrappers[prevIndex].style.setProperty('--left', '100%'); + + wrappers[nextIndex].style.setProperty('--right', '100%'); + wrappers[nextIndex].style.setProperty('--left', '100%'); + wrappers[currentIndex].style.setProperty('--left', '0%'); + wrappers[currentIndex].style.setProperty('--right', '0%'); + + animValue = 0; + + return; + } + const currentDuration = duration - value / duration; + + if (value > 0.5) { + animTween(1, currentDuration); + return; + } + + if (value < -0.5) { + animTween(-1, currentDuration); + return; + } + animTween(0, currentDuration); + } + + const currentDuration = duration - value / duration; + + if (animEndType === 'next') { + animTween(1, currentDuration); + } + + if (animEndType === 'prev') { + animTween(-1, currentDuration); + } + }; + + const increaseIndex = (duration = 1600) => { + if (stop) { + return; + } + + animTween(1, duration); + }; + + const decreaseIndex = (duration = 1600) => { + if (stop) { + return; + } + + animTween(-1, duration); + }; + + wrappers.forEach((wrapper, index) => { + if (index === currentIndex) { + wrapper.style.setProperty('--right', '0%'); + wrapper.style.setProperty('--left', '0%'); + } else { + wrapper.style.setProperty('--right', '100%'); + wrapper.style.setProperty('--left', '100%'); + } + }); + + const autoAnim = () => { + let animId = -1; + + const anim = () => { + animId = setTimeout(() => { + increaseIndex(); + anim(); + }, 3000); + }; + + anim(); + + return () => { + clearTimeout(animId); + }; + }; + + let stopAutoAnim = autoAnim(); + + let timerId = 0; + + const startTimerAutoAnim = () => { + if (timerId) { + clearTimeout(timerId); + } + + timerId = setTimeout(() => { + stopAutoAnim = autoAnim(); + }, 4000); + }; + + const next = () => { + increaseIndex(600); + stopAutoAnim(); + startTimerAutoAnim(); + }; + + const prev = () => { + decreaseIndex(600); + stopAutoAnim(); + startTimerAutoAnim(); + }; + + let stopAnim = () => { + stopAutoAnim(); + if (stop) { + stop(); + stop = null; + } + }; + + return { + stop: () => { + stopAnim(); + }, + + startAutoAnim: () => { + stopAutoAnim(); + startTimerAutoAnim(); + }, + + getAnimValue: () => { + return animValue; + }, + + setAnimValue: (value) => { + setAnimValue(value); + }, + + next: () => { + next(); + }, + + animEnd: (value: number, duration: number) => { + animEnd(value, duration); + }, + + prev: () => { + prev(); + }, + + getWidthSwiper: () => { + return widthSwiper; + }, + }; + }; + + const movePointer = ( + objSwiper, + swiper: HTMLElement, + next: HTMLElement, + prev: HTMLElement, + ) => { + prev.addEventListener('click', () => { + objSwiper.prev(); + }); + + next.addEventListener('click', () => { + objSwiper.next(); + }); + + let startCursorX: number | null = null; + let offsetValue: number | null = null; + + const pointerMove = (event: PointerEvent) => { + const target = event.target as HTMLElement; + if (!target.hasPointerCapture(event.pointerId)) return; + if (!startCursorX) { + return; + } + const ofsetX = (startCursorX - event.pageX) * 2.5; + + const value = offsetValue + ofsetX / objSwiper.getWidthSwiper(); + + const clampValue = Math.max(Math.min(value, 1), -1); + + objSwiper.setAnimValue(clampValue); + }; + + swiper.addEventListener('pointerdown', (event) => { + const target = event.target as HTMLElement; + if (!target.closest('.ex-wrapper')) { + return; + } + objSwiper.stop(); + + target.setPointerCapture(event.pointerId); + + startCursorX = event.pageX; + offsetValue = objSwiper.getAnimValue(); + + swiper.addEventListener('pointermove', pointerMove); + }); + + swiper.addEventListener('pointerup', (event: PointerEvent) => { + const target = event.target as HTMLElement; + + if (target.hasPointerCapture(event.pointerId)) { + target.releasePointerCapture(event.pointerId); + } + if (!target.closest('.ex-wrapper')) { + return; + } + swiper.removeEventListener('pointermove', pointerMove); + + objSwiper.startAutoAnim(); + objSwiper.animEnd(objSwiper.getAnimValue(), 100); + startCursorX = null; + offsetValue = null; + }); + }; + + const exSwiper1 = document.querySelector('#ex-swiper-1') as HTMLElement; + const exPagination1 = document.querySelector( + '#swiper-pagination-1', + ) as HTMLElement; + const exNext1 = document.querySelector('#ex-button-next-1') as HTMLElement; + const exPrev1 = document.querySelector('#ex-button-prev-1') as HTMLElement; + const objSwiper1 = slider(exSwiper1, exPagination1); + + movePointer(objSwiper1, exSwiper1, exNext1, exPrev1); + + const exSwiper2 = document.querySelector('#ex-swiper-2') as HTMLElement; + const exPagination2 = document.querySelector( + '#swiper-pagination-2', + ) as HTMLElement; + const exNext2 = document.querySelector('#ex-button-next-2') as HTMLElement; + const exPrev2 = document.querySelector('#ex-button-prev-2') as HTMLElement; + const objSwiper2 = slider(exSwiper2, exPagination2); + + movePointer(objSwiper2, exSwiper2, exNext2, exPrev2); +} diff --git a/src/scripts/animation-tech-specs.ts b/src/scripts/animation-tech-specs.ts new file mode 100644 index 000000000..c26b372d7 --- /dev/null +++ b/src/scripts/animation-tech-specs.ts @@ -0,0 +1,204 @@ +export const animationTechSpecs = () => { + const svg = document.getElementById('mySvg'); + + const createLine = (pointers: Array) => { + const circle1 = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'circle', + ); + const circle2 = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'circle', + ); + const polyline = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'polyline', + ); + + polyline.setAttribute('class', 'draw draw1'); + polyline.setAttribute('points', pointers.toString()); + polyline.setAttribute('stroke', 'width="4"'); + polyline.setAttribute('stroke', '#05C2DF'); + polyline.setAttribute('pathLength', '100'); + polyline.setAttribute('fill', 'none'); + + circle1.setAttribute('class', 'circle1'); + circle1.setAttribute('cx', '' + pointers[0][0]); + circle1.setAttribute('cy', '' + pointers[0][1]); + circle1.setAttribute('r', '0px'); + circle1.setAttribute('fill', '#05C2DF'); + + circle2.setAttribute('class', 'circle2'); + circle2.setAttribute('cx', '' + pointers[pointers.length - 1][0]); + circle2.setAttribute('cy', '' + pointers[pointers.length - 1][1]); + circle2.setAttribute('r', '0px'); + circle2.setAttribute('fill', '#05C2DF'); + + const setPoiner = (pointers) => { + polyline.setAttribute('points', pointers.toString()); + circle1.setAttribute('cx', '' + pointers[0][0]); + circle1.setAttribute('cy', '' + pointers[0][1]); + circle2.setAttribute('cx', '' + pointers[pointers.length - 1][0]); + circle2.setAttribute('cy', '' + pointers[pointers.length - 1][1]); + }; + + return { + element: [polyline, circle1, circle2], + setPoiner: (pointers) => { + setPoiner(pointers); + }, + }; + }; + + // circle.setAttribute("stroke", "black"); + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + document + .querySelector('.section-tech-specs__content-image-svg') + .classList.add('animate'); + svg.classList.add('animate'); + ( + document.querySelectorAll( + '.section-tech-specs__content-box', + ) as NodeListOf + ).forEach((item) => { + item.classList.add('animate'); + item.style.setProperty('--rand', (Math.random() - 0.5) * 0.5 + 's'); + }); + observer.disconnect(); + } + }); + }, + { + threshold: 0.9, + }, + ); + + observer.observe(svg); + + function observeLayout(el, callback) { + let last = getRelativeRect(el); + + function check() { + const current = getRelativeRect(el); + + const changed = + current.x !== last.x || + current.y !== last.y || + current.width !== last.width || + current.height !== last.height; + + if (changed) { + callback(current, last); + last = current; + } + + requestAnimationFrame(check); + } + + requestAnimationFrame(check); + } + + function getRelativeRect(el) { + const rect = el.getBoundingClientRect(); + + const parentRect = document + .querySelector('.section-tech-specs__svg-container') + .getBoundingClientRect(); + + return { + x: rect.left - parentRect.left, + y: rect.top - parentRect.top, + width: rect.width, + height: rect.height, + }; + } + + const createLine1 = () => { + const pointer1 = document.querySelector('#pointer-1'); + const pointer1_1 = document.querySelector('#pointer-1-1'); + + let pos = getRelativeRect(pointer1); + let pos2 = getRelativeRect(pointer1_1); + + const pose_line = () => [ + [pos2.x, pos2.y], + [pos2.x, pos.y], + [pos.x, pos.y], + ]; + + const line1 = createLine(pose_line()); + svg.append(...line1.element); + + observeLayout(pointer1, (current, previous) => { + pos = current; + line1.setPoiner(pose_line()); + }); + + observeLayout(pointer1_1, (current, previous) => { + pos2 = current; + line1.setPoiner(pose_line()); + }); + }; + + const createLine2 = () => { + const pointer1 = document.querySelector('#pointer-2'); + const pointer1_1 = document.querySelector('#pointer-2-2'); + + let pos = getRelativeRect(pointer1); + let pos2 = getRelativeRect(pointer1_1); + + const pose_line = () => [ + [pos2.x, pos2.y], + [pos.x, pos2.y], + [pos.x, pos.y], + ]; + + const line1 = createLine(pose_line()); + svg.append(...line1.element); + + observeLayout(pointer1, (current, previous) => { + pos = current; + line1.setPoiner(pose_line()); + }); + + observeLayout(pointer1_1, (current, previous) => { + pos2 = current; + line1.setPoiner(pose_line()); + }); + }; + + const createLine3 = () => { + const pointer1 = document.querySelector('#pointer-3'); + const pointer1_1 = document.querySelector('#pointer-3-3'); + + let pos = getRelativeRect(pointer1); + let pos2 = getRelativeRect(pointer1_1); + + const pose_line = () => [ + [pos2.x, pos2.y], + [pos.x, pos2.y], + [pos.x, pos.y], + ]; + + const line1 = createLine(pose_line()); + svg.append(...line1.element); + + observeLayout(pointer1, (current, previous) => { + pos = current; + line1.setPoiner(pose_line()); + }); + + observeLayout(pointer1_1, (current, previous) => { + pos2 = current; + line1.setPoiner(pose_line()); + }); + }; + + createLine1(); + createLine2(); + createLine3(); +}; diff --git a/src/scripts/button-play-video.ts b/src/scripts/button-play-video.ts new file mode 100644 index 000000000..0a1b79f81 --- /dev/null +++ b/src/scripts/button-play-video.ts @@ -0,0 +1,43 @@ +export const buttonPlayVideo = () => { + const openBtn = document.querySelector('.video-open') as HTMLElement; + const openBtn2 = document.querySelector('.video-open2') as HTMLElement; + const modal = document.querySelector('#videoModal') as HTMLElement; + const iframe = modal.querySelector( + '.video-modal__iframe', + ) as HTMLIFrameElement; + const closeBtn = modal.querySelector('.video-modal__close') as HTMLElement; + const overlay = modal.querySelector('.video-modal__overlay') as HTMLElement; + + function openModal(videoUrl) { + const separator = videoUrl.includes('?') ? '&' : '?'; + + modal.classList.add('is-active'); + + iframe.src = `${videoUrl}${separator}autoplay=1&mute=0`; + + document.body.style.overflow = 'hidden'; + } + + function closeModal() { + modal.classList.remove('is-active'); + iframe.src = ''; // стоп видео + document.body.style.overflow = ''; + } + + openBtn.addEventListener('click', () => { + openModal(openBtn.dataset.video); + }); + + openBtn2.addEventListener('click', () => { + openModal(openBtn.dataset.video); + }); + + closeBtn.addEventListener('click', closeModal); + overlay.addEventListener('click', closeModal); + + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + closeModal(); + } + }); +}; diff --git a/src/scripts/dropdowns.ts b/src/scripts/dropdowns.ts new file mode 100644 index 000000000..729f704ab --- /dev/null +++ b/src/scripts/dropdowns.ts @@ -0,0 +1,153 @@ +export const dropdowns = () => { + const menus = document.querySelectorAll( + '.dropdown', + ) as NodeListOf; + + menus.forEach((menu) => { + const list = menu.querySelector('.dropdown__list') as HTMLElement; + const trigger = menu.querySelector('.dropdown__trigger') as HTMLElement; + const itemButtons = menu.querySelectorAll( + '.dropdown__item-button', + ) as NodeListOf; + + let selected = [...itemButtons].find( + (item) => item.hasAttribute('selected') || itemButtons[0], + ); + + const setSelected = (element: HTMLElement) => { + selected.classList.remove('dropdown__item-button--selected'); + selected = element; + trigger.textContent = element.textContent; + selected.classList.add('dropdown__item-button--selected'); + + const event = new CustomEvent('setSelected', { + detail: { selected: element.textContent }, + }); + + menu.dataset.value = element.textContent; + + menu.dispatchEvent(event); + }; + + setSelected(selected); + + // dropdown__item-button--selected + const updateItemButtons = () => { + const itemButtons = menu.querySelectorAll( + '.dropdown__item-button', + ) as NodeListOf; + + itemButtons.forEach((itemButon) => { + itemButon.addEventListener('click', () => { + setSelected(itemButon); + menu.classList.toggle('dropdown--active'); + }); + }); + + setSelected(itemButtons[0]); + }; + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + updateItemButtons(); + }); + }); + + observer.observe(list, { + childList: true, + characterData: true, + }); + + updateItemButtons(); + + list.addEventListener( + 'wheel', + (event) => { + event.preventDefault(); + + list.scrollTop += event.deltaY * 0.25; + }, + { passive: false }, + ); + + trigger.addEventListener('click', () => { + menu.classList.toggle('dropdown--active'); + + document.addEventListener('click', (event) => { + if ((event.target as HTMLElement).closest('.dropdown__list') === list) { + return; + } + + if ( + (event.target as HTMLElement).closest('.dropdown__trigger') === + trigger + ) { + return; + } + + menu.classList.remove('dropdown--active'); + }); + }); + }); + + const priceEl = document.getElementById('price'); + const priceDropdownEl = document.getElementById('price-dropdown'); + + const setPrice = () => { + priceEl.textContent = `${1200 * +priceDropdownEl.dataset.value}$`; + }; + + setPrice(); + priceDropdownEl.addEventListener('setSelected', setPrice); + + const countriesCities = { + Ukraine: ['Kyiv', 'Kharkiv', 'Odesa', 'Dnipro', 'Lviv', 'Zaporizhzhia'], + + 'United Kingdom': [ + 'London', + 'Manchester', + 'Birmingham', + 'Liverpool', + 'Leeds', + 'Glasgow', + ], + + Morocco: ['Casablanca', 'Rabat', 'Marrakesh', 'Fes', 'Tangier', 'Agadir'], + + China: ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen', 'Chengdu', 'Wuhan'], + + France: ['Paris', 'Marseille', 'Lyon', 'Toulouse', 'Nice', 'Nantes'], + + Germany: [ + 'Berlin', + 'Hamburg', + 'Munich', + 'Cologne', + 'Frankfurt', + 'Stuttgart', + ], + + Italy: ['Rome', 'Milan', 'Naples', 'Turin', 'Florence', 'Bologna'], + + Spain: ['Madrid', 'Barcelona', 'Valencia', 'Seville', 'Zaragoza', 'Malaga'], + + Poland: ['Warsaw', 'Krakow', 'Lodz', 'Wroclaw', 'Poznan', 'Gdansk'], + }; + + const dropdownCountry = document.getElementById('dropdown-country'); + const dropdownCity = document.getElementById('dropdown-city'); + + const setCityDropdown = () => { + let innerHTML = ''; + countriesCities[dropdownCountry.dataset.value.trim()].forEach((city) => { + innerHTML += ``; + }); + + const list = dropdownCity.querySelector('.dropdown__list') as HTMLElement; + list.innerHTML = innerHTML; + }; + + setCityDropdown(); + + dropdownCountry.addEventListener('setSelected', setCityDropdown); +}; diff --git a/src/scripts/forms.ts b/src/scripts/forms.ts new file mode 100644 index 000000000..72dfd40dc --- /dev/null +++ b/src/scripts/forms.ts @@ -0,0 +1,409 @@ +export const forms = () => { + const form1 = document.getElementById('form-1') as HTMLFormElement; + + form1.addEventListener('submit', (event) => { + event.preventDefault(); + + const inputs = form1.querySelectorAll( + 'input, textarea', + ) as NodeListOf; + + let error = false; + inputs.forEach((input) => { + if (!input.required) { + return; + } + + if (input.name === 'name') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your name*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + + if (input.name === 'email') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your email*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value)) { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill correct email*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + + if (input.name === 'phone') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your phone*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } else if (!/^\+?[0-9\s\-()]{7,20}$/.test(input.value)) { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill correct phone*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + + if (input.name === 'message') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your massage*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + }); + + if (!error) { + form1.reset(); + } + }); + + const form2 = document.getElementById('form-2') as HTMLFormElement; + + form2.addEventListener('submit', (event) => { + event.preventDefault(); + + const inputs = form2.querySelectorAll( + 'input, textarea', + ) as NodeListOf; + + let error = false; + inputs.forEach((input) => { + if (!input.required) { + return; + } + + if (input.name === 'name') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your name*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + + if (input.name === 'adress') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your adress*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + + if (input.name === 'email') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your email*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value)) { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill correct email*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + + if (input.name === 'phone') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your phone*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } else if (!/^\+?[0-9\s\-()]{7,20}$/.test(input.value)) { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill correct phone*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + + if (input.name === 'message') { + if (input.value.trim() === '') { + input.parentElement.setAttribute('data-error', ''); + input.parentElement.querySelector('label').textContent = + 'Please, fill your massage*'; + error = true; + input.addEventListener('focus', () => { + input.parentElement.removeAttribute('data-error'); + input.parentElement.querySelector('label').textContent = + input.parentElement.getAttribute('data-text'); + }); + } + } + }); + + if (!error) { + form2.reset(); + + const buy = form2.closest('.buy'); + if (buy.classList.contains('buy--value-1')) { + buy.classList.remove('buy--value-1'); + buy.classList.add('buy--value-2'); + } + } + }); + + const payForm = () => { + const brandBox = document.getElementById('cardBrand'); + + function detectCardType(number) { + const cleaned = number.replace(/\D/g, ''); + + if (/^4/.test(cleaned)) { + return 'visa'; + } + + if ( + /^(5[1-5])/.test(cleaned) || + /^(222[1-9]|22[3-9]\d|2[3-6]\d{2}|27[01]\d|2720)/.test(cleaned) + ) { + return 'mastercard'; + } + + return null; + } + + const cardInputs = document.querySelectorAll( + '#paymentForm .form__card-number .form__field-input', + ) as NodeListOf; + const expiryInput = document.getElementById( + 'paycard-input-date', + ) as HTMLInputElement; + const form = document.getElementById('paymentForm') as HTMLFormElement; + const paycardInputCVV = document.getElementById( + 'paycard-input-CVV', + ) as HTMLInputElement; + const paycardInputName = document.getElementById( + 'paycard-input-name', + ) as HTMLInputElement; + + cardInputs.forEach((input, index) => { + input.addEventListener('input', (e) => { + let value = (e.target as HTMLInputElement).value.replace(/\D/g, ''); + (e.target as HTMLInputElement).value = value; + + if (index === 0) { + const cardType = detectCardType(value); + + if (cardType === 'mastercard') { + brandBox.classList.add('form__card-image--mastercard'); + brandBox.classList.remove('form__card-image--visa'); + } else if (cardType === 'visa') { + brandBox.classList.remove('form__card-image--mastercard'); + brandBox.classList.add('form__card-image--visa'); + } else { + brandBox.classList.remove('form__card-image--mastercard'); + brandBox.classList.remove('form__card-image--visa'); + } + } + + if (value.length === 4 && index < cardInputs.length - 1) { + cardInputs[index + 1].focus(); + } + }); + + input.addEventListener('keydown', (e) => { + if (e.key === 'Backspace' && input.value === '' && index > 0) { + cardInputs[index - 1].focus(); + } + }); + + input.addEventListener('paste', (e) => { + e.preventDefault(); + + const pasted = e.clipboardData + .getData('text') + .replace(/\D/g, '') + .slice(0, 16); + + cardInputs.forEach((field, i) => { + field.value = pasted.slice(i * 4, i * 4 + 4); + }); + + const nextEmpty = [...cardInputs].find( + (input) => input.value.length < 4, + ); + if (nextEmpty) { + nextEmpty.focus(); + } else { + cardInputs[3].focus(); + } + }); + }); + + expiryInput.addEventListener('input', (e) => { + let value = (e.target as HTMLInputElement).value + .replace(/\D/g, '') + .slice(0, 4); + + if (value.length >= 3) { + value = value.slice(0, 2) + '/' + value.slice(2); + } + + (e.target as HTMLInputElement).value = value; + }); + + form.addEventListener('submit', (e) => { + e.preventDefault(); + + const cardNumber = [...cardInputs].map((input) => input.value).join(''); + let error = false; + + const data = expiryInput.value.trim(); + const cvv = paycardInputCVV.value.trim(); + const name = paycardInputName.value.trim(); + + if (name.length < 2) { + paycardInputName.parentElement.setAttribute('data-error', ''); + const label = paycardInputName.parentElement.querySelector( + 'label', + ) as HTMLLabelElement; + label.textContent = 'Please, fill your name'; + error = true; + + paycardInputName.addEventListener('focus', () => { + label.textContent = label.getAttribute('data-text'); + paycardInputName.parentElement.removeAttribute('data-error'); + }); + } + + if (cvv.length < 3) { + paycardInputCVV.parentElement.setAttribute('data-error', ''); + const label = paycardInputCVV.parentElement.querySelector( + 'label', + ) as HTMLLabelElement; + label.textContent = 'Please, fill your CVV'; + error = true; + + paycardInputCVV.addEventListener('focus', () => { + label.textContent = label.getAttribute('data-text'); + paycardInputCVV.parentElement.removeAttribute('data-error'); + }); + } + + if (data.length < 5) { + expiryInput.parentElement.setAttribute('data-error', ''); + const label = expiryInput.parentElement.querySelector( + 'label', + ) as HTMLLabelElement; + label.textContent = 'Please, fill your date'; + error = true; + + expiryInput.addEventListener('focus', () => { + label.textContent = label.getAttribute('data-text'); + expiryInput.parentElement.removeAttribute('data-error'); + }); + } + + if (cardNumber.length !== 16) { + const label = form.querySelector( + '.form__card-number .form__field-label', + ); + const fields = form.querySelectorAll('.form__card-number .form__field'); + + fields.forEach((field) => { + field.setAttribute('data-error', ''); + }); + + label.textContent = 'Please, fill your number card'; + error = true; + + const inputs = form.querySelectorAll( + '.form__card-number .form__field-input', + ); + + inputs.forEach((input) => { + input.addEventListener('focus', () => { + fields.forEach((field) => { + field.removeAttribute('data-error'); + }); + + label.textContent = label.getAttribute('data-text'); + }); + }); + } + + if (!error) { + form.reset(); + + const buy = form2.closest('.buy'); + if (buy.classList.contains('buy--value-2')) { + buy.classList.remove('buy--value-2'); + buy.classList.add('buy--value-3'); + } + } + }); + }; + + payForm(); +}; diff --git a/src/scripts/main.js b/src/scripts/main.js deleted file mode 100644 index ad9a93a7c..000000000 --- a/src/scripts/main.js +++ /dev/null @@ -1 +0,0 @@ -'use strict'; diff --git a/src/scripts/main.ts b/src/scripts/main.ts new file mode 100644 index 000000000..f501552e8 --- /dev/null +++ b/src/scripts/main.ts @@ -0,0 +1,17 @@ +'use strict'; + +import { animationAboutUs } from './animation-about-us'; +import { animationPlayButton } from './animation-play-button'; +import { animationSlider } from './animation-slider'; +import { animationTechSpecs } from './animation-tech-specs'; +import { buttonPlayVideo } from './button-play-video'; +import { dropdowns } from './dropdowns'; +import { forms } from './forms'; + +animationAboutUs(); +animationPlayButton(); +animationSlider(); +animationTechSpecs(); +buttonPlayVideo(); +dropdowns(); +forms(); diff --git a/src/scripts/myUtils/index.ts b/src/scripts/myUtils/index.ts new file mode 100644 index 000000000..f4d03e913 --- /dev/null +++ b/src/scripts/myUtils/index.ts @@ -0,0 +1 @@ +export * as myUtils from './myUtils' diff --git a/src/scripts/myUtils/myUtils.ts b/src/scripts/myUtils/myUtils.ts new file mode 100644 index 000000000..3019b7087 --- /dev/null +++ b/src/scripts/myUtils/myUtils.ts @@ -0,0 +1,92 @@ +export function lerp(a, b, t) { + return a + (b - a) * t; +} + +export function getValueFromCurve(stops, t) { + for (let i = 0; i < stops.length - 1; i++) { + const a = stops[i]; + const b = stops[i + 1]; + + if (t >= a.pos && t <= b.pos) { + const localT = (t - a.pos) / (b.pos - a.pos); + return lerp(a.value, b.value, localT); + } + } + + return stops[stops.length - 1].value; +} + +export function smoothstep(t) { + return t * t * (3 - 2 * t); +} + +export function mapRange( + value: number, + fromMin: number, + fromMax: number, + toMin: number, + toMax: number, + interpolation: 'smooth' | 'linean' = 'smooth', +) { + const normalize = (value - fromMin) / (fromMax - fromMin); + + let interpolate = 0; + + switch (interpolation) { + case 'linean': + interpolate = normalize; + break; + case 'smooth': + interpolate = smoothstep(normalize); + break; + default: + interpolate = normalize; + break; + } + + return interpolate * (toMax - toMin) + toMin; +} + +export function tween( + from: number, + to: number, + duration: number, + onUpdate: (value: number) => void, + onComplete: (value) => void = Function, +) { + let startTime: number = performance.now(); + + let stop: Function | null; + + let stoped = false; + + stop = () => { + stoped = true; + }; + + const loop = (time) => { + if (time - startTime >= duration) { + onUpdate(to); + onComplete(to); + stop = null; + return; + } + + if (stoped) { + stop = null; + return; + } + + const increaseValue = ((to - from) / duration) * (time - startTime) + from; + + const interpolate = mapRange(increaseValue, to, from, to, from); + + onUpdate(interpolate); + + requestAnimationFrame(loop); + }; + + requestAnimationFrame(loop); + + return stop; +} diff --git a/src/styles/_fonts.scss b/src/styles/_fonts.scss index 619b8c52b..80c38fb3e 100644 --- a/src/styles/_fonts.scss +++ b/src/styles/_fonts.scss @@ -1,5 +1,5 @@ @font-face { - font-family: Roboto, Arial, Helvetica, sans-serif; + font-family: Inter, sans-serif; src: url('../fonts/FontFont_FF.Mark.Pro.otf') format('otf'); font-weight: normal; font-style: normal; diff --git a/src/styles/_typography.scss b/src/styles/_typography.scss index 1837eb46e..8ac1efb60 100644 --- a/src/styles/_typography.scss +++ b/src/styles/_typography.scss @@ -1,3 +1,50 @@ h1 { - @extend %h1; + color: $white; + font-weight: 900; + font-size: 26px; + line-height: 130%; + + & span { + color: $contrast-color; + font-weight: 400; + font-size: inherit; + line-height: inherit; + } + + @include on-small-desktop { + font-size: 46px; + } + + @include on-desktop { + font-size: 54px; + } +} + +h2 { + color: $white; + font-weight: 900; + font-size: 26px; + line-height: 100%; + + & span { + color: $contrast-color; + font-weight: 400; + font-size: inherit; + line-height: inherit; + } + + @include on-small-desktop { + font-size: 36px; + } +} + +h3 { + color: $white; + font-weight: 900; + font-size: 16px; + line-height: 100%; + + @include on-small-desktop { + font-size: 18px; + } } diff --git a/src/styles/blocks/FAQ.scss b/src/styles/blocks/FAQ.scss new file mode 100644 index 000000000..25fa2ce0c --- /dev/null +++ b/src/styles/blocks/FAQ.scss @@ -0,0 +1,225 @@ +.FAQ { + display: block; + background-color: #000000b2; + + &__main-container { + display: block; + padding: 0; + height: 100%; + + @include on-small-desktop { + display: grid; + padding-inline: var(--padding); + padding-block: 132px 184px; + } + } + + &__content { + background-color: #191536; + height: 100%; + + overflow-y: auto; + + @include on-small-desktop { + border-radius: 4px; + grid-column: 2 / -2; + } + + @include on-desktop { + grid-column: 3 / -3; + } + } + + &__close-link-wrapper { + display: flex; + justify-content: end; + padding: 20px; + + @include on-tablet { + padding: 34px; + } + + @include on-small-desktop { + padding: 18px; + } + } + + &__close-link { + display: block; + color: $white; + + transition: + color $transition, + filter $transition; + + &:hover { + color: $contrast-color; + + filter: drop-shadow(0 0 5px $contrast-color); + } + + & svg { + display: block; + } + } + + &__title { + margin-top: 10px; + grid-column: 1 / -1; + + @include on-tablet { + margin-top: 20px; + } + + @include on-small-desktop { + grid-column: 2 / -2; + } + } + + &__cards-box { + margin-top: 30px; + display: flex; + flex-direction: column; + gap: 13px; + grid-column: 1 / -1; + + @include on-tablet { + gap: 18px; + } + + @include on-small-desktop { + grid-column: 2 / -2; + } + } + + &__more { + display: flex; + flex-direction: column; + align-items: center; + justify-self: start; + gap: 7px; + color: $contrast-color; + margin-top: 50px; + margin-bottom: 50px; + + transition: color $transition; + + &::after { + width: 9px; + height: 6px; + content: ''; + background-color: $contrast-color; + mask-image: url('../images/icon/arrow-bottom.svg'); + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + -webkit-mask-image: url('../images/icon/arrow-bottom.svg'); + -webkit-mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + + transition: + background-color $transition, + transform $transition; + } + + &:hover { + color: $white; + + &::after { + background-color: $white; + transform: translateY(5px); + } + } + } + + &__inside-container { + @include on-small-desktop { + --count-columns: 10; + + padding: 0; + } + + @include on-desktop { + --count-columns: 12; + } + } + + &__card { + border: $contrast-color solid 1px; + border-radius: 4px; + + transition: + border-color $transition, + box-shadow $transition; + + &:has(&-title:hover) { + border-color: $white; + box-shadow: 0 0 5px $contrast-color; + } + + &-title { + display: flex; + justify-content: space-between; + padding: 20px 12px; + font-family: Inter, sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 150%; + color: $contrast-color; + cursor: pointer; + list-style: none; + + transition: color $transition; + + @include on-tablet { + padding: 20px 30px; + } + + &:hover { + color: #1ee1ff; + } + + &::after { + @include on-small-desktop { + content: '▶'; + font-size: 12px; + transition: transform $transition; + } + } + } + + &-box { + padding: 20px 12px; + padding-top: 0; + + @include on-tablet { + padding: 20px 30px; + padding-top: 0; + } + } + + &-p { + font-weight: 400; + font-size: 16px; + line-height: 150%; + } + + &-data { + font-family: Inter, sans-serif; + font-weight: 400; + font-size: 12px; + line-height: 150%; + color: #545454; + margin-top: 30px; + + @include on-tablet { + margin-top: 15px; + } + } + + &[open] &-title::after { + transform: rotateZ(90deg); + } + } +} diff --git a/src/styles/blocks/animSvg.scss b/src/styles/blocks/animSvg.scss new file mode 100644 index 000000000..9f29de253 --- /dev/null +++ b/src/styles/blocks/animSvg.scss @@ -0,0 +1,29 @@ +.animSvg { + width: 100%; + height: 300px; + + & .draw { + stroke-dasharray: 100; + stroke-dashoffset: 100; + filter: drop-shadow(0 0 5px $contrast-color); + } + + &.animate .draw.draw1 { + animation: + draw 600ms linear forwards, + shadow 1000ms ease-in-out infinite alternate; + animation-delay: 100ms; + } + + &.animate .circle1 { + animation: + circle-scale 200ms ease forwards, + shadow 1200ms ease-in-out infinite alternate; + } + + &.animate .circle2 { + animation: + circle-scale 200ms ease forwards, + shadow 1200ms ease-in-out infinite alternate; + } +} diff --git a/src/styles/blocks/animations.scss b/src/styles/blocks/animations.scss new file mode 100644 index 000000000..c170c86f2 --- /dev/null +++ b/src/styles/blocks/animations.scss @@ -0,0 +1,33 @@ +@keyframes trans { + from { + transform: scale(0); + } + + to { + transform: scale(1); + } +} + +@keyframes draw { + from { + stroke-dashoffset: 100; + } + to { + stroke-dashoffset: 0; + } +} + +@keyframes shadow { + from { + filter: drop-shadow(0 0 0 $contrast-color); + } + to { + filter: drop-shadow(0 0 5px $contrast-color); + } +} + +@keyframes circle-scale { + to { + r: 6px; + } +} diff --git a/src/styles/blocks/button-play.scss b/src/styles/blocks/button-play.scss new file mode 100644 index 000000000..92123329d --- /dev/null +++ b/src/styles/blocks/button-play.scss @@ -0,0 +1,139 @@ +.button-play { + background: none; + border: none; + text-decoration: none; + cursor: pointer; + width: fit-content; + height: fit-content; + display: flex; + align-items: center; + color: white; + + &__left-box { + width: 66px; + height: 66px; + position: relative; + display: flex; + justify-content: center; + align-items: center; + + transition: margin 0.3s; + pointer-events: none; + } + + &__triangle { + width: 68px; + height: 68px; + + mask-image: url('../images/triangle.svg'); + mask-size: contain; + mask-position: center; + mask-repeat: no-repeat; + background-color: #05c2df; + position: absolute; + + transition: + transform cubic-bezier(0.74, -0.53, 0.44, 1.46) 0.5s, + background-color cubic-bezier(0.74, -0.53, 0.44, 1.46) 0.5s; + } + + &__circle { + width: 68px; + height: 68px; + + mask-image: url('../images/circle.svg'); + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background-color: #05c2df; + position: absolute; + + transition: transform 0.3s; + } + + &__text { + margin-left: 40px; + font-family: Inter, sans-serif; + font-weight: 500; + font-size: 16px; + line-height: 130%; + + transition: transform cubic-bezier(0.33, -1.77, 0.21, 1.03) 0.5s; + } + + &:hover &__text { + transform: translateX(10px); + } + + &__half-circle { + width: 68px; + height: 68px; + + mask-image: url('../images/half-circle.svg'); + + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background-color: #05c2df; + position: absolute; + + transition: transform cubic-bezier(0.11, 0.48, 0.09, 1.01) 1s; + } + + &:hover &__left-box { + margin-right: 10px; + } + + &:hover &__triangle { + transform: scale(1.4); + background-color: white; + } + + &:hover &__circle { + transform: scale(1.2); + } + + &:hover &__half-circle { + transform: scale(1.2) rotateZ(2160deg); + } + + &__text-and-lines { + display: flex; + flex-direction: column; + width: fit-content; + gap: 10px; + + transition: + text-shadow 0.5s, + gap 1s; + } + + &__line { + background-color: #05c2df67; + + transition: background-color 0.3s; + } + + &:hover &__line { + background-color: #05c2df; + } + + &:hover &__text-and-lines { + text-shadow: 0 0 4px #05c2df; + gap: 15px; + } + + &__dots-line { + display: flex; + height: 2px; + align-items: center; + width: fit-content; + margin-left: -3px; + + &--hover div { + transition: + transform 0.3s, + background-color 0.3s; + } + } +} diff --git a/src/styles/blocks/button.scss b/src/styles/blocks/button.scss new file mode 100644 index 000000000..6053a918f --- /dev/null +++ b/src/styles/blocks/button.scss @@ -0,0 +1,28 @@ +.button { + display: flex; + align-items: center; + justify-content: center; + background-color: $contrast-color; + border-radius: 4px; + color: $white; + height: 48px; + width: 200px; + font-family: inherit; + font-weight: 500; + font-size: 16px; + line-height: 130%; + text-align: center; + border: none; + cursor: pointer; + + transition: + background-color $transition, + color $transition, + box-shadow $transition; + + &:hover { + color: $contrast-color; + background-color: $white; + box-shadow: 0 0 8px #05c2df; + } +} diff --git a/src/styles/blocks/buy-button-mob.scss b/src/styles/blocks/buy-button-mob.scss new file mode 100644 index 000000000..7de64c55a --- /dev/null +++ b/src/styles/blocks/buy-button-mob.scss @@ -0,0 +1,35 @@ +.buy-button-mob { + z-index: 1; + position: fixed; + display: flex; + inset: 0; + pointer-events: none; + + @include on-small-desktop { + display: none; + } + + &__gradient { + background: linear-gradient(180deg, #0000, #04040496, #050505); + height: 112px; + + width: 100%; + margin-top: auto; + } + + &__container { + height: 100%; + } + + &__button { + grid-column: 1 / -1; + width: 100%; + margin-top: auto; + margin-bottom: 10px; + pointer-events: all; + + @include on-tablet { + grid-column: 2 / -2; + } + } +} diff --git a/src/styles/blocks/buy.scss b/src/styles/blocks/buy.scss new file mode 100644 index 000000000..6242ed03f --- /dev/null +++ b/src/styles/blocks/buy.scss @@ -0,0 +1,330 @@ +.buy { + /* stylelint-disable no-descending-specificity */ + min-width: 320px; + background: $gradient; + overflow-y: auto; + + &__complete-button { + width: 100%; + margin-top: 30px; + + @include on-tablet { + width: 60%; + margin-top: 50px; + } + + @include on-small-desktop { + width: 218px; + } + + @include on-desktop { + margin-top: 70px; + } + } + + &__quantity { + &-box { + margin-top: 30px; + grid-column: 1 span; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + + @include on-tablet { + align-items: start; + grid-column: 2 span; + gap: 20px; + } + + @include on-small-desktop { + grid-column: 1 / 3; + } + } + + &-p { + font-weight: 400; + font-size: 16px; + line-height: 130%; + text-align: center; + } + + &-dropdown { + width: 60%; + } + } + + &__form { + display: none; + margin-top: 30px; + grid-column: 1 / -1; + + @include on-tablet { + margin-top: 48px; + } + + @include on-small-desktop { + grid-row: 1 / 3; + grid-column: 7 / -1; + } + + @include on-desktop { + grid-row: 1 / 3; + grid-column: 9 / -1; + } + } + + &--value-1 &__top-menu-title span:nth-child(1) { + color: $contrast-color; + } + + &--value-2 &__top-menu-title span:nth-child(2) { + color: $contrast-color; + } + + &--value-3 &__top-menu-title span:nth-child(3) { + color: $contrast-color; + } + + &--value-1 &__top-menu-line::after { + width: 33.33%; + } + + &--value-2 &__top-menu-line::after { + transform: translateX(100%); + width: 33.33%; + } + + &--value-3 &__top-menu-line::after { + transform: translateX(200%); + width: 33.33%; + } + + &--value-2 &__quantity-box { + @include on-tablet { + @media (max-width: 1280px) { + grid-column: 2 / 4; + } + } + } + + &--value-2 &__form { + @include on-tablet { + @media (max-width: 1280px) { + grid-column: 2 / -2; + } + } + } + + &__main-container { + @include on-small-desktop { + margin-top: 120px; + } + } + + &--value-3 &__main-container { + display: none; + } + + &__complete-container { + display: none; + } + + &__complete-title { + text-align: center; + margin-top: -10vh; + } + + &__complete-p { + font-weight: 400; + font-size: 16px; + line-height: 130%; + text-align: center; + color: $white; + margin-top: 16px; + + @include on-tablet { + margin-top: 20px; + } + + @include on-small-desktop { + margin-top: 36px; + } + + @include on-desktop { + margin-top: 56px; + } + } + + &--value-3 &__complete-container { + display: flex; + flex-direction: column; + justify-content: center; + + height: 70%; + align-items: center; + } + + &__top-menu { + margin-top: 20px; + display: flex; + flex-direction: column; + gap: 16px; + + @include on-small-desktop { + margin-top: 0; + } + + &-title { + display: flex; + justify-content: space-around; + + & span { + font-weight: 500; + font-size: 14px; + line-height: 130%; + color: #545454; + } + } + + &-line { + position: relative; + height: 1px; + background-color: #2f2f2f; + + &::after { + content: ''; + width: 100%; + position: absolute; + top: -1px; + height: 1px; + background-color: $contrast-color; + } + } + } + + &__image { + display: none; + + @include on-tablet { + display: block; + grid-column: 2 / -2; + background-image: url('../images/bg/1.png'); + width: 100%; + aspect-ratio: 16 / 9; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + } + + @include on-small-desktop { + grid-column: 6 span; + } + + @include on-desktop { + grid-column: 8 span; + position: relative; + left: -15%; + width: 115%; + } + } + + &__price { + &-box { + margin-top: 30px; + grid-column: 1 span; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + + @include on-tablet { + align-items: start; + grid-column: 2 span; + gap: 20px; + } + + @include on-small-desktop { + grid-column: 3 / 6; + } + } + + &-p { + font-weight: 400; + font-size: 16px; + line-height: 130%; + text-align: center; + } + + &-value { + font-weight: 500; + font-size: 26px; + color: $contrast-color; + } + } + + &__form--pay { + flex-direction: column; + gap: 30px; + } + + &--value-1 &__form--data { + display: block; + } + + &--value-2 &__form--pay { + display: flex; + } + + &__form-container { + padding: 0; + + row-gap: 30px; + & > div { + grid-column: 1/-1; + + @include on-tablet { + grid-column: 3 span; + } + + @include on-desktop { + grid-column: 4 span; + } + } + + @include on-small-desktop { + --count-columns: 6; + + margin: 0; + } + + @include on-desktop { + --count-columns: 8; + + padding: 0; + margin: 0; + } + } + + &__form-button { + grid-column: 1 / -1; + width: 100%; + + @include on-tablet { + grid-column: 2 / -2; + } + + @include on-small-desktop { + grid-column: 1 / -1; + width: 200px; + } + } + + &__dropdown-box { + display: flex; + flex-direction: column; + gap: 13px; + } + + /* stylelint-enable no-descending-specificity */ +} diff --git a/src/styles/blocks/card.scss b/src/styles/blocks/card.scss new file mode 100644 index 000000000..c34b4b3c1 --- /dev/null +++ b/src/styles/blocks/card.scss @@ -0,0 +1,88 @@ +.card { + display: flex; + flex-direction: column; + + border-radius: 10px; + padding-inline: 10px; + padding-block: 15px; + + transition: box-shadow $transition; + + @include on-tablet { + align-items: center; + } + + &__image { + display: none; + width: 110px; + height: 110px; + background-image: url('../images/more-than-gaming/1.svg'); + background-repeat: no-repeat; + background-size: contain; + background-position: center; + + transition: filter $transition; + + @include on-tablet { + display: block; + } + &--1 { + background-image: url('../images/more-than-gaming/1.svg'); + } + + &--2 { + background-image: url('../images/more-than-gaming/2.svg'); + } + + &--3 { + background-image: url('../images/more-than-gaming/3.svg'); + } + + &--4 { + background-image: url('../images/more-than-gaming/4.svg'); + } + + &--5 { + background-image: url('../images/why/1.svg'); + } + + &--6 { + background-image: url('../images/why/2.svg'); + } + + &--7 { + background-image: url('../images/why/3.svg'); + } + } + + &:hover { + box-shadow: 0 0 5px $contrast-color; + } + + &:hover &__image { + filter: drop-shadow(0 0 5px $contrast-color); + } + + &__title { + font-weight: 900; + font-size: 16px; + + @include on-tablet { + margin-top: 22px; + } + } + + &__p { + margin-top: 11px; + font-family: Inter, sans-serif; + font-weight: 400; + font-size: 16px; + line-height: 150%; + color: #929292; + + @include on-tablet { + text-align: center; + margin-top: 20px; + } + } +} diff --git a/src/styles/blocks/container.scss b/src/styles/blocks/container.scss new file mode 100644 index 000000000..51dae507f --- /dev/null +++ b/src/styles/blocks/container.scss @@ -0,0 +1,30 @@ +.container { + --count-columns: 2; + --padding: 20px; + + box-sizing: border-box; + display: grid; + padding-inline: var(--padding); + column-gap: 20px; + grid-template-columns: repeat(var(--count-columns), 1fr); + + &--no-grid { + display: block; + } + + @include on-tablet { + --count-columns: 6; + --padding: 34px; + } + + @include on-small-desktop { + --count-columns: 12; + --padding: calc(70px + (100% - 1280px) / 4); + + margin-inline: auto; + } + + @include on-desktop { + --count-columns: 16; + } +} diff --git a/src/styles/blocks/dropdown.scss b/src/styles/blocks/dropdown.scss new file mode 100644 index 000000000..55ed8c4d7 --- /dev/null +++ b/src/styles/blocks/dropdown.scss @@ -0,0 +1,135 @@ +.dropdown { + position: relative; + + --height-dropdown: 150px; + + &__trigger { + position: relative; + + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + font-weight: 400; + font-size: 16px; + line-height: 130%; + + background-color: #191536; + border-radius: 4px; + border: none; + color: $white; + width: 100%; + height: 47px; + + &--lan { + background: none; + width: 50px; + } + + transition: + color $transition, + background-color $transition; + + &:hover { + color: $contrast-color; + background-color: #1c183d; + } + + &--lan:hover { + background: none; + } + + &::after { + position: absolute; + right: 5%; + display: block; + content: ''; + width: 9px; + height: 6px; + background-image: url('../images/icon/dropdown-arrow.png'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + + transition: transform $transition; + } + } + + &__list { + z-index: 1; + position: absolute; + margin-top: 3px; + background-color: #191536; + border-radius: 4px; + border: none; + width: 100%; + height: var(--height-dropdown); + overflow: hidden; + overflow-y: auto; + color: $white; + + transition: + transform $transition, + opacity $transition; + opacity: 0; + transform: translateY(-30px) scaleY(0.8); + + &::-webkit-scrollbar { + width: 2px; + } + + scroll-behavior: smooth; + overscroll-behavior: contain; + + &::-webkit-scrollbar-thumb { + background: rgba(17, 175, 248, 0.69); + border-radius: 10px; + } + + &::-webkit-scrollbar-track { + background-color: #014954; + } + + pointer-events: none; + + // scrollbar-width: thin; + // scrollbar-color: rgba(17, 175, 248, 0.685) transparent; + } + + &--active &__list { + opacity: 1; + transform: translateY(0) scaleY(1); + pointer-events: all; + } + + &--active &__trigger::after { + transform: rotateZ(180deg); + } + + &__item { + width: 100%; + } + + &__item-button { + cursor: pointer; + width: 100%; + border-radius: 4px; + height: 47px; + background: none; + border: none; + color: $white; + font-weight: 400; + font-size: 16px; + line-height: 130%; + + transition: color $transition; + + &:hover { + color: $contrast-color; + } + + &--selected { + border: 2px solid #05c2df4f; + } + } +} diff --git a/src/styles/blocks/ex.scss b/src/styles/blocks/ex.scss new file mode 100644 index 000000000..7782a55b6 --- /dev/null +++ b/src/styles/blocks/ex.scss @@ -0,0 +1,121 @@ +.ex { + &-swiper { + position: relative; + width: 300px; + height: 300px; + touch-action: none; + user-select: none; + -webkit-user-select: none; + } + + &-wrapper { + width: 100%; + height: 100%; + position: absolute; + + overflow: hidden; + + --right: 0%; + --left: 0%; + + clip-path: inset(0 var(--right) 0 var(--left)); + } + + &-image { + width: 100%; + height: 100%; + position: relative; + background-image: url('../images/bg/3720368816_w1280_h640_3720368816.webp'); + background-position: center; + background-size: contain; + background-repeat: no-repeat; + + &--1 { + background-image: url('../images/bg/1.png'); + } + + &--2 { + background-size: 90%; + background-image: url('../images/bg/2.png'); + } + + &--3 { + background-size: 90%; + + background-image: url('../images/bg/3.png'); + } + } + + &-effect { + --width: 0; + --left: 0.4; + + position: absolute; + height: 100%; + width: 10px; + width: calc(10px * var(--width)); + margin-left: calc((100% * var(--left)) + -5px * var(--width)); + border-radius: 50%; + background: radial-gradient( + circle, + white, + $contrast-color, + transparent 100% + ); + box-shadow: + 0 0 20px $contrast-color, + 0 0 60px $contrast-color; + + &::before { + content: ''; + position: absolute; + inset: calc(-40px * var(--width)); + z-index: -1; + + background: radial-gradient(circle, $contrast-color, transparent 60%); + + filter: blur(40px); + opacity: 0.8; + } + } + + &-pagination { + --max-frame: 3; + --current-frame: 0; + + position: relative; + width: 100%; + height: 2px; + background-color: #212121; + bottom: 0; + + &::before { + content: ''; + position: absolute; + left: calc(100% / var(--max-frame) * var(--current-frame)); + width: calc(100% / var(--max-frame)); + height: 2px; + background-color: $contrast-color; + transform-origin: left; + + transition: left $transition; + } + } + + &-button { + font-weight: 500; + font-size: 16px; + line-height: 150%; + background: none; + border: none; + + transition: color $transition; + + cursor: pointer; + + color: $white; + &:hover { + color: $contrast-color; + } + } +} diff --git a/src/styles/blocks/footer.scss b/src/styles/blocks/footer.scss new file mode 100644 index 000000000..47bb23e55 --- /dev/null +++ b/src/styles/blocks/footer.scss @@ -0,0 +1,99 @@ +.footer { + background-color: black; + padding-block: 45px 100px; + + @include on-tablet { + padding-block: 20px 100px; + } + + @include on-small-desktop { + padding-block: 40px; + } + + &__containet { + @include on-tablet { + display: flex; + justify-content: space-between; + align-items: center; + } + } + + &__logo { + grid-column: 1 / 2; + padding: 0; + } + + &__links { + grid-column: 2 / -1; + grid-row: 2 / 3; + + gap: 12px; + + @include on-tablet { + display: none; + } + } + + &__nav { + margin-top: 15px; + grid-column: 1 / 2; + grid-row: 2 / 4; + + @include on-tablet { + margin-top: 0; + } + } + + &__socials { + &-list { + margin-top: 22px; + grid-column: 2 / -1; + grid-row: 3 / 4; + + display: flex; + justify-content: space-between; + width: 160px; + + @include on-tablet { + margin-top: 0; + width: fit-content; + gap: 18px; + } + } + + &-item { + display: block; + height: fit-content; + } + + &-link { + display: block; + width: 20px; + height: 20px; + background-color: $white; + mask-size: contain; + mask-position: center; + mask-repeat: no-repeat; + + transition: background-color $transition; + + &:hover { + background-color: $contrast-color; + } + + &--facebook { + mask-image: url('../images/icon/socials/facebook.svg'); + } + + &--youtube { + mask-image: url('../images/icon/socials/youtube.svg'); + } + &--twitter { + mask-image: url('../images/icon/socials/twitter.svg'); + } + &--reddit { + mask-image: url('../images/icon/socials/reddit.svg'); + } + } + } +} diff --git a/src/styles/blocks/form.scss b/src/styles/blocks/form.scss new file mode 100644 index 000000000..c0551ddbe --- /dev/null +++ b/src/styles/blocks/form.scss @@ -0,0 +1,190 @@ +.form { + /* stylelint-disable no-descending-specificity */ + display: flex; + flex-direction: column; + gap: 24px; + + &__field { + border-bottom: 1px solid #2f2f2f; + transition: border $transition; + + &--gap-17 { + display: flex; + flex-direction: column; + gap: 17px; + } + + &:hover { + border-color: $contrast-color; + } + + &-label { + pointer-events: none; + display: block; + font-size: 14px; + line-height: 150%; + font-weight: 400; + + color: #929292; + + transform: translateY(100%); + transition: + transform $transition, + color $transition; + + &--no-transform { + transform: translateY(0); + } + } + + &-input, + &-textarea { + width: 100%; + padding: 0; + border: none; + outline: none; + + font-family: inherit; + font-size: 16px; + line-height: 150%; + font-weight: 400; + letter-spacing: 0%; + + background: none; + + color: #fff; + caret-color: $contrast-color; + + &[type='number']::-webkit-outer-spin-button, + &[type='number']::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + } + + &[type='number'] { + -moz-appearance: textfield; + } + + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus, + &:-webkit-autofill:active { + -webkit-box-shadow: 0 0 0 1000px transparent inset; + caret-color: $contrast-color; + + -webkit-text-fill-color: $contrast-color; + transition: background-color 9999s ease-in-out 0s; + } + } + + &-textarea { + resize: vertical; + } + + &:has(input:focus), + &:has(textarea:focus) { + border-color: $contrast-color; + } + + &:has(input:focus) &-label, + &:has(input):not(:has(input:placeholder-shown)) &-label, + &:has(input:-webkit-autofill) &-label, + &:has(textarea:focus) &-label, + &:has(textarea):not(:has(textarea:placeholder-shown)) &-label { + transform: translateY(0); + } + + &:has(input:focus) &-label, + &:has(textarea:focus) &-label { + color: $contrast-color; + } + + &[data-error], + &[data-error]:has(input:focus) { + border-color: $red; + } + + &[data-error] &-label, + &[data-error]:has(input:focus) &-label { + color: $red; + } + + &[data-error] &-input { + caret-color: $red; + } + } + + &__card-number { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + align-items: end; + + max-width: 530px; + + @include on-tablet { + grid-template-columns: repeat(5, 1fr); + } + + &:has(input:focus) .form__field-label { + color: $contrast-color; + } + + & .form__field-input { + text-align: center; + } + } + + &__button { + width: 100%; + margin-top: 10px; + + @include on-tablet { + width: 200px; + margin-top: 24px; + } + + @include on-tablet { + margin-top: 30px; + } + } + + &__card-image { + display: none; + place-self: flex-end center; + + width: 100%; + height: 100%; + + background-position: center; + background-repeat: no-repeat; + background-size: contain; + + @include on-tablet { + display: block; + } + + &--visa { + background-image: url('../images/pay/visa.svg'); + } + + &--mastercard { + background-image: url('../images/pay/mastercard.svg'); + } + } + + &__card-number-label { + grid-column: 1 / -1; + + &:has(~ .form__field[data-error]) { + color: $red; + } + } + + &__row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 14px; + } + /* stylelint-enable no-descending-specificity */ +} diff --git a/src/styles/blocks/header.scss b/src/styles/blocks/header.scss new file mode 100644 index 000000000..89058fd31 --- /dev/null +++ b/src/styles/blocks/header.scss @@ -0,0 +1,199 @@ +.header { + background: $gradient; + + &__bottom-container { + padding-bottom: 46px; + + @include on-tablet { + padding-top: 30px; + grid-template-rows: repeat(4, auto) 60px; + padding-bottom: 25px; + } + + @include on-small-desktop { + grid-template-rows: repeat(4, auto) 84px; + padding-bottom: 87px; + } + + @include on-desktop { + padding-bottom: 107px; + padding-top: 100px; + } + } + + &__image-wrapper { + position: relative; + grid-column: 1 / -1; + width: 100%; + height: 100%; + aspect-ratio: 16 / 8.5; + + @include on-tablet { + grid-column: 4 / -1; + grid-row: 1 / 6; + right: 15%; + width: 115%; + } + + @include on-small-desktop { + grid-column: 7 / -1; + } + + @include on-desktop { + grid-column: 8 / -1; + } + } + + &__title { + grid-column: 1 / -1; + + font-size: clamp(24px, 8vw, 64px); + height: fit-content; + + @include on-tablet { + grid-row: 1 / 2; + grid-column: 1 / 4; + font-size: clamp(24px, 4vw, 54px); + } + + @include on-small-desktop { + grid-column: 1 / 7; + } + + @include on-desktop { + grid-column: 1 / 8; + } + } + + &__p { + margin-top: 17px; + grid-column: 1 / -1; + + @include on-tablet { + grid-column: 1 / 4; + width: 80%; + } + + @include on-small-desktop { + grid-column: 2 / 7; + } + + @include on-desktop { + grid-column: 2 / 8; + } + } + + &__price { + margin-top: 8px; + text-align: center; + font-weight: 500; + font-size: 18px; + color: $white; + grid-column: 1 / -1; + + @include on-tablet { + margin-top: 10px; + grid-column: 1 / 4; + text-align: start; + } + + @include on-small-desktop { + grid-column: 2 / 7; + } + + @include on-desktop { + grid-column: 2 / 8; + } + } + + &__button-play { + margin-top: 15px; + + grid-column: 1 / -1; + + @include on-tablet { + margin-top: 23px; + + grid-column: 1 / 4; + } + + @include on-small-desktop { + grid-column: 2 / 7; + } + + @include on-desktop { + grid-column: 2 / 8; + } + } + + &__bottom-nav { + display: none; + grid-template-columns: 1fr auto 1fr; + justify-content: space-between; + align-items: baseline; + + @include on-small-desktop { + grid-column: 1 / -1; + display: grid; + } + + @include on-desktop { + margin-top: 100px; + } + } + + &__help { + display: flex; + gap: 40px; + } + + &__more { + display: flex; + flex-direction: column; + align-items: center; + justify-self: start; + gap: 7px; + color: $contrast-color; + + transition: color $transition; + + &::after { + width: 9px; + height: 6px; + content: ''; + background-color: $contrast-color; + mask-image: url('../images/icon/arrow-bottom.svg'); + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + -webkit-mask-image: url('../images/icon/arrow-bottom.svg'); + -webkit-mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + + transition: + background-color $transition, + transform $transition; + } + + &:hover { + color: $white; + + &::after { + background-color: $white; + transform: translateY(5px); + } + } + } + + &__swiper-buttons { + display: grid; + grid-template-columns: repeat(2, 1fr); + justify-self: end; + gap: 10px 100px; + } + + &__swiper-pagination { + grid-column: 1 / -1; + } +} diff --git a/src/styles/blocks/help.scss b/src/styles/blocks/help.scss new file mode 100644 index 000000000..f4c8ad841 --- /dev/null +++ b/src/styles/blocks/help.scss @@ -0,0 +1,166 @@ +.help { + min-width: 320px; + min-height: 640px; + background-color: #000000b2; + + &__container { + display: block; + padding: 0; + background-color: #191536; + height: 100%; + + @include on-tablet { + height: auto; + padding-bottom: 124px; + } + + @include on-small-desktop { + --count-columns: 10; + + height: fit-content; + padding-bottom: 80px; + + grid-column: 2/-2; + border-radius: 4px; + padding-inline: 0; + } + + @include on-desktop { + grid-column: 3/-3; + } + } + + &__inside-container { + @include on-small-desktop { + --count-columns: 10; + + grid-column: 2/-2; + padding-inline: 0; + } + + @include on-desktop { + --count-columns: 12; + } + } + + &__close-link-desktop-wrapper { + display: none; + + @include on-small-desktop { + display: flex; + } + } + + &__close-link-desktop { + display: block; + margin-left: auto; + + margin-right: 18px; + margin-block: 18px; + + transition: + color $transition, + filter $transition; + + &:hover { + color: $contrast-color; + filter: drop-shadow(0 0 5px $contrast-color); + } + + @include on-desktop { + margin-right: 26px; + margin-block: 26px; + } + + & svg { + display: block; + } + } + + &__link { + color: $contrast-color; + + transition: + color $transition, + filter $transition; + + &:hover { + color: $white; + filter: drop-shadow(0 0 5px $contrast-color); + } + } + + &__main-container { + display: block; + padding-inline: 0; + height: 100%; + + @include on-small-desktop { + display: grid; + padding-inline: var(--padding); + padding-block: 134px 184px; + } + } + + &__top-bar { + @include on-small-desktop { + display: none; + } + } + + &__title { + grid-column: 1 / -1; + margin-top: 10px; + + @include on-tablet { + margin-top: 24px; + } + + @include on-small-desktop { + grid-column: 2 / -2; + } + + @include on-desktop { + margin-top: 0; + } + } + + &__p { + margin-top: 30px; + line-height: 150%; + grid-column: 1 / -1; + + @include on-small-desktop { + grid-column: 2 / -2; + margin-top: 50px; + } + } + + &__box-links1 { + display: flex; + flex-direction: column; + margin-top: 30px; + gap: 20px; + grid-column: 1 / -1; + + @include on-tablet { + grid-column: 3 span; + } + + @include on-small-desktop { + grid-column: 2/ 5; + } + } + + &__box-links2 { + gap: 11px; + margin-top: 30px; + + grid-column: 1 / -1; + + @include on-tablet { + grid-column: 3 span; + gap: 25px; + } + } +} diff --git a/src/styles/blocks/icon.scss b/src/styles/blocks/icon.scss new file mode 100644 index 000000000..2e9d5b92d --- /dev/null +++ b/src/styles/blocks/icon.scss @@ -0,0 +1,20 @@ +.icon { + &--menu .line-3 { + transform: translateX(20px); + } + + &--menu { + position: relative; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + width: 30px; + height: 13px; + + transition: filter $transition; + + &:hover { + filter: drop-shadow(0 0 4px #05c2df) brightness(1.2); + } + } +} diff --git a/src/styles/blocks/links.scss b/src/styles/blocks/links.scss new file mode 100644 index 000000000..5277c2b48 --- /dev/null +++ b/src/styles/blocks/links.scss @@ -0,0 +1,25 @@ +.links { + display: flex; + flex-direction: column; + width: fit-content; + + &__link { + font-weight: 400; + font-size: 16px; + color: $text-color; + + transition: color $transition; + + &:hover { + color: $contrast-color; + } + } +} + +.link-hover { + transition: color $transition; + + &:hover { + color: $contrast-color; + } +} diff --git a/src/styles/blocks/logo.scss b/src/styles/blocks/logo.scss new file mode 100644 index 000000000..5e1e32517 --- /dev/null +++ b/src/styles/blocks/logo.scss @@ -0,0 +1,23 @@ +.logo { + display: block; + width: 76px; + height: 16px; + padding: 5px; + + &__image { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + object-position: center; + + transition: + filter $transition, + transform 0.3s; + } + + &:hover &__image { + filter: drop-shadow(0 0 4px #05c2df) brightness(1.2); + transform: scale(1.05); + } +} diff --git a/src/styles/blocks/menu.scss b/src/styles/blocks/menu.scss new file mode 100644 index 000000000..22d9a44b1 --- /dev/null +++ b/src/styles/blocks/menu.scss @@ -0,0 +1,55 @@ +.menu { + background-color: #000000b2; + min-width: 320px; + min-height: 640px; + + &__content { + background-color: #191536; + + height: 100%; + + @include on-tablet { + height: auto; + min-height: 568px; + padding-bottom: 26px; + } + } + + &__top-bar { + justify-content: end; + } + + &__nav-item { + background-color: transparent; + padding-left: 22px; + transition: + color $transition, + background-color $transition; + + @include on-tablet { + padding-left: 152px; + } + + &:hover { + background-color: #110e25; + color: $contrast-color; + } + + &:active { + background-color: #110e25; + color: $contrast-color; + } + } + + &__nav-link { + color: inherit; + width: 100%; + height: 49px; + display: flex; + justify-content: start; + align-items: center; + font-weight: 500; + font-size: 21px; + line-height: 130%; + } +} diff --git a/src/styles/blocks/nav.scss b/src/styles/blocks/nav.scss new file mode 100644 index 000000000..28f7fecbf --- /dev/null +++ b/src/styles/blocks/nav.scss @@ -0,0 +1,44 @@ +.nav { + &-list { + display: flex; + + &--top-bar { + gap: 36px; + align-items: center; + } + + &--footer { + display: flex; + flex-direction: column; + gap: 12px; + justify-content: space-between; + + @include on-tablet { + gap: 25px; + flex-direction: row; + width: 306px; + } + } + } + + &-item { + height: fit-content; + } + + &-link { + font-weight: 400; + font-size: 14px; + line-height: 150%; + color: $white; + + transition: color $transition; + + &:hover { + color: $contrast-color; + } + + @include on-small-desktop { + font-size: 18px; + } + } +} diff --git a/src/styles/blocks/page.scss b/src/styles/blocks/page.scss new file mode 100644 index 000000000..387c28911 --- /dev/null +++ b/src/styles/blocks/page.scss @@ -0,0 +1,108 @@ +.page { + scroll-behavior: smooth; + position: relative; + font-family: Inter, sans-serif; + min-width: 320px; + background-color: $bg-color; + overflow-x: hidden; + + &__menu { + transition: + transform $transition, + opacity $transition; + transform: translateY(-100%); + opacity: 0; + + position: fixed; + inset: 0; + z-index: 1; + + &:target { + opacity: 1; + transform: translateY(0); + } + } + + &:has(&__menu:target) { + overflow: hidden; + + & .page__buy-button-mob { + display: none; + } + } + + &__help { + transition: + transform $transition, + opacity $transition; + transform: translateY(-100%); + opacity: 0; + + position: fixed; + inset: 0; + z-index: 1; + + &:target { + opacity: 1; + transform: translateY(0); + } + } + + &:has(&__help:target) { + overflow: hidden; + + & .page__buy-button-mob { + display: none; + } + } + + &__FAQ { + transition: + transform $transition, + opacity $transition; + transform: translateY(-100%); + opacity: 0; + + position: fixed; + inset: 0; + z-index: 1; + + &:target { + opacity: 1; + transform: translateY(0); + } + } + + &:has(&__FAQ:target) { + overflow: hidden; + + & .page__buy-button-mob { + display: none; + } + } + + &__buy { + transition: + transform $transition, + opacity $transition; + transform: translateY(-100%); + opacity: 0; + + position: fixed; + inset: 0; + z-index: 1; + + &:target { + opacity: 1; + transform: translateY(0); + } + } + + &:has(&__buy:target) { + overflow: hidden; + + & .page__buy-button-mob { + display: none; + } + } +} diff --git a/src/styles/blocks/section-about-product.scss b/src/styles/blocks/section-about-product.scss new file mode 100644 index 000000000..576450973 --- /dev/null +++ b/src/styles/blocks/section-about-product.scss @@ -0,0 +1,204 @@ +.section-about-product { + background: $gradient; + padding-block: 50px; + + @include on-tablet { + padding-block: 70px 50px; + } + + @include on-small-desktop { + padding-block: 100px; + } + + @include on-desktop { + padding-block: 147px; + } + + &__container-1 { + @include on-tablet { + grid-template-rows: repeat(2, min-content) 1fr; + } + } + + &__title { + grid-column: 1 / -1; + + @include on-tablet { + grid-column: 4 / -1; + } + + @include on-small-desktop { + font-size: 38px; + grid-column: 9 / -1; + } + } + + &__swiper-box { + grid-column: 1 / -1; + + @include on-tablet { + grid-row: 1 / 4; + grid-column: 1 / 4; + } + + @include on-small-desktop { + grid-column: 3 / 8; + } + } + + &__ex-buttons { + display: flex; + justify-content: space-between; + } + + &__swiper-pagination-box { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 15px; + min-width: 150px; + width: 30%; + justify-self: center; + + @include on-small-desktop { + width: 40%; + + justify-self: start; + } + } + + &__swiper { + width: 100%; + height: 50vw; + box-sizing: border-box; + border-radius: 5px; + border: 2px solid transparent; + + transition: border $transition; + + @include on-tablet { + height: 35vw; + } + + @include on-small-desktop { + height: 25vw; + } + + &:hover { + border: 2px solid $contrast-color; + } + } + + &__ex-image { + border-radius: 5px; + object-fit: cover; + object-position: center; + } + + &__p { + grid-column: 1 / -1; + margin-top: 20px; + @include on-tablet { + margin-top: 30px; + + grid-column: 4 / -1; + } + + @include on-small-desktop { + grid-column: 9 / -1; + } + } + + &__button-play { + margin-top: 15px; + grid-column: 1 / -1; + @include on-tablet { + align-self: center; + grid-column: 4 / -1; + } + + @include on-small-desktop { + grid-column: 9 / -1; + } + } + + &__text-vertical { + display: none; + overflow: hidden; + width: 100%; + grid-column: 1 / -1; + + @include on-tablet { + display: block; + grid-column: 4 / -1; + } + + @include on-small-desktop { + grid-column: 7 / -1; + } + + &-box { + display: flex; + width: fit-content; + height: 337px; + } + + &-image { + padding-right: 17px; + background-image: url('../images/ABOUT US 2.svg'); + height: 337px; + width: 83px; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + } + + & :nth-child(2n) { + transform: scaleX(-1); + } + } + + &__about-us { + grid-column: 1 / -1; + + @include on-tablet { + grid-column: 1 / 4; + height: fit-content; + align-self: center; + } + + @include on-small-desktop { + grid-column: 1 / 6; + } + + &-container { + margin-top: 70px; + + @include on-tablet { + margin-top: 80px; + } + + @include on-small-desktop { + margin-top: 180px; + } + } + + &-title { + margin-top: 11px; + } + + &-p-1 { + font-weight: 400; + font-size: 14px; + color: $contrast-color; + } + + &-p-2 { + margin-top: 22px; + + @include on-tablet { + margin-top: 32px; + } + } + } +} diff --git a/src/styles/blocks/section-contacts.scss b/src/styles/blocks/section-contacts.scss new file mode 100644 index 000000000..bad9b8544 --- /dev/null +++ b/src/styles/blocks/section-contacts.scss @@ -0,0 +1,103 @@ +.section-contacts { + padding-block: 50px; + + @include on-tablet { + padding-block: 70px 130px; + } + + @include on-small-desktop { + padding-block: 100px; + } + + @include on-desktop { + padding-block: 160px 205px; + } + + &__container { + @include on-tablet { + grid-template-rows: repeat(4, auto) 1fr; + } + } + + &__p-1 { + color: $contrast-color; + grid-column: 1/-1; + + @include on-tablet { + grid-column: 1 / 4; + } + + @include on-small-desktop { + grid-column: 1 / 5; + } + } + + &__title { + grid-column: 1/-1; + margin-top: 10px; + + @include on-tablet { + grid-column: 1 / 4; + } + + @include on-small-desktop { + grid-column: 1 / 5; + } + } + + &__form { + margin-top: 32px; + grid-column: 1/-1; + + @include on-tablet { + margin-top: 0; + grid-column: 4 / -1; + order: 1; + grid-row: 1 / -1; + } + + @include on-small-desktop { + grid-column: 7 / -1; + } + + @include on-desktop { + grid-column: 9 / -1; + } + } + + &__p-2 { + grid-column: 1/-1; + margin-top: 30px; + + @include on-tablet { + margin-top: 50px; + + grid-column: 1 / 4; + } + + @include on-small-desktop { + margin-top: 26px; + grid-column: 1 / 5; + } + + @include on-desktop { + grid-column: 1 / 6; + } + } + + &__links { + display: none; + gap: 10px; + grid-column: 1/-1; + + @include on-tablet { + display: flex; + margin-top: 76px; + grid-column: 1 / 4; + } + + @include on-small-desktop { + grid-column: 1 / 5; + } + } +} diff --git a/src/styles/blocks/section-more-than-gaming.scss b/src/styles/blocks/section-more-than-gaming.scss new file mode 100644 index 000000000..441a08f07 --- /dev/null +++ b/src/styles/blocks/section-more-than-gaming.scss @@ -0,0 +1,64 @@ +.section-more-than-gaming { + padding-block: 50px; + + background-color: $bg-color; + + @include on-tablet { + padding-block: 70px; + } + + @include on-small-desktop { + padding-block: 100px; + } + + @include on-desktop { + padding-block: 158px; + } + + &__title { + grid-column: 1 / -1; + + @include on-tablet { + text-align: center; + } + } + + &__p { + grid-column: 1 / -1; + margin-top: 12px; + color: $contrast-color; + + @include on-tablet { + text-align: center; + } + + @include on-small-desktop { + margin-top: 18px; + } + } + + &__container { + margin-top: 30px; + row-gap: 10px; + + @include on-tablet { + margin-top: 30px; + } + + @include on-small-desktop { + margin-top: 83px; + } + } + + &__card { + grid-column: 1 / -1; + + @include on-tablet { + grid-column: 3 span; + } + + @include on-desktop { + grid-column: 4 span; + } + } +} diff --git a/src/styles/blocks/section-tech-specs.scss b/src/styles/blocks/section-tech-specs.scss new file mode 100644 index 000000000..10414c0a4 --- /dev/null +++ b/src/styles/blocks/section-tech-specs.scss @@ -0,0 +1,315 @@ +.section-tech-specs { + padding-block: 50px; + background-color: $bg-color; + position: relative; + + @include on-tablet { + padding-block: 80px; + } + + @include on-small-desktop { + padding-block: 100px; + } + + @include on-desktop { + padding-block: 160px; + } + + &__title { + grid-column: 1 / -1; + + @include on-small-desktop { + grid-column: -5 / -1; + } + + @include on-desktop { + grid-column: -6 / -1; + } + } + + &__content-title { + position: relative; + display: flex; + align-items: center; + width: fit-content; + } + + &__mb-content { + display: flex; + justify-content: center; + align-items: center; + margin-top: 57px; + + @include on-small-desktop { + display: none; + } + } + + &__mb-content-container { + border-radius: 40%; + display: flex; + position: relative; + width: 60%; + aspect-ratio: 1/1; + + @include on-tablet { + width: 40%; + } + } + + &__mb-image { + width: 100%; + height: 100%; + background-image: url('../images/tech-specs/kat-vr.png'); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + + transition: filter 1s; + filter: drop-shadow(0 0 50px black); + + &:hover { + filter: drop-shadow(0 0 50px #05c2df77); + } + } + + &__mb-button { + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + height: 15%; + aspect-ratio: 1 / 1; + background-color: $contrast-color; + border-radius: 50%; + + border: #ffffff24 solid 2px; + + transition: + box-shadow $transition, + background-color $transition; + + &::after { + height: 50%; + width: 50%; + content: ''; + position: absolute; + background-image: url('../images/tech-specs/button.svg'); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + + transition: transform $transition; + } + + &--active-text { + position: absolute; + transition: + transform $transition, + top $transition, + opacity $transition; + transform-origin: top; + transform: scaleY(0); + top: 80%; + opacity: 0; + width: max-content; + border-radius: 4px; + padding: 20px 10px; + display: block; + background-color: #191536; + color: #929292; + font-weight: 400; + font-size: 12px; + line-height: 150%; + } + + &:hover { + box-shadow: 0 0 10px $contrast-color; + } + + &:hover::after { + transform: rotateZ(90deg); + } + + &--active { + background-color: #07798b; + border: #ffffff24 solid 2px; + + &::after { + background-image: url('../images/tech-specs/button-active.svg'); + } + + &:hover::after { + transform: none; + } + + & &-text { + transform: scaleY(1); + top: 110%; + opacity: 1; + } + } + + &--1 { + left: 25%; + transform: translateY(-10%); + top: -25px; + } + + &--2 { + left: 75%; + top: 42%; + transform: translateY(-10%); + } + + &--3 { + left: -10%; + bottom: 2%; + transform: translateY(-10%); + } + } + + &__content-container { + display: none; + position: relative; + margin-top: 80px; + row-gap: 10px; + + @include on-small-desktop { + display: grid; + } + } + + &__svg-container { + position: absolute; + width: 100%; + height: 100%; + pointer-events: none; + } + + &__content-pointer-1 { + position: absolute; + right: -15px; + } + + &__content-pointer-2 { + position: absolute; + left: -15px; + } + + &__content-image { + position: absolute; + width: 300px; + height: 300px; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + justify-self: center; + top: 65px; + background-image: url('../images/tech-specs/kat-vr.png'); + + transition: + transform $transition, + filter $transition; + + &:hover { + transform: scale(1.2); + filter: drop-shadow(0 0 100px #05c2df71); + } + + &-svg { + position: absolute; + transform: translateY(44px) translateX(-12px); + stroke-dasharray: 100; + stroke-dashoffset: 100; + + &.animate { + animation: draw 300ms linear forwards; + } + } + + &-pointer-1 { + position: absolute; + justify-self: center; + top: 25px; + } + + &-pointer-2 { + position: absolute; + justify-self: center; + bottom: 115px; + margin-left: 40px; + } + + &-pointer-3 { + position: absolute; + top: 30%; + left: -20px; + } + } + + &__content-box { + display: none; + transform: scaleY(0) translateY(-20px); + opacity: 0; + padding: 20px 10px; + border-radius: 10px; + + transition: + transform $transition calc(0.75s - var(--rand)), + opacity $transition, + box-shadow $transition; + + &:hover { + box-shadow: 0 0 10px $contrast-color; + } + + @include on-small-desktop { + display: block; + } + &--1 { + grid-column: 1 / 4; + + @include on-desktop { + grid-column: 3 / 7; + } + } + + &--2 { + grid-column: -5 / -1; + + @include on-desktop { + grid-column: -6 / -2; + } + } + + &--3 { + grid-column: 2 / 5; + + @include on-desktop { + grid-column: 3 / 7; + } + } + + &.animate { + transform: scaleY(1) translateY(0); + opacity: 1; + } + } + + &__content-p { + margin-top: 20px; + } + + &__particles { + position: absolute; + inset: 0; + filter: blur(14px); + mask-image: radial-gradient(circle, transparent 30%, black 70%); + -webkit-mask-image: radial-gradient(circle, transparent 30%, black 70%); + @include on-small-desktop { + display: none; + } + } +} diff --git a/src/styles/blocks/section-why.scss b/src/styles/blocks/section-why.scss new file mode 100644 index 000000000..f63afe46a --- /dev/null +++ b/src/styles/blocks/section-why.scss @@ -0,0 +1,56 @@ +.section-why { + padding-block: 50px; + background: $gradient; + + @include on-tablet { + padding-block: 70px; + } + + @include on-small-desktop { + padding-block: 100px; + } + + @include on-desktop { + padding-block: 160px; + } + + &__title { + @include on-small-desktop { + text-align: center; + } + } + + &__container { + margin-top: 32px; + + @include on-small-desktop { + margin-top: 80px; + } + } + + &__card { + grid-column: 1 / -1; + + @include on-tablet { + grid-column: 2 span; + } + + @include on-small-desktop { + grid-column: 4 span; + } + + @include on-desktop { + &--1 { + grid-column: 1 / 5; + } + + &--2 { + grid-column: 7 / 11; + } + + &--3 { + grid-column: 13 / -1; + } + } + } +} diff --git a/src/styles/blocks/slider-effect.scss b/src/styles/blocks/slider-effect.scss new file mode 100644 index 000000000..3f4fb0013 --- /dev/null +++ b/src/styles/blocks/slider-effect.scss @@ -0,0 +1,33 @@ +.slider-effect { + --width: 0px; + + position: absolute; + height: 100%; + width: 10px; + + width: calc(10px * var(--width)); + margin-left: calc(-5px * var(--width)); + + border-radius: 50%; + + background: radial-gradient(circle, white, $contrast-color, transparent 100%); + background-color: $contrast-color; + + box-shadow: + 0 0 20px $contrast-color, + 0 0 60px $contrast-color; + + &::before { + content: ''; + position: absolute; + inset: calc(-40px * var(--width)); + z-index: -1; + + // transform: translateX(-70px); + + background: radial-gradient(circle, $contrast-color, transparent 60%); + + filter: blur(40px); + opacity: 0.8; + } +} diff --git a/src/styles/blocks/top-bar.scss b/src/styles/blocks/top-bar.scss new file mode 100644 index 000000000..1db03555e --- /dev/null +++ b/src/styles/blocks/top-bar.scss @@ -0,0 +1,120 @@ +.top-bar { + padding-block: 18px; + display: flex; + + justify-content: space-between; + align-items: center; + + @include on-tablet { + padding-block: 28px; + } + + @include on-small-desktop { + padding-block: 46px; + } + + &__nav { + display: none; + + @include on-small-desktop { + display: flex; + align-items: center; + } + } + + &__left-box { + display: flex; + align-items: center; + gap: 34px; + } + + &__dropdown { + display: none; + + @include on-small-desktop { + display: block; + } + } + + &__menu-link { + z-index: 1; + padding: 5px; + display: block; + color: $white; + transition: + filter $transition, + color $transition; + + @include on-small-desktop { + display: none; + } + + & .line-2 { + transition: transform $transition; + } + + &:hover { + color: $contrast-color; + filter: drop-shadow(0 0 4px #05c2df) brightness(1.2); + + & .line-2 { + transform: translateX(5px) scaleX(1.4); + } + } + + & svg { + display: block; + width: 30px; + height: 13px; + } + } + + &__close-link { + display: flex; + width: 23px; + height: 23px; + justify-content: center; + align-items: center; + padding-inline: 5px; + color: $white; + + transition: + filter $transition, + color $transition; + + &:hover { + filter: drop-shadow(0 0 3px $contrast-color); + color: $contrast-color; + } + + & svg { + display: block; + object-fit: contain; + object-position: center; + } + } + + &__back-link { + display: flex; + width: 17px; + height: 17px; + justify-content: center; + align-items: center; + color: $white; + + transition: + filter $transition, + color $transition; + + &:hover { + filter: drop-shadow(0 0 3px $contrast-color); + color: $contrast-color; + } + + & svg { + display: block; + object-fit: contain; + object-position: center; + } + } +} diff --git a/src/styles/blocks/video-modal.scss b/src/styles/blocks/video-modal.scss new file mode 100644 index 000000000..3bbcad50b --- /dev/null +++ b/src/styles/blocks/video-modal.scss @@ -0,0 +1,59 @@ +.video-modal { + position: fixed; + inset: 0; + z-index: 1000; + display: none; + + &__iframe-container { + border-radius: 35px; + border: solid $contrast-color 2px; + overflow: hidden; + + width: 100%; + height: 100%; + position: relative; + + transition: transform $transition; + transform: scale(0); + } + + &.is-active { + display: flex; + align-items: center; + justify-content: center; + + & .video-modal__iframe-container { + animation: trans 600ms cubic-bezier(0.17, 0.67, 0.55, 1.44) forwards; + } + } + + &__overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.7); + } + + &__content { + position: relative; + z-index: 1; + width: min(900px, calc(100% - 32px)); + aspect-ratio: 16 / 9; + } + + &__iframe { + width: 100%; + height: 100%; + border: 0; + } + + &__close { + position: absolute; + right: 0; + top: -44px; + border: 0; + background: none; + color: #fff; + font-size: 36px; + cursor: pointer; + } +} diff --git a/src/styles/main.scss b/src/styles/main.scss index fb9195d12..2cfc17981 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -1,7 +1,44 @@ @import 'utils'; @import 'fonts'; @import 'typography'; +@import 'normalize'; -body { - background: $c-gray; +@import 'blocks/dropdown'; +@import 'blocks/video-modal'; +@import 'blocks/animations'; +@import 'blocks/links'; +@import 'blocks/nav'; +@import 'blocks/button'; +@import 'blocks/ex'; + +@import 'blocks/page'; +@import 'blocks/container'; +@import 'blocks/button-play'; +@import 'blocks/icon'; +@import 'blocks/top-bar'; +@import 'blocks/header'; +@import 'blocks/logo'; +@import 'blocks/buy-button-mob'; +@import 'blocks/slider-effect'; +@import 'blocks/section-more-than-gaming'; +@import 'blocks/card'; +@import 'blocks/section-about-product'; +@import 'blocks/section-tech-specs'; +@import 'blocks/animSvg'; +@import 'blocks/section-why'; +@import 'blocks/section-contacts'; +@import 'blocks/form'; +@import 'blocks/footer'; +@import 'blocks/menu'; +@import 'blocks/help'; +@import 'blocks/FAQ'; +@import 'blocks/buy'; + +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-thumb { + background: rgba(17, 175, 248, 0.69); + border-radius: 10px; } diff --git a/src/styles/normalize.scss b/src/styles/normalize.scss new file mode 100644 index 000000000..245f33e5b --- /dev/null +++ b/src/styles/normalize.scss @@ -0,0 +1,27 @@ +h1, +h2, +h3, +h4, +h5, +h6, +p, +span, +a, +ul, +li, +body { + margin: 0; + padding: 0; + text-decoration: none; + list-style: none; + color: $white; +} + +p { + color: $text-color; +} + +button { + margin: 0; + padding: 0; +} diff --git a/src/styles/utils/_mixins.scss b/src/styles/utils/_mixins.scss index 80c79780d..215e82736 100644 --- a/src/styles/utils/_mixins.scss +++ b/src/styles/utils/_mixins.scss @@ -4,3 +4,49 @@ #{$_property}: $_toValue; } } + +@mixin on-tablet { + @media (min-width: 640px) { + @content; + } +} + +@mixin on-small-desktop { + @media (min-width: 1280px) { + @content; + } +} + +@mixin on-desktop { + @media (min-width: 1440px) { + @content; + } +} + +@mixin icon($width, $height, $url, $color, $hover-color: false) { + display: block; + + width: $width; + height: $height; + + background-color: $color; + + -webkit-mask-image: #{$url}; + mask-image: #{$url}; + + -webkit-mask-size: contain; + mask-size: contain; + + -webkit-mask-position: center; + mask-position: center; + + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + + @if $hover-color { + transition: background-color $transition; + &:hover { + background-color: $hover-color; + } + } +} diff --git a/src/styles/utils/_vars.scss b/src/styles/utils/_vars.scss index aeb006ffb..ed9467b15 100644 --- a/src/styles/utils/_vars.scss +++ b/src/styles/utils/_vars.scss @@ -1 +1,8 @@ -$c-gray: #eee; +$gradient: linear-gradient(135deg, #191536, #000); +$contrast-color: #05c2df; +$bg-color: #05040b; +$dark-gray: #2f2f2f; +$text-color: #929292; +$white: #fff; +$red: #860404; +$transition: 0.3s;