Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                fieldset.category-picker
  label.cat-all 
    input(checked type="checkbox" name="category" value="all")
    span All Categories
  label.cat.cat-awards
    input(checked type="checkbox" name="category" value="awards")
    span Awards &amp; Recognitions
  label.cat.cat-update
    input(checked type="checkbox" name="category" value="update")
    span Business Update
  label.cat.cat-milestone
    input(checked type="checkbox" name="category" value="company-milestone")
    span Company Milestone
  label.cat.cat-industry
    input(checked type="checkbox" name="category" value="industry")
    span Industry
  label.cat.cat-products
    input(checked type="checkbox" name="category" value="products")
    span Products &amp; Services
  label.cat.cat-satellites
    input(checked type="checkbox" name="category" value="satellites")
    span Satellites
.timeline
 


//- Remember to update the href attribute to this pen!
a#twitter-link.social-link(target="_blank" href="https://twitter.com/intent/tweet?text=Check%20out%20this%20pen%20by%20@FrankNoirot&via=CodePen%20&hashtags=codepen%2cfrontend&url=https://codepen.io/franknoirot/")
  .social-badge
    span Share
    svg#twitter-icon.social-icon(xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24")
      title Twitter icon
      path(d="M23.954 4.569c-.885.389-1.83.654-2.825.775 1.014-.611 1.794-1.574 2.163-2.723-.951.555-2.005.959-3.127 1.184-.896-.959-2.173-1.559-3.591-1.559-2.717 0-4.92 2.203-4.92 4.917 0 .39.045.765.127 1.124C7.691 8.094 4.066 6.13 1.64 3.161c-.427.722-.666 1.561-.666 2.475 0 1.71.87 3.213 2.188 4.096-.807-.026-1.566-.248-2.228-.616v.061c0 2.385 1.693 4.374 3.946 4.827-.413.111-.849.171-1.296.171-.314 0-.615-.03-.916-.086.631 1.953 2.445 3.377 4.604 3.417-1.68 1.319-3.809 2.105-6.102 2.105-.39 0-.779-.023-1.17-.067 2.189 1.394 4.768 2.209 7.557 2.209 9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63.961-.689 1.8-1.56 2.46-2.548l-.047-.02z")

              
            
!

CSS

              
                :root {
  font-family: 'Roboto', sans-serif;
}

h1,h2,h3,h4,h5,h6 {
  font-family: "Roboto Slab", serif;
}

* {
  box-sizing: border-box;
}

body {
  height: 100vh;
  width: 98vw;
  margin: 0;
  padding: 5vmax;
  background: #125;
/*   background: url('https://museum.iridiumbeta.com/wp-content/uploads/2019/05/musuem-background.jpg'); */
  background-position: 50% 50%;
}

/* CATEGORY PICKER */
.category-picker {
  color: white;
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 2em;
  left: 3em;
  z-index: 99;
  border: 0;
  width: max-content;
  padding: 0;
  border-left: solid;
  padding-left: 8px;
/*   background: linear-gradient(to right, rgba(255,255,255,0.3), transparent); */
}

.category-picker > label {
  font-size: calc(.6em + .5vh);
  margin: .5em 0;
  user-select: none;
  position: relative;
}

.cat-all { --cat-color: 358, 80%, 50%; }
.cat-awards { --cat-color: 45, 100%, 55%; }
.cat-update { --cat-color: 80, 100%, 40%; }
.cat-milestone { --cat-color: 160, 90%, 45%; }
.cat-industry { --cat-color: 210, 100%, 64%; }
.cat-products { --cat-color: 260, 90%, 60%; }
.cat-satellites { --cat-color: 305, 85%, 50%;}


/* input sucks to style, so just get that hidden, but keep it in dom so you can accept change and input events from the label parent */
.category-picker input {
  position: absolute;
  width: 0;
  height: 0;
  left: -1000px;
}

.category-picker > label span::before,
.category-picker > label span::after {
  content: '';
  opacity: 0.6;
  width: .8em;
  height: 1.5em;
  position: absolute;
  right: calc(100% + 8px + 3px);
  top: 50%;
  transform: translateY(-50%);
  background: hsla(var(--cat-color), 1);
  transition: width .13s ease-in-out;
}

.category-picker > label:focus-within span::before {
  border-left: solid white 3px;
}

.category-picker > label span::after {
  --after-opac: .5;
  z-index: -1;
  right: initial;
  left: -8px;
  width: 0;
}

label.cat span::after {
  background: hsla(var(--cat-color), var(--after-opac));
}

.category-picker > label:hover span::before {
  width: 1.5em;
}

.category-picker input:checked ~ span::before {
  width: 1.5em;
}

