octocatstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource
via CSS Lint

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource
via JS Hint

Code Indentation

     

Save Automatically?

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

Want a Run Button?

If active, the preview will update automatically when you change code.

HTML

            
              script(src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/87050/diff.min.js")

section.col-1
  pre.html
  pre.css

section.col-2
  section.container
    div
    button.down
      span <
    button.up 
      span >
  article.description 
            
          
!

CSS

            
              /* 
  Not enough CSS for you? 
  Check out CSSTricks.com and product.hubspot.com/blog to learn more.

  Questions? ask Francisco Dias (@ffrandias) he might have put some time into this.
  Also, Thank you to Cris Necochea (@knick44), Zach Friss (@thatguyfriss) for collaborating on the original content & presentation and feedback.

  Some awesome resources for css trickery:
    - Chris Coyier's article on CSS Specificity https://css-tricks.com/specifics-on-css-specificity/
    - Estelle Weyl's cssConf talk: https://youtu.be/vTbV0ZBfxR8?
    - Mozilla Developer Network (MDN) https://developer.mozilla.org/en-US/docs/Web/CSS
    - White space characters as class names (╯°□°)╯︵ ┻━┻ http://jsbin.com/nuhinuda/1/edit
  */
            
          
!

JS

            
              const trollNumber = 9001;
const colors = {
  red: "#E82D43",
  lightRed: "#E53849",
  darkRed: "#D82440"
};

const baseCSS = `
body {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  color: cornsilk;
  -webkit-justify-content: space-around;
      -ms-flex-pack: distribute;
          justify-content: space-around;
}

a {
  color: white;
}

span.code {
  font-family: Monospace, Consolas;
  display: inline-block;
  background: ${colors.darkRed};
  padding: 0 3px;
  border-radius: 5px
}

[class^="col-"] {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -webkit-flex-direction: column;
      -ms-flex-direction: column;
          flex-direction: column;
}

.col-1 {
  width: 40%;
}

.col-2 {
  width: 60%;
}

pre {
  padding: 1rem;
  margin: 0;
  white-space: pre-wrap;
  word-wrap: break-word;
  display: flex-column;
}

pre.html {
  background: ${colors.lightRed};
  -webkit-box-flex: 0;
  -webkit-flex-grow: 0;
      -ms-flex-positive: 0;
          flex-grow: 0;
}

pre.css {
  background: ${colors.darkRed};
  -webkit-box-flex: 1;
  -webkit-flex-grow: 1;
      -ms-flex-positive: 1;
          flex-grow: 1;
  color: pink;
  overflow-y: auto;
}

.container {
  background: ${colors.red};
  position: relative;
  overflow: hidden;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-flex: 1;
  -webkit-flex-grow: 1;
      -ms-flex-positive: 1;
          flex-grow: 1;
  -webkit-box-pack: center;
  -webkit-justify-content: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
  -webkit-align-items: center;
      -ms-flex-align: center;
          align-items: center;
}

.description {
  background: ${colors.lightRed};
  -webkit-flex-shrink: 0;
      -ms-flex-negative: 0;
          flex-shrink: 0;
  overflow-y: auto;
  font-weight: lighter;
  padding: 1rem;
}

button {
  position: absolute;
  background: transparent;
  outline: 0;
  border: 0;
  color: white;
  height: 100%;
  width: 50%;
  margin: 0;
}

button.down {
  left: 0;
}

button.down span {
  left: 1.75rem;
}

button.up {
  right: 0;
}

button.up span {
  right: 1.75rem;
}

button span {
  position: absolute;
  padding: 1rem;
  border-radius: 50%;
  top: calc(50% - 1rem);
  font-size: 2rem;
  opacity: 0.4;
  -webkit-transition: opacity 150ms linear;
          transition: opacity 150ms linear;
}

button:hover {
  cursor: pointer;
}

button:hover span {
  font-weight: bold;
  opacity: 1;
}

button[disabled] span {
  opacity: 0;
}

.removed {
  text-decoration: line-through;
  color: lightcoral;
}

.added {
  color: white;
}

html, body {
  color: ghostwhite;
  font-family: sans-serif;
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  line-height: 1.25;
}

::-moz-selection {
  color: blanchedalmond;
  opacity: 0;
}

::selection {
  color: blanchedalmond;
  opacity: 0;
}

`
const stages = {
  normal: [{
    description: `All of the steps workin Chrome, most work in IE, Safari and Firefox. Here we're setting up positioning a <span class="code">div</span> and using <span class="code"><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path" target="_blank">clip-path</a></span> to set a bound for shape. Click or use arrows to navigate.`,
    specificity: `0,0,0,0,0,1`,
    html: `<div></div>`,
    css: `div {
  -webkit-flex-basis: 135px;
      -ms-flex-preferred-size: 135px;
          flex-basis: 135px;
  height: 180px;
  -webkit-clip-path: polygon(103px 43px, 135px 73px, 90px 64px, 124px 133px, 116px 135px, 104px 165px, 130px 174px, 99px 172px, 98px 176px, 92px 174px, 99px 137px, 82px 169px, 114px 180px, 77px 176px, 74px 180px, 68px 179px, 78px 140px, 68px 140px, 70px 108px, 79px 61px, 80px 45px, 75px 30px, 83px 26px, 91px 35px, 120px 26px, 103px 43px);
          clip-path: polygon(103px 43px, 135px 73px, 90px 64px, 124px 133px, 116px 135px, 104px 165px, 130px 174px, 99px 172px, 98px 176px, 92px 174px, 99px 137px, 82px 169px, 114px 180px, 77px 176px, 74px 180px, 68px 179px, 78px 140px, 68px 140px, 70px 108px, 79px 61px, 80px 45px, 75px 30px, 83px 26px, 91px 35px, 120px 26px, 103px 43px);
}

`
  }, {
    description: `Cool, A black spy! We used an <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_started/Selectors" target="_blank">element selector</a> to target our <span class="code">div</span> and give it a black background.`,
    specificity: `0,0,0,0,0,1`,
    css: `div {
  background: black;
}

`
  }, {
    description: `Woah, we just switched to a white spy by using more than one element selector. The more of these you include the more <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity" target="_blank">specific</a> your selector becomes.`,
    specificity: `0,0,0,0,0,2`,
    css: `body div {
  background: white;
}

`
  }, {
    description: `We added another element selector, <span class="code">html</span>, causing us to reach the top of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model" target="_blank">DOM</a>. This is as specific as we can get with the elements that should always be parents of a <span class="code">div</span>.`,
    specificity: `0,0,0,0,0,3`,
    css: `html body div {
  background: black;
}

`
  }, {
    description: `We just switched the way we're referring to the spy. We're now using a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors" target="_blank"><span class="code">class</span> selector</a> which is more specific than any number of element selectors.`,
    specificity: `0,0,0,0,1,0`,
    html: `<div class="spy"></div>`,
    css: `.spy {
  background: white;
}

`
  }, {
    description: `Did we just take a step back? We combined both an element and a <span class="code">class</span> selector. A <span class="code">class</span> selector with no space between it and the element selector will select the element it's actually attached to. If there is a space between a <span class="code">class</span> and something else we would be selecting a child element.`,
    specificity: `0,0,0,0,1,1`,
    css: `div.spy {
  background: black;
}

`
  }, {
    description: `Another new selector! <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes" target="_blank">Pseudo-classes</a> have the same specificity as <span class="code">class</a> selectors.`,
    specificity: `0,0,0,0,2,1`,
    css: `div.spy:only-of-type {
  background: white;
}

`
  }, {
    description: `A <span class="code">class</span> selector just looks for a <span class="code">class</span> on the element you're selecting. It doesn't do anything to exclude itself so it just stacks specificity. (Don't use this pattern.)`,
    specificity: `0,0,0,0,30,0`,
    css: `.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy.spy {
  background: black;
}

`
  }, {
    description: `Another kind of selector? An <span class="code"><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id" target="_blank">ID</a></span> is more specific than any number of class or element selectors combined. It's intended to select a single unique element on a web page.`,
    specificity: `0,0,0,1,0,0`,
    html: `<div class="spy" id="spy"></div>`,
    css: `#spy {
  background: white;
}

`
  }, {
    description: `Woah this seems new. An <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors" target="_blank">attribute selector</a> selects any attribute on an element. It also has the flexibility of using a kind of <a href="https://en.wikipedia.org/wiki/Regular_expression" target="_blank">regular expression</a> to make a selection. Attribute selectors have the same specificity as <span class="code">class</span> selectors.`,
    specificity: `0,0,0,1,1,0`,
    css: `#spy[class^="s"] {
  background: black;
}

`
  }, {
    description: `We can combine all the selectors however we wish to increase specificity.`,
    specificity: `0,0,0,1,3,3`,
    css: `html body div#spy[class="spy"]:first-of-type.spy {
  background: white;
}

`
  }, {
    description: `Just like an <span class="code">ID</span>, inline styles take precedence over any number of <span class="code">class</span>es, but it's also more specific than any number of <span class="code">ID</span>s.`,
    specificity: `0,0,1,0,0,1`,
    html: `<div class="spy" id="spy" style="background: black;"></div>`,
    css: ``
  }, {
    description: `If you need to overwrite an inline style you can use <a href="http://www.w3.org/TR/CSS2/cascade.html#important-rules" target="_blank"><span class="code">!important</span></a>—but it's not advisable because it will overwrite any of the previous selectors no matter what the selector specificity. On a larger project this quickly becomes <a href="https://31.media.tumblr.com/292014cfdbec82ae24e672aaf2fb41c8/tumblr_nghtirWqlC1tlb56zo1_400.gif" target="_blank">chaos</a>.`,
    specificity: `0,1,0,0,0,0`,
    css: `div {
  background: white !important;
}

`
  }, {
    description: `Since <span class="code">!important</span> just escalates from the initial selector you can add <span class="code">!important</span> to the most specific thing and it will take precedence. There is no css selector that will overwrite this.`,
    specificity: `1,0,0,0,0,0`,
    html: `<div class="spy" id="spy" style="background: black !important;"></div>`,
    css: ``
  }],
  crazy: [{
      description: `Wait. I thought you said nothing could be more specific than the previous stage? There is an <a href="http://taligarsiel.com/Projects/howbrowserswork1.htm#The_painting_order" target="_blank">order in which browsers will paint</a> an element. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow" target="_blank">box-shadow</a> is painted over background and can be manipulated to cover the entire surface area of the element.`,
      css: `div {
  box-shadow: inset 0 ${trollNumber}rem 0 white;
}

`
    },

    // Too bad this doesn't work with `clip-path`
    // {
    //    description: `border is rendered over box-shadow because it actually squeezes out the area which box-shadow triest to paint on.`,
    //    css: `div {
    //  border: ${trollNumber}px solid black;
    //  box-shadow: inset 0 ${trollNumber}rem 0 white;
    //}
    //
    //`
    //  }, {
    //    description: `LOL Outline, does what border does to border.`,
    //    css: `div {
    //  outline: ${trollNumber}px solid white;
    //  outline-offset: ${-1 * trollNumber}px;
    //  border: ${trollNumber}px solid black;
    //  box-shadow: inset 0 ${trollNumber}rem 0 white;
    //}
    //
    //`
    //  },

    {
      description: `<span class="code"><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter" target="_blank">Filter</a></span> is an <span class="code">SVG</span> property that will invert the colors of the content of the selected element.`,
      css: `div {
  -webkit-filter: invert(1);
          filter: invert(1);
  box-shadow: inset 0 ${trollNumber}rem 0 white;
}

`
    }, {
      description: `If you can't keep stacking things on top of an element, why not just add another element on top? The <span class="code">:after</span> pseudo element gets rendered after the element it's attached to. The spy changes positioning because <span class="code">clip-path</span> is just…really weird/behaves unexpectedly (at least to me).`,
      css: `div:after {
  content: "";
  height: ${trollNumber}px;
  width: ${trollNumber}px;
  background: black;
  top: 0;
  left: 0;
  position: absolute;
}

div {
  -webkit-filter: invert(1);
          filter: invert(1);
  box-shadow: inset 0 ${trollNumber}rem 0 white;
}

`
    }, {
      description: `HA. <span class="code">!important</span> AGAIN! But really, this a trick we've already seen. Let's keep it fresh…`,
      css: `div:after {
  content: "";
  height: ${trollNumber}px;
  width: ${trollNumber}px;
  background: white !important;
  background: black;
  top: 0;
  left: 0;
  position: absolute;
}

div {
  -webkit-filter: invert(1);
          filter: invert(1);
  box-shadow: inset 0 ${trollNumber}rem 0 white;
}

`
    }, {
      description: `What an animated comeback. <span class="code">!important</span> isn't an animatable property. After the first frame it no longer applies. Here's <a href="https://www.youtube.com/watch?v=vTbV0ZBfxR8&feature=youtu.be&t=760" target="_blank">a talk</a> about some CSS funk.`,
      css: `-webkit-@keyframes white {
  to {
    background: black;
  }
}

@keyframes white {
  to {
    background: black;
  }
}

div:after {
  animation: white 1s linear;
  animation-play-state: paused;
  animation-delay: -1s;
  animation-fill-mode: forwards;
  content: "";
  height: ${trollNumber}px;
  width: ${trollNumber}px;
  background: white !important
  background: black;
  top: 0;
  left: 0;
  position: absolute;
}

div {
  -webkit-filter: invert(1);
          filter: invert(1);
  box-shadow: inset 0 ${trollNumber}rem 0 white;
}


`
    }, {
      description: `What gets rendered over all backgrounds? Content. (<a href="https://css-tricks.com/almanac/selectors/a/after-and-before/" target="_blank">Even if it's not actually part of the DOM</a>).`,
      css: `-webkit-@keyframes white {
  to {
    background: black;
  }
}

@keyframes white {
  to {
    background: black;
  }
}

div:after {
  animation: white 1s linear infinite;
  animation-play-state: paused;
  animation-delay: -1s;
  animation-fill-mode: forwards;
  content: "█";
  line-height: 0;
  color: white;
  font-size: ${trollNumber}px;
  height: ${trollNumber}px;
  width: ${trollNumber}px;
  background: white !important
  background: black;
  top: 0;
  left: 0;
  position: absolute;
}

div {
  -webkit-filter: invert(1);
          filter: invert(1);
  box-shadow: inset 0 ${trollNumber}rem 0 white;
}

`
    }, {
      description: `That's all folks! Hopefully you learned something new that might save you from specificity spaghetti. If you have questions or suggestions message me, <a href="https://twitter.com/ffrandias" target="_blank">@ffrandias</a>. If you want to learn front-end, check out <a href="http://css-tricks.com/" target="_blank">CSS-Tricks</a> and <a href="http://product.hubspot.com/blog" target="_blank">HubSpot's product blog</a>.`,
      css: `div {
  display: none;
}

.container:after {
  content: '';
  position: absolute;
  left: calc(50% - 150px);
  top: calc(50% - 111px);
  height: 222px;
  width: 300px;
  background-image: url('http://i.giphy.com/sNsTThL1bHukU.gif');
  background-size: 100%;
}

`
    }
  ]
}

const totalStages = stages.normal.length + stages.crazy.length;

///////////////////////
// Cached selectors:
///////////////////////

const styleTag = document.getElementsByTagName('style')[0];
const buttonUp = document.getElementsByClassName('up')[0];
const buttonDown = document.getElementsByClassName('down')[0];
const htmlDisplay = document.getElementsByClassName('html')[0];
const cssDisplay = document.getElementsByClassName('css')[0];
const description = document.getElementsByClassName('description')[0];

///////////////////////
// Navigating stages:
///////////////////////

const disableStage = (number) => {
  buttonDown.setAttribute('disabled', 'true')
  buttonUp.setAttribute('disabled', 'true')

  if (number > 0) {
    buttonDown.removeAttribute('disabled');
  }
  if (number < totalStages - 1) {
    buttonUp.removeAttribute('disabled');
  }
}

const addOne = (number) => {
  if (number < totalStages - 1) {
    stage.number += 1;
    updateStage();

  }
}

const subtractOne = (number) => {
  if (number > 0) {
    stage.number -= 1;
    updateStage();
  }
}

const inputReady = () => {
  buttonUp.addEventListener('click', () => {
    addOne(stage.number);
  });

  buttonDown.addEventListener('click', () => {
    subtractOne(stage.number);
  });

  document.onkeyup = (e) => {
    if (e.keyCode === 39 || e.keyCode === 75) {
      addOne(stage.number);
    } else if (e.keyCode === 37 || e.keyCode === 74) {
      subtractOne(stage.number);
    }
  }
}

///////////////////////
// Rendering Stages:
///////////////////////

let stage = {
  number: 0,
  newStages: '',
  oldCSS: '',
  newCSS: ''
};

const makeRenderedStages = (number) => {
  let stageObjects = []
  if (number <= stages.normal.length - 1) {
    for (let i = number; i >= 0; i--) {
      stageObjects.push(stages.normal[i])
    }
  } else if (number >= stages.normal.length - 1) {
    for (let i = stages.normal.length - 1; i >= 0; i--) {
      stageObjects.push(stages.normal[i])
    }
    if (number - stages.normal.length >= 0) {
      stageObjects.unshift(stages.crazy[number - stages.normal.length])
    }
  }
  return stageObjects;
}

const renderCSS = (stageArray) => {
  let renderedCSS = '';
  stageArray.forEach((element) => {
    renderedCSS += element.css
  })
  return renderedCSS
}

const renderHTML = (renderedStage) => {
  var renderedHTML = '';
  renderedStage.reverse().forEach((element) => {
    if (element.html) {
      renderedHTML = element.html;
    }
  });
  return renderedHTML;
}

const diffCSS = (oldCSS, newCSS) => {
  let showCSS = '';

  JsDiff.diffCss(stage.oldCSS, stage.newCSS).forEach((part) => {
    let action = part.added ?
      'added' : part.removed ?
      'removed' : 'no-action';

    let span = `<span class="${action}">${part.value}</span>`;
    showCSS += span;
  })
  return showCSS;
}

const updateStage = () => {
  stage.newStages = makeRenderedStages(stage.number);
  stage.newCSS = renderCSS(stage.newStages);
  let renderedHTML = renderHTML(stage.newStages);

  cssDisplay.innerHTML = diffCSS(stage.oldCSS, stage.newCSS);
  description.innerHTML = `<b>${stage.number}</b>: ${stage.newStages[stage.newStages.length - 1].description}`;
  htmlDisplay.innerText = renderedHTML;

  styleTag.innerHTML = baseCSS + stage.newCSS;
  document.getElementsByTagName('div')[0].outerHTML = renderedHTML;

  stage.oldCSS = stage.newCSS;

  disableStage(stage.number);
}

///////////////////////
// Kick it off!
///////////////////////

const initFunc = () => {
  updateStage();
  inputReady();
}

const ready = (fn) => {
  (document.readyState != 'loading') ?
  fn() : document.addEventListener('DOMContentLoaded', fn)
}

ready(initFunc);


            
          
!
999px
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Loading ..................

Console