summaryrefslogtreecommitdiff
path: root/htdocs
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2022-07-09 19:40:17 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-07-09 19:40:17 +0300
commitf7bfdf58def6aadc922e1632f407d1418269a0d7 (patch)
treed7a0b2819e6a26c11d40ee0b27267ea827fbb345 /htdocs
initial
Diffstat (limited to 'htdocs')
-rw-r--r--htdocs/ahrefs_73b56e4c8d3bca4f4712e71f638a499c464e3faf55dd02ed02dbb5649850b8f31
-rw-r--r--htdocs/favicon.icobin0 -> 1150 bytes
-rw-r--r--htdocs/img/attachment.svg1
-rw-r--r--htdocs/img/contact.gifbin0 -> 164 bytes
-rw-r--r--htdocs/img/contact@2x.gifbin0 -> 386 bytes
-rw-r--r--htdocs/img/enter.svg1
-rw-r--r--htdocs/index.php28
-rw-r--r--htdocs/js/admin.js193
-rw-r--r--htdocs/js/common.js392
-rw-r--r--htdocs/openpgp-pubkey.txt51
-rw-r--r--htdocs/sass.php48
-rw-r--r--htdocs/scss/admin.scss3
-rw-r--r--htdocs/scss/blog.scss383
-rw-r--r--htdocs/scss/common-bundle.scss9
-rw-r--r--htdocs/scss/common.scss415
-rw-r--r--htdocs/scss/form.scss59
-rw-r--r--htdocs/scss/hljs.scss1
-rw-r--r--htdocs/scss/hljs/github.scss99
-rw-r--r--htdocs/scss/mobile.scss41
-rw-r--r--htdocs/scss/pages.scss14
-rw-r--r--htdocs/scss/vars.scss71
-rw-r--r--htdocs/yandex_3512181a57932602.html6
22 files changed, 1816 insertions, 0 deletions
diff --git a/htdocs/ahrefs_73b56e4c8d3bca4f4712e71f638a499c464e3faf55dd02ed02dbb5649850b8f3 b/htdocs/ahrefs_73b56e4c8d3bca4f4712e71f638a499c464e3faf55dd02ed02dbb5649850b8f3
new file mode 100644
index 0000000..cdf5a84
--- /dev/null
+++ b/htdocs/ahrefs_73b56e4c8d3bca4f4712e71f638a499c464e3faf55dd02ed02dbb5649850b8f3
@@ -0,0 +1 @@
+ahrefs-site-verification_73b56e4c8d3bca4f4712e71f638a499c464e3faf55dd02ed02dbb5649850b8f3 \ No newline at end of file
diff --git a/htdocs/favicon.ico b/htdocs/favicon.ico
new file mode 100644
index 0000000..d5ff579
--- /dev/null
+++ b/htdocs/favicon.ico
Binary files differ
diff --git a/htdocs/img/attachment.svg b/htdocs/img/attachment.svg
new file mode 100644
index 0000000..9026687
--- /dev/null
+++ b/htdocs/img/attachment.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="680" viewBox="-25 0 510 510.257" width="680"><path d="M32.39 314.484l37.74-37.738 169.815-169.808c31.297-31.063 81.817-30.97 112.997.21 31.18 31.18 31.273 81.7.21 112.997L183.345 390.004c-6.945 6.95-18.211 6.95-25.16 0-6.946-6.95-6.946-18.211 0-25.16l169.808-169.86c17.368-17.367 17.368-45.52 0-62.886-17.367-17.368-45.52-17.368-62.886 0L95.246 301.906l-37.738 37.735c-31.266 31.277-31.254 81.976.02 113.238 31.277 31.262 81.972 31.254 113.238-.023l31.441-31.454L378.31 245.301l12.582-12.578c43.976-45.352 43.418-117.602-1.25-162.274-44.672-44.668-116.922-45.226-162.274-1.25L38.687 257.883a17.797 17.797 0 0 1-29.765-7.977 17.796 17.796 0 0 1 4.606-17.183l188.68-188.68c59.09-58.82 154.64-58.711 213.593.246 58.957 58.953 59.066 154.504.246 213.594l-188.68 188.68-31.488 31.453c-45.41 43.617-117.375 42.89-161.89-1.641-44.52-44.527-45.227-116.492-1.598-161.89zm0 0" fill="#888"/></svg> \ No newline at end of file
diff --git a/htdocs/img/contact.gif b/htdocs/img/contact.gif
new file mode 100644
index 0000000..f4695c1
--- /dev/null
+++ b/htdocs/img/contact.gif
Binary files differ
diff --git a/htdocs/img/contact@2x.gif b/htdocs/img/contact@2x.gif
new file mode 100644
index 0000000..ab79cd3
--- /dev/null
+++ b/htdocs/img/contact@2x.gif
Binary files differ
diff --git a/htdocs/img/enter.svg b/htdocs/img/enter.svg
new file mode 100644
index 0000000..6fe49ed
--- /dev/null
+++ b/htdocs/img/enter.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="15" height="9.474"><path d="M0 4.737l4.737 4.737 1.105-1.106L3 5.526h12V.79h-1.579v3.158H3l2.842-2.842L4.737 0z"/></svg>
diff --git a/htdocs/index.php b/htdocs/index.php
new file mode 100644
index 0000000..a1199da
--- /dev/null
+++ b/htdocs/index.php
@@ -0,0 +1,28 @@
+<?php
+
+require_once __DIR__.'/../init.php';
+
+$r = (new Router())
+ // route handler input
+ // ----- ------- -----
+ ->add('/', 'index')
+ ->add('contacts/', 'contacts')
+ ->add('projects.html', 'projects')
+ ->add('blog/(\d+)/', 'post_id id=$(1)')
+ ->add('([a-z0-9-]+)/', 'auto name=$(1)')
+
+ ->add('feed.rss', 'RSS')
+
+ ->add('admin/', 'admin/index')
+ ->add('admin/{login,logout,log}/', 'admin/${1}')
+
+ ->add('([a-z0-9-]+)/{delete,edit}/', 'admin/auto_${1} short_name=$(1)')
+ ->add('([a-z0-9-]+)/create/', 'admin/page_add short_name=$(1)')
+ ->add('write/', 'admin/post_add')
+ ->add('admin/markdown-preview.ajax', 'admin/markdown_preview')
+
+ ->add('uploads/', 'admin/uploads')
+ ->add('uploads/{edit_note,delete}/(\d+)/','admin/upload_${1} id=$(1)')
+;
+
+(new RequestDispatcher($r))->dispatch(); \ No newline at end of file
diff --git a/htdocs/js/admin.js b/htdocs/js/admin.js
new file mode 100644
index 0000000..a717d5c
--- /dev/null
+++ b/htdocs/js/admin.js
@@ -0,0 +1,193 @@
+var LS = window.localStorage;
+
+var Draft = {
+ get: function() {
+ if (!LS) return null;
+
+ var title = LS.getItem('draft_title') || null;
+ var text = LS.getItem('draft_text') || null;
+
+ return {
+ title: title,
+ text: text
+ };
+ },
+
+ setTitle: function(text) {
+ if (!LS) return null;
+ LS.setItem('draft_title', text);
+ },
+
+ setText: function(text) {
+ if (!LS) return null;
+ LS.setItem('draft_text', text);
+ },
+
+ reset: function() {
+ if (!LS) return;
+ LS.removeItem('draft_title');
+ LS.removeItem('draft_text');
+ }
+};
+
+var AdminWriteForm = {
+ form: null,
+ previewTimeout: null,
+ previewRequest: null,
+
+ init: function(opts) {
+ opts = opts || {};
+
+ this.opts = opts;
+ this.form = document.forms[opts.pages ? 'pageForm' : 'postForm'];
+ this.isFixed = false;
+
+ addEvent(this.form, 'submit', this.onSubmit);
+ if (!opts.pages)
+ addEvent(this.form.title, 'input', this.onInput);
+
+ addEvent(this.form.text, 'input', this.onInput);
+ addEvent(ge('toggle_wrap'), 'click', this.onToggleWrapClick);
+
+ if (this.form.text.value !== '')
+ this.showPreview();
+
+ // TODO make it more clever and context-aware
+ /*var draft = Draft.get();
+ if (draft.title)
+ this.form.title.value = draft.title;
+ if (draft.text)
+ this.form.text.value = draft.text;*/
+
+ addEvent(window, 'scroll', this.onScroll);
+ addEvent(window, 'resize', this.onResize);
+ },
+
+ showPreview: function() {
+ if (this.previewRequest !== null) {
+ this.previewRequest.abort();
+ }
+ this.previewRequest = ajax.post('/admin/markdown-preview.ajax', {
+ title: this.form.elements.title.value,
+ md: this.form.elements.text.value,
+ use_image_previews: this.opts.pages ? 1 : 0
+ }, function(err, response) {
+ if (err)
+ return console.error(err);
+ ge('preview_html').innerHTML = response.html;
+ });
+ },
+
+ onSubmit: function(event) {
+ try {
+ var fields = ['title', 'text'];
+ if (!this.opts.pages)
+ fields.push('tags');
+ if (this.opts.edit) {
+ fields.push('new_short_name');
+ } else {
+ fields.push('short_name');
+ }
+ for (var i = 0; i < fields.length; i++) {
+ var field = fields[i];
+ if (event.target.elements[field].value.trim() === '')
+ throw 'no_'+field
+ }
+
+ // Draft.reset();
+ } catch (e) {
+ var error = typeof e == 'string' ? lang((this.opts.pages ? 'err_pages_' : 'err_blog_')+e) : e.message;
+ alert(error);
+ console.error(e);
+ return cancelEvent(event);
+ }
+ },
+
+ onToggleWrapClick: function(e) {
+ var textarea = this.form.elements.text;
+ if (!hasClass(textarea, 'nowrap')) {
+ addClass(textarea, 'nowrap');
+ } else {
+ removeClass(textarea, 'nowrap');
+ }
+ return cancelEvent(e);
+ },
+
+ onInput: function(e) {
+ if (this.previewTimeout !== null) {
+ clearTimeout(this.previewTimeout);
+ }
+ this.previewTimeout = setTimeout(function() {
+ this.previewTimeout = null;
+ this.showPreview();
+
+ // Draft[e.target.name === 'title' ? 'setTitle' : 'setText'](e.target.value);
+ }.bind(this), 300);
+ },
+
+ onScroll: function() {
+ var ANCHOR_TOP = 10;
+
+ var y = window.pageYOffset;
+ var form = this.form;
+ var td = ge('form_first_cell');
+ var ph = ge('form_placeholder');
+
+ var rect = td.getBoundingClientRect();
+
+ if (rect.top <= ANCHOR_TOP && !this.isFixed) {
+ ph.style.height = form.getBoundingClientRect().height+'px';
+
+ var w = (rect.width - (parseInt(getComputedStyle(td).paddingRight, 10) || 0));
+ form.style.display = 'block';
+ form.style.width = w+'px';
+ form.style.position = 'fixed';
+ form.style.top = ANCHOR_TOP+'px';
+
+ this.isFixed = true;
+ } else if (rect.top > ANCHOR_TOP && this.isFixed) {
+ form.style.display = '';
+ form.style.width = '';
+ form.style.position = '';
+ form.style.position = '';
+ ph.style.height = '';
+
+ this.isFixed = false;
+ }
+ },
+
+ onResize: function() {
+ if (this.isFixed) {
+ var form = this.form;
+ var td = ge('form_first_cell');
+ var ph = ge('form_placeholder');
+
+ var rect = td.getBoundingClientRect();
+ var pr = parseInt(getComputedStyle(td).paddingRight, 10) || 0;
+
+ ph.style.height = form.getBoundingClientRect().height+'px';
+ form.style.width = (rect.width - pr) + 'px';
+ }
+ }
+};
+bindEventHandlers(AdminWriteForm);
+
+var BlogUploadList = {
+ submitNoteEdit: function(action, note) {
+ if (note === null)
+ return;
+
+ var form = document.createElement('form');
+ form.setAttribute('method', 'post');
+ form.setAttribute('action', action);
+
+ var input = document.createElement('input');
+ input.setAttribute('type', 'hidden');
+ input.setAttribute('name', 'note');
+ input.setAttribute('value', note);
+
+ form.appendChild(input);
+ document.body.appendChild(form);
+ form.submit();
+ }
+};
diff --git a/htdocs/js/common.js b/htdocs/js/common.js
new file mode 100644
index 0000000..4e4199c
--- /dev/null
+++ b/htdocs/js/common.js
@@ -0,0 +1,392 @@
+if (!String.prototype.startsWith) {
+ String.prototype.startsWith = function(search, pos) {
+ pos = !pos || pos < 0 ? 0 : +pos;
+ return this.substring(pos, pos + search.length) === search;
+ };
+}
+
+if (!String.prototype.endsWith) {
+ String.prototype.endsWith = function(search, this_len) {
+ if (this_len === undefined || this_len > this.length) {
+ this_len = this.length;
+ }
+ return this.substring(this_len - search.length, this_len) === search;
+ };
+}
+
+//
+// AJAX
+//
+(function() {
+
+ var defaultOpts = {
+ json: true
+ };
+
+ function createXMLHttpRequest() {
+ if (window.XMLHttpRequest) {
+ return new XMLHttpRequest();
+ }
+
+ var xhr;
+ try {
+ xhr = new ActiveXObject('Msxml2.XMLHTTP');
+ } catch (e) {
+ try {
+ xhr = new ActiveXObject('Microsoft.XMLHTTP');
+ } catch (e) {}
+ }
+ if (!xhr) {
+ console.error('Your browser doesn\'t support XMLHttpRequest.');
+ }
+ return xhr;
+ }
+
+ function request(method, url, data, optarg1, optarg2) {
+ data = data || null;
+
+ var opts, callback;
+ if (optarg2 !== undefined) {
+ opts = optarg1;
+ callback = optarg2;
+ } else {
+ callback = optarg1;
+ }
+
+ opts = opts || {};
+
+ if (typeof callback != 'function') {
+ throw new Error('callback must be a function');
+ }
+
+ if (!url) {
+ throw new Error('no url specified');
+ }
+
+ switch (method) {
+ case 'GET':
+ if (isObject(data)) {
+ for (var k in data) {
+ if (data.hasOwnProperty(k)) {
+ url += (url.indexOf('?') == -1 ? '?' : '&')+encodeURIComponent(k)+'='+encodeURIComponent(data[k])
+ }
+ }
+ }
+ break;
+
+ case 'POST':
+ if (isObject(data)) {
+ var sdata = [];
+ for (var k in data) {
+ if (data.hasOwnProperty(k)) {
+ sdata.push(encodeURIComponent(k)+'='+encodeURIComponent(data[k]));
+ }
+ }
+ data = sdata.join('&');
+ }
+ break;
+ }
+
+ opts = extend({}, defaultOpts, opts);
+
+ var xhr = createXMLHttpRequest();
+ xhr.open(method, url);
+
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ if (method == 'POST') {
+ xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ }
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if ('status' in xhr && !/^2|1223/.test(xhr.status)) {
+ throw new Error('http code '+xhr.status)
+ }
+ if (opts.json) {
+ var resp = JSON.parse(xhr.responseText)
+ if (!isObject(resp)) {
+ throw new Error('ajax: object expected')
+ }
+ if (resp.error) {
+ throw new Error(resp.error)
+ }
+ callback(null, resp.response);
+ } else {
+ callback(null, xhr.responseText);
+ }
+ }
+ };
+
+ xhr.onerror = function(e) {
+ callback(e);
+ };
+
+ xhr.send(method == 'GET' ? null : data);
+
+ return xhr;
+ }
+
+ window.ajax = {
+ get: request.bind(request, 'GET'),
+ post: request.bind(request, 'POST')
+ }
+
+})();
+
+function bindEventHandlers(obj) {
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)
+ && typeof obj[k] == 'function'
+ && k.length > 2
+ && k.startsWith('on')
+ && k[2].charCodeAt(0) >= 65
+ && k[2].charCodeAt(0) <= 90) {
+ obj[k] = obj[k].bind(obj)
+ }
+ }
+}
+
+//
+// DOM helpers
+//
+function ge(id) {
+ return document.getElementById(id)
+}
+
+function hasClass(el, name) {
+ return el && el.nodeType === 1 && (" " + el.className + " ").replace(/[\t\r\n\f]/g, " ").indexOf(" " + name + " ") >= 0
+}
+
+function addClass(el, name) {
+ if (!el) {
+ return console.warn('addClass: el is', el)
+ }
+ if (!hasClass(el, name)) {
+ el.className = (el.className ? el.className + ' ' : '') + name
+ }
+}
+
+function removeClass(el, name) {
+ if (!el) {
+ return console.warn('removeClass: el is', el)
+ }
+ if (isArray(name)) {
+ for (var i = 0; i < name.length; i++) {
+ removeClass(el, name[i]);
+ }
+ return;
+ }
+ el.className = ((el.className || '').replace((new RegExp('(\\s|^)' + name + '(\\s|$)')), ' ')).trim()
+}
+
+function addEvent(el, type, f, useCapture) {
+ if (!el) {
+ return console.warn('addEvent: el is', el, stackTrace())
+ }
+
+ if (isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ addEvent(el, type[i], f, useCapture);
+ }
+ return;
+ }
+
+ if (el.addEventListener) {
+ el.addEventListener(type, f, useCapture || false);
+ return true;
+ } else if (el.attachEvent) {
+ return el.attachEvent('on' + type, f);
+ }
+
+ return false;
+}
+
+function removeEvent(el, type, f, useCapture) {
+ if (isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ var t = type[i];
+ removeEvent(el, type[i], f, useCapture);
+ }
+ return;
+ }
+
+ if (el.removeEventListener) {
+ el.removeEventListener(type, f, useCapture || false);
+ } else if (el.detachEvent) {
+ return el.detachEvent('on' + type, f);
+ }
+
+ return false;
+}
+
+function cancelEvent(evt) {
+ if (!evt) {
+ return console.warn('cancelEvent: event is', evt)
+ }
+
+ if (evt.preventDefault) evt.preventDefault();
+ if (evt.stopPropagation) evt.stopPropagation();
+
+ evt.cancelBubble = true;
+ evt.returnValue = false;
+
+ return false;
+}
+
+
+//
+// Cookies
+//
+function setCookie(name, value, days) {
+ var expires = "";
+ if (days) {
+ var date = new Date();
+ date.setTime(date.getTime() + (days*24*60*60*1000));
+ expires = "; expires=" + date.toUTCString();
+ }
+ document.cookie = name + "=" + (value || "") + expires + "; path=/";
+}
+
+function unsetCookie(name) {
+ document.cookie = name+'=; Max-Age=-99999999;';
+}
+
+function getCookie(name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) === ' ')
+ c = c.substring(1, c.length);
+ if (c.indexOf(nameEQ) === 0)
+ return c.substring(nameEQ.length, c.length);
+ }
+ return null;
+}
+
+//
+// Misc
+//
+function isObject(o) {
+ return Object.prototype.toString.call(o) === '[object Object]';
+}
+
+function isArray(a) {
+ return Object.prototype.toString.call(a) === '[object Array]';
+}
+
+function extend(dst, src) {
+ if (!isObject(dst)) {
+ return console.error('extend: dst is not an object');
+ }
+ if (!isObject(src)) {
+ return console.error('extend: src is not an object');
+ }
+ for (var key in src) {
+ dst[key] = src[key];
+ }
+ return dst;
+}
+
+function stackTrace(split) {
+ if (split === undefined) {
+ split = true;
+ }
+ try {
+ o.lo.lo += 0;
+ } catch(e) {
+ if (e.stack) {
+ var stack = split ? e.stack.split('\n') : e.stack;
+ stack.shift();
+ stack.shift();
+ return stack.join('\n');
+ }
+ }
+ return null;
+}
+
+function escape(str) {
+ var pre = document.createElement('pre');
+ var text = document.createTextNode(str);
+ pre.appendChild(text);
+ return pre.innerHTML;
+}
+
+//
+//
+
+function lang(key) {
+ return __lang[key] !== undefined ? __lang[key] : '{'+key+'}';
+}
+
+var DynamicLogo = {
+ dynLink: null,
+ afr: null,
+ afrUrl: null,
+
+ init: function() {
+ this.dynLink = ge('head_dyn_link');
+ this.cdText = ge('head_cd_text');
+
+ if (!this.dynLink) {
+ return console.warn('DynamicLogo.init: !this.dynLink');
+ }
+
+ var spans = this.dynLink.querySelectorAll('span.head-logo-path');
+ for (var i = 0; i < spans.length; i++) {
+ var span = spans[i];
+ addEvent(span, 'mouseover', this.onSpanOver);
+ addEvent(span, 'mouseout', this.onSpanOut);
+ }
+ },
+
+ setUrl: function(url) {
+ if (this.afr !== null) {
+ cancelAnimationFrame(this.afr);
+ }
+ this.afrUrl = url;
+ this.afr = requestAnimationFrame(this.onAnimationFrame);
+ },
+
+ onAnimationFrame: function() {
+ var url = this.afrUrl;
+
+ // update link
+ this.dynLink.setAttribute('href', url);
+
+ // update console text
+ if (this.afrUrl === '/') {
+ url = '~';
+ } else {
+ url = '~'+url.replace(/\/$/, '');
+ }
+ this.cdText.innerHTML = escape(url);
+
+ this.afr = null;
+ },
+
+ onSpanOver: function() {
+ var span = event.target;
+ this.setUrl(span.getAttribute('data-url'));
+ cancelEvent(event);
+ },
+
+ onSpanOut: function() {
+ var span = event.target;
+ this.setUrl('/');
+ cancelEvent(event);
+ }
+};
+bindEventHandlers(DynamicLogo);
+
+window.__lang = {};
+
+// set/remove retina cookie
+(function() {
+ var isRetina = window.devicePixelRatio >= 1.5;
+ if (isRetina) {
+ setCookie('is_retina', 1, 365);
+ } else {
+ unsetCookie('is_retina');
+ }
+})();
diff --git a/htdocs/openpgp-pubkey.txt b/htdocs/openpgp-pubkey.txt
new file mode 100644
index 0000000..5a96ec0
--- /dev/null
+++ b/htdocs/openpgp-pubkey.txt
@@ -0,0 +1,51 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=cgca
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/htdocs/sass.php b/htdocs/sass.php
new file mode 100644
index 0000000..eb24962
--- /dev/null
+++ b/htdocs/sass.php
@@ -0,0 +1,48 @@
+<?php
+
+require __DIR__.'/../init.php';
+global $config;
+
+$name = $_REQUEST['name'] ?? '';
+if (!$config['is_dev'] || !$name || !file_exists($path = ROOT.'/htdocs/scss/'.$name.'.scss')) {
+ // logError(__FILE__.': access denied');
+ http_response_code(403);
+ exit;
+}
+
+// logInfo(__FILE__.': continuing, path='.$path);
+
+$cmd = 'sassc -t expanded '.escapeshellarg($path);
+$descriptorspec = [
+ 0 => ['pipe', 'r'], // stdin
+ 1 => ['pipe', 'w'], // stdout
+ 2 => ['pipe', 'w'], // stderr
+];
+
+$process = proc_open($cmd, $descriptorspec, $pipes, ROOT);
+if (!is_resource($process)) {
+ http_response_code(500);
+ logError('could not open sassc process');
+ exit;
+}
+
+$stdout = stream_get_contents($pipes[1]);
+fclose($pipes[1]);
+
+$stderr = stream_get_contents($pipes[2]);
+fclose($pipes[2]);
+
+$code = proc_close($process);
+if ($code) {
+ http_response_code(500);
+ logError('sassc('.$path.') returned '.$code);
+ logError($stderr);
+ exit;
+}
+
+header('Content-Type: text/css');
+header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+
+echo $stdout;
diff --git a/htdocs/scss/admin.scss b/htdocs/scss/admin.scss
new file mode 100644
index 0000000..06808d0
--- /dev/null
+++ b/htdocs/scss/admin.scss
@@ -0,0 +1,3 @@
+.admin-page {
+ line-height: 155%;
+}
diff --git a/htdocs/scss/blog.scss b/htdocs/scss/blog.scss
new file mode 100644
index 0000000..7641683
--- /dev/null
+++ b/htdocs/scss/blog.scss
@@ -0,0 +1,383 @@
+@import 'vars';
+
+.blog-write-link-wrap {
+ margin-bottom: $base-padding;
+}
+.blog-write-table {
+ table-layout: fixed;
+ border-collapse: collapse;
+ border: 0;
+ width: 100%;
+
+ > tbody > tr > td {
+ text-align: left;
+ vertical-align: top;
+ }
+ > tbody > tr > td:first-child {
+ padding-right: 8px;
+ width: 45%;
+ }
+ > tbody > tr > td:last-child {
+ padding-left: 8px;
+ }
+}
+
+.blog-write-form {
+ .form-field-input {
+ width: 100%;
+ }
+ textarea.form-field-input {
+ height: 400px;
+ font-family: $ffMono;
+ font-size: 12px;
+ }
+ textarea.form-field-input.nowrap {
+ white-space: pre;
+ overflow-wrap: normal;
+ }
+}
+.blog-write-options-table {
+ width: 100%;
+ border-collapse: collapse;
+ table-layout: fixed;
+
+ td {
+ padding-top: 12px;
+ }
+ td:nth-child(1) {
+ width: 70%;
+ }
+ td:nth-child(2) {
+ width: 30%;
+ padding-left: 10px;
+ }
+ tr:first-child td {
+ padding-top: 0px;
+ }
+ button[type="submit"] {
+ margin-left: 3px;
+ }
+}
+
+.blog-write-form-toggle-link {
+ margin-top: 3px;
+ display: inline-block;
+}
+
+.blog-upload-form {
+ padding-bottom: $base-padding;
+}
+
+.blog-upload-list {}
+.blog-upload-item {
+ border-top: 1px $border-color solid;
+ padding: 10px 0;
+}
+.blog-upload-item-actions {
+ float: right;
+}
+.blog-upload-item-name {
+ font-weight: bold;
+ margin-bottom: 2px;
+}
+.blog-upload-item-info {
+ color: #888;
+ font-size: $fs - 2px;
+}
+.blog-upload-item-note {
+ padding: 0 0 4px;
+}
+.blog-upload-item-md {
+ margin-top: 3px;
+}
+
+.blog-post-title {
+ margin: 0 0 16px;
+}
+.blog-post-title h1 {
+ font-size: 22px;
+ font-weight: bold;
+ padding: 0;
+ margin: 0;
+}
+
+.blog-post-date {
+ color: #888;
+ margin-top: 5px;
+ font-size: $fs - 1px;
+ > a {
+ margin-left: 5px;
+ }
+}
+
+.blog-post-tags {
+ margin-top: 16px;
+ margin-bottom: -1px;
+}
+.blog-post-tags > a {
+ display: block;
+ float: left;
+ font-size: $fs - 1px;
+ margin-right: 8px;
+ cursor: pointer;
+}
+.blog-post-tags > a:last-child {
+ margin-right: 0;
+}
+.blog-post-tags > a > span {
+ opacity: 0.5;
+}
+
+.blog-post-text {}
+.blog-post-text {
+ li {
+ margin: 13px 0;
+ }
+
+ p {
+ margin-top: 13px;
+ margin-bottom: 13px;
+ }
+ p:first-child {
+ margin-top: 0;
+ }
+ p:last-child {
+ margin-bottom: 0;
+ }
+
+ pre {
+ background-color: $code-block-bg;
+ font-family: $ffMono;
+ //font-size: $fsMono;
+ overflow: auto;
+ @include radius(3px);
+ }
+
+ code {
+ background-color: $inline-code-block-bg;
+ font-family: $ffMono;
+ font-size: $fsMono;
+ padding: 3px 5px;
+ @include radius(3px);
+ }
+
+ pre code {
+ display: block;
+ padding: 12px;
+ line-height: 145%;
+ background-color: $code-block-bg;
+
+ span.term-prompt {
+ color: #999;
+ @include user-select(none);
+ }
+ }
+
+ blockquote {
+ border-left: 3px #e0e0e0 solid;
+ margin-left: 0;
+ padding: 5px 0 5px 12px;
+ color: #888;
+ }
+
+ table.table-100 {
+ border-collapse: collapse;
+ border: 0;
+ margin: 0;
+ width: 100%;
+ table-layout: fixed;
+ }
+ table.table-100 td {
+ padding: 0;
+ border: 0;
+ vertical-align: top;
+ text-align: left;
+ padding: 0 4px;
+ }
+ table.table-100 td:first-child {
+ padding-left: 0;
+ }
+ table.table-100 td:last-child {
+ padding-right: 0;
+ }
+ td > pre:first-child {
+ margin-top: 0;
+ }
+ td > pre:last-child {
+ margin-bottom: 0;
+ }
+
+ h1 {
+ margin: 40px 0 16px;
+ font-weight: 600;
+ font-size: 30px;
+ border-bottom: 1px $border-color solid;
+ padding-bottom: 8px;
+ }
+
+ h2 {
+ margin: 35px 0 16px;
+ font-weight: 500;
+ font-size: 25px;
+ border-bottom: 1px $border-color solid;
+ padding-bottom: 6px;
+ }
+
+ h3 {
+ margin: 27px 0 16px;
+ font-size: 24px;
+ font-weight: 500;
+ }
+
+ h4 {
+ font-size: 18px;
+ margin: 24px 0 16px;
+ }
+
+ h5 {
+ font-size: 15px;
+ margin: 24px 0 16px;
+ }
+
+ h6 {
+ font-size: 13px;
+ margin: 24px 0 16px;
+ color: #666;
+ }
+
+ h3:first-child,
+ h4:first-child,
+ h5:first-child,
+ h6:first-child {
+ margin-top: 0;
+ }
+ h1:first-child,
+ h2:first-child {
+ margin-top: 5px;
+ }
+
+ hr {
+ height: 1px;
+ border: 0;
+ background: $border-color;
+ margin: 17px 0;
+ }
+}
+.blog-post-comments {
+ margin-top: $base-padding;
+ padding: 12px 15px;
+ border: 1px #e0e0e0 solid;
+ @include radius(3px);
+}
+.blog-post-comments img {
+ vertical-align: middle;
+ position: relative;
+ top: -1px;
+ margin-left: 2px;
+}
+
+$blog-tags-width: 175px;
+
+.index-blog-block {
+ margin-top: 23px;
+}
+
+.blog-list {}
+.blog-list.withtags {
+ margin-right: $blog-tags-width + $base-padding*2;
+}
+.blog-list-title {
+ font-size: 22px;
+ margin-bottom: 15px;
+ > span {
+ margin-left: 2px;
+ > a {
+ font-size: 16px;
+ margin-left: 2px;
+ }
+ }
+}
+.blog-list-table-wrap {
+ padding: 5px 0;
+}
+.blog-list-table {
+ border-collapse: collapse;
+}
+.blog-list-table td {
+ vertical-align: top;
+ padding: 0 0 13px;
+}
+.blog-list-table tr:last-child td {
+ padding-bottom: 0;
+}
+td.blog-item-date-cell {
+ width: 1px;
+ white-space: nowrap;
+ text-align: right;
+ padding-right: 10px;
+}
+.blog-item-date {
+ color: #777;
+ //text-transform: lowercase;
+}
+td.blog-item-title-cell {
+ text-align: left;
+}
+.blog-item-title {
+ //font-weight: bold;
+}
+.blog-item-row {
+ font-size: $fs;
+ line-height: 140%;
+}
+.blog-item-row.ishidden a.blog-item-title {
+ color: $fg;
+}
+.blog-item-row-year {
+ td {
+ padding-top: 10px;
+ text-align: right;
+ font-size: 20px;
+ letter-spacing: -0.5px;
+ }
+ &:first-child td {
+ padding-top: 0;
+ }
+}
+a.blog-item-view-all-link {
+ display: inline-block;
+ padding: 4px 17px;
+ @include radius(5px);
+ background-color: #f4f4f4;
+ color: #555;
+ margin-top: 2px;
+}
+a.blog-item-view-all-link:hover {
+ text-decoration: none;
+ background-color: #ededed;
+}
+
+
+.blog-tags {
+ float: right;
+ width: $blog-tags-width;
+ padding-left: $base-padding - 10px;
+ border-left: 1px $border-color solid;
+}
+.blog-tags-title {
+ margin-bottom: 15px;
+ font-size: 22px;
+ padding: 0 7px;
+}
+.blog-tag-item {
+ padding: 6px 10px;
+ font-size: $fs - 1px;
+}
+.blog-tag-item > a {
+ color: #222;
+}
+.blog-tag-item-count {
+ color: #aaa;
+ margin-left: 6px;
+ text-decoration: none !important;
+}
diff --git a/htdocs/scss/common-bundle.scss b/htdocs/scss/common-bundle.scss
new file mode 100644
index 0000000..397f0c3
--- /dev/null
+++ b/htdocs/scss/common-bundle.scss
@@ -0,0 +1,9 @@
+@import "./common.scss";
+@import "./blog.scss";
+@import "./form.scss";
+@import "./hljs/github.scss";
+@import "./pages.scss";
+
+@media screen and (max-width: 600px) {
+ @import "./mobile.scss";
+}
diff --git a/htdocs/scss/common.scss b/htdocs/scss/common.scss
new file mode 100644
index 0000000..644a080
--- /dev/null
+++ b/htdocs/scss/common.scss
@@ -0,0 +1,415 @@
+@import "vars";
+
+.clearfix:after {
+ content: ".";
+ display: block;
+ clear: both;
+ visibility: hidden;
+ line-height: 0;
+ height: 0;
+}
+
+html, body {
+ padding: 0;
+ margin: 0;
+ border: 0;
+ //background-color: $bg;
+ color: $fg;
+ height: 100%;
+ min-height: 100%;
+}
+body {
+ font-family: $ff;
+ font-size: $fs;
+}
+
+.base-width {
+ max-width: $base-width;
+ margin: 0 auto;
+ position: relative;
+}
+
+body.full-width .base-width {
+ max-width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+input[type="text"], textarea {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ box-sizing: border-box;
+ border: 1px $input-border solid;
+ border-radius: 0px;
+ background-color: $input-bg;
+ color: $fg;
+ font-family: $ff;
+ font-size: $fs;
+ padding: 6px;
+ outline: none;
+ @include radius(3px);
+}
+textarea {
+ resize: vertical;
+}
+input[type="text"]:focus,
+textarea:focus {
+ border-color: $input-border-focused;
+}
+//input[type="checkbox"] {
+// margin-left: 0;
+//}
+
+//button {
+// border-radius: 2px;
+// background-color: $light-bg;
+// color: $fg;
+// padding: 7px 12px;
+// border: none;
+// /*box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);*/
+// font-family: $ff;
+// font-size: $fs - 1px;
+// outline: none;
+// cursor: pointer;
+// position: relative;
+//}
+//button:hover {
+// box-shadow: 0 1px 9px rgba(0, 0, 0, 0.2);
+//}
+//button:active {
+// top: 1px;
+//}
+
+a {
+ text-decoration: none;
+ color: $link-color;
+ outline: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+
+p, p code {
+ line-height: 150%;
+}
+
+.unicode { font-family: sans-serif; }
+
+.ff_ms { font-family: $ffMono }
+.fl_r { float: right }
+.fl_l { float: left }
+.pos_rel { position: relative }
+.pos_abs { position: absolute }
+.pos_fxd { position: fixed }
+
+.page-content {
+ padding: 0 $side-padding;
+}
+.page-content-inner {
+ padding: $base-padding 0;
+}
+
+.head {
+ padding: 0 $side-padding;
+}
+.head-inner {
+ //padding: 13px 0;
+ position: relative;
+ border-bottom: 2px $border-color solid;
+}
+.head-logo {
+ padding: 4px 0;
+ font-family: $ffMono;
+ font-size: 15px;
+ display: inline-block;
+ position: absolute;
+ left: 0;
+ background-color: transparent;
+ @include transition(background-color, 0.03s);
+}
+.head-logo {
+ padding: 16px 0;
+ background-color: #fff;
+}
+.head-logo:after {
+ content: '';
+ display: block;
+ width: 40px;
+ position: absolute;
+ right: -40px;
+ top: 0;
+ bottom: 0;
+ border-left: 8px #fff solid;
+ box-sizing: border-box;
+ background: linear-gradient(to left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); /* W3C */
+}
+.head-logo > a {
+ color: $fg;
+ font-size: 14px;
+}
+.head-logo > a:hover {
+ text-decoration: none;
+}
+.head-logo-enter {
+ display: inline;
+ opacity: 0;
+ font-size: 11px;
+ position: relative;
+ background: #eee;
+ padding: 2px 5px;
+ color: #333;
+ font-weight: normal;
+ vertical-align: middle;
+ top: -1px;
+ @include transition(opacity, 0.03s);
+}
+.head-logo-enter-icon {
+ width: 12px;
+ height: 7px;
+ display: inline-block;
+ background: url(/img/enter.svg) 0 0 no-repeat;
+ background-size: 12px 7px;
+ margin-right: 5px;
+}
+.head-logo > a:hover .head-logo-enter {
+ opacity: 1;
+}
+.head-logo-path {
+ color: $fg;
+ font-weight: bold;
+ -webkit-font-smoothing: antialiased;
+ @include transition(color, 0.03s);
+}
+.head-logo > a:hover .head-logo-path:not(.alwayshover) {
+ color: #aaa;
+}
+.head-logo-path:not(.neverhover):hover {
+ color: #000 !important;
+}
+.head-logo-dolsign {
+ color: $head-green-color;
+ font-weight: normal;
+ &.is_root {
+ color: $head-red-color;
+ }
+}
+.head-logo-cd {
+ display: none;
+}
+.head-logo > a:hover .head-logo-cd {
+ display: inline;
+}
+.head-logo-path-mapped {
+ padding: 3px 5px;
+ background: #f1f1f1;
+ pointer-events: none;
+ @include radius(3px);
+ margin: 0 2px;
+}
+
+.head-items {
+ float: right;
+ color: #777; // color of separators
+ //padding: 8px 0;
+}
+a.head-item {
+ color: $fg;
+ font-size: $fs - 1px;
+ display: block;
+ float: left;
+ padding: 16px 0;
+}
+a.head-item > span {
+ padding: 0 12px;
+ border-right: 1px #d0d0d0 solid;
+}
+a.head-item > span > span {
+ padding: 2px 0;
+}
+a.head-item:last-child > span {
+ border-right: 0;
+ padding-right: 1px;
+}
+/*a.head-item:first-child > span {
+ padding-left: 2px;
+}*/
+a.head-item:hover {
+ //color: $link-color;
+ text-decoration: none;
+}
+a.head-item:hover > span > span {
+ border-bottom: 1px #d0d0d0 solid;
+}
+
+table.contacts {
+ border: 0;
+ border-collapse: collapse;
+ margin: 8px auto 0;
+ //width: 100%;
+ //table-layout: fixed;
+}
+table.contacts td {
+ white-space: nowrap;
+ padding-bottom: 15px;
+ vertical-align: top;
+}
+table.contacts td.label {
+ text-align: right;
+ width: 30%;
+ color: #777;
+}
+table.contacts td.value {
+ text-align: left;
+ padding-left: 8px;
+}
+table.contacts td.value span {
+ background: #eee;
+ padding: 3px 7px 4px;
+ border-radius: 3px;
+ color: #333;
+ font-family: $ffMono;
+ font-size: $fs - 1px;
+}
+table.contacts td b {
+ font-weight: 600;
+}
+table.contacts td pre {
+ padding: 0;
+ margin: 0;
+ font-size: 12px;
+}
+
+table.contacts div.note {
+ font-size: $fs - 3px;
+ padding-top: 2px;
+ color: #777;
+ > a {
+ color: #777;
+ border-bottom: 1px #ccc solid;
+ &:hover {
+ text-decoration: none;
+ border-bottom-color: #999;
+ }
+ }
+}
+
+.pt {
+ margin: 5px 0 20px;
+ color: $dark-fg;
+ padding-bottom: 7px;
+ border-bottom: 2px rgba(255, 255, 255, 0.12) solid;
+}
+.pt h3 {
+ margin: 0;
+ display: inline-block;
+ font-weight: bold;
+ font-size: $fs;
+ color: $fg;
+}
+.pt h3:not(:first-child) {
+ margin-left: 5px;
+}
+.pt a {
+ margin-right: 5px;
+}
+.pt a:not(:first-child) {
+ margin-left: 5px;
+}
+.pt a, .pt h3 {
+ position: relative;
+ top: 1px;
+}
+.pt_r { margin-top: 5px }
+
+.empty {
+ text-align: center;
+ padding: 40px 20px;
+ color: $dark-fg;
+ @include radius(3px);
+ background-color: #f7f7f7;
+}
+
+.contact-img {
+ display: inline-block;
+ width: 77px;
+ height: 12px;
+ background: transparent url(/img/contact.gif?1) no-repeat;
+ background-size: 77px 12px;
+}
+@media (-o-min-device-pixel-ratio:3/2), (-webkit-min-device-pixel-ratio:1.5), (min--moz-device-pixel-ratio:1.5), (min-resolution:1.5dppx) {
+.contact-img {
+ background-image: url(/img/contact@2x.gif?1);
+}
+}
+
+.md-file-attach {
+ padding: 3px 0;
+}
+.md-file-attach-icon {
+ width: 14px;
+ height: 14px;
+ background: transparent url(/img/attachment.svg) no-repeat center center;
+ background-size: 14px 14px;
+ display: inline-block;
+ margin-right: 5px;
+ position: relative;
+ top: 1px;
+}
+.md-file-attach > a {
+ //font-weight: bold;
+}
+.md-file-attach-size {
+ color: #888;
+ margin-left: 2px;
+}
+.md-file-attach-note {
+ color: #000;
+ margin-left: 2px;
+}
+
+.md-image {
+ padding: 3px 0;
+ line-height: 0;
+ max-width: 100%;
+}
+.md-images {
+ margin-bottom: -8px;
+ padding: 3px 0;
+ max-width: 100%;
+}
+.md-images .md-image {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+.md-images > span {
+ display: block;
+ float: left;
+ margin: 0 8px 8px 0;
+ max-width: 100%;
+}
+.md-image.align-center { text-align: center; }
+.md-image.align-left { text-align: left; }
+.md-image.align-right { text-align: right; }
+.md-image-wrap {
+ display: inline-block;
+ max-width: 100%;
+ overflow: hidden;
+}
+.md-image-wrap > a {
+ display: block;
+ max-width: 100%;
+}
+.md-image-note {
+ line-height: 150%;
+ color: #777;
+ padding: 2px 0 4px;
+}
+
+.md-video video {
+ max-width: 100%;
+}
+
+.language-ascii {
+ line-height: 125% !important;
+}
diff --git a/htdocs/scss/form.scss b/htdocs/scss/form.scss
new file mode 100644
index 0000000..4faa4d1
--- /dev/null
+++ b/htdocs/scss/form.scss
@@ -0,0 +1,59 @@
+@import 'vars';
+
+$form-field-label-width: 120px;
+
+form { display: block; margin: 0; }
+
+.form-layout-h {
+ .form-field-wrap {
+ padding: 8px 0;
+ }
+ .form-field-label {
+ float: left;
+ width: $form-field-label-width;
+ text-align: right;
+ padding: 7px 0 0;
+ }
+ .form-field {
+ margin-left: $form-field-label-width + 10px;
+ }
+}
+
+.form-layout-v {
+ .form-field-wrap {
+ padding: 6px 0;
+ }
+ .form-field-wrap:first-child {
+ padding-top: 0;
+ }
+ .form-field-wrap:last-child {
+ padding-bottom: 0;
+ }
+ .form-field-label {
+ padding: 0 0 4px 4px;
+ font-weight: bold;
+ font-size: 12px;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+ color: #888;
+ }
+ .form-field {
+ //margin-left: $form-field-label-width + 10px;
+ }
+}
+
+.form-error {
+ padding: 10px 13px;
+ margin-bottom: $base-padding;
+ background-color: $error-block-bg;
+ color: $error-block-fg;
+ @include radius(3px);
+}
+
+.form-success {
+ padding: 10px 13px;
+ margin-bottom: $base-padding;
+ background-color: $success-block-bg;
+ color: $success-block-fg;
+ @include radius(3px);
+}
diff --git a/htdocs/scss/hljs.scss b/htdocs/scss/hljs.scss
new file mode 100644
index 0000000..36f52de
--- /dev/null
+++ b/htdocs/scss/hljs.scss
@@ -0,0 +1 @@
+@import "./hljs/github.css";
diff --git a/htdocs/scss/hljs/github.scss b/htdocs/scss/hljs/github.scss
new file mode 100644
index 0000000..791932b
--- /dev/null
+++ b/htdocs/scss/hljs/github.scss
@@ -0,0 +1,99 @@
+/*
+
+github.com style (c) Vasily Polovnyov <vast@whiteants.net>
+
+*/
+
+.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 0.5em;
+ color: #333;
+ background: #f8f8f8;
+}
+
+.hljs-comment,
+.hljs-quote {
+ color: #998;
+ font-style: italic;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-subst {
+ color: #333;
+ font-weight: bold;
+}
+
+.hljs-number,
+.hljs-literal,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-tag .hljs-attr {
+ color: #008080;
+}
+
+.hljs-string,
+.hljs-doctag {
+ color: #d14;
+}
+
+.hljs-title,
+.hljs-section,
+.hljs-selector-id {
+ color: #900;
+ font-weight: bold;
+}
+
+.hljs-subst {
+ font-weight: normal;
+}
+
+.hljs-type,
+.hljs-class .hljs-title {
+ color: #458;
+ font-weight: bold;
+}
+
+.hljs-tag,
+.hljs-name,
+.hljs-attribute {
+ color: #000080;
+ font-weight: normal;
+}
+
+.hljs-regexp,
+.hljs-link {
+ color: #009926;
+}
+
+.hljs-symbol,
+.hljs-bullet {
+ color: #990073;
+}
+
+.hljs-built_in,
+.hljs-builtin-name {
+ color: #0086b3;
+}
+
+.hljs-meta {
+ color: #999;
+ font-weight: bold;
+}
+
+.hljs-deletion {
+ background: #fdd;
+}
+
+.hljs-addition {
+ background: #dfd;
+}
+
+.hljs-emphasis {
+ font-style: italic;
+}
+
+.hljs-strong {
+ font-weight: bold;
+}
diff --git a/htdocs/scss/mobile.scss b/htdocs/scss/mobile.scss
new file mode 100644
index 0000000..d4d0d25
--- /dev/null
+++ b/htdocs/scss/mobile.scss
@@ -0,0 +1,41 @@
+@import 'vars';
+
+textarea {
+ -webkit-overflow-scrolling: touch;
+}
+
+// header
+.head-logo {
+ position: static;
+ display: block;
+ //padding-bottom: 6px;
+ // not very good fix:
+ overflow: hidden;
+ white-space: nowrap;
+ padding-bottom: 0;
+}
+.head-logo::after {
+ display: none;
+}
+.head-items {
+ float: none;
+}
+a.head-item:hover,
+a.head-item:active {
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important;
+}
+a.head-item:last-child > span {
+ border-right: 0;
+ padding-right: 12px;
+}
+a.head-item:first-child > span {
+ padding-left: 1px;
+}
+
+// blog
+.blog-tags {
+ display: none;
+}
+.blog-list.withtags {
+ margin-right: 0;
+}
diff --git a/htdocs/scss/pages.scss b/htdocs/scss/pages.scss
new file mode 100644
index 0000000..873a6ae
--- /dev/null
+++ b/htdocs/scss/pages.scss
@@ -0,0 +1,14 @@
+.page {
+
+}
+.page-edit-links {
+ display: none;
+ float: right;
+ font-size: 15px;
+ > a {
+ margin-left: 5px;
+ }
+}
+.page-content-inner:hover .page-edit-links {
+ display: block;
+}
diff --git a/htdocs/scss/vars.scss b/htdocs/scss/vars.scss
new file mode 100644
index 0000000..e056740
--- /dev/null
+++ b/htdocs/scss/vars.scss
@@ -0,0 +1,71 @@
+$fs: 16px;
+$fsMono: 85%;
+$ff: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
+$ffMono: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
+
+$base-width: 900px;
+//$sb-width: 120px;
+$side-padding: 25px;
+$base-padding: 18px;
+
+$footer-height: 64px;
+$head-green-color: #0bad19;
+$head-red-color: #ce1a1a;
+$link-color: #116fd4;
+
+$bg: #f7f7f7;
+$content-bg: #fff;
+$code-block-bg: #f3f3f3;
+$inline-code-block-bg: #f1f1f1;
+
+$fg: #222;
+$blue1: #729fcf;
+$blue2: #3465a4;
+$blue3: #204a87;
+$orange1: #fcaf3e;
+$orange2: #f57900;
+$orange3: #ce5c00;
+
+$light-bg: #464c4e;
+$dark-bg: #272C2D;
+$dark-fg: #999;
+
+$input-border: #e0e0e0;
+$input-border-focused: #e0e0e0;
+$input-bg: #f7f7f7;
+$border-color: #e0e0e0;
+
+$error-block-bg: #f9eeee;
+$error-block-fg: #d13d3d;
+
+$success-block-bg: #eff5f0;
+$success-block-fg: #2a6f34;
+
+@mixin radius($radius) {
+ -o-border-radius: $radius;
+ -ms-border-radius: $radius;
+ -moz-border-radius: $radius;
+ -webkit-border-radius: $radius;
+ border-radius: $radius;
+}
+
+@mixin transition($property, $duration, $easing: linear) {
+ transition: $property $duration $easing;
+ -webkit-transition: $property $duration $easing;
+ -moz-transition: $property $duration $easing;
+}
+
+@mixin linearGradient($top, $bottom){
+ background: -moz-linear-gradient(top, $top 0%, $bottom 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, $top 0%,$bottom 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, $top 0%,$bottom 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, $top 0%,$bottom 100%); /* IE10+ */
+ background: linear-gradient(to bottom, $top 0%,$bottom 100%); /* W3C */
+}
+
+@mixin user-select($value) {
+ -moz-user-select: $value;
+ -webkit-user-select: $value;
+ user-select: $value;
+}
diff --git a/htdocs/yandex_3512181a57932602.html b/htdocs/yandex_3512181a57932602.html
new file mode 100644
index 0000000..07384d2
--- /dev/null
+++ b/htdocs/yandex_3512181a57932602.html
@@ -0,0 +1,6 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ </head>
+ <body>Verification: 3512181a57932602</body>
+</html>