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 includes JSX processing.

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

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

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

              
                <header>
  Gimme  
  <select name="rhythm" id="selectRhythm">
    <option label="perfectFourth">1.414</option>
    <option label="perfectFifth">1.5</option>
    <option label="goldenRatio">1.618</option>
    <option label="minorSecond">1.067</option>
  </select> 
  rhythm for 
  <input type="text" value="Maven Pro" id="selectFont"> 
  at base size 
  <input type="number" value="15" id="selectSize"> 
  px with line height 
  <input type="text" value="1.6" id="selectLh">
  – show CSS <input type="checkbox" />
  <aside>
    <textarea id="out"></textarea>
  </aside>
</header>
<input type="number" value="5" min="0" max="10" id="selectOpac" title="grid opacity">

<p class="s">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="m">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="l">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="xl">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="xxl">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="xxxl">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _1">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _2">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _3">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _4">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _5">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _6">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _7">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _8">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _9">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _10">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _11">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _12">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _13">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _14">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _15">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
<p class="minorSecond _16">
  Sixty quick brown foxes jump over the lazy dog<br>
  In the Zoo
</p>
              
            
!

CSS

              
                html, textarea { font-family: 'Courier', 'sans'; }
*, *:before, *:after { box-sizing: inherit; }

body {
  position: relative;
  top: 0px;
  height: auto;
  min-height: 100%;
  font-family: "Maven Pro", "sans-serif";
  text-align: center;
  -webkit-font-smoothing: subpixel-antialiased;
-webkit-font-feature-settings: 'kern' 1,'onum' 1,'liga' 1;
-ms-font-feature-settings: 'kern' 1,'onum' 1,'liga' 1;
-o-font-feature-settings: 'kern' 1,'onum' 1,'liga' 1;
font-feature-settings: 'kern' 1,'onum' 1,'liga' 1;
text-rendering: optimizeLegibility;
}
body, p, h1, h2, h3, h4, h5 {
  margin: 0; padding: 0;
}
canvas { position: absolute; top: -4000px; }
header {
  background: rgba(0,0,0,.64);
  color: white;
}
input#selectOpac { position: fixed; bottom: 0px; left: 0px; width: 40px; }
input#selectFont { width: 85px; }
input#selectSize, input#selectLh { width: 40px; }
textarea#out {
  min-height: 160px;
  min-width: 160px;
  width: 61.8vw;
  border: 1rem solid rgba(0,0,0,0.4);
}
input[type="checkbox"] + aside { 
  display: none; 
  position: absolute; 
  top: -4000px;
}
input[type="checkbox"]:checked + aside { 
  display: block; 
  top: 80px;
  top: calc(2rem * var(--lineHeight) - 1px);
}
              
            
!

JS

              
                /* google webfont loader included - see Settings */
var LH = document.querySelector('#selectLh');
var SIZE = document.querySelector('#selectSize');
var FONT = document.querySelector('#selectFont');
var RHYTHM = document.querySelector('#selectRhythm');
var OPAC = document.querySelector('#selectOpac');
LH.addEventListener('change', calc);
FONT.addEventListener('blur', calc);
SIZE.addEventListener('change', calc);
RHYTHM.addEventListener('change', calc);
OPAC.addEventListener('change', calc);
calc();

