<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Front-End Challenges Club - 001</title>
<link rel="preload" as="font" type="font/woff2" href="/fonts/Inter-Black.woff2" />
<link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css" />
<link rel="stylesheet" href="/css/global.css" />
</head>
<body>
<main class="[ signup ] [ flow ]">
<h1 class="signup__heading">Sign up for the latest updates</h1>
<form id="signupForm" action="/" class="[ signup__form] [ flow ]" method="POST">
<label for="email">Email address</label>
<div class="inline-field-control">
<input type="email" name="email" id="email" autocapitalize="none" autocorrect="off" required pattern="[^@]+@[^\.]+\..+"/>
<button type="submit" class="button">
<span class="visually-hidden">Submit email</span>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="currentColor" d="M11.293 5.707L16.586 11H5a1 1 0 000 2h11.586l-5.293 5.293a.999.999 0 101.414 1.414l7-7a1.006 1.006 0 000-1.414l-7-7a.999.999 0 10-1.414 1.414z" />
</svg>
</button>
</div>
</form>
<div aria-atomic="true" role="alert" class="signup__alert"></div>
</main>
<script type="module" src="/js/main.js" async defer></script>
</body>
</html>
/**
* FONT FACE
*/
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url('/fonts/Inter-Black.woff2') format('woff2');
}
/**
* VARIABLES
*/
:root {
--color-primary: #4c2982;
--color-secondary: #f9d170;
--color-bg: #f9f7f3;
--color-text: #252525;
--color-light: #f3f3f3;
--color-success: #067973;
--color-success-bg: #f5fffe;
--color-error: #b71540;
--color-error-bg: #fdeff3;
--color-shadow: rgba(23, 11, 41, 0.12);
--font-base-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
'Helvetica Neue', sans-serif;
--font-heading-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu,
Cantarell, 'Helvetica Neue', sans-serif;
--metric-rhythm: 2rem;
--metric-gutter: 2rem;
--metric-box-padding: 1rem 1rem;
--metric-interaction-padding: 0.6rem 0.6rem;
}
/**
* GLOBAL STYLES
*/
body {
font-family: var(--font-base-family, sans-serif);
display: grid;
place-items: center;
background: var(--color-light);
color: var(--color-text);
padding: var(--metric-gutter, 2rem);
}
h1 {
font-family: var(--font-heading-family, sans-serif);
font-size: 2rem;
font-weight: 900;
color: var(--color-primary);
line-height: 1.1;
}
label {
color: var(--color-primary);
text-transform: uppercase;
font-weight: 700;
}
input[type],
button {
border: none;
margin: 0;
font: inherit;
line-height: 1;
padding: 0.8rem;
padding: var(--metric-interaction-padding);
outline-offset: -1px;
}
@media screen and (-ms-high-contrast: active) {
input[type],
button {
border: 1px solid;
}
}
/**
* GLOBAL FOCUS
*/
:focus {
outline: 1px solid var(--color-primary);
}
/**
* FLOW UTILITY
*/
.flow {
--flow-space: var(--metric-rhythm);
}
.flow > * + * {
margin-top: 1em;
margin-top: var(--flow-space);
}
/**
* VISUALLY HIDDEN UTILITY
*/
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}
/**
* SIGNUP COMPONENT
*/
.signup {
max-width: 20rem;
}
.signup__form + .signup__alert {
--flow-space: 1rem;
}
/**
* INLINE FIELD CONTROL COMPONENT
*/
.inline-field-control {
--flow-space: 0.5rem;
display: flex;
box-shadow: 0 2px 10px var(--color-shadow);
}
.inline-field-control input {
flex: auto;
}
/**
* BUTTON COMPONENT
*/
.button {
background: var(--color-secondary);
color: var(--color-primary);
font-size: 1.6rem;
min-width: 3.5rem;
cursor: pointer;
}
.button:hover {
filter: brightness(1.05);
}
.button svg {
transform: translateY(1px); /* Optical adjustment */
}
/**
* ALERT COMPONENT
*/
.alert {
--alert-text: var(--color-text);
--alert-bg: var(--color-bg);
display: flex;
align-items: flex-start;
background: var(--alert-bg);
color: var(--alert-text);
border: 1px solid;
padding: var(--metric-box-padding);
margin-top: 1rem;
animation: slide-up 250ms ease;
}
.alert[data-state='error'] {
--alert-text: var(--color-error);
--alert-bg: var(--color-error-bg);
}
.alert[data-state='success'] {
--alert-text: var(--color-success);
--alert-bg: var(--color-success-bg);
}
.alert__icon {
font-size: 1.6em;
flex-shrink: 0;
}
.alert__content {
padding-left: 0.8rem;
}
.alert__content b {
display: block;
}
/**
* ANIMATIONS
*/
@keyframes slide-up {
0% {
opacity: 0;
transform: translateY(0.4rem);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/**
* Generate an alert component based on the passed state key
* @param {String} state must be 'error' or 'success'
* @return {String} A HTML string of the component output
*/
const renderAlert = (state = 'error') => {
const iconPaths = {
error:
'M11.148 4.374a.973.973 0 01.334-.332c.236-.143.506-.178.756-.116s.474.216.614.448l8.466 14.133a.994.994 0 01-.155 1.192.99.99 0 01-.693.301H3.533a.997.997 0 01-.855-1.486zM9.432 3.346l-8.47 14.14c-.422.731-.506 1.55-.308 2.29s.68 1.408 1.398 1.822c.464.268.976.4 1.475.402H20.47a3 3 0 002.572-4.507L14.568 3.346a2.995 2.995 0 00-4.123-1.014c-.429.26-.775.615-1.012 1.014zM11 9v4a1 1 0 002 0V9a1 1 0 00-2 0zm2 8a1 1 0 10-2 0 1 1 0 002 0z',
success:
'M19.293 5.293L9 15.586l-4.293-4.293a.999.999 0 10-1.414 1.414l5 5a.999.999 0 001.414 0l11-11a.999.999 0 10-1.414-1.414z'
};
const messages = {
error: '<b>Please use a valid email.</b> Like: person@inbox.com.',
success: '<b>Yay! Thank you!</b> We’ve sent a confirmation link to your inbox.'
};
return `
<figure class="alert" data-state="${state}">
<svg class="alert__icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="currentColor" d="${iconPaths[state]}"/>
</svg>
<p class="alert__content">${messages[state]}</p>
</figure>
`;
};
/**
* Main app function. Grabs signup elements and validates email
* with regex and blocks submission and renders alert if it fails.
* If successful, it’ll allow the form to progress.
*/
const init = () => {
const emailElement = document.querySelector('#email');
const formElement = document.querySelector('#signupForm');
const alertElement = document.querySelector('[role="alert"]');
const validationRegex = new RegExp(emailElement.getAttribute('pattern') || '[^@]+@[^.]+..+', 'i');
emailElement.removeAttribute('required');
emailElement.removeAttribute('pattern');
formElement.setAttribute('novalidate', '');
formElement.addEventListener('submit', event => {
event.preventDefault();
if(!validationRegex.test(emailElement.value.trim())) {
alertElement.innerHTML = renderAlert('error');
emailElement.setAttribute('aria-invalid', 'true');
return;
}
// POST YOUR FORM WITH AJAX OR WHATNOT THEN RUN THIS
formElement.parentElement.removeChild(formElement);
alertElement.innerHTML = renderAlert('success');
});
};
init();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.