.category-picker > label:hover input:checked ~ span::before {
  width: calc(1.5em - .4em);
}

.category-picker input:checked ~ span::after {
  width: calc(100% + 16px);
}


/* TIMELINE */
.timeline {
  --cat-opac: 1;
  --stem-height: 100px;
  color: white;
  border: 0;
  top: 75%;
  left: 0;
  width: 100%;
  height: 1px;
  padding: 0;
  border-bottom: solid;
  position: absolute;
}

/* Event Categories */
.awards { --cat-color: hsla(45, 100%, 55%, var(--cat-opac)); }
.update { --cat-color: hsla(80, 100%, 40%, var(--cat-opac)); }
.company-milestone { --cat-color: hsla(160, 90%, 45%, var(--cat-opac)); }
.industry { --cat-color: hsla(210, 100%, 64%, var(--cat-opac)); }
.products { --cat-color: hsla(260, 90%, 60%, var(--cat-opac)); }
.satellites { --cat-color: hsla(305, 85%, 50%, var(--cat-opac)); }


/* BOX, LINE, and DOT */
.vis-item {
  --active-trans: 4px;
  position: absolute;
}

/* EVENT BOX */
.vis-box {
  bottom: calc(var(--stem-height) * .7);
  font-size: 13px;
  border-radius: 5px;
  border-left: solid 5px;
  overflow: hidden;
  border-color: transparent;
  transform: translate(-9px, -50px) scaleY(0) scaleX(0);
  transform-origin: 0 100%;
  opacity: 0;
  transition: all .15s .2s ease-in-out;
}
.vis-box.left {
  border-left: none;
  border-right: solid var(--cat-color) 5px;
  transform: translate(calc(11px - 100%), -50px) scaleY(0) scaleX(0);
  transform-origin: 100% 100%;
}
.vis-box.active {
  transform: translate(-9px, -50px) scaleY(1) scaleX(1);
  border-color: var(--cat-color);
  opacity: 1;
  z-index: 5;
}
.vis-box.active.left {
  transform: translate(calc(11px - 100%), -50px) scaleY(1) scaleX(1);
}
.vis-box.closed {
  transform: translate(-9px, -50px) scaleY(0) scaleX(0);
}
.vis-box.left.closed {
  transform: translate(calc(11px - 100%), -50px) scaleY(0) scaleX(0);
}

.event-box {
  width: 400px;
  height: 120px;
  transition: all .15s .13s ease-in-out;
  background: var(--cat-color);
}

.event-image, .event-info {
  position: absolute;
  display: inline-block;
}

.event-image img {
  width: 100%;
  height: 100%;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 13% 100%, 0 95%);
}
.event-box.left .event-image img {
  clip-path: unset;
}

.event-info {
  top: 0;
  right: 0;
  width: 100%;
  padding: 5px 10px;
  background: #fafafa;
  color: #333;
}
.event-box.left .event-info {
  clip-path: polygon(0 0, 100% 0, 100% 95%, 95% 100%, 0 100%)
}

.event-info * {
  margin: 0;
}

.event-info .event-title a {
  text-decoration: none;
  color: inherit;
  font-size: 120%;
}
.event-info .event-date {
  color: gray;
  margin: 5px 0;
}

/* LINE */
.vis-line {
  background: var(--cat-color);
  width: 2px;
  height: 70px;
  transition: height .12s ease-in-out;
}
.vis-line.active {
  height: 120px;
}
.vis-line.active-1 {
  height: 80px;
}
.vis-line.active-2 {
  height: 75px;
}
.vis-line.closed {
  height: 0px;
}

/* BADGE / FLAG */
.vis-line::after {
  --h: 30px;
  content: '';
  position: absolute;
  width: 20px;
  height: var(--h);
  bottom: calc(100% - var(--h) / 6);
  left: 50%;
  clip-path: polygon(0 0, 100% 0, 100% 25px, 50% 100%, 0 25px);
  z-index: 1;
  background: var(--cat-color);
  transform: translate(-50%, -4px) scale(1, 1);
  transform-origin: 50% 100%;
  transition: transform .12s ease-in-out, opacity .12s ease-in-out;
}
.vis-line.active::after {
  transform: translate(-50%, -4px);
  z-index: -1;
}
.vis-line.closed::after {
  transform: translate(-50%, -4px) scale(0, 0);
}

/* DOT */
.vis-dot {
  background: var(--cat-color);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  transform: scale(1,1);
  transform-origin: 50% 50%;
  transition: transform .12s ease-in-out;
}
.vis-dot.closed {
  transform: scale(0,0);
}






