<!DOCTYPE html> <meta charset="UTF-8"> <html> <head> <title>calligram.me</title> <style> /* latin */ @font-face { font-family: 'Ubuntu'; font-style: normal; font-weight: 400; src: url('https://fonts.gstatic.com/s/ubuntu/v20/4iCs6KVjbNBYlgoKfw72.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* latin-ext */ @font-face { font-family: 'Ubuntu'; font-style: normal; font-weight: 400; src: url('https://fonts.gstatic.com/s/ubuntu/v20/4iCs6KVjbNBYlgoKcQ72j00.woff2') format('woff2'); unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } body { background: #bcd3dc; /* Background pattern CC-BY-SA 3.0 from Irfan iLias via * <a href="https://www.toptal.com/designers/subtlepatterns" * title="Subtle Patterns">Subtle Patterns</a> */ background-image: url('./images/symphony_brown.png'); color: #000305; font-family: 'Ubuntu', 'Ubuntu Mono', 'Trebuchet MS', Trebuchet, 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; font-size:100% } #inputs { width: 100%; height: 100%; display: none; position: fixed; z-index: 1; } #hamburger { background: none; border: none; font-size:1vw; } #svg-target { width: 100%; height: 100%; overflow: visible; display: block; margin: auto; position: absolute; text-align: center; } svg { width: 92%; height: 92%; overflow: visible; display: flex; justify-content: center; align-self: stretch; position: absolute; left: 4%; right: 4%; } svg path { fill:transparent; stroke:none; stroke-width:.02em; } svg text { fill: brown; } </style> </head> <body> <button id="hamburger" onclick="showInputs()">🍔</button> <div id="inputs" style="display: none"> <p> <input id="the_text" type="text" value="" title="Place text here"> </p><p> <input id="the_svg" type="text" value="" title="Paste SVG path 'd' field here"> </p><p> <input id="the_dur" type="text" value="" title="Duration in seconds for animation" style="width: 3em"> <input id="the_offset" type="text" value="" title="Offset from 100% for animation bounds" style="width: 3em"> </p><p> <input id="the_rainbow" name = "textgradient" type="checkbox" value="Rainbow" title="Rainbow Colors?"> <input id="the_lines" name = "pathlines" type="checkbox" value="Lines" title="Show lines?"> <a href="" id=link_href title="link to this calligramme">link</a> <a href="https://git.jpnc.info/calligram.me/about/" title="link to agpl source repo">src</a> </p> </div> <div id="svg-target"></div> <script> // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt function showInputs() { const inputsDiv = document.getElementById('inputs'); if ( inputsDiv.style.display === "none" ) { inputsDiv.style.display = "block"; } else { inputsDiv.style.display = "none"; }; }; const targetDiv = document.getElementById('svg-target'); const svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svgNode.setAttributeNS(null, 'viewBox', '0 0 1500 1000'); svgNode.setAttributeNS(null, 'width', '100%'); svgNode.setAttributeNS(null, 'preserveAspectRatio', 'xMidyMid meet'); targetDiv.appendChild(svgNode); const rainbowDefs = document.createElementNS('http://www.w3.org/2000/svg', 'defs'); svgNode.appendChild(rainbowDefs); const rainbowGradients = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient'); rainbowGradients.setAttributeNS(null, 'id', 'Rainbow'); rainbowGradients.setAttributeNS(null, 'x1', '0%'); rainbowGradients.setAttributeNS(null, 'x2', '0%'); rainbowGradients.setAttributeNS(null, 'y1', '0%'); rainbowGradients.setAttributeNS(null, 'y2', '100%'); rainbowDefs.appendChild(rainbowGradients); const stop0 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop0.setAttributeNS(null, 'stop-color', 'red'); stop0.setAttributeNS(null, 'offset', '0%'); rainbowGradients.appendChild(stop0); const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop1.setAttributeNS(null, 'stop-color', 'orange'); stop1.setAttributeNS(null, 'offset', '18%'); rainbowGradients.appendChild(stop1); const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop2.setAttributeNS(null, 'stop-color', 'yellow'); stop2.setAttributeNS(null, 'offset', '34%'); rainbowGradients.appendChild(stop2); const stop3 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop3.setAttributeNS(null, 'stop-color', 'green'); stop3.setAttributeNS(null, 'offset', '50%'); rainbowGradients.appendChild(stop3); const stop4 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop4.setAttributeNS(null, 'stop-color', 'blue'); stop4.setAttributeNS(null, 'offset', '66%'); rainbowGradients.appendChild(stop4); const stop5 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop5.setAttributeNS(null, 'stop-color', 'indigo'); stop5.setAttributeNS(null, 'offset', '82%'); rainbowGradients.appendChild(stop5); const stop6 = document.createElementNS('http://www.w3.org/2000/svg', 'stop'); stop6.setAttributeNS(null, 'stop-color', 'violet'); stop6.setAttributeNS(null, 'offset', '100%'); rainbowGradients.appendChild(stop6); const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); svgPath.setAttributeNS(null, 'id', 'svgpath'); svgNode.appendChild(svgPath); const textNode = document.createElementNS('http://www.w3.org/2000/svg', 'text'); textNode.setAttributeNS(null, 'text-anchor', 'middle'); textNode.setAttributeNS(null, 'id', 'moving_text'); svgNode.appendChild(textNode); const textPath = document.createElementNS('http://www.w3.org/2000/svg', 'textPath'); textPath.setAttributeNS(null, 'startOffset', '0%'); textPath.setAttributeNS(null, 'href', '#svgpath'); textPath.textContent = "envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope envelope!"; textNode.appendChild(textPath); const animateLTR = document.createElementNS('http://www.w3.org/2000/svg', 'animate'); animateLTR.setAttributeNS(null, 'id', 'animateLTR'); animateLTR.setAttributeNS(null, 'attributeName', 'startOffset'); animateLTR.setAttributeNS(null, 'begin', 'animateRTL.end'); animateLTR.setAttributeNS(null, 'fill', 'freeze'); const animateRTL = document.createElementNS('http://www.w3.org/2000/svg', 'animate'); animateRTL.setAttributeNS(null, 'id', 'animateRTL'); animateRTL.setAttributeNS(null, 'attributeName', 'startOffset'); animateRTL.setAttributeNS(null, 'begin', '0s; animateLTR.end'); animateRTL.setAttributeNS(null, 'fill', 'freeze'); /* envelope icon by Amelia from https://thenounproject.com/icon/envelope-1526185/ under CC-BY-3.0 */ const _default_svg = "M82,20.3H18a13,13,0,0,0-13,13V66.7a13,13,0,0,0,13,13H82a13,13,0,0,0,13-13V33.3A13,13,0,0,0,82,20.3Zm-2.2,8L52.1,45.5a4,4,0,0,1-4.2,0L20.2,28.3ZM87,66.7a5,5,0,0,1-5,5H18a5,5,0,0,1-5-5V33.3h0L43.7,52.3a11.9,11.9,0,0,0,12.6,0L87,33.2h0Z"; updateSVG(_default_svg); textPath.appendChild(animateRTL); textPath.appendChild(animateLTR); // output current setup as URL for sharing/bookmarking function updateLink () { let _generated_url = ( window.location.origin != "null" ? window.location.origin : "" ) + window.location.pathname + "?lines=" + ( lines_input.checked ? 1 : 0 ) + "&rainbow=" + ( rainbow_input.checked ? 1 : 0 ) + "&dur=" + ( animateLTR.getAttribute('dur') ) + "&offset=" + ( animateLTR.getAttribute('to').slice(0,-1) - 100 ) + "&d=" + ( encodeURIComponent(svgPath.getAttribute('d')) ) + "&text=" + ( encodeURIComponent(textPath.textContent) ); document.getElementById('link_href').setAttribute('href', _generated_url); } var input = document.getElementById('the_text'); input.addEventListener("input", function () { updateValue(input.value); updateLink(); } ); function updateValue(_text) { textPath.textContent = (_text); updateAnim( (window.getComputedStyle(textPath, null).getPropertyValue('font-size').slice(0,-2)) * (100/(window.screen.width)) ); textPath.appendChild(animateRTL); textPath.appendChild(animateLTR); }; var svg_input = document.getElementById('the_svg'); svg_input.addEventListener("input", function () { updateSVG(svg_input.value); updateLink(); } ); function updateAnimNoArgs () { updateAnim( (window.getComputedStyle(textPath, null).getPropertyValue('font-size').slice(0,-2)) * (100/(window.screen.width)) ); updateLink(); }; var dur_input = document.getElementById('the_dur'); dur_input.addEventListener("input", function () { updateAnimNoArgs(); }); var offset_input = document.getElementById('the_offset'); offset_input.addEventListener("input", function () { updateAnimNoArgs(); }); function updateAnim (_font_size) { let _dur = dur_input ? Number(dur_input.value) : svgPath.getTotalLength() / ( 50 * ( _font_size ) ); animateLTR.setAttributeNS(null, 'dur', _dur); animateRTL.setAttributeNS(null, 'dur', _dur); let _offset = offset_input ? Number(offset_input.value) : ( ((( svgPath.getTotalLength() + textPath.textContent.length ) / svgPath.getTotalLength() ) * 100 ) - 100 - ( textPath.textContent.length * _font_size / 100) ) * .96; animateLTR.setAttributeNS(null, 'from', 0 - _offset + "%"); animateLTR.setAttributeNS(null, 'to', 100 + _offset + "%"); animateRTL.setAttributeNS(null, 'from', 100 + _offset + "%"); animateRTL.setAttributeNS(null, 'to', 0 - _offset + "%"); } function updateSVG(_d) { svgPath.setAttributeNS(null, 'd', _d); let _BB = svgPath.getBBox(); svgNode.setAttributeNS(null, 'viewBox', _BB.x + " " + _BB.y + " " + _BB.width + " " + _BB.height); let _font_size = ( ( ( _BB.width - _BB.x ) / window.screen.width ) * 4.5 ); targetDiv.style["font-size"] = _font_size + "vw"; updateAnim(_font_size); }; var rainbow_input = document.getElementById('the_rainbow'); rainbow_input.addEventListener("input", function () { if(rainbow_input.checked) { moving_text.style["fill"] = 'url(#Rainbow)'; } else { moving_text.style.removeProperty("fill"); } updateLink(); } ); var lines_input = document.getElementById('the_lines'); lines_input.addEventListener("input", function () { if(lines_input.checked) { svgpath.style["stroke"] = 'black'; } else { svgpath.style.removeProperty("stroke"); } updateLink(); } ); // allow inputs to be specified in query params const _queryparam_d = new URL(location.href).searchParams.get("d"); const _queryparam_text = new URL(location.href).searchParams.get("text"); const _queryparam_rainbow = new URL(location.href).searchParams.get("rainbow"); const _queryparam_lines = new URL(location.href).searchParams.get("lines"); const _queryparam_dur = new URL(location.href).searchParams.get("dur"); const _queryparam_offset = new URL(location.href).searchParams.get("offset"); if (_queryparam_d) { svg_input.setAttribute("value", _queryparam_d); updateSVG(_queryparam_d); }; if (_queryparam_text) { input.setAttribute("value", _queryparam_text); updateValue(_queryparam_text); }; if (_queryparam_rainbow == 1) { rainbow_input.click(); }; if (_queryparam_lines == 1) { lines_input.click(); }; if (_queryparam_dur) { dur_input.setAttribute("value", _queryparam_dur); updateAnimNoArgs(); }; if (_queryparam_offset) { offset_input.setAttribute("value", _queryparam_offset); updateAnimNoArgs(); }; updateLink(); // @license-end </script> </body> </html>