import {createElement} from 'react'; import mdx from 'remark-mdx'; import parse from 'remark-parse'; import {unified} from 'unified'; import {visitParents as visit} from 'unist-util-visit-parents'; import {TAU} from '@/util/math.js'; const parser = unified().use(parse).use(mdx); function computeParams(ancestors) { const params = {}; for (let i = ancestors.length - 1; i >= 0; --i) { const {dialogue} = ancestors[i]; if (dialogue) { if (!(dialogue.name in params)) { params[dialogue.name] = dialogue.params; } } } return params; } export function parseLetters(source) { let letters = []; try { const tree = parser.parse(source); tree.dialogue = { name: 'rate', params: {frequency: 0.05, length: 0}, }; visit(tree, (node, ancestors) => { switch (node.type) { case 'mdxJsxFlowElement': case 'mdxJsxTextElement': { node.dialogue = {name: node.name, params: {length: 0}}; for (const {name, value: {value}} of node.attributes) { node.dialogue.params[name] = value; } break; } case 'text': { const params = computeParams(ancestors); const split = node.value.split(''); for (let i = 0; i < split.length; ++i) { const indices = {}; for (const name in params) { indices[name] = i + params[name].length; } letters.push({character: split[i], indices, params}); } for (const name in params) { params[name].length += split.length; } break; } } }); } catch (e) { letters = source.split('').map((character) => (({ character, indices: {}, params: {rate: {frequency: 0.05}}, }))); } return letters; } export function render(letters, className) { return (caret, radians) => ( letters .map(({character, indices, params}, i) => { let color = 'inherit'; let fade = 0; let fontStyle = 'normal'; let fontWeight = 'normal'; let left = 0; let opacity = 1; let top = 0; if (params.blink) { const {frequency = 1} = params.blink; opacity = (radians * (2 / frequency) % TAU) > (TAU / 2) ? opacity : 0; } if (params.fade) { const {frequency = 1} = params.fade; fade = frequency; } if (params.wave) { const {frequency = 1, magnitude = 3} = params.wave; top += magnitude * Math.cos((radians * (1 / frequency)) + TAU * indices.wave / params.wave.length); } if (params.rainbow) { const {frequency = 1} = params.rainbow; color = `hsl(${(radians * (1 / frequency)) + TAU * indices.rainbow / params.rainbow.length}rad 100 50)`; } if (params.shake) { const {magnitude = 1} = params.shake; left += (Math.random() * magnitude * 2) - magnitude; top += (Math.random() * magnitude * 2) - magnitude; } if (params.em) { fontStyle = 'italic'; } if (params.strong) { fontWeight = 'bold'; } return ( createElement( 'span', { className, key: i, style: { color, fontStyle, fontWeight, left: `${left}px`, opacity: i <= caret ? opacity : 0, position: 'relative', top: `${top}px`, transition: `opacity ${fade}s`, }, }, character, ) ); }) ); }