~johanvandegriff/vandy.land

2fd908c2a3378ae4885db4383d2ac035ff109c1a — Johan Vandegriff 1 year, 4 months ago
initial files
5 files changed, 2513 insertions(+), 0 deletions(-)

A .static
A css-doodle-0.7.3.js
A css-doodle-0.7.3.min.js
A index.html
A style.css
A  => .static +0 -0
A  => css-doodle-0.7.3.js +2311 -0
@@ 1,2311 @@
(function (factory) {
  typeof define === 'function' && define.amd ? define(factory) :
  factory();
}(function () { 'use strict';

  function iterator(input) {
    let index = 0, col = 1, line = 1;
    return {
      curr(n = 0) {
        return input[index + n];
      },
      end() {
        return input.length <= index;
      },
      info() {
        return { index, col, line };
      },
      index(n) {
        return (n === undefined ? index : index = n);
      },
      next() {
        let next = input[index++];
        if (next == '\n') line++, col = 0;
        else col++;
        return next;
      }
    };
  }

  // I'll make it work first
  function parse(it) {
    let word = '', marks = [];
    let groups = [], result = {};

    while(!it.end()) {
      let c = it.curr();
      if (c == '(') {
        marks.push(c);
        word = '';
      }
      else if (c == ')' || c == ',') {
        if (/^\-\-.+/.test(word)) {
          if (!result.name) {
            result.name = word;
          } else {
            if (!result.alternative) {
              result.alternative = [];
            }
            result.alternative.push({
              name: word
            });
          }
        }

        if (c == ')') {
          if (marks[marks.length - 1] == '(') {
            marks.pop();
          } else {
            throw new Error('bad match');
          }
        }

        if (c == ',') {
          if (!marks.length) {
            groups.push(result);
            result = {};
          }
        }

        word = '';
      }
      else if (!/\s/.test(c)) {
        word += c;
      }
      it.next();
    }

    if (marks.length) {
      return [];
    }

    if (result.name) {
      groups.push(result);
    }
    return groups;
  }

  function parse_var(input) {
    input = input.trim();
    let result = [];
    if (!/^var\(/.test(input)) {
      return result;
    }
    let it = iterator(input);
    try {
      result = parse(it);
    } catch (e) {
      console.error(e && e.message || 'Bad variables.');
    }
    return result;
  }

  function make_array(arr) {
    return Array.isArray(arr) ? arr : [arr];
  }

  function join(arr, spliter = '\n') {
    return (arr || []).join(spliter);
  }

  function last(arr) {
    return arr[arr.length - 1];
  }

  function first(arr) {
    return arr[0];
  }

  function clone(arr) {
    return JSON.parse(JSON.stringify(arr));
  }

  function shuffle(arr) {
    let ret = Array.from ? Array.from(arr) : arr.slice();
    let m = arr.length;
    while (m) {
      let i = ~~(Math.random() * m--);
      let t = ret[m];
      ret[m] = ret[i];
      ret[i] = t;
    }
    return ret;
  }

  function flat_map(arr, fn) {
    if (Array.prototype.flatMap) return arr.flatMap(fn);
    return arr.reduce((acc, x) => acc.concat(fn(x)), []);
  }

  const Tokens = {
    func(name = '') {
      return {
        type: 'func',
        name,
        arguments: []
      };
    },
    argument() {
      return {
        type: 'argument',
        value: []
      };
    },
    text(value = '') {
      return {
        type: 'text',
        value
      };
    },
    pseudo(selector = '') {
      return {
        type: 'pseudo',
        selector,
        styles: []
      };
    },
    cond(name = '') {
      return {
        type: 'cond',
        name,
        styles: [],
        arguments: []
      };
    },
    rule(property = '') {
      return {
        type: 'rule',
        property,
        value: []
      };
    },
    keyframes(name = '') {
      return {
        type: 'keyframes',
        name,
        steps: []
      }
    },

    step(name = '') {
      return {
        type: 'step',
        name,
        styles: []
      }
    }
  };

  const is = {
    white_space(c) {
      return /[\s\n\t]/.test(c);
    },
    line_break(c) {
      return /\n/.test(c);
    },
    number(n) {
      return !isNaN(n);
    },
    pair(n) {
      return ['"', '(', ')', "'"].includes(n);
    },
    pair_of(c, n) {
      return ({ '"': '"', "'": "'", '(': ')' })[c] == n;
    }
  };

  function throw_error(msg, { col, line }) {
    console.error(
      `(at line ${ line }, column ${ col }) ${ msg }`
    );
  }

  function get_text_value(input) {
    if (input.trim().length) {
      return is.number(+input) ? +input : input.trim()
    } else {
      return input;
    }
  }

  function read_until(fn) {
    return function(it, reset) {
      let index = it.index();
      let word = '';
      while (!it.end()) {
        let c = it.next();
        if (fn(c)) break;
        else word += c;
      }
      if (reset) {
        it.index(index);
      }
      return word;
    }
  }

  function read_word(it, reset) {
    let check = c => /[^\w@]/.test(c);
    return read_until(check)(it, reset);
  }

  function read_keyframe_name(it) {
    return read_until(c => /[\s\{]/.test(c))(it);
  }

  function read_line(it, reset) {
    let check = c => is.line_break(c) || c == '{';
    return read_until(check)(it, reset);
  }

  function read_step(it, extra) {
    let c, step = Tokens.step();
    while (!it.end()) {
      if ((c = it.curr()) == '}') break;
      if (is.white_space(c)) {
        it.next();
        continue;
      }
      else if (!step.name.length) {
        step.name = read_selector(it);
      }
      else {
        step.styles.push(read_rule(it, extra));
        if (it.curr() == '}') break;
      }
      it.next();
    }
    return step;
  }

  function read_steps(it, extra) {
    const steps = [];
    let c;
    while (!it.end()) {
      if ((c = it.curr()) == '}') break;
      else if (is.white_space(c)) {
        it.next();
        continue;
      }
      else {
        steps.push(read_step(it, extra));
      }
      it.next();
    }
    return steps;
  }

  function read_keyframes(it, extra) {
    let keyframes = Tokens.keyframes(), c;
    while (!it.end()) {
      if ((c = it.curr()) == '}') break;
      else if (!keyframes.name.length) {
        read_word(it);
        keyframes.name = read_keyframe_name(it);
        if (!keyframes.name.length) {
          throw_error('missing keyframes name', it.info());
          break;
        }
        continue;
      }
      else if (c == '{') {
        it.next();
        keyframes.steps = read_steps(it, extra);
        break;
      }
      it.next();
    }
    return keyframes;
  }

  function read_comments(it, flag = {}) {
    it.next();
    while (!it.end()) {
      let c = it.curr();
      if (flag.inline) {
        if (c == '\n') break;
      }
      else {
        if ((c = it.curr()) == '*' && it.curr(1) == '/') break;
      }
      it.next();
    }
    if (!flag.inline) {
      it.next(); it.next();
    }
  }

  function read_property(it) {
    let prop = '', c;
    while (!it.end()) {
      if ((c = it.curr()) == ':') break;
      else if (!is.white_space(c)) prop += c;
      it.next();
    }
    return prop;
  }

  function read_arguments(it) {
    let args = [], group = [], stack = [], arg = '', c;
    while (!it.end()) {
      c = it.curr();

      if ((/[\('"`]/.test(c) && it.curr(-1) !== '\\')) {
        if (stack.length) {
          if (c != '(' && c === last(stack)) {
            stack.pop();
          } else {
            stack.push(c);
          }
        } else {
          stack.push(c);
        }
        arg += c;
      }
      else if (c == '@') {
        if (!group.length) {
          arg = arg.trimLeft();
        }
        if (arg.length) {
          group.push(Tokens.text(arg));
          arg = '';
        }
        group.push(read_func(it));
      }
      else if (/[,)]/.test(c)) {
        if (stack.length) {
          if (c == ')') {
            stack.pop();
          }
          arg += c;
        }

        else {
          if (arg.length) {
            if (!group.length) {
              group.push(Tokens.text(get_text_value(arg)));
            } else {
              group.push(Tokens.text(arg));
            }

            if (arg.startsWith('±')) {
              let raw = arg.substr(1);
              let cloned = clone(group);
              last(cloned).value = '-' + raw;
              args.push(normalize_argument(cloned));
              last(group).value = raw;
            }
          }

          args.push(normalize_argument(group));
          [group, arg] = [[], ''];

          if (c == ')') break;
        }
      }
      else {
        arg += c;
      }
      it.next();
    }
    return args;
  }

  function normalize_argument(group) {
    let result = group.map(arg => {
      if (arg.type == 'text' && typeof arg.value == 'string') {
        let value = String(arg.value);
        if (value.includes('`')) {
          arg.value = value = value.replace(/`/g, '"');
        }
        arg.value = value.replace(/\n+|\s+/g, ' ');
      }
      return arg;
    });

    let ft = first(result) || {};
    let ed = last(result) || {};
    if (ft.type == 'text' && ed.type == 'text') {
      let cf = first(ft.value);
      let ce  = last(ed.value);
      if (typeof ft.value == 'string' && typeof ed.value == 'string') {
        if (is.pair(cf) && is.pair_of(cf, ce)) {
          ft.value = ft.value.slice(1);
          ed.value = ed.value.slice(0, ed.value.length - 1);
        }
      }
    }
    return result;
  }

  function read_func(it) {
    let func = Tokens.func();
    let extra = '', name = '', c;
    while (!it.end()) {
      if ((c = it.curr()) == ')') break;
      if (c == '(') {
        it.next();
        func.name = name;
        func.arguments = read_arguments(it);
        if (/\d$/.test(name)) {
          func.name = name.split(/\d+/)[0];
          extra = name.split(/\D+/)[1];
        }
        if (extra.length) {
          func.arguments.unshift([{
            type: 'text',
            value: extra
          }]);
        }
        func.position = it.info().index;
        break;
      }
      else name += c;
      it.next();
    }
    return func;
  }

  function read_value(it) {
    let text = Tokens.text(), idx = 0, skip = true, c;
    const value = [], stack = [];
    value[idx] = [];

    while (!it.end()) {
      c = it.curr();

      if (skip && is.white_space(c)) {
        it.next();
        continue;
      } else {
        skip = false;
      }

      if (c == '\n' && !is.white_space(it.curr(-1))) {
        text.value += ' ';
      }
      else if (c == ',' && !stack.length) {
        if (text.value.length) {
          value[idx].push(text);
          text = Tokens.text();
        }
        value[++idx] = [];
        skip = true;
      }
      else if (/[;}]/.test(c)) {
        if (text.value.length) {
          value[idx].push(text);
          text = Tokens.text();
        }
        break;
      }
      else if (c == '@') {
        if (text.value.length) {
          value[idx].push(text);
          text = Tokens.text();
        }
        value[idx].push(read_func(it));
      }
      else if (!is.white_space(c) || !is.white_space(it.curr(-1))) {
        if (c == '(') stack.push(c);
        if (c == ')') stack.pop();
        text.value += c;
      }
      it.next();
    }
    if (text.value.length) {
      value[idx].push(text);
    }
    return value;
  }

  function read_selector(it) {
    let selector = '', c;
    while (!it.end()) {
      if ((c = it.curr()) == '{') break;
      else if (!is.white_space(c)) {
        selector += c;
      }
      it.next();
    }
    return selector;
  }

  function read_cond_selector(it) {
    let selector = { name: '', arguments: [] }, c;
    while (!it.end()) {
      if ((c = it.curr()) == '(') {
        it.next();
        selector.arguments = read_arguments(it);
      }
      else if (/[){]/.test(c)) break;
      else if (!is.white_space(c)) selector.name += c;
      it.next();
    }
    return selector;
  }

  function read_pseudo(it, extra) {
    let pseudo = Tokens.pseudo(), c;
    while (!it.end()) {
      if ((c = it.curr()) == '}') break;
      if (is.white_space(c)) {
        it.next();
        continue;
      }
      else if (!pseudo.selector) {
        pseudo.selector = read_selector(it);
      }
      else {
        let rule = read_rule(it, extra);
        if (rule.property == '@use') {
          pseudo.styles = pseudo.styles.concat(
            rule.value
          );
        } else {
          pseudo.styles.push(rule);
        }
        if (it.curr() == '}') break;
      }
      it.next();
    }
    return pseudo;
  }

  function read_rule(it, extra) {
    let rule = Tokens.rule(), c;
    while (!it.end()) {
      if ((c = it.curr()) == ';') break;
      else if (!rule.property.length) {
        rule.property = read_property(it);
        if (rule.property == '@use') {
          rule.value = read_var(it, extra);
          break;
        }
      }
      else {
        rule.value = read_value(it);
        break;
      }
      it.next();
    }
    return rule;
  }

  function read_cond(it, extra) {
    let cond = Tokens.cond(), c;
    while (!it.end()) {
      if ((c = it.curr()) == '}') break;
      else if (!cond.name.length) {
        Object.assign(cond, read_cond_selector(it));
      }
      else if (c == ':') {
        let pseudo = read_pseudo(it);
        if (pseudo.selector) cond.styles.push(pseudo);
      }
      else if (c == '@' && !read_line(it, true).includes(':')) {
        cond.styles.push(read_cond(it));
      }
      else if (!is.white_space(c)) {
        let rule = read_rule(it, extra);
        if (rule.property) cond.styles.push(rule);
        if (it.curr() == '}') break;
      }
      it.next();
    }
    return cond;
  }

  function read_property_value(extra, name) {
    let rule = '';
    if (extra && extra.get_custom_property_value) {
      rule = extra.get_custom_property_value(name);
    }
    return rule;
  }

  function evaluate_value(values, extra) {
    values.forEach && values.forEach(v => {
      if (v.type == 'text' && v.value) {
        let vars = parse_var(v.value);
        v.value = vars.reduce((ret, p) => {
          let rule = '', other = '', parsed;
          rule = read_property_value(extra, p.name);
          if (!rule && p.alternative) {
            p.alternative.every(n => {
              other = read_property_value(extra, n.name);
              if (other) {
                rule = other;
                return false;
              }
            });
          }
          try {
            parsed = parse$1(rule, extra);
          } catch (e) { }
          if (parsed) {
            ret.push.apply(ret, parsed);
          }
          return ret;
        }, []);
      }
      if (v.type == 'func' && v.arguments) {
        v.arguments.forEach(arg => {
          evaluate_value(arg, extra);
        });
      }
    });
  }

  function read_var(it, extra) {
    it.next();
    let groups = read_value(it) || [];
    return groups.reduce((ret, group) => {
      evaluate_value(group, extra);
      let [token] = group;
      if (token.value && token.value.length) {
        ret.push(...token.value);
      }
      return ret;
    }, []);
  }

  function parse$1(input, extra) {
    const it = iterator(input);
    const Tokens = [];
    while (!it.end()) {
      let c = it.curr();
      if (is.white_space(c)) {
        it.next();
        continue;
      }
      else if (c == '/' && it.curr(1) == '*') {
        read_comments(it);
      }
      else if (c == '/' && it.curr(1) == '/') {
        read_comments(it, { inline: true });
      }
      else if (c == ':') {
        let pseudo = read_pseudo(it, extra);
        if (pseudo.selector) Tokens.push(pseudo);
      }
      else if (c == '@' && read_word(it, true) === '@keyframes') {
        let keyframes = read_keyframes(it, extra);
        Tokens.push(keyframes);
      }
      else if (c == '@' && !read_line(it, true).includes(':')) {
        let cond = read_cond(it, extra);
        if (cond.name.length) Tokens.push(cond);
      }
      else if (!is.white_space(c)) {
        let rule = read_rule(it, extra);
        if (rule.property) Tokens.push(rule);
      }
      it.next();
    }
    return Tokens;
  }

  function apply_args(fn, ...args) {
    return args.reduce((f, arg) =>
      f.apply(null, make_array(arg)), fn
    );
  }

  function clamp(num, min, max) {
    return Math.max(min, Math.min(max, num));
  }

  function maybe(cond, value) {
    if (!cond) return '';
    return (typeof value === 'function') ? value() : value;
  }

  function range(start, stop, step) {
    let count = 0, old = start;
    let initial = n => (n > 0 && n < 1) ? .1 : 1;
    let length = arguments.length;
    if (length == 1) [start, stop] = [initial(start), start];
    if (length < 3) step = initial(start);
    let range = [];
    while ((step >= 0 && start <= stop)
      || (step < 0 && start > stop)) {
      range.push(start);
      start += step;
      if (count++ >= 1000) break;
    }
    if (!range.length) range.push(old);
    return range;
  }

  function alias_for(obj, names) {
    Object.keys(names).forEach(n => {
      obj[n] = obj[names[n]];
    });
    return obj;
  }

  function is_letter(c) {
    return /^[a-zA-Z]$/.test(c);
  }

  function lazy(fn) {
    let wrap = () => fn;
    wrap.lazy = true;
    return wrap;
  }

  function sequence(count, fn) {
    let ret = [];
    for (let i = 0; i < count; ++i) {
      ret.push(fn(i));
    }
    return ret;
  }

  function cell_id(x, y, z) {
    return 'cell-' + x + '-' + y + '-' + z;
  }

  const [ min, max, total ] = [ 1, 32, 32 * 32 ];

  function parse_grid(size) {
    let [x, y, z] = (size + '')
      .replace(/\s+/g, '')
      .replace(/[,,xX]+/g, 'x')
      .split('x')
      .map(Number);

    const max_xy = (x == 1 || y == 1) ? total : max;
    const max_z = (x == 1 && y == 1) ? total : min;

    const ret = {
      x: clamp(x || min, 1, max_xy),
      y: clamp(y || x || min, 1, max_xy),
      z: clamp(z || min, 1, max_z)
    };

    return Object.assign({}, ret,
      { count: ret.x * ret.y * ret.z }
    );
  }

  function create_svg_url(svg, id) {
    if (id) {
      let blob = new Blob([svg], { type: 'image/svg+xml' });
      let url = URL.createObjectURL(blob);
      return `url(${ url }#${ id })`;
    }
    else {
      let encoded = encodeURIComponent(svg);
      return `url("data:image/svg+xml;utf8,${ encoded }")`;
    }
  }

  function normalize_svg(input) {
    const xmlns = 'xmlns="http://www.w3.org/2000/svg"';
    if (!input.includes('<svg')) {
      input = `<svg ${ xmlns }>${ input }</svg>`;
    }
    if (!input.includes('xmlns')) {
      input = input.replace(/<svg([\s>])/, `<svg ${ xmlns }$1`);
    }
    return input;
  }

  function lerp(start, end, t) {
    return start * (1 - t) + end * t;
  }

  function rand(start = 0, end = start) {
    if (arguments.length == 1) {
      start = start < 1 ? .1 : 1;
    }
    return lerp(start, end, Math.random());
  }

  function pick(...items) {
    let args = items.reduce((acc, n) => acc.concat(n), []);
    return args[~~(Math.random() * args.length)];
  }

  function unique_id(prefix = '') {
    return prefix + Math.random().toString(32).substr(2);
  }

  function by_unit(fn) {
    return (...args) => {
      let unit = get_unit(args);
      return restore(fn, unit).apply(null, args);
    }
  }

  function restore(fn, unit) {
    return (...args) => {
      args = args.map(str => Number(
        String(str).replace(/\D+$/g, '')
      ));
      let result = fn.apply(null, args);
      if (!unit.length) {
        return result;
      }
      if (Array.isArray(result)) {
        return result.map(n => n + unit);
      }
      return result + unit;
    }
  }

  function get_unit(values) {
    let unit = '';
    values.some(str => {
      let input = String(str).trim();
      if (!input) return '';
      let matched = input.match(/\d(\D+)$/);
      return (unit = matched ? matched[1] : '');
    });
    return unit;
  }

  function by_charcode(fn) {
    return (...args) => {
      let codes = args.map(n => String(n).charCodeAt(0));
      let result = fn.apply(null, codes);
      return Array.isArray(result)
        ? result.map(n => String.fromCharCode(n))
        : String.fromCharCode(result);
    }
  }

  /**
   * Based on the Shunting-yard algorithm.
   */

  function calc(input) {
    const expr = infix_to_postfix(input), stack = [];
    while (expr.length) {
      let top = expr.shift();
      if (/\d+/.test(top)) stack.push(top);
      else {
        let right = stack.pop();
        let left = stack.pop();
        stack.push(compute(
          top, Number(left), Number(right)
        ));
      }
    }
    return stack[0];
  }

  const operator = {
    '*': 3, '/': 3, '%': 3,
    '+': 2, '-': 2,
    '(': 1, ')': 1
  };

  function get_tokens(input) {
    let expr = String(input);
    let tokens = [], num = '';

    for (let i = 0; i < expr.length; ++i) {
      let c = expr[i];

      if (operator[c]) {
        if (c == '-' && expr[i - 1] == 'e') {
          num += c;
        }
        else if (!tokens.length && !num.length && /[+-]/.test(c)) {
          num += c;
        } else {
          let { type, value } = last(tokens) || {};
          if (type == 'operator'
              && !num.length
              && /[^()]/.test(c)
              && /[^()]/.test(value)) {
            num += c;
          } else {
            if (num.length) {
              tokens.push({ type: 'number', value: num });
              num = '';
            }
            tokens.push({ type: 'operator', value: c });
          }
        }
      }

      else if (/\S/.test(c)) {
        num += c;
      }
    }

    if (num.length) {
      tokens.push({ type: 'number', value: num });
    }

    return tokens;
  }

  function infix_to_postfix(input) {
    let tokens = get_tokens(input);
    const op_stack = [], expr = [];

    for (let i = 0; i < tokens.length; ++i) {
      let { type, value } = tokens[i];
      if (type == 'number') {
        expr.push(value);
      }

      else if (type == 'operator') {
        if (value == '(') {
          op_stack.push(value);
        }

        else if (value == ')') {
          while (op_stack.length && last(op_stack) != '(') {
            expr.push(op_stack.pop());
          }
          op_stack.pop();
        }

        else {
          while (op_stack.length && operator[last(op_stack)] >= operator[value]) {
            let op = op_stack.pop();
            if (!/[()]/.test(op)) expr.push(op);
          }
          op_stack.push(value);
        }
      }
    }

    while (op_stack.length) {
      expr.push(op_stack.pop());
    }

    return expr;
  }

  function compute(op, a, b) {
    switch (op) {
      case '+': return a + b;
      case '-': return a - b;
      case '*': return a * b;
      case '/': return a / b;
      case '%': return a % b;
    }
  }

  const store = {};

  function memo$1(prefix, fn) {
    return (...args) => {
      let key = prefix + args.join('-');
      if (store[key]) return store[key];
      return (store[key] = fn.apply(null, args));
    }
  }

  function expand(fn) {
    return (...args) => fn.apply(null, flat_map(args, n =>
      String(n).startsWith('[') ? build_range(n) : n
    ));
  }

  function Type(type, value) {
    return { type, value };
  }

  function get_tokens$1(input) {
    let expr = String(input);
    let tokens = [], stack = [];
    if (!expr.startsWith('[') || !expr.endsWith(']')) {
      return tokens;
    }

    for (let i = 1; i < expr.length - 1; ++i) {
      let c = expr[i];
      if (c == '-' && expr[i - 1] == '-') {
        continue;
      }
      if (c == '-') {
        stack.push(c);
        continue;
      }
      if (last(stack) == '-') {
        stack.pop();
        let from = stack.pop();
        tokens.push(from
          ? Type('range', [ from, c ])
          : Type('char', c)
        );
        continue;
      }
      if (stack.length) {
        tokens.push(Type('char', stack.pop()));
      }
      stack.push(c);
    }
    if (stack.length) {
      tokens.push(Type('char', stack.pop()));
    }
    return tokens;
  }

  const build_range = memo$1('build_range', (input) => {
    let tokens = get_tokens$1(input);
    return flat_map(tokens, ({ type, value }) => {
      if (type == 'char') return value;
      let [ from, to ] = value;
      let reverse = false;
      if (from > to) {
        [from, to] = [ to, from ];
        reverse = true;
      }
      let result = by_charcode(range)(from, to);
      if (reverse) result.reverse();
      return result;
    });
  });

  const { cos, sin, sqrt, pow, PI } = Math;
  const DEG = PI / 180;

  function polygon(option, fn) {
    if (typeof arguments[0] == 'function') {
      fn = option;
      option = {};
    }

    if (!fn) {
      fn = t => [ cos(t), sin(t) ];
    }

    let split = option.split || 120;
    let scale = option.scale || 1;
    let start = DEG * (option.start || 0);
    let deg = option.deg ? (option.deg * DEG) : (PI / (split / 2));
    let points = [];

    for (let i = 0; i < split; ++i) {
      let t = start + deg * i;
      let [x, y] = fn(t);
      points.push(
        ((x * 50 * scale) + 50 + '% ') +
        ((y * 50 * scale) + 50 + '%')
      );
    }

    return option.type
      ? `polygon(${ option.type }, ${ points.join(',') })`
      : `polygon(${ points.join(',') })`;
  }

  function rotate(x, y, deg) {
    let rad = DEG * deg;
    return [
      x * cos(rad) - y * sin(rad),
      y * cos(rad) + x * sin(rad)
    ];
  }

  const shapes =  {

    circle() {
      return 'circle(49%)';
    },

    triangle() {
      return polygon({ split: 3, start: -90 }, t => [
        cos(t) * 1.1,
        sin(t) * 1.1 + .2
      ]);
    },

    rhombus() {
      return polygon({ split: 4 });
    },

    pentagon() {
      return polygon({ split: 5, start: 54 });
    },

    hexgon() {
      return polygon({ split: 6, start: 30 });
    },

    hexagon() {
      return polygon({ split: 6, start: 30 });
    },

    heptagon() {
      return polygon({ split: 7, start: -90 });
    },

    octagon() {
      return polygon({ split: 8, start: 22.5 });
    },

    star() {
      return polygon({ split: 5, start: 54, deg: 144 });
    },

    diamond() {
      return 'polygon(50% 5%, 80% 50%, 50% 95%, 20% 50%)';
    },

    cross() {
      return `polygon(
      5% 35%,  35% 35%, 35% 5%,  65% 5%,
      65% 35%, 95% 35%, 95% 65%, 65% 65%,
      65% 95%, 35% 95%, 35% 65%, 5% 65%
    )`;
    },

    clover(k = 3) {
      k = clamp(k, 3, 5);
      if (k == 4) k = 2;
      return polygon({ split: 240 }, t => {
        let x = cos(k * t) * cos(t);
        let y = cos(k * t) * sin(t);
        if (k == 3) x -= .2;
        if (k == 2) {
          x /= 1.1;
          y /= 1.1;
        }
        return [x, y];
      });
    },

    hypocycloid(k = 3) {
      k = clamp(k, 3, 6);
      let m = 1 - k;
      return polygon({ scale: 1 / k  }, t => {
        let x = m * cos(t) + cos(m * (t - PI));
        let y = m * sin(t) + sin(m * (t - PI));
        if (k == 3) {
          x = x * 1.1 - .6;
          y = y * 1.1;
        }
        return [x, y];
      });
    },

    astroid() {
      return shapes.hypocycloid(4);
    },

    infinity() {
      return polygon(t => {
        let a = .7 * sqrt(2) * cos(t);
        let b = (pow(sin(t), 2) + 1);
        return [
          a / b,
          a * sin(t) / b
        ]
      });
    },

    heart() {
      return polygon(t => {
        let x = .75 * pow(sin(t), 3);
        let y =
            cos(1 * t) * (13 / 18)
          - cos(2 * t) * (5 / 18)
          - cos(3 * t) / 18
          - cos(4 * t) / 18;
        return rotate(
          x * 1.2,
          (y + .2) * 1.1,
          180
        );
      });
    },

    bean() {
      return polygon(t => {
        let [a, b] = [pow(sin(t), 3), pow(cos(t), 3)];
        return rotate(
          (a + b) * cos(t) * 1.3 - .45,
          (a + b) * sin(t) * 1.3 - .45,
          -90
        );
      });
    },

    bicorn() {
      return polygon(t => rotate(
        cos(t),
        pow(sin(t), 2) / (2 + sin(t)) - .5,
        180
      ));
    },

    pear() {
      return polygon(t => [
        sin(t),
        (1 + sin(t)) * cos(t) / 1.4
      ]);
    },

    fish() {
      return polygon(t => [
        cos(t) - pow(sin(t), 2) / sqrt(2),
        sin(2 * t) / 2
      ]);
    },

    whale() {
      return polygon({ split: 240 }, t => {
        let r = 3.4 * (pow(sin(t), 2) - .5) * cos(t);
        return rotate(
          cos(t) * r + .75,
          sin(t) * r * 1.2,
          180
        );
      });
    },

    bud(n = 3) {
      n = clamp(n, 3, 10);
      return polygon({ split: 240 }, t => [
        ((1 + .2 * cos(n * t)) * cos(t)) * .8,
        ((1 + .2 * cos(n * t)) * sin(t)) * .8
      ]);
    },

    alien(...args) {
      let [a = 1, b = 1, c = 1, d = 1, e = 1]
        = args.map(n => clamp(n, 1, 9));
      return polygon({ split: 480, type: 'evenodd' }, t => [
        (cos(t * a) + cos(t * c) + cos(t * e)) * .31,
        (sin(t * b) + sin(t * d) + sin(t)) * .31
      ]);
    }

  };

  const Expose = {

    index({ count }) {
      return _ => count;
    },

    row({ x }) {
      return _ => x;
    },

    col({ y }) {
      return _ => y;
    },

    depth({ z }) {
      return _ => z;
    },

    size({ grid }) {
      return _ => grid.count;
    },

    ['size-row']({ grid }) {
      return _ => grid.x;
    },

    ['size-col']({ grid }) {
      return _ => grid.y;
    },

    ['size-depth']({ grid }) {
      return _ => grid.z;
    },

    id({ x, y, z }) {
      return _ => cell_id(x, y, z);
    },

    n({ idx }) {
      return _ => idx || 0;
    },

    pick({ context }) {
      return expand((...args) => (
        context.last_pick = pick(args)
      ));
    },

    ['pick-n']({ idx, context, position }) {
      let counter = 'pn-counter' + position;
      return expand((...args) => {
        if (!context[counter]) context[counter] = 0;
        context[counter] += 1;
        let max = args.length;
        let pos = ((idx === undefined ? context[counter] : idx) - 1) % max;
        return context.last_pick = args[pos];
      });
    },

    ['pick-d']({ idx, context, position }) {
      let counter = 'pd-counter' + position;
      let values = 'pd-values' + position;
      return expand((...args) => {
        if (!context[counter]) context[counter] = 0;
        context[counter] += 1;
        if (!context[values]) {
          context[values] = shuffle(args);
        }
        let max = args.length;
        let pos = ((idx === undefined ? context[counter] : idx) - 1) % max;
        return context.last_pick = context[values][pos];
      });
    },

    ['last-pick']({ context }) {
      return () => context.last_pick;
    },

    multiple: lazy((n, action) => {
      if (!action || !n) return '';
      let count = clamp(n(), 0, 65536);
      return sequence(count, i => action(i + 1)).join(',');
    }),

    ['multiple-with-space']: lazy((n, action) => {
      if (!action || !n) return '';
      let count = clamp(n(), 0, 65536);
      return sequence(count, i => action(i + 1)).join(' ');
    }),

    repeat: lazy((n, action) => {
      if (!action || !n) return '';
      let count = clamp(n(), 0, 65536);
      return sequence(count, i => action(i + 1)).join('');
    }),

    rand({ context }) {
      return (...args) => {
        let transform_type = args.every(is_letter)
          ? by_charcode
          : by_unit;
        let value = transform_type(rand).apply(null, args);
        return context.last_rand = value;
      };
    },

    ['rand-int']({ context }) {
      return (...args) => {
        let transform_type = args.every(is_letter)
          ? by_charcode
          : by_unit;
        let value = parseInt(
          transform_type(rand).apply(null, args)
        );
        return context.last_rand = value;
      }
    },

    ['last-rand']({ context }) {
      return () => context.last_rand;
    },

    calc() {
      return value => calc(value);
    },

    hex() {
      return value => parseInt(value).toString(16);
    },

    svg: lazy(input => {
      if (input === undefined) return '';
      let svg = normalize_svg(input().trim());
      return create_svg_url(svg);
    }),

    ['svg-filter']: lazy(input => {
      if (input === undefined) return '';
      let id = unique_id('filter-');
      let svg = normalize_svg(input().trim())
        .replace(
          /<filter([\s>])/,
          `<filter id="${ id }"$1`
        );
      return create_svg_url(svg, id);
    }),

    var() {
      return value => `var(${ value })`;
    },

    shape() {
      return memo('shape-function', (type = '', ...args) => {
        type = type.trim();
        if (typeof shapes[type] === 'function') {
          return shapes[type](args);
        }
        return '';
      });
    }

  };

  var Func = alias_for(Expose, {
    'm':  'multiple',
    'ms': 'multiple-with-space',

    'r':  'rand',
    'ri': 'rand-int',
    'lr': 'last-rand',

    'p':  'pick',
    'pn': 'pick-n',
    'pd': 'pick-d',
    'lp': 'last-pick',

    'i':  'index',
    'x':  'row',
    'y':  'col',
    'z':  'depth',

    'size-x': 'size-row',
    'size-y': 'size-col',
    'size-z': 'size-depth',

    // legacy names
    'multi': 'multiple',
    'pick-by-turn': 'pick-n',
    'max-row': 'size-row',
    'max-col': 'size-col'
  });

  const is_seperator = c => /[,,\s]/.test(c);

  function skip_seperator(it) {
    while (!it.end()) {
      if (!is_seperator(it.curr(1))) break;
      else it.next();
    }
  }

  function parse$2(input) {
    const it = iterator(input);
    const result = [], stack = [];
    let group = '';

    while (!it.end()) {
      let c = it.curr();
      if (c == '(') {
        group += c;
        stack.push(c);
      }

      else if (c == ')') {
        group += c;
        if (stack.length) {
          stack.pop();
        }
      }

      else if (stack.length) {
        group += c;
      }

      else if (is_seperator(c)) {
        result.push(group);
        group = '';
        skip_seperator(it);
      }

      else {
        group += c;
      }

      it.next();
    }

    if (group) {
      result.push(group);
    }

    return result;
  }

  let all = [];

  function get_props(arg) {
    if (!all.length) {
      let props = new Set();
      for (let n in document.head.style) {
        if (!n.startsWith('-')) {
          props.add(n.replace(/[A-Z]/g, '-$&').toLowerCase());
        }
      }
      if (!props.has('grid-gap')) {
        props.add('grid-gap');
      }
      all = Array.from(props);
    }
    return (arg && arg.test)
      ? all.filter(n => arg.test(n))
      : all;
  }

  function build_mapping(prefix) {
    let reg = new RegExp(`\\-?${ prefix }\\-?`);
    return get_props(reg)
      .map(n => n.replace(reg, ''))
      .reduce((obj, n) => { return obj[n] = n, obj }, {});
  }

  const props_webkit_mapping = build_mapping('webkit');
  const props_moz_mapping = build_mapping('moz');

  function prefixer(prop, rule) {
    if (props_webkit_mapping[prop]) {
      return `-webkit-${ rule } ${ rule }`;
    }
    else if (props_moz_mapping[prop]) {
      return `-moz-${ rule } ${ rule }`;
    }
    return rule;
  }

  var Property = {

    ['@size'](value, { is_special_selector }) {
      let [w, h = w] = parse$2(value);
      return `
      width: ${ w };
      height: ${ h };
      ${ is_special_selector ? '' : `
        --internal-cell-width: ${ w };
        --internal-cell-height: ${ h };
      `}
    `;
    },

    ['@min-size'](value) {
      let [w, h = w] = parse$2(value);
      return `min-width: ${ w }; min-height: ${ h };`;
    },

    ['@max-size'](value) {
      let [w, h = w] = parse$2(value);
      return `max-width: ${ w }; max-height: ${ h };`;
    },

    ['@place-cell']: (() => {
      let map_left_right = {
        'center': '50%', '0': '0%',
        'left': '0%', 'right': '100%',
        'top': '50%', 'bottom': '50%'
      };
      let map_top_bottom = {
        'center': '50%', '0': '0%',
        'top': '0%', 'bottom': '100%',
        'left': '50%', 'right': '50%',
      };

      return value => {
        let [left, top = '50%'] = parse$2(value);
        left = map_left_right[left] || left;
        top = map_top_bottom[top] || top;
        const cw = 'var(--internal-cell-width, 25%)';
        const ch = 'var(--internal-cell-height, 25%)';
        return `
        position: absolute;
        left: ${ left };
        top: ${ top };
        width: ${ cw };
        height: ${ ch };
        margin-left: calc(${ cw } / -2) !important;
        margin-top: calc(${ ch } / -2) !important;
        grid-area: unset !important;
      `;
      }
    })(),

    ['@grid'](value, options) {
      let [grid, size] = value.split('/').map(s => s.trim());
      return {
        grid: parse_grid(grid),
        size: size ? this['@size'](size, options) : ''
      };
    },

    ['@shape']: memo$1('shape-property', value => {
      let [type, ...args] = parse$2(value);
      let prop = 'clip-path';
      if (!shapes[type]) return '';
      let rules = `${ prop }: ${ shapes[type].apply(null, args) };`;
      return prefixer(prop, rules) + 'overflow: hidden;';
    }),

    ['@use'](rules) {
      if (rules.length > 2) {
        return rules;
      }
    }

  };

  function build_expr(expr) {
    return n => String(expr)
      .replace(/(\d+)(n)/g, '$1*' + n)
      .replace(/n/g, n);
  }

  function nth(input, curr, max) {
    let expr = build_expr(input);
    for (let i = 0; i <= max; ++i) {
      if (calc(expr(i)) == curr) return true;
    }
  }

  const is$1 = {
    even: n => !!(n % 2),
    odd:  n => !(n % 2)
  };

  function even_or_odd(expr) {
    return /^(even|odd)$/.test(expr);
  }

  var Selector = {

    at({ x, y }) {
      return (x1, y1) => (x == x1 && y == y1);
    },

    nth({ count, grid }) {
      return (...exprs) => exprs.some(expr =>
        even_or_odd(expr)
          ? is$1[expr](count - 1)
          : nth(expr, count, grid.count)
      );
    },

    row({ x, grid }) {
      return (...exprs) => exprs.some(expr =>
        even_or_odd(expr)
          ? is$1[expr](x - 1)
          : nth(expr, x, grid.x)
      );
    },

    col({ y, grid }) {
      return (...exprs) => exprs.some(expr =>
        even_or_odd(expr)
          ? is$1[expr](y - 1)
          : nth(expr, y, grid.y)
      );
    },

    even({ count }) {
      return _ => is$1.even(count - 1);
    },

    odd({ count }) {
      return _ => is$1.odd(count - 1);
    },

    random() {
      return (ratio = .5) => {
        if (ratio >= 1 && ratio <= 0) ratio = .5;
        return Math.random() < ratio;
      }
    }

  };

  // Expose all Math functions and constants.
  const methods = Object.getOwnPropertyNames(Math);

  var MathFunc = methods.reduce((expose, n) => {
    expose[n] = () => (...args) => {
      if (typeof Math[n] === 'number') return Math[n];
      return Math[n].apply(null, args.map(calc));
    };
    return expose;
  }, {});

  function is_host_selector(s) {
    return /^\:(host|doodle)/.test(s);
  }

  function is_parent_selector(s) {
    return /^\:(container|parent)/.test(s);
  }

  function is_special_selector(s) {
    return is_host_selector(s) || is_parent_selector(s);
  }

  class Rules {

    constructor(tokens) {
      this.tokens = tokens;
      this.rules = {};
      this.props = {};
      this.keyframes = {};
      this.grid = null;
      this.coords = [];
      this.reset();
    }

    reset() {
      this.styles = {
        host: '',
        container: '',
        cells: '',
        keyframes: ''
      };
      this.coords = [];
      for (let key in this.rules) {
        if (key.startsWith('#cell')) {
          delete this.rules[key];
        }
      }
    }

    add_rule(selector, rule) {
      let rules = this.rules[selector];
      if (!rules) {
        rules = this.rules[selector] = [];
      }

      rules.push.apply(rules, make_array(rule));
    }

    pick_func(name) {
      return Func[name] || MathFunc[name];
    }

    compose_aname(...args) {
      return args.join('-');
    }

    compose_selector({ x, y, z}, pseudo = '') {
      return `#${ cell_id(x, y, z) }${ pseudo }`;
    }

    compose_argument(argument, coords, idx) {
      let result = argument.map(arg => {
        if (arg.type == 'text') {
          return arg.value;
        }
        else if (arg.type == 'func') {
          let fn = this.pick_func(arg.name.substr(1));
          if (fn) {
            coords.idx = idx;
            coords.position = arg.position;
            let args = arg.arguments.map(n => {
              return fn.lazy
                ? idx => this.compose_argument(n, coords, idx)
                : this.compose_argument(n, coords, idx);
            });
            return apply_args(fn, coords, args);
          }
        }
      });

      return (result.length >= 2)
        ? result.join('')
        : result[0];
    }

    compose_value(value, coords) {
      if (!value || !value.reduce) return '';
      return value.reduce((result, val) => {
        switch (val.type) {
          case 'text': {
            result += val.value;
            break;
          }
          case 'func': {
            let fname = val.name.substr(1);
            let fn = this.pick_func(fname);
            if (fn) {
              coords.position = val.position;
              let args = val.arguments.map(arg => {
                if (fn.lazy) {
                  return idx => this.compose_argument(arg, coords, idx);
                } else {
                  return this.compose_argument(arg, coords);
                }
              });
              result += apply_args(fn, coords, args);
            }
          }
        }
        return result;
      }, '');
    }

    compose_rule(token, _coords, selector) {
      let coords = Object.assign({}, _coords);
      let prop = token.property;
      let value_group = token.value.reduce((ret, v) => {
        let composed = this.compose_value(v, coords);
        if (composed) ret.push(composed);
        return ret;
      }, []);

      let value = value_group.join(', ');

      if (/^animation(\-name)?$/.test(prop)) {
        this.props.has_animation = true;
        if (coords.count > 1) {
          let { count } = coords;
          switch (prop) {
            case 'animation-name': {
              value = value_group
                .map(n => this.compose_aname(n, count))
                .join(', ');
              break;
            }
            case 'animation': {
              value = value_group
                .map(n => {
                  let group = (n || '').split(/\s+/);
                  group[0] = this.compose_aname(group[0], count);
                  return group.join(' ');
                })
                .join(', ');
            }
          }
        }
      }

      if (prop == 'content') {
        if (!/["']|^none$|^(var|counter|counters|attr)\(/.test(value)) {
          value = `'${ value }'`;
        }
      }

      if (prop == 'transition') {
        this.props.has_transition = true;
      }

      let rule = `${ prop }: ${ value };`;
      rule = prefixer(prop, rule);

      if (prop == 'clip-path') {
        // fix clip bug
        rule += ';overflow: hidden;';
      }

      if (prop == 'width' || prop == 'height') {
        if (!is_special_selector(selector)) {
          rule += `--internal-cell-${ prop }: ${ value };`;
        }
      }

      if (Property[prop]) {
        let transformed = Property[prop](value, {
          is_special_selector: is_special_selector(selector)
        });
        switch (prop) {
          case '@grid': {
            if (is_host_selector(selector)) {
              this.grid = transformed.grid;
              rule = transformed.size || '';
            }
            break;
          }
          case '@place-cell': {
            if (!is_host_selector(selector)) {
              rule = transformed;
            }
          }
          case '@use': {
            if (token.value.length) {
              this.compose(coords, token.value);
            }
            rule = Property[prop](token.value);
          }
          default: {
            rule = transformed;
          }
        }
      }

      return rule;
    }

    compose(coords, tokens) {
      this.coords.push(coords);
      (tokens || this.tokens).forEach((token, i) => {
        if (token.skip) return false;
        switch (token.type) {
          case 'rule':
            this.add_rule(
              this.compose_selector(coords),
              this.compose_rule(token, coords)
            );
            break;

          case 'pseudo': {
            if (token.selector.startsWith(':doodle')) {
              token.selector = token.selector.replace(/^\:+doodle/, ':host');
            }
            let special = is_special_selector(token.selector);
            if (special) {
              token.skip = true;
            }
            token.selector.split(',').forEach(selector => {
              let pseudo = token.styles.map(s =>
                this.compose_rule(s, coords, selector)
              );
              let composed = special
                ? selector
                : this.compose_selector(coords, selector);
              this.add_rule(composed, pseudo);
            });

            break;
          }

          case 'cond': {
            let fn = Selector[token.name.substr(1)];
            if (fn) {
              let args = token.arguments.map(arg => {
                return this.compose_argument(arg, coords);
              });
              let result = apply_args(fn, coords, args);
              if (result) {
                this.compose(coords, token.styles);
              }
            }
            break;
          }

          case 'keyframes': {
            if (!this.keyframes[token.name]) {
              this.keyframes[token.name] = coords => `
              ${ join(token.steps.map(step => `
                ${ step.name } {
                  ${ join(
                    step.styles.map(s => this.compose_rule(s, coords))
                  )}
                }
              `)) }
            `;
            }
          }
        }
      });
    }

    output() {
      Object.keys(this.rules).forEach((selector, i) => {
        if (is_parent_selector(selector)) {
          this.styles.container += `
          .container {
            ${ join(this.rules[selector]) }
          }
        `;
        } else {
          let target = is_host_selector(selector) ? 'host' : 'cells';
          this.styles[target] += `
          ${ selector } {
            ${ join(this.rules[selector]) }
          }
        `;
        }
      });

      let keyframes = Object.keys(this.keyframes);
      this.coords.forEach((coords, i) => {
        keyframes.forEach(name => {
          let aname = this.compose_aname(name, coords.count);
          this.styles.keyframes += `
          ${ maybe(i == 0,
            `@keyframes ${ name } {
              ${ this.keyframes[name](coords) }
            }`
          )}
          @keyframes ${ aname } {
            ${ this.keyframes[name](coords) }
          }
        `;
        });
      });

      return {
        props: this.props,
        styles: this.styles,
        grid: this.grid
      }
    }
  }

  function generator(tokens, grid_size) {
    let rules = new Rules(tokens);
    let context = {};
    rules.compose({
      x: 1, y: 1, z: 1, count: 1, context: {},
      grid: { x: 1, y: 1, z: 1, count: 1 }
    });
    let { grid } = rules.output();
    if (grid) grid_size = grid;
    rules.reset();

    if (grid_size.z == 1) {
      for (let x = 1, count = 0; x <= grid_size.x; ++x) {
        for (let y = 1; y <= grid_size.y; ++y) {
          rules.compose({
            x, y, z: 1,
            count: ++count, grid: grid_size, context
          });
        }
      }
    }
    else {
      for (let z = 1, count = 0; z <= grid_size.z; ++z) {
        rules.compose({
          x: 1, y: 1, z,
          count: ++count, grid: grid_size, context
        });
      }
    }

    return rules.output();
  }

  class Doodle extends HTMLElement {
    constructor() {
      super();
      this.doodle = this.attachShadow({ mode: 'open' });
      this.extra = {
        get_custom_property_value: this.get_custom_property_value.bind(this)
      };
    }
    connectedCallback(again) {
      setTimeout(() => {
        let compiled;
        let use = this.getAttribute('use') || '';
        if (use) use = `@use:${ use };`;
        if (!this.innerHTML.trim() && !use) return false;
        try {
          let parsed = parse$1(use + this.innerHTML, this.extra);
          this.grid_size = parse_grid(this.getAttribute('grid'));
          compiled = generator(parsed, this.grid_size);
          compiled.grid && (this.grid_size = compiled.grid);
          this.build_grid(compiled);
        } catch (e) {
          this.innerHTML = '';
          console.error(e && e.message || 'Error in css-doodle.');
        }
        if (!again && this.hasAttribute('click-to-update')) {
          this.addEventListener('click', e => this.update());
        }
      });
    }

    get_custom_property_value(name) {
      return getComputedStyle(this).getPropertyValue(name)
        .trim()
        .replace(/^\(|\)$/g, '');
    }

    cell(x, y, z) {
      let cell = document.createElement('div');
      cell.id = cell_id(x, y, z);
      return cell;
    }

    build_grid(compiled) {
      const { has_transition, has_animation } = compiled.props;
      const { keyframes, host, container, cells } = compiled.styles;

      this.doodle.innerHTML = `
      <style>
        ${ this.style_basic() }
      </style>
      <style class="style-keyframes">
        ${ keyframes }
      </style>
      <style class="style-container">
        ${ this.style_size() }
        ${ host }
        ${ container }
      </style>
      <style class="style-cells">
        ${ (has_transition || has_animation) ? '' : cells }
      </style>
      <div class="container"></div>
    `;

      this.doodle.querySelector('.container')
        .appendChild(this.html_cells());

      if (has_transition || has_animation) {
        setTimeout(() => {
          this.set_style('.style-cells', cells);
        }, 50);
      }
    }

    inherit_props(p) {
      return get_props(/grid/)
        .map(n => `${ n }: inherit;`)
        .join('');
    }

    style_basic() {
      return `
      :host {
        display: block;
        visibility: visible;
        width: 1em;
        height: 1em;
      }
      .container {
        position: relative;
        width: 100%;
        height: 100%;
        display: grid;
        ${ this.inherit_props() }
      }
      .container div:empty {
        position: relative;
        line-height: 1;
        box-sizing: border-box;
        display: flex;
        justify-content: center;
        align-items: center;
      }
    `;
    }

    style_size() {
      let { x, y } = this.grid_size;
      return `
      :host {
        grid-template-rows: repeat(${ x }, 1fr);
        grid-template-columns: repeat(${ y }, 1fr);
      }
    `;
    }

    html_cells() {
      let { x, y, z } = this.grid_size;
      let root = document.createDocumentFragment();
      if (z == 1) {
        for (let i = 1; i <= x; ++i) {
          for (let j = 1; j <= y; ++j) {
            root.appendChild(this.cell(i, j, 1));
          }
        }
      }
      else {
        let temp = null;
        for (let i = 1; i <= z; ++i) {
          let cell = this.cell(1, 1, i);
          (temp || root).appendChild(cell);
          temp = cell;
        }
        temp = null;
      }
      return root;
    }

    set_style(selector, styles) {
      const el = this.shadowRoot.querySelector(selector);
      el && (el.styleSheet
        ? (el.styleSheet.cssText = styles )
        : (el.innerHTML = styles));
    }

    update(styles) {
      let use = this.getAttribute('use') || '';
      if (use) use = `@use:${ use };`;

      if (!styles) styles = this.innerHTML;
      this.innerHTML = styles;

      if (!this.grid_size) {
        this.grid_size = parse_grid(this.getAttribute('grid'));
      }

      const compiled = generator(parse$1(use + styles, this.extra), this.grid_size);

      if (compiled.grid) {
        let { x, y, z } = compiled.grid;
        let { x: gx, y: gy, z: gz } = this.grid_size;
        if (gx !== x || gy !== y || gz !== z) {
          Object.assign(this.grid_size, compiled.grid);
          return this.build_grid(compiled);
        }
        Object.assign(this.grid_size, compiled.grid);
      }

      else {
        let grid = parse_grid(this.getAttribute('grid'));
        let { x, y, z } = grid;
        let { x: gx, y: gy, z: gz } = this.grid_size;
        if (gx !== x || gy !== y || gz !== z) {
          Object.assign(this.grid_size, grid);
          return this.build_grid(
            generator(parse$1(use + styles, this.extra), this.grid_size)
          );
        }
      }

      this.set_style('.style-keyframes',
        compiled.styles.keyframes
      );

      if (compiled.props.has_animation) {
        this.set_style('.style-cells', '');
        this.set_style('.style-container', '');
      }

      setTimeout(() => {
        this.set_style('.style-container',
            this.style_size()
          + compiled.styles.host
          + compiled.styles.container
        );
        this.set_style('.style-cells',
          compiled.styles.cells
        );
      });
    }

    get grid() {
      return Object.assign({}, this.grid_size);
    }

    set grid(grid) {
      this.setAttribute('grid', grid);
      this.connectedCallback(true);
    }

    get use() {
      return this.getAttribute('use');
    }

    set use(use) {
      this.setAttribute('use', use);
      this.connectedCallback(true);
    }

    static get observedAttributes() {
      return ['grid', 'use'];
    }

    attributeChangedCallback(name, old_val, new_val) {
      if (old_val == new_val) {
        return false;
      }
      if (name == 'grid' && old_val) {
        this.grid = new_val;
      }
      if (name == 'use' && old_val) {
        this.use = new_val;
      }
    }
  }

  customElements.define('css-doodle', Doodle);

}));

A  => css-doodle-0.7.3.min.js +1 -0
@@ 1,1 @@
!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";function e(e){let t=0,n=1,r=1;return{curr:(n=0)=>e[t+n],end:()=>e.length<=t,info:()=>({index:t,col:n,line:r}),index:e=>void 0===e?t:t=e,next(){let s=e[t++];return"\n"==s?(r++,n=0):n++,s}}}function t(t){t=t.trim();let n=[];if(!/^var\(/.test(t))return n;let r=e(t);try{n=function(e){let t="",n=[],r=[],s={};for(;!e.end();){let i=e.curr();if("("==i)n.push(i),t="";else if(")"==i||","==i){if(/^\-\-.+/.test(t)&&(s.name?(s.alternative||(s.alternative=[]),s.alternative.push({name:t})):s.name=t),")"==i){if("("!=n[n.length-1])throw new Error("bad match");n.pop()}","==i&&(n.length||(r.push(s),s={})),t=""}else/\s/.test(i)||(t+=i);e.next()}return n.length?[]:(s.name&&r.push(s),r)}(r)}catch(e){console.error(e&&e.message||"Bad variables.")}return n}function n(e){return Array.isArray(e)?e:[e]}function r(e,t="\n"){return(e||[]).join(t)}function s(e){return e[e.length-1]}function i(e){return e[0]}function l(e,t){return Array.prototype.flatMap?e.flatMap(t):e.reduce((e,n)=>e.concat(t(n)),[])}const u={func:(e="")=>({type:"func",name:e,arguments:[]}),argument:()=>({type:"argument",value:[]}),text:(e="")=>({type:"text",value:e}),pseudo:(e="")=>({type:"pseudo",selector:e,styles:[]}),cond:(e="")=>({type:"cond",name:e,styles:[],arguments:[]}),rule:(e="")=>({type:"rule",property:e,value:[]}),keyframes:(e="")=>({type:"keyframes",name:e,steps:[]}),step:(e="")=>({type:"step",name:e,styles:[]})},o={white_space:e=>/[\s\n\t]/.test(e),line_break:e=>/\n/.test(e),number:e=>!isNaN(e),pair:e=>['"',"(",")","'"].includes(e),pair_of:(e,t)=>({'"':'"',"'":"'","(":")"})[e]==t};function a(e,{col:t,line:n}){console.error(`(at line ${n}, column ${t}) ${e}`)}function c(e){return function(t,n){let r=t.index(),s="";for(;!t.end();){let n=t.next();if(e(n))break;s+=n}return n&&t.index(r),s}}function p(e,t){return c(e=>/[^\w@]/.test(e))(e,t)}function h(e){return c(e=>/[\s\{]/.test(e))(e)}function f(e,t){return c(e=>o.line_break(e)||"{"==e)(e,t)}function d(e,t){let n,r=u.step();for(;!e.end()&&"}"!=(n=e.curr());)if(o.white_space(n))e.next();else{if(r.name.length){if(r.styles.push(j(e,t)),"}"==e.curr())break}else r.name=k(e);e.next()}return r}function g(e,t){const n=[];let r;for(;!e.end()&&"}"!=(r=e.curr());)o.white_space(r)?e.next():(n.push(d(e,t)),e.next());return n}function m(e,t){let n,r=u.keyframes();for(;!e.end()&&"}"!=(n=e.curr());)if(r.name.length){if("{"==n){e.next(),r.steps=g(e,t);break}e.next()}else if(p(e),r.name=h(e),!r.name.length){a("missing keyframes name",e.info());break}return r}function y(e,t={}){for(e.next();!e.end();){let n=e.curr();if(t.inline){if("\n"==n)break}else if("*"==(n=e.curr())&&"/"==e.curr(1))break;e.next()}t.inline||(e.next(),e.next())}function x(e){let t,n="";for(;!e.end()&&":"!=(t=e.curr());)o.white_space(t)||(n+=t),e.next();return n}function _(e){let t,n=[],r=[],i=[],l="";for(;!e.end();){if(t=e.curr(),/[\('"`]/.test(t)&&"\\"!==e.curr(-1))i.length&&"("!=t&&t===s(i)?i.pop():i.push(t),l+=t;else if("@"==t)r.length||(l=l.trimLeft()),l.length&&(r.push(u.text(l)),l=""),r.push(b(e));else if(/[,)]/.test(t))if(i.length)")"==t&&i.pop(),l+=t;else{if(l.length&&(r.length?r.push(u.text(l)):r.push(u.text((c=l).trim().length?o.number(+c)?+c:c.trim():c)),l.startsWith("±"))){let e=l.substr(1),t=(a=r,JSON.parse(JSON.stringify(a)));s(t).value="-"+e,n.push(v(t)),s(r).value=e}if(n.push(v(r)),[r,l]=[[],""],")"==t)break}else l+=t;e.next()}var a,c;return n}function v(e){let t=e.map(e=>{if("text"==e.type&&"string"==typeof e.value){let t=String(e.value);t.includes("`")&&(e.value=t=t.replace(/`/g,'"')),e.value=t.replace(/\n+|\s+/g," ")}return e}),n=i(t)||{},r=s(t)||{};if("text"==n.type&&"text"==r.type){let e=i(n.value),t=s(r.value);"string"==typeof n.value&&"string"==typeof r.value&&o.pair(e)&&o.pair_of(e,t)&&(n.value=n.value.slice(1),r.value=r.value.slice(0,r.value.length-1))}return t}function b(e){let t,n=u.func(),r="",s="";for(;!e.end()&&")"!=(t=e.curr());){if("("==t){e.next(),n.name=s,n.arguments=_(e),/\d$/.test(s)&&(n.name=s.split(/\d+/)[0],r=s.split(/\D+/)[1]),r.length&&n.arguments.unshift([{type:"text",value:r}]),n.position=e.info().index;break}s+=t,e.next()}return n}function $(e){let t,n=u.text(),r=0,s=!0;const i=[],l=[];for(i[r]=[];!e.end();)if(t=e.curr(),s&&o.white_space(t))e.next();else{if(s=!1,"\n"!=t||o.white_space(e.curr(-1)))if(","!=t||l.length){if(/[;}]/.test(t)){n.value.length&&(i[r].push(n),n=u.text());break}"@"==t?(n.value.length&&(i[r].push(n),n=u.text()),i[r].push(b(e))):o.white_space(t)&&o.white_space(e.curr(-1))||("("==t&&l.push(t),")"==t&&l.pop(),n.value+=t)}else n.value.length&&(i[r].push(n),n=u.text()),i[++r]=[],s=!0;else n.value+=" ";e.next()}return n.value.length&&i[r].push(n),i}function k(e){let t,n="";for(;!e.end()&&"{"!=(t=e.curr());)o.white_space(t)||(n+=t),e.next();return n}function z(e){let t,n={name:"",arguments:[]};for(;!e.end();){if("("==(t=e.curr()))e.next(),n.arguments=_(e);else{if(/[){]/.test(t))break;o.white_space(t)||(n.name+=t)}e.next()}return n}function w(e,t){let n,r=u.pseudo();for(;!e.end()&&"}"!=(n=e.curr());)if(o.white_space(n))e.next();else{if(r.selector){let n=j(e,t);if("@use"==n.property?r.styles=r.styles.concat(n.value):r.styles.push(n),"}"==e.curr())break}else r.selector=k(e);e.next()}return r}function j(e,t){let n,r=u.rule();for(;!e.end()&&";"!=(n=e.curr());){if(r.property.length){r.value=$(e);break}if(r.property=x(e),"@use"==r.property){r.value=S(e,t);break}e.next()}return r}function A(e,t){let n,r=u.cond();for(;!e.end()&&"}"!=(n=e.curr());){if(r.name.length)if(":"==n){let t=w(e);t.selector&&r.styles.push(t)}else if("@"!=n||f(e,!0).includes(":")){if(!o.white_space(n)){let n=j(e,t);if(n.property&&r.styles.push(n),"}"==e.curr())break}}else r.styles.push(A(e));else Object.assign(r,z(e));e.next()}return r}function M(e,t){let n="";return e&&e.get_custom_property_value&&(n=e.get_custom_property_value(t)),n}function S(e,n){return e.next(),($(e)||[]).reduce((e,r)=>{!function e(n,r){n.forEach&&n.forEach(n=>{if("text"==n.type&&n.value){let e=t(n.value);n.value=e.reduce((e,t)=>{let n,s="",i="";!(s=M(r,t.name))&&t.alternative&&t.alternative.every(e=>{if(i=M(r,e.name))return s=i,!1});try{n=C(s,r)}catch(e){}return n&&e.push.apply(e,n),e},[])}"func"==n.type&&n.arguments&&n.arguments.forEach(t=>{e(t,r)})})}(r,n);let[s]=r;return s.value&&s.value.length&&e.push(...s.value),e},[])}function C(t,n){const r=e(t),s=[];for(;!r.end();){let e=r.curr();if(o.white_space(e))r.next();else{if("/"==e&&"*"==r.curr(1))y(r);else if("/"==e&&"/"==r.curr(1))y(r,{inline:!0});else if(":"==e){let e=w(r,n);e.selector&&s.push(e)}else if("@"==e&&"@keyframes"===p(r,!0)){let e=m(r,n);s.push(e)}else if("@"!=e||f(r,!0).includes(":")){if(!o.white_space(e)){let e=j(r,n);e.property&&s.push(e)}}else{let e=A(r,n);e.name.length&&s.push(e)}r.next()}}return s}function E(e,...t){return t.reduce((e,t)=>e.apply(null,n(t)),e)}function O(e,t,n){return Math.max(t,Math.min(n,e))}function L(e,t,n){let r=0,s=e,i=e=>e>0&&e<1?.1:1,l=arguments.length;1==l&&([e,t]=[i(e),e]),l<3&&(n=i(e));let u=[];for(;(n>=0&&e<=t||n<0&&e>t)&&(u.push(e),e+=n,!(r++>=1e3)););return u.length||u.push(s),u}function T(e){return/^[a-zA-Z]$/.test(e)}function N(e){let t=()=>e;return t.lazy=!0,t}function H(e,t){let n=[];for(let r=0;r<e;++r)n.push(t(r));return n}function W(e,t,n){return"cell-"+e+"-"+t+"-"+n}const[R,D,I]=[1,32,1024];function q(e){let[t,n,r]=(e+"").replace(/\s+/g,"").replace(/[,,xX]+/g,"x").split("x").map(Number);const s=1==t||1==n?I:D,i=1==t&&1==n?I:R,l={x:O(t||R,1,s),y:O(n||t||R,1,s),z:O(r||R,1,i)};return Object.assign({},l,{count:l.x*l.y*l.z})}function P(e,t){if(t){let n=new Blob([e],{type:"image/svg+xml"});return`url(${URL.createObjectURL(n)}#${t})`}return`url("data:image/svg+xml;utf8,${encodeURIComponent(e)}")`}function U(e){const t='xmlns="http://www.w3.org/2000/svg"';return e.includes("<svg")||(e=`<svg ${t}>${e}</svg>`),e.includes("xmlns")||(e=e.replace(/<svg([\s>])/,`<svg ${t}$1`)),e}function B(e=0,t=e){return 1==arguments.length&&(e=e<1?.1:1),function(e,t,n){return e*(1-n)+t*n}(e,t,Math.random())}function J(e){return(...t)=>{let n=function(e){let t="";return e.some(e=>{let n=String(e).trim();if(!n)return"";let r=n.match(/\d(\D+)$/);return t=r?r[1]:""}),t}(t);return function(e,t){return(...n)=>{n=n.map(e=>Number(String(e).replace(/\D+$/g,"")));let r=e.apply(null,n);return t.length?Array.isArray(r)?r.map(e=>e+t):r+t:r}}(e,n).apply(null,t)}}function Z(e){return(...t)=>{let n=t.map(e=>String(e).charCodeAt(0)),r=e.apply(null,n);return Array.isArray(r)?r.map(e=>String.fromCharCode(e)):String.fromCharCode(r)}}function F(e){const t=function(e){let t=function(e){let t=String(e),n=[],r="";for(let e=0;e<t.length;++e){let i=t[e];if(V[i])if("-"==i&&"e"==t[e-1])r+=i;else if(n.length||r.length||!/[+-]/.test(i)){let{type:e,value:t}=s(n)||{};"operator"==e&&!r.length&&/[^()]/.test(i)&&/[^()]/.test(t)?r+=i:(r.length&&(n.push({type:"number",value:r}),r=""),n.push({type:"operator",value:i}))}else r+=i;else/\S/.test(i)&&(r+=i)}r.length&&n.push({type:"number",value:r});return n}(e);const n=[],r=[];for(let e=0;e<t.length;++e){let{type:i,value:l}=t[e];if("number"==i)r.push(l);else if("operator"==i)if("("==l)n.push(l);else if(")"==l){for(;n.length&&"("!=s(n);)r.push(n.pop());n.pop()}else{for(;n.length&&V[s(n)]>=V[l];){let e=n.pop();/[()]/.test(e)||r.push(e)}n.push(l)}}for(;n.length;)r.push(n.pop());return r}(e),n=[];for(;t.length;){let e=t.shift();if(/\d+/.test(e))n.push(e);else{let t=n.pop(),r=n.pop();n.push(X(e,Number(r),Number(t)))}}return n[0]}const V={"*":3,"/":3,"%":3,"+":2,"-":2,"(":1,")":1};function X(e,t,n){switch(e){case"+":return t+n;case"-":return t-n;case"*":return t*n;case"/":return t/n;case"%":return t%n}}const G={};function K(e,t){return(...n)=>{let r=e+n.join("-");return G[r]?G[r]:G[r]=t.apply(null,n)}}function Q(e){return(...t)=>e.apply(null,l(t,e=>String(e).startsWith("[")?ee(e):e))}function Y(e,t){return{type:e,value:t}}const ee=K("build_range",e=>{return l(function(e){let t=String(e),n=[],r=[];if(!t.startsWith("[")||!t.endsWith("]"))return n;for(let e=1;e<t.length-1;++e){let i=t[e];if("-"!=i||"-"!=t[e-1])if("-"!=i)if("-"!=s(r))r.length&&n.push(Y("char",r.pop())),r.push(i);else{r.pop();let e=r.pop();n.push(e?Y("range",[e,i]):Y("char",i))}else r.push(i)}return r.length&&n.push(Y("char",r.pop())),n}(e),({type:e,value:t})=>{if("char"==e)return t;let[n,r]=t,s=!1;n>r&&([n,r]=[r,n],s=!0);let i=Z(L)(n,r);return s&&i.reverse(),i})}),{cos:te,sin:ne,sqrt:re,pow:se,PI:ie}=Math,le=ie/180;function ue(e,t){"function"==typeof arguments[0]&&(t=e,e={}),t||(t=(e=>[te(e),ne(e)]));let n=e.split||120,r=e.scale||1,s=le*(e.start||0),i=e.deg?e.deg*le:ie/(n/2),l=[];for(let e=0;e<n;++e){let n=s+i*e,[u,o]=t(n);l.push(50*u*r+50+"% "+(50*o*r+50)+"%")}return e.type?`polygon(${e.type}, ${l.join(",")})`:`polygon(${l.join(",")})`}function oe(e,t,n){let r=le*n;return[e*te(r)-t*ne(r),t*te(r)+e*ne(r)]}const ae={circle:()=>"circle(49%)",triangle:()=>ue({split:3,start:-90},e=>[1.1*te(e),1.1*ne(e)+.2]),rhombus:()=>ue({split:4}),pentagon:()=>ue({split:5,start:54}),hexgon:()=>ue({split:6,start:30}),hexagon:()=>ue({split:6,start:30}),heptagon:()=>ue({split:7,start:-90}),octagon:()=>ue({split:8,start:22.5}),star:()=>ue({split:5,start:54,deg:144}),diamond:()=>"polygon(50% 5%, 80% 50%, 50% 95%, 20% 50%)",cross:()=>"polygon(\n 5% 35%, 35% 35%, 35% 5%, 65% 5%,\n 65% 35%, 95% 35%, 95% 65%, 65% 65%,\n 65% 95%, 35% 95%, 35% 65%, 5% 65%\n )",clover:(e=3)=>(4==(e=O(e,3,5))&&(e=2),ue({split:240},t=>{let n=te(e*t)*te(t),r=te(e*t)*ne(t);return 3==e&&(n-=.2),2==e&&(n/=1.1,r/=1.1),[n,r]})),hypocycloid(e=3){let t=1-(e=O(e,3,6));return ue({scale:1/e},n=>{let r=t*te(n)+te(t*(n-ie)),s=t*ne(n)+ne(t*(n-ie));return 3==e&&(r=1.1*r-.6,s*=1.1),[r,s]})},astroid:()=>ae.hypocycloid(4),infinity:()=>ue(e=>{let t=.7*re(2)*te(e),n=se(ne(e),2)+1;return[t/n,t*ne(e)/n]}),heart:()=>ue(e=>{return oe(1.2*(.75*se(ne(e),3)),1.1*(te(1*e)*(13/18)-te(2*e)*(5/18)-te(3*e)/18-te(4*e)/18+.2),180)}),bean:()=>ue(e=>{let[t,n]=[se(ne(e),3),se(te(e),3)];return oe((t+n)*te(e)*1.3-.45,(t+n)*ne(e)*1.3-.45,-90)}),bicorn:()=>ue(e=>oe(te(e),se(ne(e),2)/(2+ne(e))-.5,180)),pear:()=>ue(e=>[ne(e),(1+ne(e))*te(e)/1.4]),fish:()=>ue(e=>[te(e)-se(ne(e),2)/re(2),ne(2*e)/2]),whale:()=>ue({split:240},e=>{let t=3.4*(se(ne(e),2)-.5)*te(e);return oe(te(e)*t+.75,ne(e)*t*1.2,180)}),bud:(e=3)=>(e=O(e,3,10),ue({split:240},t=>[(1+.2*te(e*t))*te(t)*.8,(1+.2*te(e*t))*ne(t)*.8])),alien(...e){let[t=1,n=1,r=1,s=1,i=1]=e.map(e=>O(e,1,9));return ue({split:480,type:"evenodd"},e=>[.31*(te(e*t)+te(e*r)+te(e*i)),.31*(ne(e*n)+ne(e*s)+ne(e))])}},ce={index:({count:e})=>t=>e,row:({x:e})=>t=>e,col:({y:e})=>t=>e,depth:({z:e})=>t=>e,size:({grid:e})=>t=>e.count,"size-row":({grid:e})=>t=>e.x,"size-col":({grid:e})=>t=>e.y,"size-depth":({grid:e})=>t=>e.z,id:({x:e,y:t,z:n})=>r=>W(e,t,n),n:({idx:e})=>t=>e||0,pick:({context:e})=>Q((...t)=>e.last_pick=function(...e){let t=e.reduce((e,t)=>e.concat(t),[]);return t[~~(Math.random()*t.length)]}(t)),"pick-n"({idx:e,context:t,position:n}){let r="pn-counter"+n;return Q((...n)=>{t[r]||(t[r]=0),t[r]+=1;let s=n.length,i=((void 0===e?t[r]:e)-1)%s;return t.last_pick=n[i]})},"pick-d"({idx:e,context:t,position:n}){let r="pd-counter"+n,s="pd-values"+n;return Q((...n)=>{t[r]||(t[r]=0),t[r]+=1,t[s]||(t[s]=function(e){let t=Array.from?Array.from(e):e.slice(),n=e.length;for(;n;){let e=~~(Math.random()*n--),r=t[n];t[n]=t[e],t[e]=r}return t}(n));let i=n.length,l=((void 0===e?t[r]:e)-1)%i;return t.last_pick=t[s][l]})},"last-pick":({context:e})=>()=>e.last_pick,multiple:N((e,t)=>{if(!t||!e)return"";return H(O(e(),0,65536),e=>t(e+1)).join(",")}),"multiple-with-space":N((e,t)=>{if(!t||!e)return"";return H(O(e(),0,65536),e=>t(e+1)).join(" ")}),repeat:N((e,t)=>{if(!t||!e)return"";return H(O(e(),0,65536),e=>t(e+1)).join("")}),rand:({context:e})=>(...t)=>{let n=(t.every(T)?Z:J)(B).apply(null,t);return e.last_rand=n},"rand-int":({context:e})=>(...t)=>{let n=t.every(T)?Z:J,r=parseInt(n(B).apply(null,t));return e.last_rand=r},"last-rand":({context:e})=>()=>e.last_rand,calc:()=>e=>F(e),hex:()=>e=>parseInt(e).toString(16),svg:N(e=>{if(void 0===e)return"";return P(U(e().trim()))}),"svg-filter":N(e=>{if(void 0===e)return"";let t=function(e=""){return e+Math.random().toString(32).substr(2)}("filter-");return P(U(e().trim()).replace(/<filter([\s>])/,`<filter id="${t}"$1`),t)}),var:()=>e=>`var(${e})`,shape:()=>memo("shape-function",(e="",...t)=>(e=e.trim(),"function"==typeof ae[e]?ae[e](t):""))};var pe,he,fe=(pe=ce,he={m:"multiple",ms:"multiple-with-space",r:"rand",ri:"rand-int",lr:"last-rand",p:"pick",pn:"pick-n",pd:"pick-d",lp:"last-pick",i:"index",x:"row",y:"col",z:"depth","size-x":"size-row","size-y":"size-col","size-z":"size-depth",multi:"multiple","pick-by-turn":"pick-n","max-row":"size-row","max-col":"size-col"},Object.keys(he).forEach(e=>{pe[e]=pe[he[e]]}),pe);const de=e=>/[,,\s]/.test(e);function ge(e){for(;!e.end()&&de(e.curr(1));)e.next()}function me(t){const n=e(t),r=[],s=[];let i="";for(;!n.end();){let e=n.curr();"("==e?(i+=e,s.push(e)):")"==e?(i+=e,s.length&&s.pop()):s.length?i+=e:de(e)?(r.push(i),i="",ge(n)):i+=e,n.next()}return i&&r.push(i),r}let ye=[];function xe(e){if(!ye.length){let e=new Set;for(let t in document.head.style)t.startsWith("-")||e.add(t.replace(/[A-Z]/g,"-$&").toLowerCase());e.has("grid-gap")||e.add("grid-gap"),ye=Array.from(e)}return e&&e.test?ye.filter(t=>e.test(t)):ye}function _e(e){let t=new RegExp(`\\-?${e}\\-?`);return xe(t).map(e=>e.replace(t,"")).reduce((e,t)=>(e[t]=t,e),{})}const ve=_e("webkit"),be=_e("moz");function $e(e,t){return ve[e]?`-webkit-${t}${t}`:be[e]?`-moz-${t}${t}`:t}var ke={"@size"(e,{is_special_selector:t}){let[n,r=n]=me(e);return`\n width:${n};height:${r};${t?"":`\n --internal-cell-width:${n};--internal-cell-height:${r};`}`},"@min-size"(e){let[t,n=t]=me(e);return`min-width:${t};min-height:${n};`},"@max-size"(e){let[t,n=t]=me(e);return`max-width:${t};max-height:${n};`},"@place-cell":(()=>{let e={center:"50%",0:"0%",left:"0%",right:"100%",top:"50%",bottom:"50%"},t={center:"50%",0:"0%",top:"0%",bottom:"100%",left:"50%",right:"50%"};return n=>{let[r,s="50%"]=me(n);const i="var(--internal-cell-width, 25%)",l="var(--internal-cell-height, 25%)";return`\n position:absolute;left:${r=e[r]||r};top:${s=t[s]||s};width:${i};height:${l};margin-left:calc(${i}/ -2) !important;margin-top:calc(${l}/ -2) !important;grid-area:unset !important;`}})(),"@grid"(e,t){let[n,r]=e.split("/").map(e=>e.trim());return{grid:q(n),size:r?this["@size"](r,t):""}},"@shape":K("shape-property",e=>{let[t,...n]=me(e);return ae[t]?$e("clip-path",`clip-path:${ae[t].apply(null,n)};`)+"overflow:hidden;":""}),"@use"(e){if(e.length>2)return e}};function ze(e,t,n){let r=function(e){return t=>String(e).replace(/(\d+)(n)/g,"$1*"+t).replace(/n/g,t)}(e);for(let e=0;e<=n;++e)if(F(r(e))==t)return!0}const we={even:e=>!!(e%2),odd:e=>!(e%2)};function je(e){return/^(even|odd)$/.test(e)}var Ae={at:({x:e,y:t})=>(n,r)=>e==n&&t==r,nth:({count:e,grid:t})=>(...n)=>n.some(n=>je(n)?we[n](e-1):ze(n,e,t.count)),row:({x:e,grid:t})=>(...n)=>n.some(n=>je(n)?we[n](e-1):ze(n,e,t.x)),col:({y:e,grid:t})=>(...n)=>n.some(n=>je(n)?we[n](e-1):ze(n,e,t.y)),even:({count:e})=>t=>we.even(e-1),odd:({count:e})=>t=>we.odd(e-1),random:()=>(e=.5)=>(e>=1&&e<=0&&(e=.5),Math.random()<e)};var Me=Object.getOwnPropertyNames(Math).reduce((e,t)=>(e[t]=(()=>(...e)=>"number"==typeof Math[t]?Math[t]:Math[t].apply(null,e.map(F))),e),{});function Se(e){return/^\:(host|doodle)/.test(e)}function Ce(e){return/^\:(container|parent)/.test(e)}function Ee(e){return Se(e)||Ce(e)}class Oe{constructor(e){this.tokens=e,this.rules={},this.props={},this.keyframes={},this.grid=null,this.coords=[],this.reset()}reset(){this.styles={host:"",container:"",cells:"",keyframes:""},this.coords=[];for(let e in this.rules)e.startsWith("#cell")&&delete this.rules[e]}add_rule(e,t){let r=this.rules[e];r||(r=this.rules[e]=[]),r.push.apply(r,n(t))}pick_func(e){return fe[e]||Me[e]}compose_aname(...e){return e.join("-")}compose_selector({x:e,y:t,z:n},r=""){return`#${W(e,t,n)}${r}`}compose_argument(e,t,n){let r=e.map(e=>{if("text"==e.type)return e.value;if("func"==e.type){let r=this.pick_func(e.name.substr(1));if(r){t.idx=n,t.position=e.position;let s=e.arguments.map(e=>r.lazy?n=>this.compose_argument(e,t,n):this.compose_argument(e,t,n));return E(r,t,s)}}});return r.length>=2?r.join(""):r[0]}compose_value(e,t){return e&&e.reduce?e.reduce((e,n)=>{switch(n.type){case"text":e+=n.value;break;case"func":{let r=n.name.substr(1),s=this.pick_func(r);if(s){t.position=n.position;let r=n.arguments.map(e=>s.lazy?n=>this.compose_argument(e,t,n):this.compose_argument(e,t));e+=E(s,t,r)}}}return e},""):""}compose_rule(e,t,n){let r=Object.assign({},t),s=e.property,i=e.value.reduce((e,t)=>{let n=this.compose_value(t,r);return n&&e.push(n),e},[]),l=i.join(", ");if(/^animation(\-name)?$/.test(s)&&(this.props.has_animation=!0,r.count>1)){let{count:e}=r;switch(s){case"animation-name":l=i.map(t=>this.compose_aname(t,e)).join(", ");break;case"animation":l=i.map(t=>{let n=(t||"").split(/\s+/);return n[0]=this.compose_aname(n[0],e),n.join(" ")}).join(", ")}}"content"==s&&(/["']|^none$|^(var|counter|counters|attr)\(/.test(l)||(l=`'${l}'`)),"transition"==s&&(this.props.has_transition=!0);let u=`${s}:${l};`;if(u=$e(s,u),"clip-path"==s&&(u+=";overflow:hidden;"),"width"!=s&&"height"!=s||Ee(n)||(u+=`--internal-cell-${s}:${l};`),ke[s]){let t=ke[s](l,{is_special_selector:Ee(n)});switch(s){case"@grid":Se(n)&&(this.grid=t.grid,u=t.size||"");break;case"@place-cell":Se(n)||(u=t);case"@use":e.value.length&&this.compose(r,e.value),u=ke[s](e.value);default:u=t}}return u}compose(e,t){this.coords.push(e),(t||this.tokens).forEach((t,n)=>{if(t.skip)return!1;switch(t.type){case"rule":this.add_rule(this.compose_selector(e),this.compose_rule(t,e));break;case"pseudo":{t.selector.startsWith(":doodle")&&(t.selector=t.selector.replace(/^\:+doodle/,":host"));let n=Ee(t.selector);n&&(t.skip=!0),t.selector.split(",").forEach(r=>{let s=t.styles.map(t=>this.compose_rule(t,e,r)),i=n?r:this.compose_selector(e,r);this.add_rule(i,s)});break}case"cond":{let n=Ae[t.name.substr(1)];if(n){let r=t.arguments.map(t=>this.compose_argument(t,e));E(n,e,r)&&this.compose(e,t.styles)}break}case"keyframes":this.keyframes[t.name]||(this.keyframes[t.name]=(e=>`\n ${r(t.steps.map(t=>`\n ${t.name}{${r(t.styles.map(t=>this.compose_rule(t,e)))}}`))}`))}})}output(){Object.keys(this.rules).forEach((e,t)=>{if(Ce(e))this.styles.container+=`\n .container{${r(this.rules[e])}}`;else{let t=Se(e)?"host":"cells";this.styles[t]+=`\n ${e}{${r(this.rules[e])}}`}});let e=Object.keys(this.keyframes);return this.coords.forEach((t,n)=>{e.forEach(e=>{let r=this.compose_aname(e,t.count);this.styles.keyframes+=`\n ${function(e,t){return e?"function"==typeof t?t():t:""}(0==n,`@keyframes ${e}{${this.keyframes[e](t)}}`)}@keyframes ${r}{${this.keyframes[e](t)}}`})}),{props:this.props,styles:this.styles,grid:this.grid}}}function Le(e,t){let n=new Oe(e),r={};n.compose({x:1,y:1,z:1,count:1,context:{},grid:{x:1,y:1,z:1,count:1}});let{grid:s}=n.output();if(s&&(t=s),n.reset(),1==t.z)for(let e=1,s=0;e<=t.x;++e)for(let i=1;i<=t.y;++i)n.compose({x:e,y:i,z:1,count:++s,grid:t,context:r});else for(let e=1,s=0;e<=t.z;++e)n.compose({x:1,y:1,z:e,count:++s,grid:t,context:r});return n.output()}customElements.define("css-doodle",class extends HTMLElement{constructor(){super(),this.doodle=this.attachShadow({mode:"open"}),this.extra={get_custom_property_value:this.get_custom_property_value.bind(this)}}connectedCallback(e){setTimeout(()=>{let t,n=this.getAttribute("use")||"";if(n&&(n=`@use:${n};`),!this.innerHTML.trim()&&!n)return!1;try{let e=C(n+this.innerHTML,this.extra);this.grid_size=q(this.getAttribute("grid")),(t=Le(e,this.grid_size)).grid&&(this.grid_size=t.grid),this.build_grid(t)}catch(e){this.innerHTML="",console.error(e&&e.message||"Error in css-doodle.")}!e&&this.hasAttribute("click-to-update")&&this.addEventListener("click",e=>this.update())})}get_custom_property_value(e){return getComputedStyle(this).getPropertyValue(e).trim().replace(/^\(|\)$/g,"")}cell(e,t,n){let r=document.createElement("div");return r.id=W(e,t,n),r}build_grid(e){const{has_transition:t,has_animation:n}=e.props,{keyframes:r,host:s,container:i,cells:l}=e.styles;this.doodle.innerHTML=`\n<style>${this.style_basic()}</style><style class="style-keyframes">${r}</style><style class="style-container">${this.style_size()}${s}${i}</style><style class="style-cells">${t||n?"":l}</style><div class="container"></div>`,this.doodle.querySelector(".container").appendChild(this.html_cells()),(t||n)&&setTimeout(()=>{this.set_style(".style-cells",l)},50)}inherit_props(e){return xe(/grid/).map(e=>`${e}:inherit;`).join("")}style_basic(){return`\n:host{display:block;visibility:visible;width:1em;height:1em;}.container{position:relative;width:100%;height:100%;display:grid;${this.inherit_props()}}.container div:empty{position:relative;line-height:1;box-sizing:border-box;display:flex;justify-content:center;align-items:center;}`}style_size(){let{x:e,y:t}=this.grid_size;return`\n:host{grid-template-rows:repeat(${e}, 1fr);grid-template-columns:repeat(${t}, 1fr);}`}html_cells(){let{x:e,y:t,z:n}=this.grid_size,r=document.createDocumentFragment();if(1==n)for(let n=1;n<=e;++n)for(let e=1;e<=t;++e)r.appendChild(this.cell(n,e,1));else{let e=null;for(let t=1;t<=n;++t){let n=this.cell(1,1,t);(e||r).appendChild(n),e=n}e=null}return r}set_style(e,t){const n=this.shadowRoot.querySelector(e);n&&(n.styleSheet?n.styleSheet.cssText=t:n.innerHTML=t)}update(e){let t=this.getAttribute("use")||"";t&&(t=`@use:${t};`),e||(e=this.innerHTML),this.innerHTML=e,this.grid_size||(this.grid_size=q(this.getAttribute("grid")));const n=Le(C(t+e,this.extra),this.grid_size);if(n.grid){let{x:e,y:t,z:r}=n.grid,{x:s,y:i,z:l}=this.grid_size;if(s!==e||i!==t||l!==r)return Object.assign(this.grid_size,n.grid),this.build_grid(n);Object.assign(this.grid_size,n.grid)}else{let n=q(this.getAttribute("grid")),{x:r,y:s,z:i}=n,{x:l,y:u,z:o}=this.grid_size;if(l!==r||u!==s||o!==i)return Object.assign(this.grid_size,n),this.build_grid(Le(C(t+e,this.extra),this.grid_size))}this.set_style(".style-keyframes",n.styles.keyframes),n.props.has_animation&&(this.set_style(".style-cells",""),this.set_style(".style-container","")),setTimeout(()=>{this.set_style(".style-container",this.style_size()+n.styles.host+n.styles.container),this.set_style(".style-cells",n.styles.cells)})}get grid(){return Object.assign({},this.grid_size)}set grid(e){this.setAttribute("grid",e),this.connectedCallback(!0)}get use(){return this.getAttribute("use")}set use(e){this.setAttribute("use",e),this.connectedCallback(!0)}static get observedAttributes(){return["grid","use"]}attributeChangedCallback(e,t,n){if(t==n)return!1;"grid"==e&&t&&(this.grid=n),"use"==e&&t&&(this.use=n)}})});
\ No newline at end of file

A  => index.html +53 -0
@@ 1,53 @@
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">
<!--
This HTML source code is licensed under the GNU Affero General Public License 3.0. (https://www.gnu.org/licenses/agpl-3.0.html)

The code is available at: https://gitlab.com/johanvandegriff/vandy.land
-->
<!--[if lt IE 9]>
  <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<![endif]-->
<link rel="stylesheet" type="text/css" href="style.css">
<!-- <link href="https://fonts.googleapis.com/css?family=Titan+One&display=swap" rel="stylesheet"> -->
<script src="css-doodle-0.7.3.min.js"></script>
<title>vandy land</title>

<meta charset="utf-8">
<meta name="description" content="">
<meta name="robots" content="index, follow">
<meta name="author" content="Johan Vandegriff">

<meta property="og:type" content="website">
<meta property="og:title" content="vandy land">
<meta property="og:site_name" content="vandy land">
<meta property="og:url" content="vandy.land">
<meta property="og:description" content="">
<meta property="og:image" content="">

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="vandy.land">
<meta name="twitter:title" content="vandy land">
<meta name="twitter:description" content="">
<meta name="twitter:image" content="">

<link rel="icon" href="/favicon.ico?" type="image/x-icon">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
</head>
<body>
<css-doodle use="var(--rule)"></css-doodle>

<div class="siteTitle">vandy land</div>

<div class="loaderWrapper">
  <div class="loader"></div>
</div>
<section>

</section>
<footer>
Site made by <a target="_blank" href="https://johanv.xyz/">Johan Vandegriff</a><br/>
<a target="_blank" href="https://gitlab.com/johanvandegriff/vandy.land">source code</a> released under the GNU AGPLv3<br/>
Theme from <a target="_blank" href="https://codemenatalie.com/blog" target="_blank">CodeMeNatalie</a> on <a target="_blank" href="https://codepen.io/CodeMeNatalie/pen/WNNadWy">CodePen</a>
</footer>
</body>
</html>

A  => style.css +148 -0
@@ 1,148 @@
@charset "UTF-8";
body {
  position: relative;
  margin: 0;
  padding: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: white;
  background-color: white;
  background: linear-gradient(to bottom, #e6f8f9 20%, #b1e8ed 100%);
  overflow: hidden;
}
body .siteTitle {
  background-size: 30px 30px;
  /* font-family: "Titan One", cursive; */
  /* font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; */
  /* font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; */
  /* font-family: sans-serif; */
  /* font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; */
  /* font-family: Verdana, Geneva, Tahoma, sans-serif; */
  /* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; */
  /* font-family: Arial, Helvetica, sans-serif; */
  font-family: Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  /* font-style: italic; */
  font-variant: small-caps;
  font-weight: bolder;
  font-size: 100px;
  color: #d33144;
  -webkit-text-stroke: 3px #ffffee;
  z-index: 5;
  padding-bottom: 40px;
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
  animation: textBounce 12s infinite 5s cubic-bezier(0, 0.03, 0.83, 1.41) both;
}
body .loaderWrapper {
  border-radius: 50px;
  width: 400px;
  border: 5px solid #ffffee;
  z-index: 5;
  overflow: hidden;
  box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.3);
}
body .loaderWrapper .loader {
  height: 60px;
  width: 800px;
  background-image: linear-gradient(-45deg, #d33144 25%, #ffffee 25%, #ffffee 50%, #d33144 50%, #d33144 75%, #ffffee 75%, #ffffee 100%);
  background-size: 55px 55px;
  box-shadow: 0px 0px 10px rgba(255, 50, 0, 0.8) inset;
  animation: load 3s linear -0.1s infinite;
}
body footer {
  font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
  position: absolute;
  /* left: calc(50% - 96px); */
  align-self: center;
  bottom: 0;
  font-style: italic;
  font-size: 12px;
  color: #212121;
  background-color: white;
  padding: 10px 20px;
  border-radius: 5px 5px 0 0;
  box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.25);
  z-index: 10;
  text-align: center;
}

@keyframes load {
  0% {
    transform: translateX(-200px);
  }
  100% {
    transform: translateX(-35px);
  }
}
@keyframes textBounce {
  from, 10% {
    transform: rotate(0deg);
  }
  1%, 3%, 5%, 7%, 9% {
    transform: rotate(10deg);
  }
  2%, 4%, 6%, 8% {
    transform: rotate(-10deg);
  }
}
css-doodle {
  --rule: ( :doodle {
    position: absolute;
    width: 100%;
    @grid: 10 / 101% 100vh;
    overflow: hidden;
  }
  animation: bounce linear @r(7s, 15s) @r(-1s, -5s) infinite;
  opacity: @r(0.3, 1);
  @place-cell: @r(100%) @r(100%);
  @random(.3) {
  :after {
    content: '🍭';
    position: absolute;
    font-size: @r(25px, 35px);
    transform: rotate(@r(360deg));
  }
  }
  @random(.2) {
    :after {
      content: '🍫';
      position: absolute;
      @place-cell: @r(100%) @r(100%);
      font-size: @r(15px, 25px);
      z-index: @p(1, 2);
      transform: rotate(@r(360deg));
    }
  }
  @random(.2) {
    :before {
      content: '🧁';
      position: absolute;
      @place-cell: @r(100%) @r(100%);
      font-size: @r(15px, 25px);
      z-index: @p(1, 2);
      transform: rotate(@r(360deg));
    }
  }
  @random(.2) {
    :before {
      content: '🍪';
      position: absolute;
      @place-cell: @r(100%) @r(100%);
      font-size: @r(15px, 25px);
      z-index: @p(1, 2);
      transform: rotate(@r(360deg));
    }
  }

  @keyframes bounce {
    0% {
      transform: translateY(@r(-101vh, -110vh));
    }
    100% {
      transform: translateY(@r(101vh, 110vh));
    }
  }
  );
}