/* Gets the exact font metrics for any google font
// TODO FIXME a whitelist for web safe fonts will be added too.
// This is needed to get exact Cap Height per Font per Size : 
*/
function getMetrics(fontName, fontSize) {
  // NOTE: if there is no getComputedStyle, this library won't work.
  if(!document.defaultView.getComputedStyle) {
    throw("ERROR: 'document.defaultView.getComputedStyle' not found. This library only works in browsers that can report computed CSS values.");
  }
  if (!document.querySelector('canvas')) {
    var _canvas = document.createElement('canvas');
    _canvas.width = 220; _canvas.height = 220;
    document.body.appendChild(_canvas);
  }
  // Store the old text metrics function on the Canvas2D prototype
  CanvasRenderingContext2D.prototype.measureTextWidth = CanvasRenderingContext2D.prototype.measureText;
  /**
   *  Shortcut function for getting computed CSS values
   */
  var getCSSValue = function(element, property) {
    return document.defaultView.getComputedStyle(element,null).getPropertyValue(property);
  };
  /**
   * The new text metrics function
   */
  CanvasRenderingContext2D.prototype.measureText = function(textstring) {
    var metrics = this.measureTextWidth(textstring),
        fontFamily = getCSSValue(this.canvas,"font-family"),
        fontSize = getCSSValue(this.canvas,"font-size").replace("px",""),
        isSpace = !(/\S/.test(textstring));
        metrics.fontsize = fontSize;

    // For text lead values, we meaure a multiline text container.
    var leadDiv = document.createElement("div");
    leadDiv.style.position = "absolute";
    leadDiv.style.margin = 0;
    leadDiv.style.padding = 0;
    leadDiv.style.opacity = 0;
    leadDiv.style.font = fontSize + "px " + fontFamily;
    leadDiv.innerHTML = textstring + "<br/>" + textstring;
    document.body.appendChild(leadDiv);
    // Make some initial guess at the text leading (using the standard TeX ratio)
    metrics.leading = 1.2 * fontSize;
    // Try to get the real value from the browser
    var leadDivHeight = getCSSValue(leadDiv,"height");
    leadDivHeight = leadDivHeight.replace("px","");
    if (leadDivHeight >= fontSize * 2) { metrics.leading = (leadDivHeight/2) | 0; }
    document.body.removeChild(leadDiv);
    // if we're not dealing with white space, we can compute metrics
    if (!isSpace) {
        // Have characters, so measure the text
        var canvas = document.createElement("canvas");
        var padding = 100;
        canvas.width = metrics.width + padding;
        canvas.height = 3*fontSize;
        canvas.style.opacity = 1;
        canvas.style.fontFamily = fontFamily;
        canvas.style.fontSize = fontSize;
        var ctx = canvas.getContext("2d");
        ctx.font = fontSize + "px " + fontFamily;

        var w = canvas.width,
            h = canvas.height,
            baseline = h/2;

        // Set all canvas pixeldata values to 255, with all the content
        // data being 0. This lets us scan for data[i] != 255.
        ctx.fillStyle = "white";
        ctx.fillRect(-1, -1, w+2, h+2);
        ctx.fillStyle = "black";
        ctx.fillText(textstring, padding/2, baseline);
        var pixelData = ctx.getImageData(0, 0, w, h).data;

        // canvas pixel data is w*4 by h*4, because R, G, B and A are separate,
        // consecutive values in the array, rather than stored as 32 bit ints.
        var i = 0,
            w4 = w * 4,
            len = pixelData.length;

        // Finding the ascent uses a normal, forward scanline
        while (++i < len && pixelData[i] === 255) {}
        var ascent = (i/w4)|0;

        // Finding the descent uses a reverse scanline
        i = len - 1;
        while (--i > 0 && pixelData[i] === 255) {}
        var descent = (i/w4)|0;

        // find the min-x coordinate
        for(i = 0; i<len && pixelData[i] === 255; ) {
          i += w4;
          if(i>=len) { i = (i-len) + 4; }}
        var minx = ((i%w4)/4) | 0;

        // find the max-x coordinate
        var step = 1;
        for(i = len-3; i>=0 && pixelData[i] === 255; ) {
          i -= w4;
          if(i<0) { i = (len - 3) - (step++)*4; }}
        var maxx = ((i%w4)/4) + 1 | 0;

        // set font metrics
        metrics.ascent = (baseline - ascent);
        metrics.descent = (descent - baseline);
        metrics.bounds = { minx: minx - (padding/2),
                           maxx: maxx - (padding/2),
                           miny: 0,
                           maxy: descent-ascent };
        metrics.height = 1+(descent - ascent);
    } else {
        // Only whitespace, so we can't measure the text
        metrics.ascent = 0;
        metrics.descent = 0;
        metrics.bounds = { minx: 0,
                           maxx: metrics.width, // Best guess
                           miny: 0,
                           maxy: 0 };
        metrics.height = 0;
    }
    return metrics;
  };
  WebFont.load({
    google: {
      families: [ [encodeURIComponent(fontName),'::latin'].join('') ] 
    }
  });
  
  document.body.style.fontFamily = ['"'+fontName+'"', "Arial sans"].join(' ')
  var canvas = document.querySelector('canvas'),
      context = canvas.getContext("2d");

  var w=220, h=220;

  var pxFontSize = fontSize+'px'
  canvas.style.font = [pxFontSize, fontName].join(' ');
  context.font = [pxFontSize, fontName].join(' ');
  context.clearRect(0, 0, canvas.width, canvas.height);
  // draw bounding box and text
  var xHeight = context.measureText("x").height;
  var capHeight = context.measureText("H").height;
  var metrics = context.measureText("Hxy");
  var xStart = (w - metrics.width)/2;
  context.fontFamily = fontName;
  context.fillStyle = "#FFAF00";
  context.fillRect(xStart, h/2-metrics.ascent, metrics.bounds.maxx-metrics.bounds.minx, 1+metrics.bounds.maxy-metrics.bounds.miny);
  context.fillStyle = "#333333";
  context.fillText("Hxy", xStart, h/2);
  metrics.fontsize = parseInt(metrics.fontsize);
  metrics.offset = Math.ceil((metrics.leading - metrics.height) / 2);
  metrics.width = JSON.parse(JSON.stringify(metrics.width));
  metrics.capHeight = capHeight;
  metrics.xHeight = xHeight - 1;
  metrics.ascender = metrics.capHeight - metrics.xHeight;
  metrics.descender = metrics.descent;
  
  var myMetrics = {
    px: JSON.parse(JSON.stringify(metrics)),
    relative: {
      fontsize: 1,
      offset: (metrics.offset / metrics.fontsize),
      height: (metrics.height / metrics.fontsize),
      capHeight: (metrics.capHeight / metrics.fontsize),
      ascender: (metrics.ascender / metrics.fontsize),
      xHeight: (metrics.xHeight / metrics.fontsize),
      descender: (metrics.descender / metrics.fontsize)
    }
  }
  
  Array.prototype.slice.call(
    document.getElementsByTagName('canvas'), 0
  ).forEach(function(c, i){
    if (i > 0) document.body.removeChild(c);
  });
  // Restore CanvasRenderingContext2D, needed for reusing this function ...
  CanvasRenderingContext2D.prototype.measureText = CanvasRenderingContext2D.prototype.measureTextWidth
  return myMetrics;
}

