website: major update

This commit is contained in:
Rasmus Andersson 2018-02-20 01:38:51 -08:00
parent 761638ef42
commit 9f367901ef
22 changed files with 2139 additions and 1030 deletions

262
docs/dynmetrics/index.css Normal file
View file

@ -0,0 +1,262 @@
body {
padding-bottom: 0;
}
const {
display: inline;
font-size: 1.2em;
font-style: italic;
font-family: 'Times New Roman', Times, serif;
}
sup {
/*background:lightpink;*/
display: inline-block;
font-size:0.92em;
position: relative;
top:-0.4em;
letter-spacing: 0.001em;
vertical-align: baseline;
}
.row.first {
padding-bottom:1em;
}
formula {
display: inline-flex;
align-items: center;
background: white;
border-radius: 5px;
padding: 0 1em;
line-height: 3em;
height: 3em;
overflow: hidden;
margin-right: 1em;
margin-bottom: 1em;
}
.row.white formula {
background: #f5f5f5;
}
formula:last-child {
margin-right: 0;
}
formula.code {
white-space: pre;
font-family: "SFMono-Regular", Menlo, Consolas, Inconsolata, monospace;
font-size:0.96em;
}
formula > * {
margin: 0 0.2em 0 0.2em;
}
formula > const {
margin-bottom: 0.11em;
}
formula > sup {
margin-left: 0;
}
.samples {
display: flex;
flex-wrap: wrap;
overflow: auto;
overflow-wrap: break-word;
word-break: break-word;
}
.samples .sample {
/*background: lightpink;*/
color: #111;
flex: 0 1 auto;
outline: none;
margin-right: 50px;
margin-bottom: 50px;
}
.samples .sample .di {
display: block;
background-color: #ccc;
height: 1px;
width: 100%;
margin-bottom: 8px;
}
.samples .sample .di > span {
display: block;
background-color: #333;
height: 100%;
}
.samples .sample .di.match > span {
background-color: #0d3;
}
.samples .sample .di.unavailable {
background-color: #eee;
}
.samples .sample .di.unavailable > span {
visibility: hidden;
}
.samples .sample .info {
display: block;
font-size: 11px !important;
line-height: 11px;
margin-bottom: 9px;
color: #bbb;
}
.font-style-regular { font-weight:400 !important; font-style:normal !important; }
.font-style-italic { font-weight:400 !important; font-style:italic !important; }
.font-style-medium { font-weight:500 !important; font-style:normal !important; }
.font-style-medium-italic { font-weight:500 !important; font-style:italic !important; }
.font-style-bold { font-weight:700 !important; font-style:normal !important; }
.font-style-bold-italic { font-weight:700 !important; font-style:italic !important; }
.font-style-black { font-weight:900 !important; font-style:normal !important; }
.font-style-black-italic { font-weight:900 !important; font-style:italic !important; }
.row.with-sidebar {
padding: 0;
}
.row.with-sidebar > *:first-child {
flex: 1 1 auto;
padding: 50px 0 0 50px; /* note: samples have 50px right margin */
}
.row.with-sidebar > .sidebar {
flex: 0 0 auto;
}
div.controls {
box-sizing: border-box;
width: 250px;
max-width: 250px;
flex: 0 0 auto;
padding: 10px 0;
border-left: 4px solid #f4f4f4;
display: flex;
flex-direction: column;
overflow: hidden;
}
div.controls hr {
border: none;
height: 2px;
background: #f4f4f4;
margin-top: 10px;
margin-bottom: 10px;
}
div.controls hr.without-bottom-margin { margin-bottom: 0; }
div.controls hr.without-top-margin { margin-top: 0; }
div.controls hr.without-margins { margin: 0; }
div.controls .control {
display: flex;
justify-content: space-between;
align-items: center;
overflow: hidden;
height: 30px;
margin: 0 16px;
}
div.controls > h3 {
margin: 0 16px;
}
div.controls > textarea {
border: none;
padding:16px;
height: 400px;
font-family: "SFMono-Regular", Menlo, Consolas, Inconsolata, monospace;
outline: none;
}
div.controls .control > * {
/*max-width: 50%;*/
flex: 1 1 auto;
margin:0;
margin-right: 16px;
box-sizing: border-box;
}
div.controls .control > :last-child {
margin-right: 0;
}
div.controls .control > select {
min-width: 6em;
align-items: center;
justify-content: center;
}
div.controls .control > input,
div.controls .control > select {
width: 0;
outline: none;
}
div.controls .control > input[type="number"],
div.controls .control > input[type="text"] {
background: none;
border: none;
padding: 4px 0;
font-size: 13px;
}
div.controls .control > input[type="number"] {
max-width: 60px;
-moz-font-feature-settings: 'calt' 1, 'zero' 1, 'tnum' 1;
-ms-font-feature-settings: 'calt' 1, 'zero' 1, 'tnum' 1;
-o-font-feature-settings: 'calt' 1, 'zero' 1, 'tnum' 1;
-webkit-font-feature-settings: 'calt' 1, 'zero' 1, 'tnum' 1;
font-feature-settings: 'calt' 1, 'zero' 1, 'tnum' 1;
}
div.controls .control > input[type=number]::-webkit-inner-spin-button,
div.controls .control > input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
div.controls .control > input[type="number"][readonly] {
max-width: 40px;
}
div.controls .control > input.wide[type="number"] {
max-width: 100%;
}
div.controls .control > input[type="range"] {
/*max-width: 80%;*/
flex: 1 1 auto;
display: block;
}
div.controls .control > img.icon,
div.controls .control > label {
font-family: georgia, serif;
font-style: italic;
line-height: 16px;
color: black;
width: 16px;
height: 16px;
flex: 0 0 auto;
margin-right: 16px;
opacity: 0.6;
}
div.controls canvas {
height: 200px;
}
.row.small-window {
margin-top:0;
padding-top:0;
}
@media only screen and (min-width: 541px) {
.small-window {
display: none;
}
}
@media only screen and (max-width: 540px) {
.row.with-sidebar {
overflow: auto;
}
div.controls {
display: none;
}
div.controls .graphplot,
div.controls hr.without-top-margin,
div.controls h3,
div.controls #ideal-values
{
display: none;
}
.row.with-sidebar {
flex-direction: column;
}
}

