diff --git a/index.html b/index.html index 1d8fe6c..20f2f55 100644 --- a/index.html +++ b/index.html @@ -4,12 +4,10 @@ - + - Mónica Pastor Resume - - - + Mónica Pastor — Security Engineer & Developer + diff --git a/src/App.jsx b/src/App.jsx index 4e5f393..249c16f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,9 +19,11 @@ class App extends Component { return (
+
+
); diff --git a/src/App.test.jsx b/src/App.test.jsx index fbb5d52..4cbe836 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -63,17 +63,50 @@ describe('portfolio after dependency upgrades', () => { window.dispatchEvent(new Event('scroll')); expect(container.querySelector('#nav li.current a').hash).toBe('#about'); expect(nav.classList.contains('opaque')).toBe(true); - expect(nav.style.display).toBe('none'); + expect(nav.style.display).toBe(''); + expect(container.querySelector('#nav li.current a').getAttribute('aria-current')).toBe('location'); vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(375); window.dispatchEvent(new Event('resize')); expect(nav.style.display).toBe(''); }); - it('resizes the hero and removes global listeners on unmount', () => { + it('opens and closes mobile navigation with accessible state', () => { + renderApp(); + const toggle = container.querySelector('.menu-toggle'); + expect(toggle.getAttribute('aria-expanded')).toBe('false'); + act(() => toggle.click()); + expect(toggle.getAttribute('aria-expanded')).toBe('true'); + expect(container.querySelector('#nav').classList.contains('is-open')).toBe(true); + container.querySelector('#about').scrollIntoView = vi.fn(); + act(() => container.querySelector('#nav a[href="#about"]').click()); + expect(toggle.getAttribute('aria-expanded')).toBe('false'); + }); + + it('moves keyboard focus to the destination when the mobile menu closes', () => { + renderApp(); + const toggle = container.querySelector('.menu-toggle'); + const target = container.querySelector('#about'); + const link = container.querySelector('#nav a[href="#about"]'); + target.scrollIntoView = vi.fn(); + const focus = vi.spyOn(target, 'focus'); + act(() => toggle.click()); + link.focus(); + expect(document.activeElement).toBe(link); + // Native keyboard activation of an anchor produces a click with detail 0. + act(() => link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, detail: 0 }))); + expect(toggle.getAttribute('aria-expanded')).toBe('false'); + expect(container.querySelector('#nav').classList.contains('is-open')).toBe(false); + expect(document.activeElement).toBe(target); + expect(target.getAttribute('tabindex')).toBe('-1'); + expect(focus).toHaveBeenCalledWith({ preventScroll: true }); + expect(target.scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' }); + }); + + it('leaves hero sizing to responsive CSS and removes global listeners on unmount', () => { const removeWindowListener = vi.spyOn(window, 'removeEventListener'); const removeDocumentListener = vi.spyOn(document, 'removeEventListener'); renderApp(); - expect(container.querySelector('header').style.height).toBe(`${window.innerHeight}px`); + expect(container.querySelector('header').style.height).toBe(''); act(() => root.unmount()); expect(removeWindowListener).toHaveBeenCalledWith('resize', expect.any(Function)); expect(removeWindowListener).toHaveBeenCalledWith('scroll', expect.any(Function)); diff --git a/src/components/About.jsx b/src/components/About.jsx index 6784e83..3310667 100644 --- a/src/components/About.jsx +++ b/src/components/About.jsx @@ -1,39 +1,12 @@ -import React, { Component } from 'react'; -export default class About extends Component { - render() { - let data = this.props.data; - return ( -
-
- -
- -

About Me

-

- { - data.aboutme - } -

- -
- -
- -

Contact Details

-

- {data.name} -

- - {data.address} - -

- {data.url} -

-
-
-
-
-
- ); - } -} \ No newline at end of file +import React from 'react'; + +export default function About({ data }) { + return ( +
+
+

01 / ABOUT

A developer’s perspective.
A security mindset.

{data.aboutme}

+ +
+
+ ); +} diff --git a/src/components/ContactUs.jsx b/src/components/ContactUs.jsx index 3647a72..ad559a5 100644 --- a/src/components/ContactUs.jsx +++ b/src/components/ContactUs.jsx @@ -1,25 +1,9 @@ -import React, { Component } from 'react'; -export default class ContactUs extends Component { - render() { - let data = this.props.data; - return ( -
-
-
-
-

- Let’s create your next

- experience together

- {data.contactEmail} -
    - {data.socialLinks.map((link,index)=>( -
  • {link.name}
  • - ))} -
-
-
-
-
- ); - } -} \ No newline at end of file +import React from 'react'; + +export default function ContactUs({ data }) { + return ( +
+

03 / GET IN TOUCH

Let’s create your
next experience
together.

Have something in mind?
Let’s start a conversation.

{data.contactEmail}
+
+ ); +} diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index e555832..9d81bf7 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -1,29 +1,5 @@ -import React, { Component } from 'react'; -export default class Footer extends Component { - render() { - let data = this.props.data; - return ( - - ); - } -} \ No newline at end of file +import React from 'react'; + +export default function Footer({ data }) { + return ; +} diff --git a/src/components/Header.jsx b/src/components/Header.jsx index f0290e0..52c8ba3 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,50 +1,40 @@ -import React, { Component } from 'react'; -export default class Header extends Component { - render() { - let data = this.props.data; - return ( - - -
- +import React, { useState } from 'react'; -
-
-

Hi 👋 I am {data.name}.

-

I am a {data.role}.{data.roleDescription} -

-
-
    - { - data.socialLinks && data.socialLinks.map(item =>{ - return( -
  • - -
  • - ) - } - ) - } -
-
-
- -

- -

- -
-
- ); - } -} \ No newline at end of file +export default function Header({ data }) { + const [menuOpen, setMenuOpen] = useState(false); + return ( +
+ Skip to content + +
+
+

{data.address}

+

Hi, I’m
{data.name}.

+

{data.role}

+

{data.roleDescription}.

+ + +
+
+ ); +} diff --git a/src/components/Portfolio.jsx b/src/components/Portfolio.jsx index 29dcaed..44bc6b3 100644 --- a/src/components/Portfolio.jsx +++ b/src/components/Portfolio.jsx @@ -1,34 +1,12 @@ -import React, { Component } from 'react'; -export default class Porfolio extends Component { - render() { - let data = this.props.data; - return ( -
-
-

Check out some of my work

-
- { - data.portfolio && data.portfolio.map((item)=>{ - return( -
-
- work -
-
-
{item.description}
-
-
-
- -

{item.name}

-
-
- ) - }) - } -
+import React from 'react'; + +export default function Portfolio({ data }) { + return ( +
+
+

02 / SELECTED WORK

Ideas put into practice.

Security, development & data exploration.

+
-
- ); - } -} \ No newline at end of file +
+ ); +} diff --git a/src/index.css b/src/index.css index 6c6da19..ebf1c40 100644 --- a/src/index.css +++ b/src/index.css @@ -1,21 +1,915 @@ +:root { + font-family:"Avenir Next",Avenir,"Segoe UI",sans-serif; + color:#172333; + background:#101b2a; + font-synthesis:none; + -webkit-font-smoothing:antialiased; + --navy:#101b2a; + --muted:#9cabbc; + --aquamarine:#80e8d5; + --paper:#f2f8f8 +} + +* { + box-sizing:border-box +} + +html { + scroll-behavior:smooth; + scroll-padding-top:100px +} + body { - margin: 0; - padding: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - - -moz-osx-font-smoothing: grayscale; + margin:0 +} + +a { + color:inherit; + text-decoration:none +} + +button { + font:inherit +} + +ul { + padding:0; + margin:0; + list-style:none +} + +h1,h2,h3,p { + margin:0 +} + +a,button { + touch-action:manipulation +} + +a:focus-visible,button:focus-visible { + outline:3px solid #147983; + outline-offset:6px +} + +header a:focus-visible,footer a:focus-visible,#contact a:focus-visible,header button:focus-visible { + outline-color:var(--aquamarine) +} + +::selection { + background:var(--aquamarine); + color:var(--navy) +} + +.page-width { + width:min(1160px,calc(100% - 96px)); + margin-inline:auto +} + +.eyebrow { + font-size:11px; + line-height:1.6; + font-weight:650; + letter-spacing:2px +} + +.skip-link { + position:fixed; + top:-100px; + left:24px; + padding:14px 24px; + background:var(--aquamarine); + color:var(--navy); + z-index:20 +} + +.skip-link:focus { + top:12px +} + +header { + position:relative; + color:var(--paper); + background:radial-gradient(ellipse at 85% 35%,#1d2e3b 0,transparent 50%),var(--navy); + min-height:760px +} + +#nav-wrap { + position:fixed; + top:0; + left:0; + right:0; + z-index:10; + display:flex; + align-items:center; + justify-content:space-between; + gap:24px; + padding:22px max(48px,calc((100vw - 1160px)/2)); + transition:background .2s,box-shadow .2s +} + +#nav-wrap.opaque { + background:#101b2af5; + box-shadow:0 1px 0 #ffffff12; + backdrop-filter:blur(12px) +} + +.brand { + font-size:32px; + letter-spacing:-3px; + font-weight:800; + line-height:1 +} + +.brand span { + color:var(--aquamarine) +} + +.nav { + display:flex; + gap:30px; + align-items:center +} + +.nav a { + font-size:13px; + color:#bec7d1; + padding:9px 0; + display:block +} + +.nav a:hover,.nav .current a { + color:var(--aquamarine) +} + +.nav .resume-link { + border:1px solid #ffffff3d; + border-radius:6px; + padding:9px 15px; + color:var(--paper) +} + +.resume-link span { + margin-left:12px +} + +.menu-toggle { + display:none +} + +.hero { + display:grid; + grid-template-columns:1.2fr 1fr; + gap:72px; + align-items:center; + padding-top:180px; + padding-bottom:88px +} + +.hero .eyebrow { + color:#b5ced0; + display:flex; + gap:12px; + align-items:center +} + +.status-dot { + width:6px; + height:6px; + background:var(--aquamarine); + border-radius:50% +} + +h1 { + font-size:clamp(52px,5.8vw,80px); + letter-spacing:-4px; + line-height:1.08; + font-weight:650; + margin:24px 0 +} + +.hero h1 span { + color:var(--aquamarine) +} + +.hero-role { + font-size:19px; + line-height:1.55; + max-width:380px +} + +.hero-description { + color:var(--muted); + font-size:15px; + line-height:1.8; + max-width:440px; + margin-top:14px +} + +.hero-actions { + display:flex; + gap:12px; + margin:30px 0 +} + +.button { + display:inline-flex; + gap:22px; + justify-content:space-between; + align-items:center; + padding:15px 20px; + border-radius:5px; + font-size:13px; + font-weight:600; + transition:transform .2s,background .2s +} + +.button:hover { + transform:translateY(-3px) +} + +.button-primary { + background:var(--aquamarine); + color:var(--navy) +} + +.button-primary:hover { + background:#a1f0e7 +} + +.button-outline { + border:1px solid #ffffff35 +} + +.button-outline:hover { + background:#ffffff0b +} + +.social { + display:flex; + gap:24px; + color:var(--muted); + font-size:12px +} + +.social a:hover { + color:var(--aquamarine) +} + +.social span { + margin-left:7px +} + +.hero-bottom { + display:flex; + justify-content:space-between; + padding:24px 0; + border-top:1px solid #ffffff15; + font-size:11px; + color:var(--muted); + letter-spacing:.5px +} + +.hero-bottom a span { + margin-left:20px; + color:var(--aquamarine) +} + +.security-visual { + border:1px solid #ffffff1a; + border-radius:12px; + position:relative; + background:linear-gradient(145deg,#ffffff03,#ffffff00); + overflow:hidden; + transform:rotate(-3deg) +} + +.visual-topline,.visual-bottomline { + display:flex; + justify-content:space-between; + gap:8px; + padding:20px; + font-family:monospace; + font-size:9px; + letter-spacing:1px; + color:#9daeb8 +} + +.visual-topline { + border-bottom:1px solid #ffffff0d +} + +.visual-dots { + color:#64838a; + letter-spacing:4px +} + +.visual-bottomline { + border-top:1px solid #ffffff0d; + font-size:9px; + letter-spacing:0 +} + +.security-orbit { + height:340px; + position:relative; + background-image:radial-gradient(#ffffff12 1px,transparent 1px); + background-size:20px 20px +} + +.orbit-ring { + position:absolute; + top:50%; + left:50%; + border:1px solid #80e8d530; + border-radius:50%; + transform:translate(-50%,-50%) +} + +.ring-one { + width:180px; + height:180px +} + +.ring-two { + width:280px; + height:280px; + border-style:dashed; + border-color:#80e8d519 +} + +.shield-mark { + position:absolute; + left:50%; + top:50%; + transform:translate(-50%,-50%); + width:100px; + height:110px; + padding:18px; + color:var(--aquamarine); + background:#80e8d508; + box-shadow:0 0 80px #80e8d508; + border:1px solid #80e8d522; + border-radius:24px +} + +.shield-mark svg { + width:100%; + height:100% +} + +.orbit-label { + position:absolute; + background:#1b2a35; + border:1px solid #ffffff1c; + padding:10px 13px; + border-radius:5px; + font-family:monospace; + font-size:10px; + color:#c5d8da } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; +.label-one { + top:50px; + left:20px } -html { scroll-behavior: smooth; } +.label-two { + top:143px; + right:20px; + color:var(--aquamarine) +} + +.label-three { + bottom:38px; + left:34px +} + +.orbit-point { + position:absolute; + width:7px; + height:7px; + border-radius:50%; + background:var(--aquamarine); + right:94px; + bottom:81px; + box-shadow:0 0 14px #80e8d5 +} + +.section-light { + background:var(--paper) +} + +section { + padding:100px 0; + scroll-margin-top:0 +} + +.section-label { + margin-bottom:24px; + color:#365f68 +} + +h2 { + font-size:clamp(32px,3.5vw,46px); + letter-spacing:-1.7px; + line-height:1.18; + font-weight:600 +} + +#about h2 span { + color:#507780 +} + +.about-grid { + display:grid; + grid-template-columns:1.6fr 1fr; + gap:100px +} + +.about-copy { + font-size:16px; + line-height:1.9; + margin-top:28px; + color:#57616b; + max-width:630px +} + +.about-details { + border-left:1px solid #cbdfe2; + padding-left:42px +} + +.about-details .eyebrow { + color:#4d727b; + margin-bottom:16px +} + +.about-details h3 { + font-size:20px; + letter-spacing:-.4px; + margin-bottom:8px +} + +.about-details>p:not(.eyebrow) { + font-size:14px; + color:#426d77; + margin-bottom:12px +} + +.about-details>a { + font-size:13px; + border-bottom:1px solid #9cbcc2; + padding-bottom:5px +} + +.about-details>a span { + margin-left:12px +} + +.expertise { + margin-top:30px +} + +.expertise ul { + display:flex; + flex-wrap:wrap; + gap:8px +} + +.expertise li { + font-size:11px; + border:1px solid #c3dce1; + border-radius:4px; + padding:8px 11px; + color:#365d68 +} + +#portfolio { + padding-top:0; + padding-bottom:110px +} + +.section-heading { + border-top:1px solid #cbdfe2; + padding-top:60px; + display:flex; + justify-content:space-between; + align-items:flex-end; + gap:24px; + margin-bottom:36px +} + +.section-heading>p { + font-size:13px; + line-height:1.7; + color:#4d727b +} + +.section-heading h2 span { + color:#246f7b +} + +.project-grid { + display:grid; + grid-template-columns:1fr 1fr; + gap:28px +} + +.project-link { + display:block; + border:1px solid #cfe1e5; + border-radius:10px; + overflow:hidden; + background:#f8fcfc; + transition:transform .2s,box-shadow .2s +} + +.project-link:hover { + transform:translateY(-5px); + box-shadow:0 14px 35px #1723330a +} + +.project-image { + height:280px; + position:relative; + overflow:hidden; + background:#d8edef; + padding:32px 28px 0 +} + +.portfolio-item:nth-child(2) .project-image { + background:#dcebed +} + +.project-image img { + width:100%; + height:100%; + object-fit:cover; + object-position:top; + border-radius:5px 5px 0 0; + box-shadow:0 8px 24px #1723331a; + transition:transform .3s +} + +.project-link:hover img { + transform:scale(1.035) +} + +.project-number { + position:absolute; + top:12px; + left:16px; + font-size:10px; + letter-spacing:1px; + color:#3e616c +} + +.project-arrow { + position:absolute; + right:14px; + top:14px; + background:var(--paper); + border-radius:50%; + width:32px; + height:32px; + display:grid; + place-items:center +} + +.project-info { + padding:26px +} + +.project-info h3 { + font-size:21px; + letter-spacing:-.6px +} + +.project-info p { + font-size:13px; + color:#47707a; + margin-top:8px; + line-height:1.6 +} + +.project-source { + display:block; + font-size:11px; + font-weight:600; + margin-top:24px +} + +.project-source span { + margin-left:10px +} + +#contact { + color:var(--paper); + background:var(--navy); + padding:96px 0 +} + +.contact-grid { + display:grid; + grid-template-columns:1.2fr 1fr; + gap:80px; + align-items:center +} + +#contact .section-label { + color:var(--muted) +} + +#contact h2 { + font-size:clamp(40px,4.5vw,60px) +} + +#contact h2 span { + color:var(--aquamarine) +} + +.contact-links>p { + color:var(--muted); + font-size:15px; + line-height:1.8; + margin-bottom:26px +} + +.email { + font-size:clamp(16px,1.9vw,23px); + letter-spacing:-.5px; + display:flex; + justify-content:space-between; + gap:10px; + border-bottom:1px solid #ffffff30; + padding-bottom:16px; + overflow-wrap:anywhere +} + +.email span { + color:var(--aquamarine) +} + +.contact-links ul { + display:flex; + gap:24px; + margin-top:26px; + font-size:12px; + color:#aebcc7 +} + +.contact-links li span { + margin-left:10px +} + +.contact-links a:hover { + color:var(--aquamarine) +} + +footer { + color:var(--muted); + background:var(--navy) +} + +.footer-inner { + display:flex; + align-items:center; + justify-content:space-between; + gap:20px; + border-top:1px solid #ffffff14; + padding:28px 0 +} + +.footer-inner p,.back-top { + font-size:11px +} + +.footer-inner p span { + margin:0 10px +} + +.footer-inner .brand { + color:var(--paper) +} + +.back-top span { + margin-left:14px; + color:var(--aquamarine) +} + +@media(min-width:1600px) { + header { + min-height:850px + } + .hero { + padding-top:210px; + padding-bottom:130px + } +} + +@media(max-width:1000px) { + .page-width { + width:calc(100% - 64px) + } + #nav-wrap { + padding:22px 32px + } + .hero { + gap:32px; + padding-top:150px + } + .hero h1 { + letter-spacing:-3px + } + .security-orbit { + height:300px + } + .ring-two { + width:240px; + height:240px + } + .label-one { + left:10px; + top:38px + } + .label-two { + right:10px + } + .label-three { + left:14px + } + .hero-actions { + flex-wrap:wrap + } + .about-grid { + gap:50px + } + .contact-grid { + gap:40px + } + .project-image { + height:240px + } +} + +@media(max-width:700px) { + .page-width { + width:calc(100% - 40px) + } + header { + min-height:auto + } + #nav-wrap { + padding:20px; + background:var(--navy) + } + .menu-toggle { + display:flex; + gap:20px; + align-items:center; + background:none; + color:var(--paper); + border:1px solid #ffffff35; + border-radius:5px; + padding:8px 12px; + font-size:12px + } + .menu-toggle span { + font-size:18px + } + .nav { + display:none; + position:absolute; + top:77px; + left:0; + right:0; + background:var(--navy); + padding:12px 20px 24px; + box-shadow:0 10px 20px #0003; + border-bottom:1px solid #ffffff15 + } + .nav.is-open { + display:flex; + align-items:stretch; + gap:2px; + flex-direction:column + } + .nav a { + padding:13px + } + .nav .resume-link { + margin-top:8px; + display:inline-flex; + align-items:center + } + .hero { + grid-template-columns:1fr; + gap:48px; + padding-top:135px; + padding-bottom:48px + } + .hero h1 { + font-size:clamp(48px,10vw,64px); + letter-spacing:-2.5px + } + .hero-role { + font-size:18px + } + .hero-description { + font-size:14px + } + .hero-actions { + margin:26px 0 + } + .button { + gap:14px; + padding:14px 17px + } + .security-visual { + max-width:440px; + width:100%; + justify-self:center; + transform:none + } + .security-orbit { + height:270px + } + .hero-bottom { + font-size:10px; + padding:20px 0 + } + .hero-bottom a span { + margin-left:8px + } + section { + padding:68px 0 + } + .about-grid,.contact-grid { + grid-template-columns:1fr; + gap:36px + } + .about-details { + padding:24px 0 0; + border-left:0; + border-top:1px solid #cbdfe2 + } + .about-copy { + font-size:15px + } + .section-heading { + padding-top:40px; + align-items:flex-start; + flex-direction:column; + gap:18px + } + .section-heading>p br { + display:none + } + .project-grid { + grid-template-columns:1fr; + gap:24px + } + .project-image { + height:270px + } + #portfolio { + padding-bottom:68px + } + #contact { + padding:68px 0 + } + .contact-links>p { + margin-bottom:22px + } + .email { + font-size:18px + } + .footer-inner { + flex-wrap:wrap + } + .footer-inner p { + order:3; + width:100%; + text-align:center; + font-size:10px + } + .footer-inner .brand { + font-size:28px + } + .back-top { + font-size:11px + } +} -@media (prefers-reduced-motion: reduce) { - html { scroll-behavior: auto; } +@media(prefers-reduced-motion:reduce) { + html { + scroll-behavior:auto + } + *,*::before,*::after { + transition:none!important; + animation:none!important + } + .button:hover,.project-link:hover,.project-link:hover img { + transform:none + } } diff --git a/src/siteInteractions.js b/src/siteInteractions.js index a46802f..25bc738 100644 --- a/src/siteInteractions.js +++ b/src/siteInteractions.js @@ -1,41 +1,38 @@ export function initializeSiteInteractions() { const header = document.querySelector('header'); const navigation = document.querySelector('#nav-wrap'); - const headline = document.querySelector('.responsive-headline'); const sections = [...document.querySelectorAll('header[id], section[id]')]; const links = [...document.querySelectorAll('#nav a[href^="#"]')]; - function resize() { - header.style.height = `${window.innerHeight}px`; - headline.style.fontSize = `${Math.max(40, Math.min(90, headline.parentElement.clientWidth / 10))}px`; - updateNavigation(); - } - function updateNavigation() { - const height = header.getBoundingClientRect().height; - const scroll = window.scrollY; - navigation.classList.toggle('opaque', scroll >= height * 0.2); - navigation.style.display = scroll > height * 0.2 && scroll < height && window.innerWidth > 768 ? 'none' : ''; + navigation.classList.toggle('opaque', window.scrollY > 24); const active = sections.filter(section => section.getBoundingClientRect().top <= window.innerHeight * 0.35).at(-1) || header; - links.forEach(link => link.parentElement.classList.toggle('current', link.hash === `#${active.id}`)); + links.forEach(link => { + const current = link.hash === `#${active.id}`; + link.parentElement.classList.toggle('current', current); + if (current) link.setAttribute('aria-current', 'location'); + else link.removeAttribute('aria-current'); + }); } function scrollToSection(event) { const link = event.target.closest('a.smoothscroll'); - if (!link) return; + if (!link || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return; const target = document.getElementById(link.hash.slice(1)); if (!target) return; event.preventDefault(); target.scrollIntoView({ behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'instant' : 'smooth' }); - window.location.hash = link.hash; + window.history.pushState(null, '', link.hash); + if (!target.hasAttribute('tabindex')) target.setAttribute('tabindex', '-1'); + target.focus({ preventScroll: true }); } - resize(); - window.addEventListener('resize', resize); + updateNavigation(); + window.addEventListener('resize', updateNavigation); window.addEventListener('scroll', updateNavigation, { passive: true }); document.addEventListener('click', scrollToSection); return () => { - window.removeEventListener('resize', resize); + window.removeEventListener('resize', updateNavigation); window.removeEventListener('scroll', updateNavigation); document.removeEventListener('click', scrollToSection); }; diff --git a/src/yourdata.js b/src/yourdata.js index 540ba84..0418981 100644 --- a/src/yourdata.js +++ b/src/yourdata.js @@ -32,6 +32,9 @@ let data = { }, { "skillname":"Pentesting" + }, + { + "skillname":"AI" } ], "portfolio":[