~xigoi/whylebrate

8b5d9781d07c0ac62a95d2d3bc6de023a09a2359 — Adam Blažek 8 months ago
Proof of concept
4 files changed, 135 insertions(+), 0 deletions(-)

A index.html
A index.xd
A q.min.js
A whylebrate.js
A  => index.html +9 -0
@@ 1,9 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="generator" content="xidoc"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="data:," /><title>Whylebrate — find a reason to celebrate on any date</title><style>:root{--serif:Constantia,"Lucida Bright",Lucidabright,"Lucida Serif",Lucida,"DejaVu Serif","Bitstream Vera Serif","Liberation Serif",Georgia,serif} :root{--sans:-apple-system,BlinkMacSystemFont,"Avenir Next",Avenir,"Nimbus Sans L",Roboto,Noto,"Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif} a{color:#08f;} body{width:min(800px, calc(100% - 8px)); margin:auto; line-height:1.4; background-color:#000; color:#fff; font-family:var(--serif);} h1,h2,h3,h4,h5,h6{font-family:var(--sans);} section{padding-left:.3rem; border-width:0 0 0 .1rem; border-style:solid; border-color:#222;} summary{cursor:pointer;}</style></head><body> <script src="./q.min.js"></script> <script type="module">import whylebrate from "./whylebrate.js";
  const date = new Date();
  const model = {
    whylebrate: whylebrate,
    year: date.getFullYear(),
    month: date.getMonth() + 1,
    day: date.getDate(),
  };
  Q(document.body, model)</script> <h1>Whylebrate — find a reason to celebrate on any date</h1> <div><input type="number" q-model="year" /> <input type="number" q-model="month" /> <input type="number" q-model="day" /> <ul q-each="whylebrate(year, month, day)"><li q-text="$it.text"></li></ul></div> </body></html>

A  => index.xd +49 -0
@@ 1,49 @@
[style
  [var serif; Constantia,"Lucida Bright",Lucidabright,"Lucida Serif",Lucida,"DejaVu Serif","Bitstream Vera Serif","Liberation Serif",Georgia,serif]
  [var sans; -apple-system,BlinkMacSystemFont,"Avenir Next",Avenir,"Nimbus Sans L",Roboto,Noto,"Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif]
  [rule a;
    [: color; #08f]
  ]
  [rule body;
    [: width; min(800px, calc(100% - 8px))]
    [: margin; auto]
    [: line-height; 1.4]
    [: background-color; #000]
    [: color; #fff]
    [: font-family; [var serif]]
  ]
  [rule [h*];
    [: font-family; [var sans]]
  ]
  [rule section;
    [: padding-left; .3rem]
    [: border-width; 0 0 0 .1rem]
    [: border-style; solid]
    [: border-color; #222]
  ]
  [rule summary;
    [: cursor; pointer]
  ]
]
[add-to-head [<link> rel="icon"; href="data:,"]]
[<script> src="./q.min.js"; ]
[<script> type="module"; [pass-raw
  import whylebrate from "./whylebrate.js";
  const date = new Date();
  const model = {
    whylebrate: whylebrate,
    year: date.getFullYear(),
    month: date.getMonth() + 1,
    day: date.getDate(),
  };
  Q(document.body, model)
]]
[title Whylebrate [---] find a reason to celebrate on any date]
[<div>
  [<input> type="number"; q-model="year"]
  [<input> type="number"; q-model="month"]
  [<input> type="number"; q-model="day"]
  [<ul> q-each="whylebrate(+year, +month, +day)";
    [<li> q-html="$it.text"; ]
  ]
]

A  => q.min.js +1 -0
@@ 1,1 @@
let call=(t,e)=>new Function(`with(this){${"return "+t}}`).bind(e)(),directives={html:(t,e,n,o)=>t.innerHTML=call(n,o),text:(t,e,n,o)=>t.innerText=call(n,o),if:(t,e,n,o)=>t.hidden=!call(n,o),on:(t,e,n,o)=>t["on"+e]=()=>call(n,o),model:(t,e,n,o)=>{t.value=o[n],t.oninput=()=>o[n]=t.value},bind:(t,e,n,o)=>{let i=call(n,o);if("style"===e){t.removeAttribute("style");for(let e in i)t.style[e]=i[e]}else"class"===e?t.setAttribute("class",[].concat(i).join(" ")):i?t.setAttribute(e,i):t.removeAttribute(e)},each:(t,e,n,o)=>{let i=call(n,o);t.$each||(t.$each=t.children[0]),t.innerText="";for(let e of i){let n=document.importNode(t.$each),i={$parent:o,$it:e};n.$q=i,Q(n,i),t.appendChild(n)}}};let $dep;let walk=(t,e)=>{for(let{name:n,value:o}of t.attributes)if(n.startsWith("q-")){let[i,l]=n.substring(2).split(":"),s=directives[i];$dep=()=>s(t,l,o,e),$dep(),$dep=void 0}for(let n of t.children)n.$q||walk(n,e)},proxy=t=>{let e={};for(let n in t){e[n]=[];let o=t[n];Object.defineProperty(t,n,{get:()=>($dep&&e[n].push($dep),o),set(t){if(o=t,!n.startsWith("$"))for(let o of e[n])o(t)}})}return t},Q=(t,e)=>walk(t,proxy(e));

A  => whylebrate.js +76 -0
@@ 1,76 @@
const toBase = (n, base) => {
  const digits = [];
  while (n !== 0) {
    digits.push(n % base);
    n = Math.floor(n / base);
  }
  return digits.reverse();
};

const fromBase = (digits, base) => digits.reduce((a, b) => a * base + b);

const niceDigits = (digits, base, didPermutation) => {
  let reasons = [];
  if (!didPermutation && new Set(digits).size === 1) {
    reasons.push("contains only one unique digit");
  }
  if (!didPermutation && base > 2 && new Set(digits).size === 2) {
    reasons.push("contains only two unique digits");
  }
  if (digits.every((d, i) => d === digits.at(~i))) {
    reasons.push("is a palindrome");
  }
  for (let base1 = base; base1 <= 36; ++base1) {
    const n = fromBase(digits, base1);
    for (let logBase = 2; logBase <= 1000; ++logBase) {
      const log = Math.log(n) / Math.log(logBase);
      if (log === Math.round(log)) {
        reasons.push(`is a power of ${logBase} in base ${base1}`);
      }
    }
  }
  const formatted = formatDigits(digits, base);
  let match;
  if ((match = formatted.match(/^(..+?)\1+$/))) {
    reasons.push(
      `consists of ${formatted.length / match[1].length} repetitions of "${
        match[1]
      }"`
    );
  }
  if (formatted.match(/^((.)\2+)+$/)) {
    reasons.push("consists of multiple blocks of repeated digits");
  }
  return reasons;
};

const formatDigits = (digits) =>
  digits
    .map((digit) =>
      digit < 10 ? String(digit) : String.fromCharCode(55 + digit)
    )
    .join("");

export default (year, month, day) => {
  let reasons = [];
  for (let base = 2; base <= 36; ++base) {
    let digits, nice;
    digits = [year, month, day].flatMap((n) => toBase(n, base));
    reasons = reasons.concat(
      niceDigits(digits, base, false).map((nice) => ({
        text: `The date written in base ${base} in YMD format is ${formatDigits(
          digits
        )}, which ${nice}`,
      }))
    );
    digits = [day, month, year].flatMap((n) => toBase(n, base));
    reasons = reasons.concat(
      niceDigits(digits, base, true).map((nice) => ({
        text: `The date written in base ${base} in DMY format is ${formatDigits(
          digits
        )}, which ${nice}`,
      }))
    );
  }
  return reasons;
};