/* Share badge styles */
.social-badge {
  --twitter-primary:#55acee;
  --twitter-dark: #292f33;
  --twitter-light: #e1e8ed;
  position: fixed;
  padding: .5em 1em;
  border-radius: 1em;
  top: 10px;
  right: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 5em;
  background: var(--twitter-dark);
  color: var(--twitter-light);
  font-weight: 500;
  box-shadow: 0 4px 1px var(--twitter-light);
  transition: all .2s ease-in-out;
}

.social-badge * {
  box-sizing: content-box;
}

a .social-badge:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 3px var(--twitter-light);
}

.social-badge .social-icon {
  display: inline-block;
  height: 1.5em;
}

.social-icon path {
  fill: var(--twitter-light);
}

.social-badge span {
  text-transform: uppercase;
  vertical-align: middle;
}


/* Media Queries */
@media only screen and (max-width: 500px){
  .social-badge {
    width: inherit;
  }
  .social-badge span {
    display: none;
  }
}
              
            
!

JS

              
                // Debug only: Create an arbitrary number of randomly-categorized events

const categories = ['awards', 'update', 'company-milestone', 'industry', 'products', 'satellites'];
const timeline = document.querySelector('.timeline');
let windowWidth = document.body.getBoundingClientRect().width;
const cardWidth = 400, cardHeight = 120;
const numEvents = windowWidth / 23;

for (let i=0; i < numEvents; i++) {
  // increment id, randomize category and position
  let evtID = 'event-'+i;
  let evtCat = categories[Math.floor(Math.random()*(categories.length))];
  let boxLeft = Math.random() * 90 + 5;
  let openDir = (boxLeft/100 * windowWidth + cardWidth > windowWidth) ? 'left' : 'right';
  function elt(type, classes, styles) { // helper function to quickly create elements with a set of classes and inline styles
    let node = document.createElement(type);
    node.classList.add(...classes);
    if (styles) {
      for (let s of styles) {
        node.style[s[0]]  = s[1];
      }
    }
    
    return node;
  }
  
  // Event Card
  let visBox = elt('div',
                  ['vis-item', 'vis-box', evtCat, evtID, 'vis-selected', 'vis-readonly', openDir],
                  [['left', `${boxLeft}%`]]);
  let itemContent = elt('div', ['vis-item-content']);
  let evtBox = elt('div',
                  ['event', 'event-box', 'event-normal', evtCat, 'event-left', `event-type-${categories.indexOf(evtCat)}`, openDir]);
  evtBox.id = evtID;
  let evtInfo = elt('div',
                   ['event-info'],
                   [['width', '280px'], ['height', '120px']]);
  let infoWrap = elt('div', ['event-info-wrapper']);
  let title = elt('div', ['event-title'])
  let titleP = document.createElement('p');
  let titleA = document.createElement('a');
  titleA.innerText = faker.lorem.words(7);
  titleA.href = '#'
  titleA.tabindex = 0
  title.appendChild(titleP);
  titleP.appendChild(titleA);
  let evtDate = elt('div', ['event-date']);
  let dateP = document.createElement('p');
  evtDate.appendChild(dateP);
  dateP.innerText = faker.date.past().toDateString();
  let evtContent = elt('div', ['event-content']);
  let contentP = document.createElement('p');
  contentP.innerText = faker.lorem.words(10);
  evtContent.appendChild(contentP);
  let evtImg = elt('div', ['event-image']);
  let img = elt('img', [], [['width', '120px'], ['height', '120px']]);
  img.src = 'https://picsum.photos/200';
  evtImg.appendChild(img);
  [title, evtDate, evtContent].forEach(el => infoWrap.appendChild(el));
  evtInfo.appendChild(infoWrap);
  [evtInfo, evtImg].forEach(el => evtBox.appendChild(el));
  itemContent.appendChild(evtBox);
  visBox.appendChild(itemContent);
  timeline.appendChild(visBox);
  
  // Event Line
  let visLine = elt('div',
                    ['vis-item', 'vis-line', evtCat, evtID, 'vis-readonly'],
                    [['left', `${boxLeft}%`, openDir], ['bottom', '-6px']])
  timeline.appendChild(visLine);
  
  // Event Dot
  let visDot = elt('div',
                   ['vis-item', 'vis-dot', evtCat, evtID, 'vis-readonly'],
                  [['left', `calc(${boxLeft}% - 4px)`, openDir], ['bottom', '-6px']])
  timeline.appendChild(visDot);
}
// END DEBUG



let eventBoxes = [].slice.call(document.querySelectorAll('.event-box'));
let eventIds = [];
eventBoxes.forEach((box, i) => {
  eventIds[i] = box.id;
});
let eventLines = [].slice.call(document.querySelectorAll('.vis-line'));
let currActive = undefined; // string of event id

