diff --git a/.github/workflows/test.yml-template b/.github/workflows/test.yml-template new file mode 100644 index 000000000..8b5743ecb --- /dev/null +++ b/.github/workflows/test.yml-template @@ -0,0 +1,29 @@ +name: Test + +on: + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test + - name: Upload HTML report(backstop data) + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: report + path: backstop_data diff --git a/.stylelintrc.js b/.stylelintrc.js index 2e345321d..eaca91ca5 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -1,7 +1,9 @@ module.exports = { extends: "@mate-academy/stylelint-config", - plugins: [ - "stylelint-scss" - ], - rules: {} + plugins: ["stylelint-scss"], + rules: { + // The task layout is tuned by block, so property order should not create noise. + "order/order": null, + "order/properties-order": null, + }, }; diff --git a/README.md b/README.md index 302783df5..67bee55c7 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://weuler007.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..9ced4adea 100644 --- a/index.html +++ b/index.html @@ -6,14 +6,1065 @@ name="viewport" content="width=device-width, initial-scale=1.0" /> - Title + KatVR + - -

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$

+ + +
+ + KAT VR locomotion sensors + +
+ + + + More + + +
+ + +
+
+
+
+ + +
+
+
+

+ 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 +

+
+
+
+
+ + +
+
+
+
+ KAT loco sensor attached to jeans +
+ + +
+
+ +
+

1/5

+

+ About product +

+

+ 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 +

+
+

About us

+
+
+
+ + +
+
+

Tech specs

+ KAT VR sensor technical specifications +
+
+ + +
+
+

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! +

+
+
+ + + Buy Now + +
+
+ + +
+
+
+ +
    +
  1. Place Order
  2. +
  3. Pay
  4. +
  5. Order complete
  6. +
+
+ +
+ +
+ KAT loco sensors set +
+ + +

1200$

+
+
+ +
+ + + + + + + + +
+
+ +
+
+ KAT loco sensors set +
+ + +

1200$

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

+ Thank you for your order! +

+

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

+ + Back to homepage + +
+ +
+
+ + +
+
+
+

Have any questions?

+

Get in touch

+

+ Our manager will reply you within 15 minutes +

+
+ +86-0571-86105373 + global@katvr.com + overseas@katvr.com +
+
+ +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 87fcefd81..e0b0d490d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@linthtml/linthtml": "^0.9.6", "@mate-academy/bemlint": "latest", "@mate-academy/linthtml-config": "latest", - "@mate-academy/scripts": "^2.1.2", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/stylelint-config": "latest", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -1510,10 +1510,11 @@ "dev": true }, "node_modules/@mate-academy/scripts": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.2.tgz", - "integrity": "sha512-gUXFdqqOfYzF9R3RSx2pCa5GLdOkxB9bFbF+dpUpzucdgGAANqOGdqpmNnMj+e3xA9YHraUWq3xo9cwe5vD9pQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", diff --git a/package.json b/package.json index 7149ff15d..2f8e901ad 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "kate_vr", "version": "1.0.0", "description": "KateVR", - "homepage": "layout_KateVR", + "homepage": "/layout_KateVR", "scripts": { "init": "mate-scripts init", "start": "mate-scripts start", @@ -23,7 +23,7 @@ "@linthtml/linthtml": "^0.9.6", "@mate-academy/bemlint": "latest", "@mate-academy/linthtml-config": "latest", - "@mate-academy/scripts": "^2.1.2", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/stylelint-config": "latest", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", diff --git a/src/images/Logo.png b/src/images/Logo.png new file mode 100644 index 000000000..4cf049ea9 Binary files /dev/null and b/src/images/Logo.png differ diff --git a/src/images/MoreThanBlocks.png b/src/images/MoreThanBlocks.png new file mode 100644 index 000000000..4347c31cc Binary files /dev/null and b/src/images/MoreThanBlocks.png differ diff --git a/src/images/Rectangle.png b/src/images/Rectangle.png new file mode 100644 index 000000000..437730d98 Binary files /dev/null and b/src/images/Rectangle.png differ diff --git a/src/images/botao.png b/src/images/botao.png new file mode 100644 index 000000000..dd5707d30 Binary files /dev/null and b/src/images/botao.png differ diff --git a/src/images/button.png b/src/images/button.png new file mode 100644 index 000000000..a0ffc1770 Binary files /dev/null and b/src/images/button.png differ diff --git a/src/images/customtext.png b/src/images/customtext.png new file mode 100644 index 000000000..2cd303ce0 Binary files /dev/null and b/src/images/customtext.png differ diff --git a/src/images/efeito.png b/src/images/efeito.png new file mode 100644 index 000000000..c4d12570e Binary files /dev/null and b/src/images/efeito.png differ diff --git a/src/images/group 3.13.png b/src/images/group 3.13.png new file mode 100644 index 000000000..c5b06f396 Binary files /dev/null and b/src/images/group 3.13.png differ diff --git a/src/images/group.png b/src/images/group.png new file mode 100644 index 000000000..c4893bde1 Binary files /dev/null and b/src/images/group.png differ diff --git a/src/images/group10.png b/src/images/group10.png new file mode 100644 index 000000000..0cfc00667 Binary files /dev/null and b/src/images/group10.png differ diff --git a/src/images/group100.png b/src/images/group100.png new file mode 100644 index 000000000..a289c27ed Binary files /dev/null and b/src/images/group100.png differ diff --git a/src/images/group101.png b/src/images/group101.png new file mode 100644 index 000000000..99c531cd3 Binary files /dev/null and b/src/images/group101.png differ diff --git a/src/images/group103.png b/src/images/group103.png new file mode 100644 index 000000000..c4893bde1 Binary files /dev/null and b/src/images/group103.png differ diff --git a/src/images/group111.png b/src/images/group111.png new file mode 100644 index 000000000..3070dbf08 Binary files /dev/null and b/src/images/group111.png differ diff --git a/src/images/group133.png b/src/images/group133.png new file mode 100644 index 000000000..399465d53 Binary files /dev/null and b/src/images/group133.png differ diff --git a/src/images/group155.png b/src/images/group155.png new file mode 100644 index 000000000..48acd1f52 Binary files /dev/null and b/src/images/group155.png differ diff --git a/src/images/group2.1.png b/src/images/group2.1.png new file mode 100644 index 000000000..14a3e946e Binary files /dev/null and b/src/images/group2.1.png differ diff --git a/src/images/group2.png b/src/images/group2.png new file mode 100644 index 000000000..941b27634 Binary files /dev/null and b/src/images/group2.png differ diff --git a/src/images/group3.1.png b/src/images/group3.1.png new file mode 100644 index 000000000..db8ab5ef2 Binary files /dev/null and b/src/images/group3.1.png differ diff --git a/src/images/group3.png b/src/images/group3.png new file mode 100644 index 000000000..db8ab5ef2 Binary files /dev/null and b/src/images/group3.png differ diff --git a/src/images/group4.png b/src/images/group4.png new file mode 100644 index 000000000..f8a31c43b Binary files /dev/null and b/src/images/group4.png differ diff --git a/src/images/group7.png b/src/images/group7.png new file mode 100644 index 000000000..663570224 Binary files /dev/null and b/src/images/group7.png differ diff --git a/src/images/group800.png b/src/images/group800.png new file mode 100644 index 000000000..9e1db6224 Binary files /dev/null and b/src/images/group800.png differ diff --git a/src/images/groupicone.png b/src/images/groupicone.png new file mode 100644 index 000000000..259551b0d Binary files /dev/null and b/src/images/groupicone.png differ diff --git a/src/images/groupicone2.png b/src/images/groupicone2.png new file mode 100644 index 000000000..f29246a10 Binary files /dev/null and b/src/images/groupicone2.png differ diff --git a/src/images/icone1.png b/src/images/icone1.png new file mode 100644 index 000000000..57229f488 Binary files /dev/null and b/src/images/icone1.png differ diff --git a/src/images/icone2.png b/src/images/icone2.png new file mode 100644 index 000000000..302573663 Binary files /dev/null and b/src/images/icone2.png differ diff --git a/src/images/icone3.png b/src/images/icone3.png new file mode 100644 index 000000000..1d4e8205a Binary files /dev/null and b/src/images/icone3.png differ diff --git a/src/images/icone4.png b/src/images/icone4.png new file mode 100644 index 000000000..40d3e8c2c Binary files /dev/null and b/src/images/icone4.png differ diff --git a/src/images/image.png b/src/images/image.png new file mode 100644 index 000000000..3bc04d5aa Binary files /dev/null and b/src/images/image.png differ diff --git a/src/images/image2.3.png b/src/images/image2.3.png new file mode 100644 index 000000000..da4439d4f Binary files /dev/null and b/src/images/image2.3.png differ diff --git a/src/images/layer100.png b/src/images/layer100.png new file mode 100644 index 000000000..1dfffdd9a Binary files /dev/null and b/src/images/layer100.png differ diff --git a/src/images/menu.png b/src/images/menu.png new file mode 100644 index 000000000..422373ae5 Binary files /dev/null and b/src/images/menu.png differ diff --git a/src/images/network.png b/src/images/network.png new file mode 100644 index 000000000..92ea5ce35 Binary files /dev/null and b/src/images/network.png differ diff --git a/src/images/new.png b/src/images/new.png new file mode 100644 index 000000000..ee771cf96 Binary files /dev/null and b/src/images/new.png differ diff --git "a/src/images/p\320\265\321\201\321\203\321\200\321\2011.png" "b/src/images/p\320\265\321\201\321\203\321\200\321\2011.png" new file mode 100644 index 000000000..5172805fe Binary files /dev/null and "b/src/images/p\320\265\321\201\321\203\321\200\321\2011.png" differ diff --git "a/src/images/p\320\265\321\201\321\203\321\200\321\201104.png" "b/src/images/p\320\265\321\201\321\203\321\200\321\201104.png" new file mode 100644 index 000000000..18cfbb320 Binary files /dev/null and "b/src/images/p\320\265\321\201\321\203\321\200\321\201104.png" differ diff --git "a/src/images/p\320\265\321\201\321\203\321\200\321\2012.png" "b/src/images/p\320\265\321\201\321\203\321\200\321\2012.png" new file mode 100644 index 000000000..8d0d7444a Binary files /dev/null and "b/src/images/p\320\265\321\201\321\203\321\200\321\2012.png" differ diff --git a/src/images/rectangle4.1.png b/src/images/rectangle4.1.png new file mode 100644 index 000000000..5770ccfeb Binary files /dev/null and b/src/images/rectangle4.1.png differ diff --git a/src/images/rectanglemenu.png b/src/images/rectanglemenu.png new file mode 100644 index 000000000..ffd4ed472 Binary files /dev/null and b/src/images/rectanglemenu.png differ diff --git a/src/images/skyline.png b/src/images/skyline.png new file mode 100644 index 000000000..37e80e88b Binary files /dev/null and b/src/images/skyline.png differ diff --git a/src/images/title.png b/src/images/title.png new file mode 100644 index 000000000..1e96e5cb3 Binary files /dev/null and b/src/images/title.png differ diff --git a/src/images/uikitlibrary.png b/src/images/uikitlibrary.png new file mode 100644 index 000000000..6249947b5 Binary files /dev/null and b/src/images/uikitlibrary.png differ diff --git a/src/images/vector1.png b/src/images/vector1.png new file mode 100644 index 000000000..d9e709eea Binary files /dev/null and b/src/images/vector1.png differ diff --git a/src/images/visa.png b/src/images/visa.png new file mode 100644 index 000000000..aa07f39c6 Binary files /dev/null and b/src/images/visa.png differ diff --git a/src/scripts/main.js b/src/scripts/main.js index ad9a93a7c..6ea3a783b 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1 +1,123 @@ 'use strict'; + +const page = document.querySelector('.page'); +const menuButton = document.querySelector('.header__menu-button'); +const menuLinks = document.querySelectorAll('.nav__link'); +const playButtons = document.querySelectorAll('.play'); +const modalOpeners = document.querySelectorAll('[data-modal-open]'); +const modals = document.querySelectorAll('[data-modal]'); +const orderStages = document.querySelectorAll('[data-order-stage]'); +const orderSteps = document.querySelectorAll('.order__step'); +const orderForms = document.querySelectorAll('[data-order-form]'); + +const resetFormAfterValidSubmit = (form, callback) => { + form.addEventListener('submit', event => { + event.preventDefault(); + + if (!form.reportValidity()) { + return; + } + + form.reset(); + callback(); + }); +}; + +const bindValidSubmitHandlers = (forms, callback) => { + forms.forEach(form => { + resetFormAfterValidSubmit(form, () => callback(form)); + }); +}; + +const scrollToSection = selector => { + document.querySelector(selector).scrollIntoView({ behavior: 'smooth' }); +}; + +// Body state keeps the mobile menu and modal overlay from fighting for scroll. +const setMenuState = isOpen => { + page.classList.toggle('page--menu-open', isOpen); + menuButton.setAttribute('aria-expanded', String(isOpen)); +}; + +menuButton.addEventListener('click', () => { + setMenuState(!page.classList.contains('page--menu-open')); +}); + +menuLinks.forEach(link => { + link.addEventListener('click', () => setMenuState(false)); +}); + +playButtons.forEach(button => { + button.addEventListener('click', () => { + window.open(button.dataset.videoUrl, '_blank', 'noopener,noreferrer'); + }); +}); + +// Modals share one opener/closer so FAQ and Help behave consistently. +const openModal = modalName => { + const modal = document.querySelector(`[data-modal="${modalName}"]`); + + if (!modal) { + return; + } + + modal.classList.add('modal--open'); + modal.setAttribute('aria-hidden', 'false'); + page.classList.add('page--modal-open'); +}; + +const closeModals = () => { + modals.forEach(modal => { + modal.classList.remove('modal--open'); + modal.setAttribute('aria-hidden', 'true'); + }); + page.classList.remove('page--modal-open'); +}; + +modalOpeners.forEach(button => { + button.addEventListener('click', () => openModal(button.dataset.modalOpen)); +}); + +modals.forEach(modal => { + modal.addEventListener('click', event => { + if (event.target === modal || event.target.hasAttribute('data-modal-close')) { + closeModals(); + } + }); +}); + +document.addEventListener('keydown', event => { + if (event.key === 'Escape') { + closeModals(); + } +}); + +// Order screens follow the three Figma steps without sending payment data. +const setOrderStage = stageName => { + const stepByStage = { + details: 0, + pay: 1, + complete: 2, + }; + + orderStages.forEach(stage => { + stage.classList.toggle( + 'order__stage--active', + stage.dataset.orderStage === stageName, + ); + }); + + orderSteps.forEach((step, index) => { + step.classList.toggle('order__step--active', index === stepByStage[stageName]); + }); +}; + +bindValidSubmitHandlers(orderForms, form => { + setOrderStage(form.dataset.orderForm === 'details' ? 'pay' : 'complete'); + scrollToSection('#order'); +}); + +// Contact form stays on-page: no 405 request, then smooth scrolls to the top. +bindValidSubmitHandlers(document.querySelectorAll('.contact__form'), () => { + window.scrollTo({ top: 0, behavior: 'smooth' }); +}); diff --git a/src/styles/_fonts.scss b/src/styles/_fonts.scss index 619b8c52b..559eeee88 100644 --- a/src/styles/_fonts.scss +++ b/src/styles/_fonts.scss @@ -1,6 +1,23 @@ @font-face { - font-family: Roboto, Arial, Helvetica, sans-serif; - src: url('../fonts/FontFont_FF.Mark.Pro.otf') format('otf'); - font-weight: normal; + font-family: 'Mark Pro'; + src: url('../fonts/FontFont_FF.Mark.Pro.otf') format('opentype'); + font-weight: 400; font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Mark Pro'; + src: url('../fonts/FontFont_FF.Mark.Pro.Medium.otf') format('opentype'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Mark Pro'; + src: url('../fonts/FontFont_FF.Mark.Pro.Heavy.otf') format('opentype'); + font-weight: 800; + font-style: normal; + font-display: swap; } diff --git a/src/styles/_typography.scss b/src/styles/_typography.scss index 1837eb46e..5dc36879a 100644 --- a/src/styles/_typography.scss +++ b/src/styles/_typography.scss @@ -1,3 +1,28 @@ -h1 { - @extend %h1; +@use 'utils' as *; + +html { + font-family: 'Mark Pro', Arial, sans-serif; + color: $color-text; + scroll-behavior: smooth; +} + +body, +h1, +h2, +h3, +p { + margin: 0; +} + +a { + color: inherit; + cursor: pointer; + text-decoration: none; +} + +button, +input, +select, +textarea { + font: inherit; } diff --git a/src/styles/_utils.scss b/src/styles/_utils.scss index 3280c3fe1..9b3deb755 100644 --- a/src/styles/_utils.scss +++ b/src/styles/_utils.scss @@ -1,3 +1,3 @@ -@import 'utils/vars'; -@import 'utils/mixins'; -@import 'utils/extends'; +@forward 'utils/vars'; +@forward 'utils/mixins'; +@forward 'utils/extends'; diff --git a/src/styles/blocks/_about.scss b/src/styles/blocks/_about.scss new file mode 100644 index 000000000..af74d4021 --- /dev/null +++ b/src/styles/blocks/_about.scss @@ -0,0 +1,198 @@ +@use '../utils' as *; + +.about { + // A secao usa grid para centralizar os dois blocos principais no fundo escuro. + display: grid; + align-items: center; + background: + url('../images/rectangle4.1.png') center / cover no-repeat, + linear-gradient(90deg, $color-panel 0, $color-page 100%); + + @include desktop { + min-height: 1070px; + } + + &__container { + @include section-container; + + // O espacamento vertical separa produto e empresa como no mockup. + display: grid; + gap: 92px; + + @include desktop { + gap: 132px; + } + } + + &__product, + &__company { + // Mobile empilha conteudo; desktop usa duas colunas para imagem e texto. + display: grid; + gap: 32px; + + @include desktop { + grid-template-columns: 1.1fr 0.9fr; + align-items: center; + column-gap: 82px; + } + } + + &__media { + position: relative; + } + + &__image { + // A foto mantem proporcao controlada e ganha feedback visual no hover. + display: block; + width: min(100%, 430px); + max-height: 340px; + cursor: pointer; + object-fit: cover; + transition: + filter 0.3s ease, + transform 0.3s ease; + will-change: filter, transform; + + &:hover { + filter: saturate(1.15); + transform: scale(1.02); + } + } + + &__slider { + // Controles de slider reproduzem a linha inferior do layout. + position: relative; + display: flex; + justify-content: space-between; + width: min(100%, 294px); + margin-top: 24px; + padding-bottom: 18px; + font-size: 13px; + line-height: 1; + + &::before, + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + height: 2px; + } + + &::before { + width: 100%; + background: rgba(255, 255, 255, 0.12); + } + + &::after { + width: 98px; + background: $color-accent; + } + + & > :first-child { + color: $color-muted; + } + + &-button { + padding: 0; + border: 0; + background: transparent; + color: inherit; + cursor: pointer; + font: inherit; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-accent; + transform: translateY(-2px); + } + + &::before, + &::after { + display: inline-block; + width: 7px; + height: 7px; + border-bottom: 1px solid currentColor; + border-left: 1px solid currentColor; + content: ''; + } + + &--previous::before { + margin-right: 8px; + transform: rotate(45deg); + } + + &--previous::after, + &--next::before { + display: none; + } + + &--next::after { + margin-left: 8px; + transform: rotate(-135deg); + } + } + } + + &__count, + &__hello { + color: $color-accent; + font-size: 13px; + } + + &__title { + // Titulos da secao seguem o padrao uppercase com acento azul no span. + margin-top: 8px; + font-size: 28px; + font-weight: 800; + text-transform: uppercase; + + &-accent { + color: $color-accent; + font-weight: 400; + } + } + + &__text { + max-width: 460px; + margin-top: 24px; + color: $color-muted; + font-size: 14px; + line-height: 1.55; + } + + &__play { + margin-top: 28px; + } + + &__company { + // O bloco institucional troca a proporcao das colunas no desktop. + .about__text { + max-width: 429px; + font-size: 14px; + line-height: 1.55; + } + + @include desktop { + grid-template-columns: minmax(300px, 480px) 1fr; + } + } + + &__watermark { + // A marca lateral aparece apenas no desktop, como elemento decorativo. + display: none; + + @include desktop { + display: block; + width: 498px; + height: 347px; + overflow: hidden; + background: url('../images/efeito.png') center / contain no-repeat; + color: transparent; + justify-self: center; + } + } +} diff --git a/src/styles/blocks/_benefits.scss b/src/styles/blocks/_benefits.scss new file mode 100644 index 000000000..dc484910c --- /dev/null +++ b/src/styles/blocks/_benefits.scss @@ -0,0 +1,122 @@ +@use '../utils' as *; + +.benefits { + // Secao centralizada com fundo escuro para destacar os diferenciais. + display: grid; + align-items: center; + background: + linear-gradient(90deg, rgba(20, 16, 46, 0.92), rgba(5, 4, 10, 0.96)), + $color-page; + + @include desktop { + min-height: 720px; + } + + &__container { + @include section-container; + + // Todo o bloco e alinhado ao centro no mockup. + text-align: center; + } + + &__title { + // O span azul separa visualmente a pergunta do nome do produto. + font-size: 28px; + font-weight: 800; + text-transform: uppercase; + + span { + color: $color-accent; + font-weight: 400; + } + } + + &__grid { + // Mobile empilha cards; tablet/desktop distribuem em tres colunas. + display: grid; + gap: 24px; + margin-top: 48px; + + @include tablet { + grid-template-columns: repeat(3, 1fr); + } + + @include desktop { + margin-top: 76px; + } + } + + &__item { + // A borda aparece so no hover para manter o visual limpo. + min-height: 246px; + box-sizing: border-box; + padding: 32px 24px; + border: 1px solid transparent; + transition: border-color 0.3s ease; + + &:hover { + border-color: $color-line; + } + } + + &__icon { + // Icones exportados do Figma mantem fidelidade aos cards de beneficios. + position: relative; + width: 70px; + height: 70px; + margin: 0 auto 24px; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + cursor: pointer; + transition: + filter 0.3s ease, + transform 0.3s ease; + will-change: filter, transform; + + &--compatible { + background-image: url('../images/pесурс1.png'); + } + + &--control { + background-image: url('../images/groupicone.png'); + } + + &--wireless { + background-image: url('../images/groupicone2.png'); + } + } + + &__item:hover &__icon { + // Hover destaca o icone sem alterar o tamanho do card. + filter: drop-shadow(0 0 12px rgba(5, 194, 223, 0.48)); + transform: scale(1.1); + } + + &__name { + font-size: 13px; + font-weight: 800; + text-transform: uppercase; + } + + &__text { + width: min(100%, 340px); + margin-top: 14px; + margin-inline: auto; + color: $color-muted; + font-size: 13px; + line-height: 1.45; + } + + &__nowrap { + // Em telas maiores algumas frases permanecem em uma linha para bater o layout. + @include tablet { + white-space: nowrap; + } + } + + &__button { + min-width: 200px; + margin-top: 52px; + } +} diff --git a/src/styles/blocks/_button.scss b/src/styles/blocks/_button.scss new file mode 100644 index 000000000..6b6e4de93 --- /dev/null +++ b/src/styles/blocks/_button.scss @@ -0,0 +1,31 @@ +@use '../utils' as *; + +.button { + // Botao base reutilizado nos CTAs para manter tamanho e interacao consistentes. + display: inline-flex; + align-items: center; + justify-content: center; + max-width: 100%; + min-width: 168px; + min-height: 48px; + padding: 0 28px; + border: 0; + border-radius: 4px; + background: $color-accent; + color: $color-text; + cursor: pointer; + font-weight: 500; + line-height: 1; + transition: + background 0.3s ease, + box-shadow 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + // O hover aproxima o botao do brilho azul usado nos elementos interativos. + background: $color-accent-dark; + box-shadow: 0 10px 28px rgba(5, 194, 223, 0.24); + transform: translateY(-2px); + } +} diff --git a/src/styles/blocks/_contact.scss b/src/styles/blocks/_contact.scss new file mode 100644 index 000000000..37c0fd3d5 --- /dev/null +++ b/src/styles/blocks/_contact.scss @@ -0,0 +1,90 @@ +@use '../utils' as *; + +.contact { + // Bloco final da pagina, com texto de apoio ao lado do formulario no desktop. + display: grid; + align-items: center; + background: $color-page; + + @include desktop { + min-height: 620px; + } + + &__container { + @include section-container; + + // O grid empilha no mobile e abre em duas colunas no desktop. + display: grid; + gap: 40px; + + @include desktop { + grid-template-columns: 0.82fr 1fr; + gap: 96px; + } + } + + &__eyebrow { + color: $color-accent; + font-size: 13px; + } + + &__title { + margin-top: 6px; + font-size: 28px; + font-weight: 800; + text-transform: uppercase; + + span { + color: $color-accent; + font-weight: 400; + } + } + + &__text { + margin-top: 18px; + color: $color-muted; + line-height: 1.5; + } + + &__address { + // Address agrupa canais diretos mantendo semantica de contato. + display: grid; + gap: 8px; + margin-top: 48px; + color: $color-muted; + font-style: normal; + + a { + width: fit-content; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-accent; + transform: translateX(4px); + } + } + } + + &__form { + // Formulario compacto no mobile e alinhado a direita no desktop. + display: grid; + gap: 18px; + + @include desktop { + max-width: 520px; + justify-self: end; + } + } + + &__button { + // O CTA do formulario fica centralizado em telas maiores. + margin-top: 14px; + + @include tablet { + justify-self: center; + } + } +} diff --git a/src/styles/blocks/_features.scss b/src/styles/blocks/_features.scss new file mode 100644 index 000000000..267067a1d --- /dev/null +++ b/src/styles/blocks/_features.scss @@ -0,0 +1,96 @@ +@use '../utils' as *; + +.features { + // Secao de cards com usos alternativos do produto. + display: grid; + align-items: center; + background: $color-page; + + @include desktop { + min-height: 620px; + } + + &__container { + @include section-container; + } + + &__grid { + // A grade cresce de uma coluna no mobile para quatro no desktop. + display: grid; + gap: 36px; + margin-top: 48px; + + @include tablet { + grid-template-columns: repeat(2, 1fr); + } + + @include desktop { + grid-template-columns: repeat(4, 1fr); + gap: 36px; + margin-top: 70px; + } + } + + &__item { + // Cada item centraliza icone, titulo e descricao curta. + text-align: center; + } + + &__icon { + // Os icones usam imagens exportadas para preservar o desenho do Figma. + width: 64px; + height: 64px; + margin: 0 auto 24px; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + cursor: pointer; + transition: + filter 0.3s ease, + transform 0.3s ease; + will-change: filter, transform; + + &--education { + background-image: url('../images/pесурс2.png'); + } + + &--estate { + background-image: url('../images/skyline.png'); + } + + &--fitness { + background-image: url('../images/group2.png'); + } + + &--social { + background-image: url('../images/network.png'); + } + } + + &__item:hover &__icon { + // O hover da vida ao card sem mudar o fluxo da grade. + filter: drop-shadow(0 0 12px rgba(5, 194, 223, 0.48)); + transform: translateY(-8px) rotate(3deg) scale(1.08); + } + + &__name { + font-size: 13px; + font-weight: 800; + text-transform: uppercase; + } + + &__text { + width: min(100%, 250px); + margin: 14px auto 0; + color: $color-muted; + font-size: 13px; + line-height: 1.45; + } + + &__nowrap { + // Em tablet para cima, algumas linhas ficam travadas para seguir o mockup. + @include tablet { + white-space: nowrap; + } + } +} diff --git a/src/styles/blocks/_field.scss b/src/styles/blocks/_field.scss new file mode 100644 index 000000000..15f8a0da4 --- /dev/null +++ b/src/styles/blocks/_field.scss @@ -0,0 +1,81 @@ +@use '../utils' as *; + +.field { + // Campo base usado nos formularios de contato, endereco e pagamento. + display: grid; + gap: 8px; + + &__label { + color: $color-muted; + font-size: 13px; + } + + &__control { + // Inputs seguem o visual minimalista com linha inferior e fundo transparente. + width: 100%; + min-height: 44px; + box-sizing: border-box; + border: 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.14); + background: transparent; + color: $color-text; + outline: none; + transition: + border-color 0.3s ease, + box-shadow 0.3s ease; + + &:focus { + border-color: $color-accent; + box-shadow: 0 1px 0 $color-accent; + } + + // A validacao nativa do navegador replica o estado de erro vermelho do UI kit. + &:user-invalid { + border-bottom-color: #ff4d4d; + box-shadow: 0 1px 0 #ff4d4d; + } + + &::placeholder { + color: rgba(146, 146, 146, 0.64); + } + + option { + background: $color-field; + color: $color-text; + } + + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus { + // Autofill do Chrome recebe fundo escuro para nao quebrar o tema visual. + border-bottom-color: $color-accent; + -webkit-text-fill-color: $color-text; + box-shadow: 0 0 0 1000px $color-page inset; + caret-color: $color-text; + transition: background-color 5000s ease-in-out 0s; + } + + &--area { + min-height: 84px; + resize: vertical; + } + } + + select.field__control { + // Select customizado desenha a seta com gradientes em vez do estilo nativo. + padding-inline: 24px 42px; + border-bottom: 0; + border-radius: 2px; + appearance: none; + background: + linear-gradient(45deg, transparent 50%, $color-accent 50%) + calc(100% - 18px) 50% / 5px 5px no-repeat, + linear-gradient(135deg, $color-accent 50%, transparent 50%) + calc(100% - 13px) 50% / 5px 5px no-repeat, + $color-field; + + &:focus { + box-shadow: 0 0 0 1px $color-accent; + } + } +} diff --git a/src/styles/blocks/_footer.scss b/src/styles/blocks/_footer.scss new file mode 100644 index 000000000..11e97171d --- /dev/null +++ b/src/styles/blocks/_footer.scss @@ -0,0 +1,119 @@ +@use '../utils' as *; + +.footer { + // Rodape simples com logo, navegacao, redes sociais e botao de voltar ao topo. + display: grid; + align-items: center; + min-height: 88px; + background: #000; + padding-block: 28px; + + @include desktop { + padding-block: 0; + } + + &__container { + @include section-container; + + // Desktop distribui os grupos em uma linha; mobile empilha com respiro. + display: grid; + gap: 24px; + align-items: center; + + @include desktop { + grid-template-columns: auto 1fr auto auto; + } + } + + &__nav, + &__socials { + // Links do rodape podem quebrar linha sem estourar a largura. + display: flex; + flex-wrap: wrap; + color: $color-text; + font-size: 13px; + } + + &__nav { + gap: 24px; + + @include desktop { + justify-content: center; + } + } + + &__socials { + gap: 16px; + } + + &__link { + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-accent; + transform: translateY(-2px); + } + } + + &__social { + // Cada rede social usa a imagem exportada e recebe feedback no hover. + display: grid; + width: 14px; + height: 14px; + place-items: center; + cursor: pointer; + transition: + filter 0.3s ease, + opacity 0.3s ease, + transform 0.3s ease; + will-change: filter, transform; + + img { + display: block; + max-width: 14px; + max-height: 14px; + object-fit: contain; + } + + &:hover { + filter: drop-shadow(0 0 10px rgba(5, 194, 223, 0.58)); + opacity: 0.82; + transform: translateY(-2px) scale(1.08); + } + } + + &__top { + // O circulo desenha a seta via CSS para evitar mais um asset pequeno. + position: relative; + display: grid; + justify-self: start; + width: 34px; + height: 34px; + border: 1px solid rgba(5, 194, 223, 0.6); + border-radius: 50%; + place-items: center; + transition: + border-color 0.3s ease, + box-shadow 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &::before { + content: ''; + width: 10px; + height: 10px; + border-top: 2px solid $color-accent; + border-left: 2px solid $color-accent; + transform: translateY(3px) rotate(45deg); + } + + &:hover { + border-color: $color-accent; + box-shadow: 0 0 18px rgba(5, 194, 223, 0.28); + transform: scale(1.14); + } + } +} diff --git a/src/styles/blocks/_header.scss b/src/styles/blocks/_header.scss new file mode 100644 index 000000000..9c0cf2cd2 --- /dev/null +++ b/src/styles/blocks/_header.scss @@ -0,0 +1,190 @@ +@use '../utils' as *; + +// O header desktop foi medido no frame de 1280px do Figma. +// Manter esses valores nomeados deixa o alinhamento do topo explicito. +$header-content-top: 64px; +$header-link-height: 16px; +$header-button-top: 47px; +$header-button-height: 48px; +$header-nav-button-gap: 37px; + +.header { + // O header pertence apenas a primeira tela, entao absolute mantem ele na + // area do hero sem acompanhar o scroll da pagina. + position: absolute; + inset: 0 0 auto; + z-index: 11; + background: transparent; + + &__container { + @include section-container; + + // No mobile, o header mantem so o logo e o botao de menu nos cantos. + display: grid; + grid-template-columns: auto auto; + justify-content: space-between; + align-items: center; + min-height: 60px; + gap: 24px; + + @include desktop { + // No desktop, seguimos o frame de 1280px: conteudo com 1060px, + // laterais de 110px e espacos fixos entre os grupos do header. + display: flex; + align-items: start; + min-height: $header-link-height; + padding-top: $header-content-top; + gap: 0; + } + } + + &__logo { + // O logo precisa ficar acima dos controles quando o menu mobile abre. + z-index: 12; + + @include desktop { + flex: 0 0 78px; + } + } + + &__menu-button { + // No mobile, o icone exportado vira duas linhas de CSS ao abrir o menu. + z-index: 12; + display: grid; + justify-self: end; + width: 48px; + height: 48px; + padding: 0; + border: 0; + background: transparent url('../images/Group.png') center / 20px 10px + no-repeat; + cursor: pointer; + place-content: center; + transition: + background-image 0.3s ease, + border-color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + transform: scale(1.04); + } + + @include desktop { + display: none; + } + } + + &__menu-line { + // As linhas ficam ocultas ate o menu abrir e entao viram o icone de fechar. + width: 20px; + height: 2px; + opacity: 0; + background: $color-text; + transition: + opacity 0.3s ease, + transform 0.3s ease; + will-change: opacity, transform; + } + + &__nav.nav { + @include desktop { + // A nav desktop nao deve herdar o painel alto usado no menu mobile. + align-items: flex-start; + place-self: start end; + flex: 0 0 330px; + width: 330px; + min-height: $header-link-height; + height: $header-link-height; + margin-left: auto; + margin-top: 0; + margin-right: $header-nav-button-gap; + padding: 0; + line-height: $header-link-height; + transform: none; + } + } + + &__actions { + // O CTA fica oculto no mobile porque a navegacao compacta fica no menu. + display: none; + align-items: center; + gap: 40px; + + @include desktop { + display: flex; + flex: 0 0 200px; + height: $header-button-height; + + // O botao comeca em y=47px, enquanto logo/nav comecam em y=64px. + margin-top: $header-button-top - $header-content-top; + } + } + + &__button { + @include desktop { + width: 200px; + height: $header-button-height; + min-width: 200px; + min-height: $header-button-height; + } + } + + &__language { + // O seletor de idioma fica no header desktop; no mobile aparece no menu. + position: relative; + display: none; + align-items: center; + gap: 7px; + padding: 0; + border: 0; + background: transparent; + color: $color-accent; + cursor: pointer; + font: inherit; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-text; + transform: translateY(-2px); + } + + @include desktop { + display: inline-flex; + margin-left: 18px; + } + + &::after { + content: ''; + width: 0; + height: 0; + border-inline: 4px solid transparent; + border-top: 5px solid $color-accent; + transform: translateY(2px); + } + } +} + +.page--menu-open .header__menu-line:first-child { + opacity: 1; + transform: translateY(1px) rotate(45deg); +} + +.page--menu-open .header__menu-line:last-child { + opacity: 1; + transform: translateY(-1px) rotate(-45deg); +} + +.page--menu-open .header__menu-button { + background-image: none; +} + +.page--menu-open .header__logo { + @media (max-width: 1023px) { + opacity: 0; + pointer-events: none; + } +} diff --git a/src/styles/blocks/_hero.scss b/src/styles/blocks/_hero.scss new file mode 100644 index 000000000..9e2df1754 --- /dev/null +++ b/src/styles/blocks/_hero.scss @@ -0,0 +1,287 @@ +@use '../utils' as *; + +.hero { + // A primeira tela controla o fundo visual e serve de base para o header + // absoluto ficar posicionado sobre ela. + position: relative; + min-height: calc(100vh - 60px); + padding: 42px 0 38px; + background: + radial-gradient( + circle at 62% 44%, + rgba(25, 20, 61, 0.72), + transparent 24rem + ), + url('../images/rectangle.png') center / cover no-repeat, + linear-gradient(105deg, #171332 0%, #080710 70%); + overflow: hidden; + + @include desktop { + // A altura do hero no frame desktop do Figma e 714px. + min-height: 714px; + padding: 0; + } + + &__container { + @include section-container; + + // No mobile, a imagem vem antes do texto; no desktop, vira duas colunas. + display: grid; + min-width: 0; + min-height: calc(100vh - 140px); + grid-template-rows: auto; + gap: 32px; + + @include desktop { + // No desktop, mantemos a mesma largura de 1060px usada no header. + width: min(100% - 220px, 1060px); + min-height: 714px; + grid-template-columns: minmax(320px, 455px) minmax(520px, 1fr); + grid-template-rows: 1fr auto; + align-items: start; + gap: 44px; + } + } + + &__content { + // O texto fica acima da imagem quando as areas se sobrepoem. + z-index: 1; + align-self: start; + min-width: 0; + + @media (max-width: #{$desktop-min - 1px}) { + grid-row: 2; + text-align: center; + } + + @include desktop { + // O bloco principal de texto comeca em y=196px no layout desktop. + grid-row: auto; + align-self: start; + text-align: left; + padding-top: 196px; + transform: none; + } + } + + &__eyebrow, + &__title { + // Clamp mantem o titulo forte em tablets sem estourar no mobile. + font-size: clamp(30px, 8vw, 46px); + font-weight: 800; + line-height: 1.18; + text-transform: uppercase; + + @media (max-width: #{$tablet-min - 1px}) { + font-size: 28px; + } + } + + &__title { + color: $color-accent; + font-weight: 400; + } + + &__eyebrow { + @include desktop { + // Evita que o titulo desktop quebre antes de "OF". + white-space: nowrap; + } + } + + &__description { + // A largura do texto acompanha a coluna estreita do mockup. + max-width: 340px; + margin-top: 30px; + margin-inline: auto; + color: $color-muted; + font-size: 17px; + line-height: 1.45; + + @include desktop { + margin-inline: 0; + } + } + + &__price { + // O preco no mobile e maior; no mockup desktop ele aparece mais compacto. + margin-top: 0; + font-size: 34px; + font-weight: 500; + + @include desktop { + margin-top: 24px; + font-size: 20px; + } + } + + &__play { + // O grafico exportado do play tem proporcao fixa, por isso travamos medidas. + margin-top: 42px; + + @media (max-width: #{$desktop-min - 1px}) { + width: min(100%, 334px); + min-width: 0; + min-height: 93px; + } + + @include desktop { + margin-left: 0; + margin-top: 22px; + min-width: 248px; + min-height: 69px; + } + } + + &__image { + // A imagem do produto e interativa, mas continua ancorada pelas regras do grid. + place-self: center; + max-width: 100%; + width: min(100%, 640px); + cursor: pointer; + filter: drop-shadow(0 36px 24px rgba(0, 0, 0, 0.5)); + transition: transform 0.3s ease; + will-change: filter, transform; + + &:hover { + transform: scale(1.03); + } + + @media (max-width: #{$desktop-min - 1px}) { + grid-row: 1; + } + + @media (max-width: #{$tablet-min - 1px}) { + width: min(100%, 328px); + } + + @include desktop { + // No desktop, a imagem fica abaixo do texto para bater com o mockup. + align-self: start; + width: min(100%, 720px); + margin-top: 230px; + } + } + + &__bottom { + // Os controles inferiores aparecem so no desktop do frame fornecido. + display: none; + + @include desktop { + grid-column: 1 / -1; + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: end; + padding-bottom: 74px; + } + } + + &__links, + &__pager { + // FAQ/Help e paginador usam a mesma tipografia pequena de controle. + display: flex; + gap: 38px; + font-weight: 500; + } + + &__link { + // Usamos botoes, nao links, porque eles abrem modais locais. + padding: 0; + border: 0; + background: transparent; + color: inherit; + cursor: pointer; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-accent; + transform: translateY(-2px); + } + } + + &__more { + // A seta pequena e desenhada em CSS para seguir o marcador do Figma. + position: relative; + color: $color-accent; + font-weight: 500; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-text; + transform: translateY(-2px); + } + + &::after { + content: ''; + position: absolute; + left: 50%; + bottom: -16px; + border-inline: 5px solid transparent; + border-top: 6px solid $color-accent; + transform: translateX(-50%); + } + } + + &__pager { + justify-self: end; + + @include desktop { + // Mantem Previous/Next alinhados a borda direita do frame de conteudo. + justify-content: space-between; + width: 230px; + margin-right: 0; + gap: 0; + } + } + + &__pager-button { + // O sublinhado pertence a cada acao do paginador e vem do ::after. + position: relative; + display: inline-flex; + align-items: center; + gap: 10px; + padding: 0 0 14px 18px; + border: 0; + background: transparent; + color: $color-text; + cursor: pointer; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-accent; + transform: translateY(-2px); + } + + &::after { + content: ''; + position: absolute; + right: 0; + bottom: 0; + width: 78px; + height: 1px; + background: $color-accent; + } + + &--muted { + color: $color-muted; + } + + &:not(.hero__pager-button--muted) { + padding-right: 18px; + padding-left: 0; + } + } + + &__pager-icon { + flex: 0 0 auto; + } +} diff --git a/src/styles/blocks/_logo.scss b/src/styles/blocks/_logo.scss new file mode 100644 index 000000000..0d0445c0a --- /dev/null +++ b/src/styles/blocks/_logo.scss @@ -0,0 +1,46 @@ +@use '../utils' as *; + +.logo { + // O logo visual vem de imagem, enquanto o texto continua acessivel no HTML. + position: relative; + display: inline-flex; + width: 78px; + height: 16px; + background: url('../images/logo.png') center / contain no-repeat; + line-height: 1; + letter-spacing: 0; + transition: + opacity 0.3s ease, + transform 0.3s ease; + will-change: opacity, transform; + + &:hover { + opacity: 0.78; + transform: translateY(-2px); + } + + @include desktop { + width: 78px; + height: 16px; + } + + &__text { + // Texto oculto visualmente, mas preservado para leitores de tela. + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + clip-path: inset(50%); + white-space: nowrap; + } + + &__accent { + // Mantem o "VR" sem aparecer duplicado sobre a imagem do logo. + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + clip-path: inset(50%); + white-space: nowrap; + } +} diff --git a/src/styles/blocks/_modal.scss b/src/styles/blocks/_modal.scss new file mode 100644 index 000000000..d2f42fbda --- /dev/null +++ b/src/styles/blocks/_modal.scss @@ -0,0 +1,351 @@ +@use '../utils' as *; + +.modal { + // FAQ, Help e Idioma compartilham a mesma camada fixa sobre o fundo escurecido. + position: fixed; + z-index: 20; + inset: 0; + display: grid; + padding: 80px 16px; + background: + linear-gradient(rgba(5, 4, 10, 0.82), rgba(5, 4, 10, 0.92)), + url('../images/image.png') right 8% center / min(32vw, 360px) auto no-repeat; + opacity: 0; + overflow-y: auto; + pointer-events: none; + transition: opacity 0.3s ease; + will-change: opacity; + place-items: center; + + &--open { + // A classe aberta libera clique e mostra o overlay com transicao. + opacity: 1; + pointer-events: auto; + } + + &__panel { + // Painel base dos modais; variacoes ajustam medidas por tipo de conteudo. + position: relative; + width: min(100%, 880px); + box-sizing: border-box; + padding: 56px 80px 44px; + border: 1px solid $color-line; + background: rgba(21, 17, 44, 0.98); + + @media (max-width: 760px) { + padding: 44px 24px 32px; + } + + &--help { + // Help precisa de mais altura por conter texto e lista de contatos. + min-height: 550px; + padding: 60px 88px 74px; + + @media (max-width: 760px) { + min-height: auto; + padding: 56px 24px 36px; + } + } + + &--faq { + // No mobile, o FAQ fica estreito e com borda azul como no layout. + @media (max-width: 760px) { + width: min(100%, 280px); + min-height: 608px; + padding: 20px 0 122px; + border-color: $color-accent; + } + } + + &--language { + // O seletor de idioma usa painel compacto e lista vertical. + width: min(100%, 320px); + min-height: 405px; + padding: 75px 0 30px; + border-color: $color-accent; + } + } + + &__close { + // O X de fechar e desenhado com pseudo-elementos para manter nitidez. + position: absolute; + top: 18px; + right: 18px; + width: 22px; + height: 22px; + border: 0; + background: transparent; + cursor: pointer; + transition: + border-color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &::before, + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 12px; + height: 1px; + background: $color-accent; + } + + &::before { + transform: translate(-50%, -50%) rotate(45deg); + } + + &::after { + transform: translate(-50%, -50%) rotate(-45deg); + } + + &:hover { + border-color: $color-text; + transform: scale(1.1); + } + } + + &__back { + // Botao de voltar usado no modal de idioma. + position: absolute; + top: 23px; + left: 20px; + width: 24px; + height: 24px; + border: 0; + background: transparent; + cursor: pointer; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &::before { + content: ''; + position: absolute; + top: 7px; + left: 8px; + width: 9px; + height: 9px; + border-bottom: 1px solid $color-text; + border-left: 1px solid $color-text; + transform: rotate(45deg); + } + + &:hover { + transform: translateX(-2px); + } + } + + &__title { + // Titulos dos modais repetem o padrao branco com destaque azul. + max-width: 520px; + font-size: clamp(24px, 4vw, 34px); + font-weight: 800; + line-height: 1.15; + text-transform: uppercase; + + span { + color: $color-accent; + font-weight: 400; + } + } + + &__item { + // Cada pergunta do FAQ usa details/summary para abrir sem JS extra. + max-width: 700px; + margin-top: 18px; + border: 1px solid $color-line; + color: $color-muted; + + summary { + padding: 16px 24px; + color: $color-accent; + cursor: pointer; + } + + p { + padding: 0 24px 16px; + line-height: 1.55; + } + } + + &__panel--faq &__close { + @media (max-width: 760px) { + top: 4px; + right: 4px; + width: 17px; + height: 17px; + } + } + + &__panel--faq &__title { + @media (max-width: 760px) { + max-width: 180px; + margin-left: 20px; + font-size: 11px; + line-height: 1.1; + } + } + + &__panel--faq &__item { + // Ajustes especificos deixam o FAQ legivel dentro do painel mobile. + @media (max-width: 760px) { + width: 100%; + max-width: none; + box-sizing: border-box; + margin-top: 18px; + border-color: $color-accent; + color: $color-muted; + + summary { + padding: 10px 20px; + font-size: 11px; + line-height: 1.25; + } + + p { + padding: 0 20px 12px; + font-size: 11px; + line-height: 1.45; + } + + &:not([open]) { + margin-top: 4px; + } + } + } + + &__updated { + color: rgba(146, 146, 146, 0.56); + font-size: 13px; + } + + &__panel--faq &__updated { + @media (max-width: 760px) { + font-size: 10px; + line-height: 1.4; + } + } + + &__more { + // Botao visual para expandir conteudo futuro; no mobile fica oculto. + display: grid; + margin: 44px auto 0; + border: 0; + background: transparent; + color: $color-accent; + cursor: pointer; + font: inherit; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &::after { + content: ''; + width: 0; + height: 0; + margin: 8px auto 0; + border-inline: 4px solid transparent; + border-top: 5px solid currentColor; + } + + &:hover { + color: $color-text; + transform: translateY(-2px); + } + } + + &__panel--faq &__more { + @media (max-width: 760px) { + display: none; + } + } + + &__text { + // Texto corrido dos modais usa largura controlada para leitura confortavel. + margin-top: 22px; + color: $color-muted; + line-height: 1.55; + + a { + color: $color-accent; + } + + &--help { + max-width: 702px; + margin-top: 28px; + font-size: 16px; + line-height: 1.5; + } + } + + &__actions { + // Links e contatos do Help formam pares de acao e informacao. + display: grid; + gap: 12px; + margin-top: 24px; + + a { + width: fit-content; + padding-bottom: 3px; + border-bottom: 1px solid $color-accent; + color: $color-accent; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover { + color: $color-text; + transform: translateX(4px); + } + } + + span { + color: $color-muted; + } + + &--help { + grid-template-columns: 203px 1fr; + gap: 14px 62px; + max-width: 702px; + margin-top: 24px; + + @media (max-width: 540px) { + grid-template-columns: 1fr; + } + } + } + + &__languages { + // Lista simples para manter todos os idiomas clicaveis no painel. + display: grid; + gap: 0; + } + + &__language { + // Cada idioma ocupa uma linha inteira para facilitar clique/toque. + width: 100%; + min-height: 27px; + padding: 0 20px; + border: 0; + background: transparent; + color: $color-text; + cursor: pointer; + font: inherit; + line-height: 1.5; + transition: + color 0.3s ease, + transform 0.3s ease; + will-change: transform; + + &:hover, + &--active { + background: rgba(5, 4, 10, 0.36); + color: $color-accent; + } + } +} diff --git a/src/styles/blocks/_nav.scss b/src/styles/blocks/_nav.scss new file mode 100644 index 000000000..2d2cedcab --- /dev/null +++ b/src/styles/blocks/_nav.scss @@ -0,0 +1,138 @@ +@use '../utils' as *; + +.nav { + // Offset usado para alinhar os links dentro do painel mobile exportado do Figma. + --nav-list-offset: 15px; + + // Por padrao, a nav e o menu mobile fechado. + position: fixed; + inset: 0 0 auto auto; + z-index: 10; + display: flex; + flex-direction: column; + align-items: flex-start; + width: min(100%, 220px); + min-height: 390px; + gap: 14px; + padding: 61px 0 32px; + overflow: hidden; + background: $color-panel-soft url('../images/rectanglemenu.png') center / + cover no-repeat; + color: $color-text; + font-size: 16px; + font-weight: 500; + line-height: 1.2; + opacity: 0; + pointer-events: none; + transform: translateY(-12px); + transition: + opacity 0.3s ease, + transform 0.3s ease; + will-change: opacity, transform; + + &::before { + content: ''; + position: absolute; + inset: 0 0 auto; + height: 51px; + background: $color-panel; + } + + // No tablet, o mesmo menu vira um overlay maior. + @include tablet { + --nav-list-offset: 54.3vw; + + inset: 0; + width: auto; + min-height: 100vh; + gap: 12px; + padding-top: 66px; + font-size: 21px; + font-weight: 800; + line-height: 1; + + &::before { + height: 66px; + } + } + + @include desktop { + // No desktop, a nav volta ao fluxo normal do header. + --nav-list-offset: 0; + + position: static; + z-index: auto; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + gap: 0; + min-height: 0; + padding: 0; + background: transparent; + font-size: 16px; + font-weight: 500; + line-height: 16px; + opacity: 1; + overflow: visible; + pointer-events: auto; + transform: none; + + &::before { + display: none; + } + } + + &__link { + // Links e botoes dividem a mesma aparencia para desktop e menu mobile. + position: relative; + z-index: 1; + display: block; + width: 100%; + min-width: 0; + padding: 0 0 0 var(--nav-list-offset); + border: 0; + background: transparent; + color: inherit; + font: inherit; + text-align: left; + cursor: pointer; + + @include hover(color, $color-accent); + + &:first-child { + // Primeiro item fica destacado apenas no painel mobile. + padding-block: 8px; + background: rgba(5, 4, 10, 0.36); + color: $color-accent; + } + + @include desktop { + width: auto; + padding: 0; + + &:first-child { + padding-block: 0; + background: transparent; + color: inherit; + } + } + } + + &__link--mobile { + // Itens extras existem so no menu mobile. + @include desktop { + display: none; + } + } +} + +.page--menu-open .nav { + // Classe aplicada pelo JS para revelar o menu. + opacity: 1; + pointer-events: auto; + transform: none; + + @include desktop { + transform: none; + } +} diff --git a/src/styles/blocks/_order.scss b/src/styles/blocks/_order.scss new file mode 100644 index 000000000..8892f866e --- /dev/null +++ b/src/styles/blocks/_order.scss @@ -0,0 +1,306 @@ +@use '../utils' as *; + +.order { + // A secao de compra fica escondida ate o usuario navegar para #order. + display: none; + background: + linear-gradient(90deg, rgba(19, 15, 50, 0.95), rgba(5, 4, 10, 0.94)), + url('../images/rectangle.png') center / cover no-repeat; + + &:target { + // O hash da URL abre o fluxo sem depender de classe inicial no HTML. + display: block; + } + + @media (max-width: 760px) { + background: + linear-gradient(rgba(19, 15, 50, 0.95), rgba(5, 4, 10, 0.96)), + url('../images/rectangle.png') center / cover no-repeat; + } + + &__container { + @include section-container; + + // Mantem espaco vertical suficiente para as telas do fluxo. + display: grid; + gap: 42px; + min-height: 520px; + + @include desktop { + min-height: 560px; + } + } + + &__top { + // Topo do pedido junta logo e indicador de etapas. + display: grid; + gap: 24px; + + @include tablet { + grid-template-columns: auto 1fr; + align-items: center; + } + } + + &__steps { + // Lista ordenada mostra a progressao Place Order -> Pay -> Complete. + display: flex; + justify-content: space-between; + gap: 8px; + margin: 0; + padding: 0; + color: rgba(146, 146, 146, 0.7); + font-size: 13px; + font-weight: 800; + list-style: none; + text-transform: capitalize; + + @include tablet { + gap: 18px; + } + } + + &__step { + // Cada etapa desenha sua linha/bolinha de progresso com pseudo-elementos. + position: relative; + flex: 1; + padding-bottom: 18px; + text-align: center; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 1px; + background: rgba(255, 255, 255, 0.18); + } + + &::before { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + display: none; + width: 4px; + height: 4px; + background: $color-accent; + transform: translateX(-50%); + } + + @include tablet { + padding-bottom: 16px; + + &::after { + display: none; + } + } + + &--active { + color: $color-accent; + + &::after { + background: $color-accent; + } + + &::before { + display: block; + } + } + } + + &__stage { + // Apenas uma tela do pedido fica visivel enquanto o JS atualiza a etapa. + display: none; + gap: 42px; + + &--active { + display: grid; + + @include desktop { + grid-template-columns: 0.85fr 1fr; + align-items: end; + } + } + + &--complete.order__stage--active { + display: grid; + grid-template-columns: 1fr; + place-items: center; + min-height: 360px; + text-align: center; + } + } + + &__content { + // Imagem e resumo do produto ficam juntos em cada etapa. + display: grid; + align-content: end; + + @include tablet { + justify-items: start; + } + } + + &__image { + // Produto ganha sombra e deslocamento leve no desktop. + width: min(100%, 430px); + margin-inline: auto; + filter: drop-shadow(0 24px 22px rgba(0, 0, 0, 0.42)); + + @include desktop { + width: min(100%, 520px); + margin-inline: 0; + transform: translateY(-24px); + } + } + + &__title { + color: $color-accent; + font-size: 28px; + font-weight: 800; + text-transform: uppercase; + } + + &__text { + max-width: 360px; + margin-top: 16px; + color: $color-muted; + line-height: 1.5; + } + + &__summary { + // Quantidade e preco formam um resumo compacto ao lado da imagem. + display: grid; + grid-template-columns: 96px auto; + align-items: end; + width: max-content; + margin-top: 40px; + column-gap: 40px; + } + + &__label { + grid-column: 1; + color: $color-muted; + font-size: 13px; + } + + &__select { + // Select pequeno usa seta customizada para combinar com os campos. + grid-column: 1; + width: 56px; + height: 40px; + border: 0; + border-radius: 2px; + appearance: none; + background: + linear-gradient(45deg, transparent 50%, $color-accent 50%) + calc(100% - 17px) 50% / 5px 5px no-repeat, + linear-gradient(135deg, $color-accent 50%, transparent 50%) + calc(100% - 12px) 50% / 5px 5px no-repeat, + $color-field; + color: $color-text; + cursor: pointer; + padding: 0 22px 0 0; + text-align: center; + transition: + background 0.3s ease, + box-shadow 0.3s ease; + + &:hover, + &:focus { + background: #3a3654; + box-shadow: 0 0 0 1px $color-accent; + outline: none; + } + } + + &__price { + grid-row: 1 / 3; + grid-column: 2; + color: $color-accent; + font-size: 28px; + font-weight: 500; + } + + &__form { + // Forms empilham no mobile e passam para duas colunas em telas maiores. + display: grid; + gap: 18px; + + @include tablet { + grid-template-columns: repeat(2, 1fr); + column-gap: 24px; + } + + &--payment { + align-self: end; + + @include desktop { + max-width: 430px; + margin-bottom: 4px; + } + } + } + + &__field-wide { + // Campos largos ocupam toda a grade em tablet/desktop. + @include tablet { + grid-column: 1 / -1; + } + } + + &__card-number { + // Reserva espaco para a marca VISA dentro do campo de cartao. + position: relative; + + .field__control { + padding-right: 72px; + } + } + + &__card-brand { + // Badge visual da bandeira do cartao. + position: absolute; + right: 4px; + bottom: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 54px; + height: 34px; + background: $color-text; + color: #0057a8; + font-size: 14px; + font-weight: 800; + font-style: italic; + } + + &__complete-title { + // Tela final usa titulo grande com acento azul no span. + font-size: clamp(28px, 5vw, 44px); + font-weight: 800; + text-transform: uppercase; + + span { + color: $color-accent; + font-weight: 400; + } + } + + &__complete-text { + max-width: 720px; + color: $color-muted; + line-height: 1.6; + } + + &__button { + margin-top: 14px; + + @include tablet { + grid-column: 1 / -1; + justify-self: center; + } + } +} diff --git a/src/styles/blocks/_page.scss b/src/styles/blocks/_page.scss new file mode 100644 index 000000000..37ecf0426 --- /dev/null +++ b/src/styles/blocks/_page.scss @@ -0,0 +1,47 @@ +@use '../utils' as *; + +.page { + // Base global da pagina: largura minima, fundo geral e bloqueio horizontal. + min-width: 320px; + margin: 0; + overflow-x: hidden; + background: + radial-gradient( + circle at 18% 5%, + rgba(26, 23, 67, 0.95), + transparent 28rem + ), + linear-gradient(180deg, #15112d 0, $color-page 34rem); + color: $color-text; + + &--menu-open, + &--modal-open { + // Evita scroll do body quando menu ou modal estao abertos. + overflow: hidden; + } + + &__section { + // Espacamento padrao das secoes internas e compensacao para links ancora. + box-sizing: border-box; + scroll-margin-top: 96px; + padding-block: 56px; + + @include tablet { + padding-block: 72px; + } + + @include desktop { + scroll-margin-top: 120px; + padding-block: 92px; + } + } +} + +section { + // Garante que qualquer ancora de secao pare abaixo do header visual. + scroll-margin-top: 96px; + + @include desktop { + scroll-margin-top: 120px; + } +} diff --git a/src/styles/blocks/_play.scss b/src/styles/blocks/_play.scss new file mode 100644 index 000000000..543cc0bcc --- /dev/null +++ b/src/styles/blocks/_play.scss @@ -0,0 +1,46 @@ +@use '../utils' as *; + +.play { + // O bitmap exportado do Figma mantem o desenho exato do botao play. + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 248px; + min-height: 69px; + padding: 0; + border: 0; + background: url('../images/botao.png') center / contain no-repeat; + color: $color-text; + cursor: pointer; + transition: + color 0.3s ease, + filter 0.3s ease, + transform 0.3s ease; + will-change: filter, transform; + + &:hover { + // Hover reforca que o botao abre o video externo. + transform: translateX(4px); + filter: drop-shadow(0 0 16px rgba(5, 194, 223, 0.35)); + } + + &__icon { + // Partes internas ficam ocultas porque a arte completa vem do bitmap. + display: none; + } + + &__line { + display: none; + } + + &__text { + // Texto fica acessivel no HTML, mas nao aparece sobre a imagem exportada. + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + clip-path: inset(50%); + white-space: nowrap; + } +} diff --git a/src/styles/blocks/_section-heading.scss b/src/styles/blocks/_section-heading.scss new file mode 100644 index 000000000..44d198591 --- /dev/null +++ b/src/styles/blocks/_section-heading.scss @@ -0,0 +1,26 @@ +@use '../utils' as *; + +.section-heading { + // Cabecalho reutilizavel para secoes com titulo centralizado e subtitulo curto. + text-align: center; + + &__title { + // Titulo padrao das secoes: caixa alta e destaque azul no span. + font-size: 28px; + font-weight: 800; + line-height: 1.2; + text-transform: uppercase; + + span { + color: $color-accent; + font-weight: 400; + } + } + + &__text { + // Subtitulo pequeno fica em azul para conectar com a identidade visual. + margin-top: 10px; + color: $color-accent; + font-size: 14px; + } +} diff --git a/src/styles/blocks/_tech.scss b/src/styles/blocks/_tech.scss new file mode 100644 index 000000000..4e6eb0a76 --- /dev/null +++ b/src/styles/blocks/_tech.scss @@ -0,0 +1,55 @@ +@use '../utils' as *; + +.tech { + // Secao de especificacoes tecnicas centralizada no fundo escuro. + display: grid; + align-items: center; + background: $color-page; + + @include desktop { + min-height: 675px; + } + + &__container { + @include section-container; + } + + &__title { + // No desktop, o titulo fica deslocado para alinhar com a composicao da imagem. + text-align: center; + font-size: 28px; + font-weight: 800; + text-transform: uppercase; + + @include desktop { + width: min(100%, 900px); + margin-inline: auto; + padding-left: 650px; + box-sizing: border-box; + text-align: left; + } + + span { + color: $color-accent; + font-weight: 400; + } + } + + &__image { + // A imagem traz linhas e labels do Figma em um unico asset. + display: block; + width: 100%; + max-width: 900px; + margin: 52px auto 0; + cursor: pointer; + transition: + filter 0.3s ease, + transform 0.3s ease; + will-change: filter, transform; + + &:hover { + filter: drop-shadow(0 0 18px rgba(5, 194, 223, 0.22)); + transform: scale(1.01); + } + } +} diff --git a/src/styles/main.scss b/src/styles/main.scss index fb9195d12..0add17bd6 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -1,7 +1,19 @@ -@import 'utils'; -@import 'fonts'; -@import 'typography'; - -body { - background: $c-gray; -} +@use 'fonts'; +@use 'typography'; +@use 'blocks/page'; +@use 'blocks/logo'; +@use 'blocks/button'; +@use 'blocks/header'; +@use 'blocks/nav'; +@use 'blocks/play'; +@use 'blocks/field'; +@use 'blocks/hero'; +@use 'blocks/section-heading'; +@use 'blocks/features'; +@use 'blocks/about'; +@use 'blocks/tech'; +@use 'blocks/benefits'; +@use 'blocks/order'; +@use 'blocks/contact'; +@use 'blocks/modal'; +@use 'blocks/footer'; diff --git a/src/styles/utils/_mixins.scss b/src/styles/utils/_mixins.scss index 80c79780d..1e5ab437d 100644 --- a/src/styles/utils/_mixins.scss +++ b/src/styles/utils/_mixins.scss @@ -1,6 +1,35 @@ +@use 'vars' as *; + @mixin hover($_property, $_toValue) { transition: #{$_property} 0.3s; + &:hover { #{$_property}: $_toValue; } } + +@mixin tablet { + @media (min-width: $tablet-min) { + @content; + } +} + +@mixin desktop { + @media (min-width: $desktop-min) { + @content; + } +} + +// Keeps each section aligned to the 1280px Figma frame. +@mixin section-container { + width: min(100% - 32px, $content-width); + margin-inline: auto; + + @include tablet { + width: min(100% - 64px, $content-width); + } + + @include desktop { + width: min(100% - 220px, 1060px); + } +} diff --git a/src/styles/utils/_vars.scss b/src/styles/utils/_vars.scss index aeb006ffb..43b199f9c 100644 --- a/src/styles/utils/_vars.scss +++ b/src/styles/utils/_vars.scss @@ -1 +1,12 @@ -$c-gray: #eee; +$content-width: 1280px; +$tablet-min: 640px; +$desktop-min: 1024px; +$color-page: #05040a; +$color-panel: #100d22; +$color-panel-soft: #15112c; +$color-accent: #05c2df; +$color-accent-dark: #0097b1; +$color-text: #fff; +$color-muted: #929292; +$color-line: rgba(5, 194, 223, 0.7); +$color-field: #2f2b47;