/* Writes the calculated CSS rules to the document
// ...
*/
function addCSSRule(sheet, selector, rules, index) {
	if("insertRule" in sheet) {
		sheet.insertRule(selector + "{" + rules + "}", index);
	}
	else if("addRule" in sheet) {
		sheet.addRule(selector, rules, index);
	}
}

function styleMinor(visible = 'block') {
  Array.prototype.forEach.call(document.querySelectorAll('.minorSecond'), (el) => {
    el.style.display = visible;
  });
}

/* Calculates the baseline metrics (font-size, line-height, margin/padding)
// for 6 sizes as CSS rules
*/
function calc() {
  
  var S = Number(SIZE.value);
  LH.value = Math.round(S * Number(LH.value)) / S;
  
  var sizeRules = ['s','m','l','xl','xxl','xxxl'];
  if (Number(RHYTHM.value) === 1.067) {
    styleMinor();
    for (var i = 1; i < 17; i++) { sizeRules.push(`_${i}`); }
  } else {
    styleMinor('none');
  }
  sizeRules = sizeRules.map((s, i) => {
    var rhythm = Number(RHYTHM.value);
    var baseSize = Number(SIZE.value);
    var lineHeight = Number(LH.value);

    var fontName = FONT.value;
    var pxSize = Math.round(Math.pow(rhythm, i) * baseSize);
    var scale = pxSize / baseSize;
    var rhythm = lineHeight * pxSize / scale; // px
    var lines = Math.ceil( ( Number(pxSize) + 0.001 ) / rhythm );
    var pxLh = rhythm * lines;
    var lh = pxLh / pxSize;
    var pxOffset = (pxLh - Math.floor(pxLh));
    var offset = (pxOffset < 1 && pxOffset >= .5) ? 1 : 0;    
    var myMetrics = getMetrics(fontName, pxSize);
    var capHeight = myMetrics.relative.capHeight || 0.68;
    var baseline = (lh - capHeight) / 2;
    var rules = `font-size: ${scale}rem;
  line-height: ${lh};
  padding-top: ${baseline}em;
  margin-bottom: ${lh - baseline}em;
  /* --capHeight: ${capHeight}; */
  `
    var o = {
      sel: `.${s}`,
      rules: rules,
      offset: offset,
      css: `
/* ${pxSize}px */
.${s} { 
  ${rules}
}`
    };
    return o;
  });
  
  
  var offsets = sizeRules.filter(o => !!o.offset).length;
  var bgPos = !offsets ? 1 : (offsets < sizeRules.length/2) ? -1 : 0-SIZE.value;
  
  var rootRules = `--lineHeight: ${LH.value}; 
  --gridColor: rgba(255, 0, 0, ${(OPAC.value*10)/100});
  font-family: "${FONT.value}", ${FONT.value};
  font-size: ${SIZE.value}px;
  line-height: var(--lineHeight);
  box-sizing: border-box;
  background: -webkit-linear-gradient(var(--gridColor), var(--gridColor) 1px, transparent 1px);
  background: linear-gradient(var(--gridColor), var(--gridColor) 1px, transparent 1px);
  background-size: 1px calc(1em * var(--lineHeight));
  background-position: 0px ${bgPos}px`;
  
  var typoCSS = `/* ${RHYTHM.value} rythm for "${FONT.value}": ${SIZE.value}px / ${LH.value} */
:root { 
  ${rootRules}
}
${sizeRules.map(o => o.css).join('')}
`
  
  document.getElementById('out').value = typoCSS;
  var CSS = document.getElementById('redaktorTypoTmp');
  if (!!CSS) { document.head.removeChild(CSS) }
  var _CSS = document.createElement('style');
  _CSS.setAttribute('media', 'screen');
  _CSS.appendChild(document.createTextNode(''));
  _CSS.id = 'redaktorTypoTmp';
  document.head.appendChild(_CSS); 

  CSS = document.styleSheets[document.styleSheets.length - 1];
  //CSS.cssRules.forEach((o,i) => _CSS.deleteRule(i));
  addCSSRule(CSS, ':root', rootRules, 0);
  sizeRules.map((o, i) => {
    addCSSRule(CSS, o.sel, o.rules, i+1)
  });
  document.body.style.fontFamily = `"${FONT.value}", ${FONT.value}`;
  
  //console.log(CSS);
  return typoCSS;
}



              
            
!
999px

Console