<!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();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.