522
docs/dynmetrics/index.html Normal file
View file

@ -0,0 +1,522 @@
---
layout: default
title: Dynamic Metrics
---
{%
capture url_root
%}{% if site.safe == false %}/{% else %}/inter/{% endif
%}{%
endcapture %}{%
for file in site.static_files %}{%
assign _path = file.path | remove_first: "/inter" %}{%
if _path == "/dynmetrics/index.css" %}{%
assign index_css_v = file.modified_time | date: "%Y%m%d%H%M%S" %}{%
elsif _path == "/res/bindings.js" %}{%
assign bindings_js_v = file.modified_time | date: "%Y%m%d%H%M%S" %}{%
elsif _path == "/res/graphplot.js" %}{%
assign graphplot_js_v = file.modified_time | date: "%Y%m%d%H%M%S" %}{%
endif %}{%
endfor
%}
<link rel="stylesheet" href="index.css?v={{ index_css_v }}">
<script src="{{url_root}}res/bindings.js?v={{ bindings_js_v }}"></script>
<script src="{{url_root}}res/graphplot.js?v={{ graphplot_js_v }}"></script>
<div class="row first"><div>
<h1>Dynamic Metrics</h1>
<p>
There's of course no absolute right or wrong when it comes to expressing
yourself with typography, but Inter UI Dynamic Metrics provides guidelines
for <em>good</em> typography.
You simply provide the optical font size, and the tracking and leading
(letter spacing and line height) will be calculated using the following
formula:
</p>
<p>
<formula>
tracking =
<const>a</const> + <const>b</const> ×
<const>e</const><sup>(<const>c</const> × fontSize)</sup>
</formula>
<formula>
leading = <const>l</const> × fontSize
</formula>
<formula>
<const>e</const><num>2.718</num>
</formula>
</p>
<p class="small-window">
<small>View this on a larger screen to enable interactive control.</small>
</p>
</div></div>
<!-- <div class="row small-window"><div>
Hello
</div></div> -->
<div class="white full-width row with-sidebar">
<div class="samples">
<p contenteditable class="sample">
<span class="di"><span></span></span>
<span class="info"
title="size, tracking, (ideal tracking) — progress bar shows distance from ideal"
>15 &nbsp; 0.0 &nbsp; ( 0.0 )</span>
Such a riot of sea and wind strews the whole extent of beach with whatever has been lost or thrown overboard, or torn out of sunken ships. Many a man has made a good weeks work in a single day by what he has found while walking along the Beach when the surf was down.
</p>
</div>
<div class="sidebar controls">
<div class="control">
<label title="Number of ideal matches">ni</label>
<input title="Number of ideal matches" type="number" readonly data-binding="ideal-count">
<label title="Distance from ideal">di</label>
<input title="Distance from ideal" type="number" class="wide" readonly data-binding="ideal-distance">
</div>
<div class="control">
<img title="Style" class="icon" src="../samples/icons/style.svg">
<select data-binding="style">
<option value="regular" default>Regular</option>
<option value="italic">Italic</option>
<option value="medium" default>Medium</option>
<option value="medium-italic">Medium Italic</option>
<option value="bold" default>Bold</option>
<option value="bold-italic">Bold Italic</option>
<option value="black" default>Black</option>
<option value="black-italic">Black Italic</option>
</select>
</div>
<div class="control">
<img title="Base tracking" class="icon" src="../samples/icons/letter-spacing.svg">
<input type="range" min="-0.05" max="0.06" step="0.001" data-binding="base-tracking">
<input type="number" min="-0.15" max="1" step="0.001" data-binding="base-tracking">
</div>
<hr>
<div class="control">
<label title="Constant a">a</label>
<input type="range" min="-0.05" max="0" step="0.001" data-binding="var-a">
<input type="number" min="-0.05" max="0" step="0.0001" data-binding="var-a">
</div>
<div class="control">
<label title="Constant b">b</label>
<input type="range" min="0" max="1" step="0.01" data-binding="var-b">
<input type="number" min="0" max="1" step="0.001" data-binding="var-b">
</div>
<div class="control">
<label title="Constant c">c</label>
<input type="range" min="-1" max="0" step="0.01" data-binding="var-c">
<input type="number" min="-1" max="0" step="0.001" data-binding="var-c">
</div>
<hr>
<div class="control">
<label title="Constant l controls line height">l</label>
<input type="range" min="1" max="2" step="0.1" data-binding="var-l">
<input type="number" min="1" max="2" step="0.1" data-binding="var-l">
</div>
<hr class="without-bottom-margin">
<canvas class="graphplot">Canvas not Supported</canvas>
<hr class="without-top-margin">
<h3>Ideal values</h3>
<textarea id="ideal-values"></textarea>
<hr class="without-top-margin">
</div>
</div>
<script type="text/javascript">(function(){
// internal variables that are user-controllable, but are not part of
// Dynamic Metrics
var baseTracking = 0
var weightClass = 400
// Ideal values designed one by one, by hand.
// font size => tracking
var idealValues = {}
var idealValuesList = []
var idealValuesTextArea = $('textarea#ideal-values')
function setIdealValues(valueMap) {
idealValues = valueMap
idealValuesList = []
Object.keys(idealValues).forEach(function(fontSize) {
idealValuesList.push([fontSize, idealValues[fontSize]])
})
if (idealValuesTextArea.value == '') {
idealValuesTextArea.value = idealValuesList.map(function(t){
return t[0] + '\t' + t[1]
}).join('\n')
}
}
function parseValues(s) {
var values = {}
s = s.replace(/^[\s\r\t\n]+|[\s\r\t\n]+$/g, '') // trim whitespace
s.split(/\s*\r?\n\s*/).map(function(line) {
var t = line.split(/\s+/)
if (t.length == 2) {
t[0] = parseInt(t[0])
t[1] = parseFloat(t[1])
values[t[0]] = t[1]
}
})
return values
}
setIdealValues({
// set-2018-02-20
6: 0.055,
7: 0.044,
8: 0.034,
9: 0.024,
10: 0.018,
11: 0.012,
12: 0.007,
13: 0.003,
14: 0.001,
15: -0.002,
16: -0.004,
17: -0.006,
18: -0.008,
20: -0.01,
24: -0.013,
})
// setIdealValues({
// // set-2018-02-19
// 6: 0.059,
// 7: 0.043,
// 8: 0.032,
// 9: 0.022,
// 10: 0.014,
// 11: 0.009,
// 12: 0.004,
// 13: 0,
// 14: -0.003,
// 15: -0.005,
// 16: -0.0075,
// 17: -0.0084,
// 18: -0.0095,
// 20: -0.012,
// 24: -0.014,
// })
// setIdealValues({
// // set-2018-02-18
// 6: 0.06,
// 7: 0.04,
// 8: 0.03,
// 9: 0.02,
// 10: 0.01,
// 11: 0.005,
// 12: 0.002,
// 13: 0.001,
// 14: 0,
// 15: -0.002,
// 16: -0.005,
// 17: -0.007,
// 18: -0.009,
// 20: -0.011,
// 24: -0.011,
// })
// Variables for constants involved in Dynamic Metrics
var a = -0.013
// -0.0119
// -0.0101
// -0.0092
// -0.012
// -0.013
var b = 0.23
// 0.455
// 0.421
// 0.26
// 0.23
// 0.251
var c = -0.21
// -0.29
// -0.23
// -0.21
// -0.222
// a = -0.012; b = 0.23; c = -0.21; // di=0.002514 on set-2018-02-18
// a = -0.013; b = 0.251; c = -0.222 // di=0.001742 on set-2018-02-18
// a = -0.015; b = 0.283; c = -0.23; // di=0.00221 on set-2018-02-18
// a = -0.0149; b = 0.298; c = -0.23; // di=0.000484 on set-2018-02-19
a = -0.016; b = 0.21; c = -0.18; // di=0.000532 on set-2018-02-20
// these have a short di, but stray far away from larger font sizes
// a = -0.0077
// b = 0.377
var l = 1.4
// InterUIDynamicTracking takes the font size in points or pixels and returns
// the compensating tracking in EM.
//
function InterUIDynamicTracking(fontSize, weightClass) {
// if (!weightClass) { -- currently unused
// weightClass = 400
// }
//
// y = -0.01021241 + 0.3720623 * e ^ (-0.2808687 * x)
// [6-35] 0.05877 .. -0.0101309 (13==0; stops growing around 30-35)
// See https://gist.github.com/rsms/8efdbca5f8145a584ed08a7c3d6e5788
//
return a + b * Math.pow(Math.E, c * fontSize)
// [6 - 38] 0.05798 .. -0.01099 (midpoint = 12.533)
// y = 0.025 - (ln(x) * 0.01)
// return 0.025 - Math.log(fontSize) * 0.01
}
function InterUIDynamicLineHeight(fontSize, weightClass) {
return Math.round(fontSize * l)
}
var NPOS = Number.MAX_SAFE_INTEGER
function Sample(fontSize) {
this.rootEl = sampleTemplate.cloneNode(true)
this.infoEl = $('.info', this.rootEl)
this.diEl = $('.di', this.rootEl)
this.diEl.classList.add('unavailable')
this.style = this.rootEl.style
this.maxBoxWidth = 0
this.fontSize = 0
this.tracking = 0
this.lineHeight = 0
this.idealTracking = NPOS
this.matchesIdeal = false
this.setFontSize(fontSize)
this.render()
}
Sample.prototype.idealDistance = function(fontSize) {
// returns the distance from the ideal (or NPOS if no "ideal" data available)
if (this.idealTracking == NPOS) {
return NPOS
}
return (
Math.max(this.tracking, this.idealTracking) -
Math.min(this.tracking, this.idealTracking)
)
}
Sample.prototype.setFontSize = function(fontSize) {
this.fontSize = fontSize
this.tracking = baseTracking + InterUIDynamicTracking(fontSize, weightClass)
this.lineHeight = InterUIDynamicLineHeight(fontSize, weightClass)
this.maxBoxWidth = Math.round(fontSize * (this.tracking + 1) * 25)
var idealTracking = idealValues[this.fontSize]
if (idealTracking === undefined) {
if (this.idealTracking != NPOS) {
this.diEl.classList.add('unavailable')
}
this.idealTracking = NPOS
this.matchesIdeal = false
} else {
if (this.idealTracking == NPOS) {
this.diEl.classList.remove('unavailable')
}
this.idealTracking = idealTracking
// match to a precision of 3
this.matchesIdeal = this.tracking.toFixed(3) == idealTracking.toFixed(3)
var di = 1
if (this.matchesIdeal) {
this.diEl.classList.add('match')
} else {
this.diEl.classList.remove('match')
di = 1 - Math.min(1, this.idealDistance() * 50)
}
this.diEl.firstChild.style.width = (di * this.maxBoxWidth) + 'px'
}
}
Sample.prototype.render = function() {
this.style.fontSize = this.fontSize + 'px'
this.style.letterSpacing = this.tracking + 'em'
this.style.lineHeight = this.lineHeight + 'px'
var SP = '\u00A0\u00A0\u00A0'
this.infoEl.innerText = (
this.fontSize +
SP + this.tracking.toFixed(3) +
// SP + this.lineHeight.toFixed(3) +
(
this.idealTracking != NPOS ? (
SP + '( ' + this.idealTracking +
(this.matchesIdeal ? ' \u2713' : '') +
' )'
) : ''
)
)
this.style.maxWidth = this.maxBoxWidth + 'px'
}
var bindings = new Bindings()
var sampleTemplate
var samples = [] // Sample[]
var graph = new GraphPlot($('canvas.graphplot'))
graph.setOrigin(-0.2, 0.8)
graph.setScale(0.049, 5)
function addChildren(targetNode, children) {
targetNode.style.visibility = 'hidden'
var i = 0;
for (; i < children.length; i++) {
targetNode.appendChild(children[i])
}
targetNode.style.visibility = null
}
function initSamples() {
var samplesEl = $('.samples')
sampleTemplate = $('.sample', samplesEl)
samplesEl.removeChild(sampleTemplate)
// small and medium sizes
var i, fontSize = 6, endFontSize = 16
for (; fontSize <= endFontSize; fontSize++) {
samples.push(new Sample(fontSize))
}
// a few select large samples
samples.push(new Sample(17))
samples.push(new Sample(18))
samples.push(new Sample(20))
samples.push(new Sample(24))
samples.push(new Sample(30))
samples.push(new Sample(40))
// add to dom in one go
addChildren(samplesEl, samples.map(function(s) { return s.rootEl }))
}
function updateSample(sample) {
sample.setFontSize(sample.fontSize) // updates derived values
sample.render()
}
function updateSamples() {
samples.forEach(updateSample)
updateIdealMatches()
updateGraphPlot()
}
function updateIdealMatches() {
// ideal-distance
var idealCount = 0
var distance = 0
var ndistances = 0
samples.forEach(function(sample) {
if (sample.matchesIdeal) {
idealCount++
}
var di = sample.idealDistance()
if (di != NPOS) {
distance += di
ndistances++
}
})
distance = distance / ndistances
bindings.setValue('ideal-distance', distance.toFixed(6))
bindings.setValue('ideal-count', idealCount)
}
function updateGraphPlot() {
graph.clear()
graph.plotLine(idealValuesList, '#0d3')
graph.plotf(function(x) {
return InterUIDynamicTracking(x, weightClass)
})
}
bindings.configure('base-tracking', 0, 'float', function (tracking) {
baseTracking = tracking
updateSamples()
})
bindings.configure('var-a', a, 'float', function (v) {
a = v
updateSamples()
})
bindings.configure('var-b', b, 'float', function (v) {
b = v
updateSamples()
})
bindings.configure('var-c', c, 'float', function (v) {
c = v
updateSamples()
})
bindings.configure('var-l', l, 'float', function (v) {
l = v
updateSamples()
})
bindings.configure('ideal-count', 0, 'int', function (v) {})
bindings.configure('ideal-distance', 0, 'float', function (v) {})
bindings.configure('style', null, null, function (style) {
var cl = $('.samples').classList
cl.remove('font-style-regular')
cl.remove('font-style-italic')
cl.remove('font-style-medium')
cl.remove('font-style-medium-italic')
cl.remove('font-style-bold')
cl.remove('font-style-bold-italic')
cl.remove('font-style-black')
cl.remove('font-style-black-italic')
cl.add('font-style-' + style)
weightClass = (
style.indexOf('black') != -1 ? 900 :
style.indexOf('bold') != -1 ? 700 :
style.indexOf('medium') != -1 ? 500 :
400
)
updateSamples()
})
bindings.bindAllInputs('.control input')
bindings.bindAllInputs('.control select')
// double-click base tracking to reset
$('input[type="range"][data-binding="base-tracking"]').addEventListener(
'dblclick',
function(ev) { bindings.setValue('base-tracking', 0) }
)
// allow editing of ideal values
idealValuesTextArea.addEventListener('input', function(ev) {
setIdealValues(parseValues(idealValuesTextArea.value))
updateSamples()
})
// start
initSamples()
updateSamples()
})();</script>