document.addEventListener('mousemove', e => {
  if (timeline.getBoundingClientRect().top - 2 * cardHeight - 15 > e.pageY
      || timeline.getBoundingClientRect().bottom + cardHeight < e.pageY) return;
  
  let filteredLines = eventLines.filter(el => !el.classList.contains('closed'));
  let mouseDiffs = filteredLines.map((line, i) => {
    let {width, left} = line.getBoundingClientRect();
    
    // returns array of objects, each with distance from mouse and original index (bc we're about to sort this array)
    return {  
              dist: Math.abs(e.pageX - (left + width/2)),
              id: i
           };
  });
  
  // sort smallest to largest
  let sorted = mouseDiffs.sort((a,b) => a.dist - b.dist).slice(0,5);
  
  // transform to an array event-## string from the classList of each element
  let nearest = sorted.map(diff => filteredLines[diff.id].classList.value.match(/event-\d+/)[0]
);
  
  //Check if user is mousing over previously activated card
  let onCard = false;
  if (currActive) {
    let currCard = document.querySelector('.vis-box.'+currActive[0]);
    if (currCard) {
      let rect = currCard.getBoundingClientRect();

      // '15' is arbitrary padding added to the bottom of the card's bounding box.
      if (rect.left < e.pageX && rect.top < e.pageY &&
          rect.right > e.pageX && rect.bottom + 15 > e.pageY) onCard = true;      
    }
  }
  
  // add or remove active classes from all elements with a provided "evt" CSS class
  function updateDOM(evt, index, method) {
    let dom = [].slice.call(document.querySelectorAll('.'+ evt));
    dom.push(document.getElementById(evt));
    let state = (index > 0) ? "-"+Math.ceil(index/2) : '';
    
    if (method === "add") {
      dom.forEach(el => el.classList.add(`active${state}`));
    } if (method === "remove") {
      dom.forEach(el => el.classList.remove(`active${state}`));  
    }
  }
  
  
  if (!currActive) { //check for first time running
    nearest.forEach((event, i) => updateDOM(event, i, 'add'));
    currActive = nearest;
  } else if (!onCard && currActive[0] !== nearest[0]) {
    // remove active classe from current list of elements
    currActive.forEach((event, i) => updateDOM(event, i, 'remove')); 
    //add active class to new list of elements
    nearest.forEach((event, i) => updateDOM(event, i, 'add'));
    
    // set new current
    currActive = nearest;
  }
});




// CATEGORY SELECTION LOGIC

let checkboxes = [].slice.call(document.querySelectorAll('label.cat input'))
                         
// Handle clicks on category toggles (except 'all-categories')
let catOptions = [].slice.call(document.querySelectorAll('.cat')).map(el => el.children[0]);
catOptions.forEach(el => {
  el.addEventListener('click', e => {
    let allCats = document.querySelector('.cat-all');

    // FILTER array of checkboxes to remove the one just clicked
    let filteredOpts = catOptions.filter(opt => opt !== el);    
    
    if (!e.target.checked && filteredOpts.every(el => el.checked)) {
      filteredOpts.forEach(opt => opt.checked = false);
      e.target.checked = true;
    }
    
    if (catOptions.every(el => !el.checked)) catOptions.forEach(el => el.checked = true);
    
    let allChecked = catOptions.every(el => el.checked);
    allCats.children[0].checked = (allChecked) ? true : false;
        
    
    // Updateing Event elements
    let checkboxClasses = checkboxes.filter(el => el.checked)
                                    .map(el => el.value);
    let timeEvents = [].slice.call(document.querySelectorAll('.vis-box, .vis-line, .vis-dot'));
    timeEvents.forEach(el => {
      let found = false;
      for (let j=0; j<checkboxClasses.length; j++) {
        if (el.classList.contains(checkboxClasses[j])) found = true;
      }
      
      if (found == true) el.classList.remove('closed');
      else el.classList.add('closed');
    });
      
  });
});

// Select all button separate
document.querySelector('.cat-all').addEventListener('click', (e) => {
  // uhhh why do html checkboxes fire twice for 'click' events, with the first event firing as typeof element.checked === undefined??????
  if (typeof e.target.checked === 'undefined') return;
  let allChecked = e.target.checked;
  
  // high-order functions will be the next .com bubble for sure.
  let fields = [].slice
                 .call(e.target.parentElement.parentElement.children)
                 .filter(el => !el.classList.contains('cat-all'))
                 .map(el => el.children[0]);
  fields.forEach(f => f.checked = allChecked);
  
  
  let timeEvents = [].slice.call(document.querySelectorAll('.vis-box, .vis-line, .vis-dot'))
                           .forEach(el => {
                             if (e.target.checked) el.classList.remove('closed');
                             else el.classList.add('closed');
                           });
});

              
            
!
999px

Console