Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
<meta charset="utf-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#000000">
<meta name="theme-color" content="#101b2a">
<link rel="manifest" href="/manifest.json">
<title>Mónica Pastor Resume</title>
<link rel="stylesheet" href="/css/default.css">
<link rel="stylesheet" href="/css/layout.css">
<link rel="stylesheet" href="/css/media-queries.css">
<title>Mónica Pastor — Security Engineer & Developer</title>
<meta name="description" content="Mónica Pastor, Product Security Engineer and Developer in Madrid. Explore projects in application security, secure development, and data analysis.">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ class App extends Component {
return (
<div className="App">
<Header data={data}/>
<main>
<About data={data}/>
<Portfolio data={data}/>
<ContactUs data={data}/>
</main>
<Footer data={data}/>
</div>
);
Expand Down
39 changes: 36 additions & 3 deletions src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
51 changes: 12 additions & 39 deletions src/components/About.jsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,12 @@
import React, { Component } from 'react';
export default class About extends Component {
render() {
let data = this.props.data;
return (
<section id="about">
<div className="row">

<div className="nine columns main-col">

<h2>About Me</h2>
<p>
{
data.aboutme
}
</p>

<div className="row">

<div className="columns contact-details">

<h2>Contact Details</h2>
<p className="address">
<span>{data.name}</span>
<br></br>
<span>
{data.address}
</span>
<br></br>
<a href={`${data.url}`} target="_blank" rel="noopener noreferrer">{data.url}</a>
</p>
</div>
</div>
</div>
</div>
</section>
);
}
}
import React from 'react';

export default function About({ data }) {
return (
<section id="about" className="section-light">
<div className="page-width about-grid">
<div><p className="eyebrow section-label">01 / ABOUT</p><h2>A developer’s perspective.<br /><span>A security mindset.</span></h2><p className="about-copy">{data.aboutme}</p></div>
<aside className="about-details"><p className="eyebrow">THE DETAILS</p><h3>{data.name}</h3><p>{data.address}</p><a href={data.url} target="_blank" rel="noopener noreferrer">mpast.github.io <span aria-hidden="true">↗</span></a><div className="expertise"><p className="eyebrow">AREAS OF FOCUS</p><ul>{data.skills.map(skill => <li key={skill.skillname}>{skill.skillname}</li>)}</ul></div></aside>
</div>
</section>
);
}
34 changes: 9 additions & 25 deletions src/components/ContactUs.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import React, { Component } from 'react';
export default class ContactUs extends Component {
render() {
let data = this.props.data;
return (
<section id="contact">
<div className="row section-head">
<div className="ten columns">
<div className="lead">
<h4>
Let’s create your next<br></br>
experience together</h4>
<a href={`mailto:${data.contactEmail}`} className='email'>{data.contactEmail}</a>
<ul>
{data.socialLinks.map((link,index)=>(
<li key={index}><a target='_blank' rel="noopener noreferrer" href={link.url}>{link.name}</a></li>
))}
</ul>
</div>
</div>
</div>
</section>
);
}
}
import React from 'react';

export default function ContactUs({ data }) {
return (
<section id="contact">
<div className="page-width contact-grid"><div><p className="eyebrow section-label">03 / GET IN TOUCH</p><h2>Let’s create your<br />next experience<br /><span>together.</span></h2></div><div className="contact-links"><p>Have something in mind?<br />Let’s start a conversation.</p><a href={`mailto:${data.contactEmail}`} className="email">{data.contactEmail} <span aria-hidden="true">↗</span></a><ul>{data.socialLinks.map(link => <li key={link.name}><a target="_blank" rel="noopener noreferrer" href={link.url}>{link.name === 'linkedin' ? 'LinkedIn' : 'GitHub'} <span aria-hidden="true">↗</span></a></li>)}</ul></div></div>
</section>
);
}
34 changes: 5 additions & 29 deletions src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,5 @@
import React, { Component } from 'react';
export default class Footer extends Component {
render() {
let data = this.props.data;
return (
<footer>
<div className="row">
<div className="twelve columns">
<ul className="social-links">
{
data.socialLinks && data.socialLinks.map((item)=>{
return(
<li key={`${item.url}`}>
<a href={item.url}>
<i className={item.className} />
</a>
</li>
)
})
}
</ul>

</div>
<div id="go-top"><a className="smoothscroll" title="Back to Top" href="#home"><i className="icon-up-open" /></a></div>
</div>
</footer>
);
}
}
import React from 'react';

export default function Footer({ data }) {
return <footer><div className="page-width footer-inner"><a className="brand smoothscroll" href="#home" aria-label={`${data.name}, home`}>mp<span>.</span></a><p>{data.name} <span aria-hidden="true">·</span> {data.address}</p><a className="smoothscroll back-top" href="#home">Back to top <span aria-hidden="true">↑</span></a></div></footer>;
}
88 changes: 39 additions & 49 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,40 @@
import React, { Component } from 'react';
export default class Header extends Component {
render() {
let data = this.props.data;
return (
<React.Fragment>

<header id="home">
<nav id="nav-wrap">
<a className="mobile-btn" href="#nav-wrap" title="Show navigation">Show navigation</a>
<a className="mobile-btn" href="#home" title="Hide navigation">Hide navigation</a>
<ul id="nav" className="nav">
<li className="current"><a className="smoothscroll" href="#home">Home</a></li>
<li><a className="smoothscroll" href="#about">About</a></li>
<li><a href="CV.pdf" target="_blank" rel="noopener noreferrer">Resume</a></li>
<li><a className="smoothscroll" href="#portfolio">Work</a></li>
<li><a className="smoothscroll" href="#contact">Contact</a></li>
</ul>
</nav>
import React, { useState } from 'react';

<div className="row banner">
<div className="banner-text">
<h1 className="responsive-headline">Hi 👋 I am {data.name}.</h1>
<h3 style={{color:'#fff', fontFamily:'sans-serif '}}>I am a {data.role}.{data.roleDescription}
</h3>
<hr/>
<ul className="social">
{
data.socialLinks && data.socialLinks.map(item =>{
return(
<li key={item.name}>
<a href={item.url} target="_blank" rel="noopener noreferrer"><i className={item.className}></i></a>
</li>
)
}
)
}
</ul>
</div>
</div>

<p className="scrolldown">
<a className="smoothscroll" href="#about"><i className="icon-down-circle"></i></a>
</p>

</header>
</React.Fragment>
);
}
}
export default function Header({ data }) {
const [menuOpen, setMenuOpen] = useState(false);
return (
<header id="home">
<a className="skip-link" href="#about">Skip to content</a>
<nav id="nav-wrap" aria-label="Main navigation">
<a className="brand smoothscroll" href="#home" aria-label={`${data.name}, home`}>mp<span>.</span></a>
<button className="menu-toggle" aria-expanded={menuOpen} aria-controls="nav" onClick={() => setMenuOpen(!menuOpen)}>{menuOpen ? 'Close' : 'Menu'} <span aria-hidden="true">{menuOpen ? '×' : '+'}</span></button>
<ul id="nav" className={menuOpen ? 'nav is-open' : 'nav'} onClick={() => setMenuOpen(false)}>
<li className="current"><a className="smoothscroll" href="#home">Home</a></li>
<li><a className="smoothscroll" href="#about">About</a></li>
<li><a className="smoothscroll" href="#portfolio">Work</a></li>
<li><a className="smoothscroll" href="#contact">Contact</a></li>
<li><a className="resume-link" href="CV.pdf" target="_blank" rel="noopener noreferrer">Resume <span aria-hidden="true">↗</span></a></li>
</ul>
</nav>
<div className="hero page-width">
<div className="hero-copy">
<p className="eyebrow"><span className="status-dot" /> {data.address}</p>
<h1 className="responsive-headline">Hi, I’m<br /><span>{data.name}.</span></h1>
<p className="hero-role">{data.role}</p>
<p className="hero-description">{data.roleDescription}.</p>
<div className="hero-actions">
<a className="button button-primary smoothscroll" href="#portfolio">Explore my work <span aria-hidden="true">↗</span></a>
<a className="button button-outline smoothscroll" href="#contact">Let’s connect <span aria-hidden="true">→</span></a>
</div>
<ul className="social">{data.socialLinks.map(item => <li key={item.name}><a href={item.url} target="_blank" rel="noopener noreferrer">{item.name === 'linkedin' ? 'LinkedIn' : 'GitHub'} <span aria-hidden="true">↗</span></a></li>)}</ul>
</div>
<div className="security-visual" aria-hidden="true">
<div className="visual-topline"><span>SECURITY × DEVELOPMENT</span><span className="visual-dots">● ● ●</span></div>
<div className="security-orbit"><div className="orbit-ring ring-two" /><div className="orbit-ring ring-one" /><div className="shield-mark"><svg viewBox="0 0 80 90" fill="none"><path d="M40 5 70 17v26c0 20-15 34-30 42C25 77 10 63 10 43V17L40 5Z" stroke="currentColor" strokeWidth="2" /><path d="m26 44 10 10 20-23" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" /></svg></div><span className="orbit-label label-one">Secure development</span><span className="orbit-label label-two">DevSecOps</span><span className="orbit-label label-three">AI</span><span className="orbit-point" /></div>
<div className="visual-bottomline"><span>Built with a security mindset.</span><span>01 / 03</span></div>
</div>
</div>
<div className="hero-bottom page-width"><span>Security. Code. Curiosity.</span><a className="smoothscroll" href="#about">Get to know me <span aria-hidden="true">↓</span></a></div>
</header>
);
}
44 changes: 11 additions & 33 deletions src/components/Portfolio.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,12 @@
import React, { Component } from 'react';
export default class Porfolio extends Component {
render() {
let data = this.props.data;
return (
<section id="portfolio">
<div className="row">
<h1>Check out some of my work</h1>
<div id="portfolio-wrapper" className="bgrid-halves s-bgrid-halves cf">
{
data.portfolio && data.portfolio.map((item)=>{
return(
<div key={`${item.name}`} className="columns portfolio-item">
<div className="item-wrap">
<img src={`${item.imgurl}`} className="item-img" alt="work"/>
<div className="overlay">
<div className="portfolio-item-meta">
<h5>{item.description}</h5>
</div>
</div>
</div>
<a href={`${item.url}`} target="_blank" rel="noopener noreferrer">
<h4>{item.name}</h4>
</a>
</div>
)
})
}
</div>
import React from 'react';

export default function Portfolio({ data }) {
return (
<section id="portfolio" className="section-light">
<div className="page-width">
<div className="section-heading"><div><p className="eyebrow section-label">02 / SELECTED WORK</p><h2>Ideas put into practice<span>.</span></h2></div><p>Security, development &amp; data exploration.</p></div>
<div id="portfolio-wrapper" className="project-grid">{data.portfolio.map((item, index) => <article key={item.name} className="portfolio-item"><a className="project-link" href={item.url} target="_blank" rel="noopener noreferrer"><div className="project-image"><img src={item.imgurl} alt={`${item.name} application preview`} loading="lazy" /><span className="project-number">0{index + 1}</span><span className="project-arrow" aria-hidden="true">↗</span></div><div className="project-info"><h3>{item.name}</h3><p>{item.description}</p><span className="project-source">View project on GitHub <span aria-hidden="true">↗</span></span></div></a></article>)}</div>
</div>
</section>
);
}
}
</section>
);
}
Loading
Loading