Change the noise parameters.
Change the color mapping.
Enjoy !
Created
October 27, 2019 03:09
-
-
Save artemuzz/4f2f65640ffca493cca1ab0f31bea5f0 to your computer and use it in GitHub Desktop.
simplex noise and color map
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id = 'general'> | |
<div id = 'menu'> | |
<div class="group"> | |
<p>width (200-2000 pixels) : <input type = 'text' size=3 id ='tWidth' value="400"></p> | |
<p>height (200-2000 pixels) : <input type = 'text' size=3 id ='tHeight' value="400"></p> | |
<hr> | |
<p>noise : </p> | |
<p>period (0.05 - 20) : <input type = 'text' size=5 id ='period' value="0.5"></p> | |
<p>octaves (0 - 5): <input type = 'text' size=3 id ='octaves' value="2"></p> | |
<p>coeff. / octave (0.01 - 1) : <input type = 'text' size=3 id ='attenuation' value="0.2"></p> | |
<hr> | |
<p style = "text-align:center"><button type="button" id="newPicture" class="ld">new picture</button></p> | |
</div> | |
<div class="group"> | |
<p>color map :</p> | |
<canvas width = 330 height=60 id = "cMap"></canvas> | |
<p><button type="button" id="previous">< previous</button> | |
Stop <span id="nStop">1</span> of <span id="nbStops">2</span> | |
<button type="button" style ="float:right" id="next">next ></button></p> | |
<p>position : <input type = 'text' size = 5 id="position"> | |
color : <input type = 'color' id="color"></p> | |
<p> | |
<button type="button" id="insert">insert after</button> | |
<button type="button" style ="float:right" id="delete">delete</button> | |
</p> | |
<p style = "text-align:center"> | |
<button type="button" id="apply">Apply</button> | |
</p> | |
</div> | |
</div> | |
<div> | |
<canvas width = 100 height = 100 id = 'destCanvas'></canvas> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
window.onload = function() { | |
/* values from user interface */ | |
let tWidth,tHeight; | |
let period,pperiod, octaves, attenuation; | |
/* end of values from user interface */ | |
let destCtx; | |
let srcData; | |
let dimx, dimy; | |
/* canvas for color map */ | |
let ctxMap; | |
let hMap, wMap; | |
let x0Ruler, x1Ruler; // left & right position of rulers in color map | |
let yRuler1; // top position | |
const hRuler = 20; // width of ruler | |
const colorResol = 1024; // resolution of color map | |
let tbMap; // table for color mapping | |
let tbMap2; // tbMap 'exploded' in 'colorResol' levels | |
let tbMap3; // == tbMap2, with separate r,g,b | |
let nStop = 0; // number of current selected stop in UI | |
/* shortcuts for Math */ | |
const mrandom = frandom(9578634); | |
// const mrandom = Math.random; | |
const mfloor = Math.floor; | |
const mround = Math.round; | |
const mceil = Math.ceil; | |
const mabs = Math.abs; | |
const mmin = Math.min; | |
const mmax = Math.max; | |
const mPI = Math.PI; | |
const mPIS2 = Math.PI / 2; | |
const m2PI = Math.PI * 2; | |
const msin = Math.sin; | |
const mcos = Math.cos; | |
const matan2 = Math.atan2; | |
const mexp = Math.exp; | |
const mhypot = Math.hypot; | |
const msqrt = Math.sqrt; | |
//----------------------------------------------------------------------------- | |
// miscellaneous functions | |
//----------------------------------------------------------------------------- | |
//----------------------------------------------------------------------------- | |
/* returns a random number generator function | |
the number returned by the returned function is in the range [0..1[ | |
without seed (or seed evaluating to false) the returned function is truly unpredictable | |
with the same integer value (10 figures typical) for seed, the returned function | |
will allways produce the same sequence. | |
*/ | |
function frandom (seed) { | |
let m_w = 123456789; | |
let m_z = 987654321; | |
const mask = 0xffffffff; | |
// Takes any integer | |
m_w = seed || (4294967296 * Math.random()); | |
if (! seed) m_z = (4294967296 * Math.random()) | |
// Returns number between 0 (inclusive) and 1.0 (exclusive), | |
// just like Math.random(). | |
return function() { | |
m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask; | |
m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask; | |
let result = ((m_z << 16) | m_w) & mask; | |
result /= 4294967296; | |
return result + 0.5; | |
} | |
// much inspired by https://prograide.com/pregunta/5883/appro-aleatoire | |
} // frandom | |
//----------------------------------------------------------------------------- | |
/* rgbToHsl and hslToRgb : code from stackOverflow */ | |
/** | |
* Converts an RGB color value to HSL. Conversion formula | |
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. | |
* Assumes r, g, and b are contained in the set [0, 255] and | |
* returns h, s, and l in the set [0, 1]. | |
* | |
* @param {number} r The red color value | |
* @param {number} g The green color value | |
* @param {number} b The blue color value | |
* @return {Array} The HSL representation | |
*/ | |
function rgbToHsl(r, g, b){ // this function copied from somewhere on Stack Overflow | |
r /= 255, g /= 255, b /= 255; | |
var max = mmax(r, g, b), min = mmin(r, g, b); | |
var h, s, l = (max + min) / 2; | |
if(max == min){ | |
h = s = 0; // achromatic | |
} else { | |
var d = max - min; | |
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | |
switch(max){ | |
case r: h = (g - b) / d + (g < b ? 6 : 0); break; | |
case g: h = (b - r) / d + 2; break; | |
case b: h = (r - g) / d + 4; break; | |
} | |
h /= 6; | |
} | |
return [h, s, l]; | |
} // function rgbToHsl | |
/** | |
* Converts an HSL color value to RGB. Conversion formula | |
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. | |
* Assumes h, s, and l are contained in the set [0, 1] and | |
* returns r, g, and b in the set [0, 255]. | |
* | |
* @param {number} h The hue | |
* @param {number} s The saturation | |
* @param {number} l The lightness | |
* @return {Array} The RGB representation | |
*/ | |
function hslToRgb(h, s, l){ | |
var r, g, b; | |
if(s == 0){ | |
r = g = b = l; // achromatic | |
}else{ | |
var hue2rgb = function hue2rgb(p, q, t){ | |
if(t < 0) t += 1; | |
if(t > 1) t -= 1; | |
if(t < 1/6) return p + (q - p) * 6 * t; | |
if(t < 1/2) return q; | |
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; | |
return p; | |
} | |
var q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
var p = 2 * l - q; | |
r = hue2rgb(p, q, h + 1/3); | |
g = hue2rgb(p, q, h); | |
b = hue2rgb(p, q, h - 1/3); | |
} | |
return [mround(r * 255), mround(g * 255), mround(b * 255)]; | |
} // function hslToRgb | |
//------------------------------------------------------------------------------ | |
function toHex2(number) { | |
var s = number.toString(16).toUpperCase(); | |
return (((s.length)<2) ?'0':'')+s; | |
} // toHex2 | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function rgbToCssString(arRgb) { | |
return '#' + toHex2(mround(arRgb[0]))+ | |
toHex2(mround(arRgb[1]))+ | |
toHex2(mround(arRgb[2])); | |
} // rgbToCssString | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function cssStringToRgb (str) { | |
return [parseInt ('0x' + str.substring(1,3), 16), | |
parseInt ('0x' + str.substring(3,5), 16), | |
parseInt ('0x' + str.substring(5,7), 16)]; | |
} // cssStringToRgb | |
//-------------------------------------------------------------------- | |
function lerp (x, x0, x1, y0, y1) { | |
return (y0 * (x1 - x) + y1 * (x - x0)) / (x1 - x0) | |
} // lerp | |
//-------------------------------------------------------------------- | |
function Interpolator (x0, x1, color0, color1) { | |
/* returns a function for color interpolation | |
supposed to make a smart choice between hsl and rgb interpolation | |
- x0 and x1 are numeric values. The returned function is supposed to be | |
called later with a parameter between x0 and x1 | |
- color0 and color1 are given in [r, g, b] arrays format. The returned function | |
will return a value with the same format. | |
*/ | |
let [r0, g0, b0] = color0; | |
let [r1, g1, b1] = color1; | |
let [h0, s0, l0] = rgbToHsl (r0, g0, b0); | |
let [h1, s1, l1] = rgbToHsl (r1, g1, b1); | |
let h, s, l; | |
let dx = x1 - x0; | |
// if x interval too short, just return color1 | |
if (mabs(dx) < 0.001) return ()=> color1; | |
/* if hue is not very well defined (small saturation or too dark or too light) | |
at at least one end, interpolate in rgb | |
else interpolate in hsl | |
Threshold levels subject to change */ | |
if (s0 < 0.1 || s1 < 0.1 || l0 < 0.1 || l1 < 0.1 || l0 > 0.9 || l1 > 0.9) { | |
return x => { | |
let dx0 = x - x0; | |
let dx1 = x1 - x; | |
return [(r0 * dx1 + r1 * dx0) / dx, | |
(g0 * dx1 + g1 * dx0) / dx, | |
(b0 * dx1 + b1 * dx0) / dx]; | |
}; | |
} // if rgb interpolation | |
/* hsl interpolation : find shorter path from h0 to h1 */ | |
if (h1 > h0 + 0.5) h0 += 1; | |
if (h0 > h1 + 0.5) h1 += 1; | |
// here, h0 and h1 may be greater than 1 | |
return x => { | |
let dx0 = x - x0; | |
let dx1 = x1 - x; | |
h = ((h0 * dx1 + h1 * dx0) / dx) % 1; // back to the 0-1 interval | |
s = (s0 * dx1 + s1 * dx0) / dx; | |
l = (l0 * dx1 + l1 * dx0) / dx; | |
return hslToRgb(h, s, l); | |
}; | |
} // Interpolator | |
//-------------------------------------------------------------------- | |
function createImage() { | |
let line, yNoise; | |
let x1, y2, ns; | |
let sn = (new SimplexNoise()); | |
/* read user parameters */ | |
readUI(); | |
dimx = tWidth; | |
dimy = tHeight; | |
const decalx = 0.123456; | |
const decaly = 2.123456; | |
let noise; | |
{ // block to limit the scope to some functions | |
let ampl = 1; | |
let accAmpl = 0; | |
for (let k = 0; k <= octaves; ++ k) { | |
accAmpl += ampl; | |
ampl *= attenuation; | |
} // for k | |
noise = function(x, y) { | |
let ampl = 1; | |
let per = period * dimx; | |
let acc = sn.noise2D( x / per, y / per); | |
for (let k = 1; k <= octaves; ++k) { | |
ampl *= attenuation; | |
per /= 2; | |
acc += sn.noise2D( x / per + decalx, y / per + decaly) * ampl; | |
} // for k | |
return mround((acc / accAmpl + 1 ) * 511.5); // 0..1023 | |
} // function noise | |
} | |
srcData = []; | |
for (let ky = 0; ky < dimy; ++ky) { | |
line = srcData[ky] = []; | |
for (let kx = 0; kx < dimx; ++kx) { | |
line[kx] = noise(kx, ky); | |
} // for kx | |
} // for ky | |
colorImage(); | |
} // function createImage | |
//-------------------------------------------------------------------- | |
function colorImage() { | |
let kx, ky; | |
let line, pix, offs; | |
let destCanvas = document.getElementById('destCanvas'); | |
destCanvas.width = dimx; | |
destCanvas.height = dimy; | |
destCtx = destCanvas.getContext ('2d'); | |
destCtx.fillStyle = '#000'; | |
destCtx.fillRect (0, 0, dimx, dimy); | |
let nData = destCtx.createImageData(dimx, dimy); | |
offs = 0; | |
for (ky = 0 ; ky < dimy; ++ky) { | |
line = srcData[ky]; | |
for (kx = 0 ; kx < dimx; ++kx) { | |
pix = tbMap3[line[kx]]; | |
nData.data[offs] = pix[0]; // r | |
nData.data[offs+1] = pix[1]; // g | |
nData.data[offs+2] = pix[2]; // b | |
nData.data[offs+3] = 255; // opacity | |
offs += 4; | |
} // for kx | |
} // for ky | |
destCtx.putImageData(nData, 0, 0); | |
} // filterImage | |
//-------------------------------------------------------------------- | |
//-------------------------------------------------------------------- | |
function readUI() { | |
// read user interface - except color map | |
getTWidth(); | |
getTHeight(); | |
getPeriod(); | |
getOctaves(); | |
getAttenuation(); | |
} // readUI | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function getTWidth() { | |
// width | |
let ctrl = document.getElementById('tWidth'); | |
let x = parseInt(ctrl.value, 10); | |
if (isNaN (x)) { x = tWidth } | |
if (x < 200) x = 200; | |
if (x > 2000) x = 2000; | |
ctrl.value = tWidth = x; | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function getTHeight() { | |
// height | |
let ctrl = document.getElementById('tHeight'); | |
let x = parseInt(ctrl.value, 10); | |
if (isNaN (x)) { x = tHeight } | |
if (x < 200) x = 200; | |
if (x > 2000) x = 2000; | |
ctrl.value = tHeight = x; | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function getPeriod() { | |
// period for noise | |
let ctrl = document.getElementById('period'); | |
let x = parseFloat(ctrl.value, 10); | |
if (isNaN (x)) { x = period } | |
if (x < 0.05) x = 0.05; | |
if (x > 20) x = 20; | |
ctrl.value = period = x; | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function getOctaves() { | |
// number of octaves | |
let ctrl = document.getElementById('octaves'); | |
let x = parseInt(ctrl.value, 10); | |
if (isNaN (x)) { x = octaves } | |
if (x < 0) x = 0; | |
if (x > 5) x = 5; | |
ctrl.value = octaves = x; | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function getAttenuation() { | |
// multiplying coeff for each octave | |
let ctrl = document.getElementById('attenuation'); | |
let x = parseFloat(ctrl.value); | |
if (isNaN (x)) { x = attenuation } | |
if (x < 0.01) x = 0.01; | |
if (x > 1) x = 1; | |
ctrl.value = attenuation = x; | |
} | |
//-------------------------------------------------------------------- | |
function drawRuler1 () { | |
ctxMap.clearRect(x0Ruler, yRuler1, x1Ruler - x0Ruler, hRuler); | |
ctxMap.lineWidth = 2; | |
for (let k = 0; k < (x1Ruler - x0Ruler); ++k) { | |
ctxMap.beginPath(); | |
ctxMap.moveTo (x0Ruler + k, yRuler1); | |
ctxMap.lineTo (x0Ruler + k, yRuler1 + hRuler); | |
ctxMap.strokeStyle = tbMap2[mround(k * colorResol / (x1Ruler - x0Ruler) )]; | |
ctxMap.stroke(); | |
} // for k | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function explodeTbMap () { | |
/* re-orders the points of tbMap by level | |
pre-computes the (colorResol) shades to use for final drawing */ | |
let level0, color0; | |
let level1, color1; | |
let level; | |
let interp, rInterp; | |
let ky = 0; | |
tbMap.sort((vala, valb) => { | |
if (vala.level < valb.level) return -1; | |
if (vala.level > valb.level) return 1; | |
return 0; | |
}); | |
tbMap2 = []; | |
tbMap3 = []; | |
let p1 = tbMap[0]; | |
for (let k = 1 ; k < tbMap.length; ++k) { | |
({level: level0, color: color0} = p1); | |
p1 = tbMap[k]; | |
({level: level1, color:color1} = p1); | |
interp = Interpolator (level0, level1, color0, color1); | |
while (ky <= colorResol && (level = ky / colorResol) <= level1) { | |
tbMap3[ky] = rInterp = interp(level); | |
tbMap2[ky++] = rgbToCssString(rInterp); | |
} // while ky | |
} // for k | |
interp = null; | |
} // | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function updateUIMap() { | |
/* updates user interface for mapping of colors BUT explodeTbMap and drawRuler1 */ | |
if (nStop < 0) nStop = 0; | |
if (nStop >= tbMap.length) nStop = tbMap.length - 1; | |
document.getElementById("nStop").innerHTML = nStop + 1; // humans begin to count at 1 | |
document.getElementById("nbStops").innerHTML = tbMap.length; | |
document.getElementById("previous").style.visibility = ((nStop == 0) ? 'hidden' : 'visible'); | |
document.getElementById("next").style.visibility = ((nStop >= tbMap.length - 1) ? 'hidden' : 'visible'); | |
document.getElementById("insert").style.visibility = ((nStop >= tbMap.length - 1) ? 'hidden' : 'visible'); | |
document.getElementById("delete").style.visibility = ((nStop == 0 || nStop >= tbMap.length - 1) ? 'hidden' : 'visible'); | |
if (nStop == 0 || nStop >= tbMap.length - 1) { | |
document.getElementById("position").setAttribute("disabled",""); | |
} else { | |
document.getElementById("position").removeAttribute("disabled"); | |
} | |
document.getElementById("position").value = tbMap[nStop].level; | |
document.getElementById("color").value = rgbToCssString(tbMap[nStop].color); | |
// draw triangle to display current stop | |
ctxMap.clearRect(x0Ruler - 5 , yRuler1 + hRuler + 1 , x1Ruler - x0Ruler + 10, 12); | |
let x = x0Ruler + (x1Ruler - x0Ruler) * tbMap[nStop].level; | |
ctxMap.beginPath(); | |
ctxMap.fillStyle = '#FFFFFF'; | |
ctxMap.moveTo (x, yRuler1 + hRuler + 2); | |
ctxMap.lineTo (x + 4, yRuler1 + hRuler + 2 + 10); | |
ctxMap.lineTo (x - 4, yRuler1 + hRuler + 2 + 10); | |
ctxMap.fill(); | |
} // | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function incNStop() { | |
++nStop; | |
updateUIMap(); | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function decNStop() { | |
--nStop; | |
updateUIMap(); | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function changePosition() { | |
let vs; | |
if (nStop == 0 || nStop >= tbMap.length - 1) return; // should not happen | |
let curStop = tbMap[nStop]; | |
let curV = curStop.level; | |
let v = Number.parseFloat (this.value); | |
if (Number.isNaN(v)) v = tbMap[nStop].level; | |
if (v <= 0.0001) v = 0.0001; | |
if (v >= 0.9999) v = 0.9999; | |
vs = v.toFixed(4); // string representation | |
v = Number.parseFloat(vs); // adjust value to representation | |
this.value = v; // update display to exact retained value | |
if (curV == v) return; // no real change | |
curStop.level = v; // update position in map | |
explodeTbMap(); // re-order stops and explode tbMap | |
// nStop may have changed | |
nStop = tbMap.findIndex(elem => (elem==curStop)); | |
updateUIMap(); | |
drawRuler1(); | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function changeColor() { | |
tbMap[nStop].color = cssStringToRgb(this.value); | |
explodeTbMap(); // re-order stops and explode tbMap | |
drawRuler1(); | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function insertStop() { | |
if (nStop >= tbMap.length - 1) return; // should not happen | |
let {level: lev0, color: color0 } = tbMap[nStop]; | |
let {level: lev1, color: color1 } = tbMap[nStop + 1]; | |
let newStop = {}; | |
newStop.level = (lev0 + lev1) / 2 | |
newStop.level = Number.parseFloat(newStop.level.toFixed(4)); | |
let interp = Interpolator (lev0, lev1, color0, color1); | |
newStop.color = interp(newStop.level); | |
tbMap.splice(++nStop, 0, newStop); | |
explodeTbMap(); | |
updateUIMap(); | |
drawRuler1(); | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function deleteStop() { | |
if (nStop == 0 || nStop >= tbMap.length - 1) return; // should not happen | |
tbMap.splice(nStop, 1); | |
--nStop; | |
explodeTbMap(); | |
updateUIMap(); | |
drawRuler1(); | |
} | |
//-------------------------------------------------------------------- | |
// beginning of execution | |
/* UI | |
/* initialize color map rules */ | |
ctxMap = document.getElementById('cMap').getContext('2d'); | |
hMap = hRuler + 30 ; | |
ctxMap.canvas.height = hMap; | |
wMap = ctxMap.canvas.width; | |
yRuler1 = 10; | |
x0Ruler = 10; | |
x1Ruler = wMap - x0Ruler; | |
// initial color map : shades of green | |
// 1st point MUST have level 0 and last point MUST have level 1 | |
tbMap = [{level: 0, color: [0, 0, 0]}, | |
{level: 0.25, color: [0, 0, 255]}, | |
{level: 0.345, color: [255, 0, 255]}, | |
{level: 0.346, color: [255, 255, 100]}, | |
{level: 0.399, color: [255, 255, 100]}, | |
{level: 0.4, color: [255, 0, 255]}, | |
{level: 0.55, color: [255, 180, 0]}, | |
{level: 0.80, color: [128, 90, 0]}, | |
{level: 0.81, color: [0, 0, 0]}, | |
{level: 1, color: [0, 0, 0]}]; | |
explodeTbMap(); | |
drawRuler1(); | |
updateUIMap(); | |
document.getElementById('newPicture').addEventListener ('click', createImage); | |
document.getElementById('tWidth').addEventListener ('change', getTWidth); | |
document.getElementById('tHeight').addEventListener ('change', getTHeight); | |
document.getElementById('period').addEventListener ('change', getPeriod); | |
document.getElementById('octaves').addEventListener ('change', getOctaves); | |
document.getElementById('attenuation').addEventListener ('change', getAttenuation); | |
document.getElementById("previous").addEventListener('click', decNStop); | |
document.getElementById("next").addEventListener('click', incNStop); | |
document.getElementById("position").addEventListener('change', changePosition); | |
document.getElementById("color").addEventListener('change', changeColor); | |
document.getElementById("insert").addEventListener('click', insertStop); | |
document.getElementById("delete").addEventListener('click', deleteStop); | |
document.getElementById("apply").addEventListener('click', colorImage); | |
createImage(); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://codepen.io/Dillo/pen/GRRNgVp"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
position:relative; | |
margin:0; | |
background-color: #444; | |
color: #FFF; | |
} | |
a, p, h1, h2, h3, h4, h5, h6, th, td, li { | |
font-family: Verdana, "Bitstream Vera Sans", "Lucida Grande", sans-serif; | |
} | |
#general { | |
display:flex; | |
} | |
#menu { | |
width: 340px; | |
} | |
#menu p, #menu button { | |
margin: 5px; | |
} | |
#destCanvas { | |
margin: 5px; | |
box-shadow: 5px 5px 5px #888; | |
background-color : #000; | |
} | |
#menu button.ld { | |
width:15ex; | |
} | |
div.group { | |
border: solid 1px #FFF; | |
width: 330px; | |
margin:5px; | |
} | |
.remark { | |
font-size : 80%; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment