1
1
mirror of https://github.com/rsms/inter.git synced 2024-11-30 01:15:54 +03:00
inter/docs/dynmetrics/index.html

684 lines
19 KiB
HTML
Raw Normal View History

2018-02-20 12:38:51 +03:00
---
layout: default
title: Dynamic Metrics
---
{%
2019-02-06 22:49:18 +03:00
if site.safe == false %}{%
assign url_root = "/" %}{% else %}{%
assign url_root = "/inter/" %}{% endif %}{%
2018-02-20 12:38:51 +03:00
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" %}{%
2020-04-10 01:54:01 +03:00
elsif _path == "/r/bindings.js" %}{%
2018-02-20 12:38:51 +03:00
assign bindings_js_v = file.modified_time | date: "%Y%m%d%H%M%S" %}{%
2020-04-10 01:54:01 +03:00
elsif _path == "/r/graphplot.js" %}{%
2018-02-20 12:38:51 +03:00
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 }}">
2020-04-10 01:54:01 +03:00
<script src="{{url_root}}r/bindings.js?v={{ bindings_js_v }}"></script>
<script src="{{url_root}}r/graphplot.js?v={{ graphplot_js_v }}"></script>
2018-02-20 12:38:51 +03:00
<div class="row first"><div>
<h1>Dynamic Metrics</h1>
<p>
There's of course no absolute right or wrong when it comes to expressing
2019-02-03 01:29:56 +03:00
yourself with typography, but Inter Dynamic Metrics provides guidelines
for how to best use Inter.
2018-02-21 05:12:29 +03:00
You simply provide the optical font size,
2019-02-03 08:01:00 +03:00
and the tracking and line height is calculated for you through the following
2018-02-20 12:38:51 +03:00
formula:
</p>
<p>
<formula>
tracking =
<const>a</const> + <const>b</const> ×
2018-02-21 23:20:04 +03:00
<const title="Base of natural logarithm; ≈2.718">e</const><sup>(<const>c</const> × <const>z</const>)</sup>
2018-02-20 12:38:51 +03:00
</formula>
2019-02-06 22:49:18 +03:00
<!--formula>
2019-02-03 08:01:00 +03:00
line height = <num data-binding="var-l">1.4</num> × <const>z</const>
2019-02-06 22:49:18 +03:00
</formula-->
2019-02-03 01:29:56 +03:00
<formula title="Values for Inter">
2019-02-08 05:18:10 +03:00
<g><const title="Constant a">a</const> = <num data-binding="var-a">-0.0223</num></g> &nbsp;&nbsp;
<g><const title="Constant b">b</const> = <num data-binding="var-b">0.185</num></g> &nbsp;&nbsp;
<g><const title="Constant c">c</const> = <num data-binding="var-c">-0.1745</num></g> &nbsp;&nbsp;
2018-02-21 23:20:04 +03:00
<g><const>z</const> = font size</g>
2018-02-20 12:38:51 +03:00
</formula>
</p>
2018-02-21 05:12:29 +03:00
<p class="wide-window">
The controls below can be used to play around with the
<const>a</const>, <const>b</const> and <const>c</const> constants to discover
how they affect tracking.
</p>
<p class="narrow-window">
2018-02-20 12:38:51 +03:00
<small>View this on a larger screen to enable interactive control.</small>
</p>
</div></div>
<div class="white full-width row with-sidebar">
<div class="samples">
2018-02-21 05:12:29 +03:00
<p class="sample">
2018-02-20 12:38:51 +03:00
<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>
2018-02-21 05:12:29 +03:00
<span contenteditable class="content">
2018-02-20 12:38:51 +03:00
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.
2018-02-21 05:12:29 +03:00
</span>
2018-02-20 12:38:51 +03:00
</p>
</div>
<div class="sidebar controls">
2020-02-09 04:05:32 +03:00
2018-02-20 12:38:51 +03:00
<div class="control">
2019-02-06 22:49:18 +03:00
<img title="Style" class="icon" src="icons/style.svg">
2018-02-20 12:38:51 +03:00
<select data-binding="style">
2020-02-09 04:05:32 +03:00
<option value="thin">Thin</option>
<option value="extra-light">Extra Ligh</option>
<option value="light">Light</option>
<option value="regular" default selected>Regular</option>
<option value="medium">Medium</option>
<option value="semi-bold">Semi Bold</option>
<option value="bold">Bold</option>
<option value="extra-bold">Extra Bold</option>
<option value="black">Black</option>
2020-02-09 04:05:32 +03:00
<option disabled>————————————</option>
<option value="thin-italic">Thin Italic</option>
<option value="extra-light-italic">Extra Light Italic</option>
<option value="light-italic">Light Italic</option>
<option value="italic">Italic</option>
<option value="medium-italic">Medium Italic</option>
<option value="semi-bold-italic">Semi Bold Italic</option>
<option value="bold-italic">Bold Italic</option>
<option value="extra-bold-italic">Extra Bold Italic</option>
2018-02-20 12:38:51 +03:00
<option value="black-italic">Black Italic</option>
</select>
</div>
<div class="control">
2019-02-06 22:49:18 +03:00
<img title="Base tracking" class="icon" src="icons/letter-spacing.svg">
2018-02-20 12:38:51 +03:00
<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>
2019-02-06 22:49:18 +03:00
<div class="control">
<img title="Line height" class="icon" src="icons/line-height.svg">
<input type="range" min="1" max="2" step="0.01" data-binding="var-l">
<input type="number" min="1" max="2" step="0.01" data-binding="var-l">
</div>
2018-02-20 12:38:51 +03:00
<hr>
2019-02-06 22:49:18 +03:00
2018-02-20 12:38:51 +03:00
<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>
2020-02-09 04:05:32 +03:00
2018-02-20 12:38:51 +03:00
<hr>
2020-02-09 04:05:32 +03:00
2018-02-20 12:38:51 +03:00
<div class="control">
2019-02-06 22:49:18 +03:00
<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">
2018-02-20 12:38:51 +03:00
</div>
2019-02-06 22:49:18 +03:00
2018-02-20 12:38:51 +03:00
<hr class="without-bottom-margin">
2019-02-06 22:49:18 +03:00
2018-02-20 12:38:51 +03:00
<canvas class="graphplot">Canvas not Supported</canvas>
2019-02-06 22:49:18 +03:00
2018-02-21 05:12:29 +03:00
<hr class="when-selection without-top-margin">
<h3 class="when-selection">CSS</h3>
<textarea class="when-selection" readonly id="code-output"></textarea>
2018-02-20 12:38:51 +03:00
<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
2018-02-21 05:12:29 +03:00
function round(num, prec) {
return parseFloat(num.toFixed(prec))
}
2018-02-20 12:38:51 +03:00
// 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({
2019-02-06 22:49:18 +03:00
// // 2018
// 6: 0.021,
// 7: 0.017,
// 8: 0.013,
// 9: 0.01,
// 10: 0.007,
// 11: 0.005,
// 12: 0.002,
// 13: 0,
// 14: -0.002,
// 15: -0.004,
// 16: -0.005,
// 17: -0.007,
// 18: -0.008,
// 20: -0.01,
// 24: -0.013,
// 30: -0.016,
// 40: -0.02,
// 80: -0.02,
// 2019-02-02
// 6: 0.066,
// 7: 0.05,
// 8: 0.036,
// 9: 0.025,
// 10: 0.015,
// 11: 0.007,
// 12: 0,
// 13: -0.005,
// 14: -0.01,
// 15: -0.014,
// 16: -0.017,
// 17: -0.02,
// 18: -0.022,
// 20: -0.026,
// 24: -0.03,
// 30: -0.033,
// 40: -0.034,
// 80: -0.034,
2019-02-08 05:18:10 +03:00
// // 2019-02-06
// 6: 0.04,
// 7: 0.032,
// 8: 0.024,
// 9: 0.017,
// 10: 0.01,
// 11: 0.005,
// 12: 0,
// 13: -0.004,
// 14: -0.008,
// 15: -0.011,
// 16: -0.014,
// 17: -0.017,
// 18: -0.019,
// 20: -0.023,
// 24: -0.029,
// 30: -0.034,
// 40: -0.037,
// 80: -0.038,
// 2019-02-07
6: 0.043,
2019-02-06 22:49:18 +03:00
7: 0.032,
8: 0.024,
2019-02-08 05:18:10 +03:00
9: 0.016,
2019-02-06 22:49:18 +03:00
10: 0.01,
11: 0.005,
12: 0,
2019-02-08 05:18:10 +03:00
13: -0.0025,
14: -0.006,
15: -0.009,
16: -0.011,
17: -0.013,
18: -0.014,
20: -0.017,
24: -0.019,
30: -0.021,
40: -0.022,
80: -0.022,
2018-02-20 12:38:51 +03:00
})
// Variables for constants involved in Dynamic Metrics
// var a = -0.012, b = 0.23, c = -0.21; // di=0.002514 on set-2018-02-18
// var a = -0.013, b = 0.251, c = -0.222 // di=0.001742 on set-2018-02-18
// var a = -0.015, b = 0.283, c = -0.23; // di=0.00221 on set-2018-02-18
// var a = -0.0149, b = 0.298, c = -0.23; // di=0.000484 on set-2018-02-19
2018-09-28 23:33:56 +03:00
// var a = -0.018, b = 0.21, c = -0.18; // di=0.000532 on set-2018-02-20
2019-02-03 08:01:00 +03:00
// var a = -0.017, b = 0.202, c = -0.175; // 2018-09-28
2019-02-06 22:49:18 +03:00
// var a = -0.02, b = 0.0755, c = -0.102 // 2019-02-02
2019-02-08 05:18:10 +03:00
// var a = -0.038, b = 0.161, c = -0.12 // 2019-02-06
var a = -0.0223, b = 0.185, c = -0.1745 // 2019-02-07
2019-02-03 08:01:00 +03:00
2018-02-20 12:38:51 +03:00
var l = 1.4
2019-02-03 01:29:56 +03:00
// _InterDynamicTracking is a version of InterDynamicTracking that
2018-02-21 23:20:04 +03:00
// uses some global variables that are adjustable.
2018-02-20 12:38:51 +03:00
//
2019-02-03 01:29:56 +03:00
function _InterDynamicTracking(fontSize, weightClass) {
// Note: weightClass is currently unused
2018-02-20 12:38:51 +03:00
//
// 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)
}
2019-02-03 01:29:56 +03:00
function InterDynamicLineHeight(fontSize, weightClass) {
2018-02-20 12:38:51 +03:00
return Math.round(fontSize * l)
}
var NPOS = Number.MAX_SAFE_INTEGER
function Sample(fontSize) {
this.rootEl = sampleTemplate.cloneNode(true)
this.infoEl = $('.info', this.rootEl)
2018-02-21 05:12:29 +03:00
this.contentEl = $('.content', this.rootEl)
2018-02-20 12:38:51 +03:00
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()
2018-02-21 05:12:29 +03:00
// listen for focus events on the editable content
var s = this
this.contentEl.addEventListener(
'focus',
function(){ s.onReceivedFocus() },
{passive:true, capture:false}
)
this.contentEl.addEventListener(
'blur',
function(){ s.onLostFocus() },
{passive:true, capture:false}
)
2018-02-20 12:38:51 +03:00
}
2018-02-21 05:12:29 +03:00
Sample.prototype.onReceivedFocus = function() {}
Sample.prototype.onLostFocus = function() {}
2018-02-20 12:38:51 +03:00
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
2019-02-03 01:29:56 +03:00
this.tracking = baseTracking + _InterDynamicTracking(fontSize, weightClass)
this.lineHeight = InterDynamicLineHeight(fontSize, weightClass)
2018-02-20 12:38:51 +03:00
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'
}
}
2018-02-21 05:12:29 +03:00
Sample.prototype.cssProperties = function() {
2018-02-21 23:20:04 +03:00
return [
['font-size', round(this.fontSize, 3) + 'px'],
['letter-spacing', round(this.tracking, 3) + 'em'],
['line-height', round(this.lineHeight, 3) + 'px'],
]
2018-02-21 05:12:29 +03:00
}
2019-02-03 08:01:00 +03:00
Sample.prototype.setText = function(text) {
this.contentEl.innerText = text
this.render()
}
2018-02-20 12:38:51 +03:00
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()
2018-02-21 05:12:29 +03:00
var samplesEl = $('.samples')
2018-02-20 12:38:51 +03:00
var sampleTemplate
var samples = [] // Sample[]
2018-02-21 05:12:29 +03:00
var focusedSample = null // Sample | null
2018-02-20 12:38:51 +03:00
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() {
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))
2019-02-03 08:01:00 +03:00
samples.push(new Sample(80))
2018-02-20 12:38:51 +03:00
2018-02-21 05:12:29 +03:00
// connect focus events
var onSampleReceivedFocus = function() { setSelectedSample(this) }
samples.forEach(function(s) {
s.onReceivedFocus = onSampleReceivedFocus
})
2018-02-20 12:38:51 +03:00
// add to dom in one go
addChildren(samplesEl, samples.map(function(s) { return s.rootEl }))
}
2018-02-21 05:12:29 +03:00
function setSelectedSample(sample) {
if (focusedSample !== sample) {
if (focusedSample) {
focusedSample.rootEl.classList.remove('selected')
}
if (sample) {
sample.rootEl.classList.add('selected')
}
focusedSample = sample
updateSelection()
}
}
2018-02-20 12:38:51 +03:00
function updateSample(sample) {
sample.setFontSize(sample.fontSize) // updates derived values
sample.render()
}
function updateSamples() {
samples.forEach(updateSample)
updateIdealMatches()
updateGraphPlot()
2018-02-21 05:12:29 +03:00
updateCodeView()
2018-02-20 12:38:51 +03:00
}
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
2020-02-09 04:05:32 +03:00
2018-02-20 12:38:51 +03:00
bindings.setValue('ideal-distance', distance.toFixed(6))
bindings.setValue('ideal-count', idealCount)
}
function updateGraphPlot() {
graph.clear()
graph.plotLine(idealValuesList, '#0d3')
2019-02-08 05:18:10 +03:00
graph.plotf(
x => _InterDynamicTracking(x, weightClass),
'rgba(0, 0, 0, 0.5)'
)
2018-02-21 05:12:29 +03:00
if (focusedSample) {
var graphedFontSize = Math.min(24, focusedSample.fontSize) // clamp to [-inf,24]
graph.plotPoints([
[graphedFontSize, focusedSample.tracking]
], 'rgb(45, 143, 255)')
}
}
var codeOutput = $('#code-output')
codeOutput.addEventListener('focus', function(ev) {
ev.preventDefault()
ev.stopPropagation()
codeOutput.select()
}, {passive:false,capture:true})
2018-02-21 23:20:04 +03:00
codeOutput.addEventListener('pointerdown', function(ev) {
// TODO: don't do this if codeOutput is focused
ev.preventDefault()
ev.stopPropagation()
codeOutput.select()
document.execCommand("copy")
HUDNotification.show('Copied to clipboard')
}, {passive:false,capture:true})
2018-02-21 05:12:29 +03:00
function updateCodeView() {
var s = ''
if (focusedSample) {
2018-02-21 23:20:04 +03:00
var props = focusedSample.cssProperties()
2018-02-21 05:12:29 +03:00
props.forEach(function(prop, i) {
2018-02-21 23:20:04 +03:00
var name = prop[0], value = prop[1]
s += name + ': ' + value + ';'
2018-02-21 05:12:29 +03:00
if (i != props.length-1) {
s += '\n'
}
})
}
codeOutput.value = s
}
function updateSelection() {
var controlsEl = $('.controls')
if (focusedSample) {
controlsEl.classList.add('has-selected-sample')
} else {
controlsEl.classList.remove('has-selected-sample')
}
updateGraphPlot()
updateCodeView()
2018-02-20 12:38:51 +03:00
}
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) {
2018-02-21 05:12:29 +03:00
var cl = samplesEl.classList
2018-02-20 12:38:51 +03:00
cl.remove('font-style-regular')
cl.remove('font-style-italic')
cl.remove('font-style-medium')
cl.remove('font-style-medium-italic')
2018-09-28 23:33:56 +03:00
cl.remove('font-style-semi-bold')
cl.remove('font-style-semi-bold-italic')
2018-02-20 12:38:51 +03:00
cl.remove('font-style-bold')
cl.remove('font-style-bold-italic')
2018-09-28 23:33:56 +03:00
cl.remove('font-style-extra-bold')
cl.remove('font-style-extra-bold-italic')
2018-02-20 12:38:51 +03:00
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()
})
2018-02-20 13:10:20 +03:00
bindings.bindAllInputs('[data-binding]')
2018-02-20 12:38:51 +03:00
// double-click base tracking to reset
$('input[type="range"][data-binding="base-tracking"]').addEventListener(
'dblclick',
function(ev) { bindings.setValue('base-tracking', 0) }
)
2018-09-28 23:33:56 +03:00
;[
['var-a',a],
['var-b',b],
['var-c',c],
['var-l',l],
['base-tracking', 0]
].forEach(p => {
let bindname = p[0], defval = p[1]
$$('input[type="range"][data-binding="'+bindname+'"]').forEach(e => {
e.addEventListener('dblclick', ev =>
bindings.setValue(bindname, defval))
})
})
2018-02-20 12:38:51 +03:00
// allow editing of ideal values
idealValuesTextArea.addEventListener('input', function(ev) {
setIdealValues(parseValues(idealValuesTextArea.value))
updateSamples()
})
2018-02-21 05:12:29 +03:00
// listen for clicks onto "background", to deselect any selected sample
document.body.addEventListener('pointerdown', function(ev){
if (
ev.target === document.body ||
ev.target === samplesEl ||
ev.target.classList && ev.target.classList.contains('row')
) {
setSelectedSample(null)
}
})
2018-02-20 12:38:51 +03:00
// start
initSamples()
updateSamples()
})();</script>