diff --git a/repo/registry.json b/repo/registry.json index ce291cf..a3b4c38 100644 --- a/repo/registry.json +++ b/repo/registry.json @@ -23,5 +23,6 @@ "collapse-properties", "scroll-to-top", "indentation-lines", - "focus-mode" + "focus-mode", + "word-counter" ] diff --git a/repo/theming/patches.css b/repo/theming/patches.css index ceab96d..74542c6 100644 --- a/repo/theming/patches.css +++ b/repo/theming/patches.css @@ -4,6 +4,97 @@ * (https://notion-enhancer.github.io/) under the MIT license */ +/** layout **/ + +.notion-frame + > .notion-scroller.vertical.horizontal + > .pseudoSelection + > div + > div:nth-child(3)[style*='width: 900px'], +.notion-frame + > .notion-scroller.vertical.horizontal + > .pseudoSelection + + div + > :nth-child(1)[style*='width: 900px'], +.notion-frame + > .notion-scroller.vertical.horizontal + > :nth-child(2) + > :nth-child(2)[style*='display: flex; width: 100%; justify-content: center;'] + > :nth-child(1)[style*='width: 900px'] { + width: var(--theme--page-width) !important; +} +.notion-frame + > .notion-scroller.vertical.horizontal + > .pseudoSelection + > div + > div:nth-child(3):not([style*='width: 900px']), +.notion-frame + > .notion-scroller.vertical.horizontal + > .pseudoSelection + + div + > :nth-child(1):not([style*='width: 900px']), +.notion-frame + > .notion-scroller.vertical.horizontal + > :nth-child(2) + > :nth-child(2)[style*='display: flex; width: 100%; justify-content: center;'] + > :nth-child(1):not([style*='width: 900px']) { + width: var(--theme--page-width_full) !important; +} +.notion-page-content [style*='width: 100%; max-width:'][style*='align-self: center;'] { + max-width: 100% !important; +} +.notion-frame [style*='padding-right: calc(96px + env(safe-area-inset-right));'] { + padding-right: var(--theme--page-padding) !important; +} +.notion-frame [style*='padding-left: calc(96px + env(safe-area-inset-left));'] { + padding-left: var(--theme--page-padding) !important; +} +[style^='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 30vh;'], +[style^='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 30vh;'] + img { + height: var(--theme--page_banner-height) !important; + background: transparent !important; +} + +.notion-peek-renderer > :nth-child(2) { + max-width: var(--theme--page_preview-width) !important; +} +.notion-peek-renderer + .notion-scroller.vertical + [style*='padding-left: calc(126px + env(safe-area-inset-left));'] { + padding-left: var(--theme--page_preview-padding) !important; +} +.notion-peek-renderer + .notion-scroller.vertical + [style*='padding-right: calc(126px + env(safe-area-inset-right));'] { + padding-right: var(--theme--page_preview-padding) !important; +} +.notion-peek-renderer + .notion-scroller.vertical + [style*='margin-left: calc(126px + env(safe-area-inset-left));'] { + margin-left: var(--theme--page_preview-padding) !important; +} +.notion-peek-renderer + .notion-scroller.vertical + [style*='margin-right: calc(126px + env(safe-area-inset-right));'] { + margin-right: var(--theme--page_preview-padding) !important; +} +.notion-peek-renderer .notion-page-content { + padding-left: var(--theme--page_preview-padding) !important; + padding-right: var(--theme--page_preview-padding) !important; + width: 100%; +} +.notion-peek-renderer + .notion-scroller.vertical + [style*='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 20vh;'], +.notion-peek-renderer + .notion-scroller.vertical + [style*='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 20vh;'] + img { + height: var(--theme--page_preview_banner-height) !important; + background: transparent !important; +} + /* typography */ [style*='Segoe UI'] { @@ -25,6 +116,19 @@ border-radius: 0px !important; } +/** scrollbars **/ + +::-webkit-scrollbar-track, +::-webkit-scrollbar-corner { + background: var(--theme--scrollbar_track) !important; +} +::-webkit-scrollbar-thumb { + background: var(--theme--scrollbar_thumb) !important; +} +::-webkit-scrollbar-thumb:hover { + background: var(--theme--scrollbar_thumb-hover) !important; +} + /** consistent corner button styling **/ .notion-overlay-container @@ -54,3 +158,59 @@ box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 0px 0px 1px, var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 2px 4px !important; } + +/* backgrounds */ + +.notion-discussion-input > div > div[style*='background'], +.notion-body.dark + .notion-default-overlay-container + [style*='grid-template-columns: [boolean-start] 60px [boolean-end property-start] 120px [property-end opererator-start] 110px [operator-end value-start] auto [value-end menu-start] 32px [menu-end];'] + [style*='grid-column: property-start / value-end; background: rgba(255, 255, 255, 0.02);'], +.notion-body:not(.dark) + .notion-default-overlay-container + [style*='grid-template-columns: [boolean-start] 60px [boolean-end property-start] 120px [property-end opererator-start] 110px [operator-end value-start] auto [value-end menu-start] 32px [menu-end];'] + [style*='grid-column: property-start / value-end; background: rgba(0, 0, 0, 0.02);'], +.notion-board-view [style*='width: 20px; margin-left: -20px; margin-top: -8px;'], +.notion-page-block > div > div > div[style*='background-color: white;'], +.line-numbers.notion-code-block + div .notion-focusable:not(:hover), +.notion-overlay-container + [style*='position: relative; max-width: calc(100vw - 24px); box-shadow:'] + > [style*='display: flex; align-items: center; padding: 8px 10px; width: 100%; background:'], +.notion-default-overlay-container + > div:nth-child(3) + > div + > div:nth-child(2) + > div:nth-child(2) + > div + > div + > div + > div + > div + > div:nth-child(2)[style*='position: absolute; display: inline-flex; min-width: 100%; height: 32px; z-index: 1; background:'], +.notion-default-overlay-container + > div:nth-child(2) + > div + > div:nth-child(2) + > div:nth-child(2) + > div + > div + > div + > div + > div + > div:nth-child(2)[style*='position: absolute; display: inline-flex; min-width: 100%; height: 32px; z-index: 1; background:'], +.notion-frame .notion-scroller[style*='background:'], +.notion-page-template-modal .notion-scroller[style*='background:'], +.notion-peek-renderer .notion-scroller[style*='background:'], +.notion-frame > div > div > div[style*='max-width: 100%'][style*='background-color'], +.notion-peek-renderer + > div + > .notion-scroller + > div + > div[style*='max-width: 100%'][style*='background-color'], +.notion-page-template-modal + > div + > .notion-scroller + > div + > div[style*='max-width: 100%'][style*='background-color'] { + background: transparent !important; +} diff --git a/repo/theming/theme.css b/repo/theming/theme.css index 8c03308..18352c6 100644 --- a/repo/theming/theme.css +++ b/repo/theming/theme.css @@ -4,97 +4,6 @@ * (https://notion-enhancer.github.io/) under the MIT license */ -/** layout **/ - -.notion-frame - > .notion-scroller.vertical.horizontal - > .pseudoSelection - > div - > div:nth-child(3)[style*='width: 900px'], -.notion-frame - > .notion-scroller.vertical.horizontal - > .pseudoSelection - + div - > :nth-child(1)[style*='width: 900px'], -.notion-frame - > .notion-scroller.vertical.horizontal - > :nth-child(2) - > :nth-child(2)[style*='display: flex; width: 100%; justify-content: center;'] - > :nth-child(1)[style*='width: 900px'] { - width: var(--theme--page-width) !important; -} -.notion-frame - > .notion-scroller.vertical.horizontal - > .pseudoSelection - > div - > div:nth-child(3):not([style*='width: 900px']), -.notion-frame - > .notion-scroller.vertical.horizontal - > .pseudoSelection - + div - > :nth-child(1):not([style*='width: 900px']), -.notion-frame - > .notion-scroller.vertical.horizontal - > :nth-child(2) - > :nth-child(2)[style*='display: flex; width: 100%; justify-content: center;'] - > :nth-child(1):not([style*='width: 900px']) { - width: var(--theme--page-width_full) !important; -} -.notion-page-content [style*='width: 100%; max-width:'][style*='align-self: center;'] { - max-width: 100% !important; -} -.notion-frame [style*='padding-right: calc(96px + env(safe-area-inset-right));'] { - padding-right: var(--theme--page-padding) !important; -} -.notion-frame [style*='padding-left: calc(96px + env(safe-area-inset-left));'] { - padding-left: var(--theme--page-padding) !important; -} -[style^='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 30vh;'], -[style^='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 30vh;'] - img { - height: var(--theme--page_banner-height) !important; - background: transparent !important; -} - -.notion-peek-renderer > :nth-child(2) { - max-width: var(--theme--page_preview-width) !important; -} -.notion-peek-renderer - .notion-scroller.vertical - [style*='padding-left: calc(126px + env(safe-area-inset-left));'] { - padding-left: var(--theme--page_preview-padding) !important; -} -.notion-peek-renderer - .notion-scroller.vertical - [style*='padding-right: calc(126px + env(safe-area-inset-right));'] { - padding-right: var(--theme--page_preview-padding) !important; -} -.notion-peek-renderer - .notion-scroller.vertical - [style*='margin-left: calc(126px + env(safe-area-inset-left));'] { - margin-left: var(--theme--page_preview-padding) !important; -} -.notion-peek-renderer - .notion-scroller.vertical - [style*='margin-right: calc(126px + env(safe-area-inset-right));'] { - margin-right: var(--theme--page_preview-padding) !important; -} -.notion-peek-renderer .notion-page-content { - padding-left: var(--theme--page_preview-padding) !important; - padding-right: var(--theme--page_preview-padding) !important; - width: 100%; -} -.notion-peek-renderer - .notion-scroller.vertical - [style*='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 20vh;'], -.notion-peek-renderer - .notion-scroller.vertical - [style*='position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; height: 20vh;'] - img { - height: var(--theme--page_preview_banner-height) !important; - background: transparent !important; -} - /* backgrounds */ .OnboardingHighlight + div > [style*='background-color'] { @@ -256,60 +165,6 @@ body, background: var(--theme--bg_card) !important; } -.notion-discussion-input > div > div[style*='background'], -.notion-body.dark - .notion-default-overlay-container - [style*='grid-template-columns: [boolean-start] 60px [boolean-end property-start] 120px [property-end opererator-start] 110px [operator-end value-start] auto [value-end menu-start] 32px [menu-end];'] - [style*='grid-column: property-start / value-end; background: rgba(255, 255, 255, 0.02);'], -.notion-body:not(.dark) - .notion-default-overlay-container - [style*='grid-template-columns: [boolean-start] 60px [boolean-end property-start] 120px [property-end opererator-start] 110px [operator-end value-start] auto [value-end menu-start] 32px [menu-end];'] - [style*='grid-column: property-start / value-end; background: rgba(0, 0, 0, 0.02);'], -.notion-board-view [style*='width: 20px; margin-left: -20px; margin-top: -8px;'], -.notion-page-block > div > div > div[style*='background-color: white;'], -.line-numbers.notion-code-block + div .notion-focusable:not(:hover), -.notion-overlay-container - [style*='position: relative; max-width: calc(100vw - 24px); box-shadow:'] - > [style*='display: flex; align-items: center; padding: 8px 10px; width: 100%; background:'], -.notion-default-overlay-container - > div:nth-child(3) - > div - > div:nth-child(2) - > div:nth-child(2) - > div - > div - > div - > div - > div - > div:nth-child(2)[style*='position: absolute; display: inline-flex; min-width: 100%; height: 32px; z-index: 1; background:'], -.notion-default-overlay-container - > div:nth-child(2) - > div - > div:nth-child(2) - > div:nth-child(2) - > div - > div - > div - > div - > div - > div:nth-child(2)[style*='position: absolute; display: inline-flex; min-width: 100%; height: 32px; z-index: 1; background:'], -.notion-frame .notion-scroller[style*='background:'], -.notion-page-template-modal .notion-scroller[style*='background:'], -.notion-peek-renderer .notion-scroller[style*='background:'], -.notion-frame > div > div > div[style*='max-width: 100%'][style*='background-color'], -.notion-peek-renderer - > div - > .notion-scroller - > div - > div[style*='max-width: 100%'][style*='background-color'], -.notion-page-template-modal - > div - > .notion-scroller - > div - > div[style*='max-width: 100%'][style*='background-color'] { - background: transparent !important; -} - .notion-timeline-view > div > div @@ -331,19 +186,6 @@ body, ) !important; } -/** scrollbars **/ - -::-webkit-scrollbar-track, -::-webkit-scrollbar-corner { - background: var(--theme--scrollbar_track) !important; -} -::-webkit-scrollbar-thumb { - background: var(--theme--scrollbar_thumb) !important; -} -::-webkit-scrollbar-thumb:hover { - background: var(--theme--scrollbar_thumb-hover) !important; -} - /** ui **/ .notion-page-mention-token.notion-enable-hover:hover { diff --git a/repo/word-counter/app.css b/repo/word-counter/app.css new file mode 100644 index 0000000..46ce67c --- /dev/null +++ b/repo/word-counter/app.css @@ -0,0 +1,59 @@ +/* + * word counter + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * under the MIT license + */ + +#word-counter-details { + width: 100%; + margin-bottom: 2em; +} +#word-counter-details > div { + display: flex; + flex-wrap: wrap; + margin: -0.5em; +} +#word-counter-details > div > p { + margin: 0.5em; + cursor: pointer; + font-size: var(--theme--font_label-size); + color: var(--theme--text); + border-radius: 3px; + padding: 0.25rem 0.5rem; + background: var(--theme--interactive_hover); + border: 1px solid transparent; +} +#word-counter-details > div > p:hover { + background: transparent; + border: 1px solid var(--theme--interactive_hover); +} + +#word-counter-details > div > span { + max-width: 10em; + padding: 0.4rem 0.5rem 0.25rem 0.5rem; + font-size: calc(var(--theme--font_label-size) * 0.8); + color: var(--theme--text_ui_info); +} + +#word-counter-details > div > p > svg { + height: 1em; + width: 1em; + margin: 0 0 -2px 0.3em; + color: var(--theme--text_ui_info); +} + +#word-counter-details-tooltip { + pointer-events: none; + position: absolute; + padding: 0.25em 0.5em; + border-radius: 3px; + box-shadow: var(--theme--box-shadow_strong); + border-right-width: 1px; + font-size: calc(var(--theme--font_label-size) * 0.8); + background: var(--theme--interactive_hover); + opacity: 0; + transition: opacity 120ms ease-in; +} +#word-counter-details-tooltip.active { + opacity: 1; +} diff --git a/repo/word-counter/client.css b/repo/word-counter/client.css new file mode 100644 index 0000000..5bd4fb3 --- /dev/null +++ b/repo/word-counter/client.css @@ -0,0 +1,39 @@ +/* + * notion-enhancer: word counter + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +#word-counter--notice { + color: var(--theme--text_secondary); + font-size: 14px; +} + +.word-counter--stat { + display: block; + background: var(--theme--ui_interactive-hover); + color: var(--theme--text); + font-size: 14px; + line-height: 1.2; + border: 1px solid transparent; + border-radius: 3px; + padding: 6px 8px; + cursor: pointer; + user-select: none; +} +.word-counter--stat:focus, +.word-counter--stat:hover { + background: transparent; + border: 1px solid var(--theme--ui_interactive-hover); +} +.word-counter--stat:active { + background: var(--theme--ui_interactive-active); +} + +.word-counter--stat svg { + display: inline-block; + height: 1em; + width: 1em; + margin: 0 0.4em -2px 0; + color: var(--theme--icon_secondary); +} diff --git a/repo/word-counter/client.mjs b/repo/word-counter/client.mjs new file mode 100644 index 0000000..44fe96d --- /dev/null +++ b/repo/word-counter/client.mjs @@ -0,0 +1,111 @@ +/* + * notion-enhancer: word counter + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +const copyToClipboard = async (str) => { + try { + await navigator.clipboard.writeText(str); + } catch { + const el = document.createElement('textarea'); + el.value = str; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + } + }, + humanTime = (mins) => { + let readable = ''; + if (1 <= mins) { + readable += `${Math.floor(mins)} min`; + if (2 <= mins) readable += 's'; + } + const secs = Math.round((mins % 1) * 60); + if (1 <= secs) { + if (1 <= mins) readable += ' '; + readable += `${secs} sec`; + if (2 <= secs) readable += 's'; + } + return readable; + }; + +export default async function ({ web, components }, db) { + const dbNoticeText = 'Open a normal page to see word count.', + pageNoticeText = 'Click a stat to copy it.', + $notice = web.html`${dbNoticeText}`; + + const $wordCount = web.html`12`, + $characterCount = web.html`12`, + $sentenceCount = web.html`12`, + $blockCount = web.html`12`, + $readingTime = web.html`10 mins`, + $readingTooltip = web.html`${await components.feather('info')}`, + $speakingTime = web.html`18 secs`, + $speakingTooltip = web.html`${await components.feather('info')}`, + $statContainer = web.render( + web.html`
`, + web.render(web.html`

`, $wordCount, ' words'), + web.render(web.html`

`, $characterCount, ' characters'), + web.render(web.html`

`, $sentenceCount, ' sentences'), + web.render(web.html`

`, $blockCount, ' blocks'), + web.render( + web.html`

`, + $readingTooltip, + $readingTime, + ' reading time' + ), + web.render( + web.html`

`, + $speakingTooltip, + $speakingTime, + ' speaking time' + ) + ); + $statContainer.querySelectorAll('.word-counter--stat').forEach(($stat) => { + $stat.addEventListener('click', () => copyToClipboard($stat.innerText)); + }); + components.setTooltip($readingTooltip, '**~ 275 wpm**'); + components.setTooltip($speakingTooltip, '**~ 180 wpm**'); + await components.addPanelView({ + icon: await components.feather('type'), + title: 'Word Counter', + $content: web.render(web.html`
`, $notice, $statContainer), + }); + + let $page; + const updateStats = () => { + if (!$page) return; + const words = $page.innerText.split(/[^\w]+/).length; + $wordCount.innerText = words; + $characterCount.innerText = $page.innerText.length; + $sentenceCount.innerText = $page.innerText.split('.').length; + $blockCount.innerText = $page.querySelectorAll('[data-block-id]').length; + $readingTime.innerText = humanTime(words / 275); + $speakingTime.innerText = humanTime(words / 180); + }, + pageObserver = () => { + if (document.contains($page)) { + updateStats(); + } else { + $page = document.getElementsByClassName('notion-page-content')[0]; + if ($page) { + $notice.innerText = pageNoticeText; + $statContainer.style.display = ''; + updateStats(); + } else { + $notice.innerText = dbNoticeText; + $statContainer.style.display = 'none'; + } + } + }; + web.addDocumentObserver(pageObserver, [ + '.notion-page-content', + '.notion-collection_view_page-block', + ]); + pageObserver(); +} diff --git a/repo/word-counter/mod.js b/repo/word-counter/mod.js new file mode 100644 index 0000000..fa2ccbc --- /dev/null +++ b/repo/word-counter/mod.js @@ -0,0 +1,166 @@ +/* + * word counter + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 admiraldus (https://github.com/admiraldus) + * under the MIT license + */ + +'use strict'; + +const { createElement } = require('../../pkg/helpers.js'); + +module.exports = { + id: 'b99deb52-6955-43d2-a53b-a31540cd19a5', + tags: ['extension'], + name: 'word counter', + desc: + 'add page details: word/character/sentence/block count & speaking/reading times.', + version: '0.2.0', + author: 'dragonwocky', + options: [ + { + key: 'hide_page_details_text', + label: 'hide "page details" text', + type: 'toggle', + value: false, + }, + ], + hacks: { + 'renderer/preload.js'(store, __exports) { + const copyToClipboard = (str) => { + const el = document.createElement('textarea'); + el.value = str; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + }, + humanTime = (mins) => { + let readable = ''; + if (1 <= mins) { + readable += `${Math.floor(mins)} min`; + if (2 <= mins) readable += 's'; + } + const secs = Math.round((mins % 1) * 60); + if (1 <= secs) { + if (1 <= mins) readable += ' '; + readable += `${secs} sec`; + if (2 <= secs) readable += 's'; + } + return readable; + }; + + document.addEventListener('readystatechange', (event) => { + if (document.readyState !== 'complete') return false; + let queue = [], + $page = document.getElementsByClassName('notion-page-content')[0]; + const DOCUMENT_OBSERVER = new MutationObserver((list, observer) => { + if (!queue.length) requestIdleCallback(() => handle(queue)); + queue.push(...list); + }), + PAGE_OBSERVER = new MutationObserver(showPageWordDetails); + DOCUMENT_OBSERVER.observe(document.body, { + childList: true, + subtree: true, + }); + function handle(list) { + queue = []; + for (let { addedNodes } of list) { + if ( + addedNodes[0] && + addedNodes[0].className === 'notion-page-content' + ) { + $page = addedNodes[0]; + showPageWordDetails(); + + PAGE_OBSERVER.disconnect(); + PAGE_OBSERVER.observe($page, { + childList: true, + subtree: true, + characterData: true, + }); + } + } + } + const $container = createElement( + `
` + ), + $tooltip = createElement( + `` + ); + function showPageWordDetails() { + const details = { + words: $page.innerText.replace(/\s+/g, ' ').split(' ').length, + characters: $page.innerText.length, + sentences: $page.innerText.split('.').length, + blocks: $page.querySelectorAll('[data-block-id]').length, + }; + details['reading time'] = [ + humanTime(details.words / 275), + '~275 wpm', + ]; + details['speaking time'] = [ + humanTime(details.words / 180), + '~180 wpm', + ]; + + $container.children[0].innerHTML = ` + ${store().hide_page_details_text ? '' : 'page details
(click to copy)
'} + ${Object.keys(details).reduce( + (prev, key) => + prev + + (Array.isArray(details[key]) + ? `

+ ${details[key][0]} ${key} + + + + +

` + : `

${details[key]} ${key}

`), + '' + )}`; + $page.previousElementSibling.children[0].appendChild($container); + if (!$container.offsetParent) return; + $container.offsetParent.appendChild($tooltip); + $container + .querySelectorAll('p') + .forEach((p) => + p.addEventListener('click', (e) => + copyToClipboard(e.target.innerText) + ) + ); + $container.querySelectorAll('[data-tooltip]').forEach((el) => { + el.addEventListener('mouseenter', (e) => { + $tooltip.innerText = el.getAttribute('data-tooltip'); + $tooltip.style.top = el.parentElement.offsetTop + 2.5 + 'px'; + $tooltip.style.left = + el.parentElement.offsetLeft + + el.parentElement.offsetWidth - + 5 + + 'px'; + $tooltip.classList.add('active'); + }); + el.addEventListener('mouseleave', (e) => + $tooltip.classList.remove('active') + ); + }); + } + }); + }, + }, +}; diff --git a/repo/word-counter/mod.json b/repo/word-counter/mod.json new file mode 100644 index 0000000..97fad0e --- /dev/null +++ b/repo/word-counter/mod.json @@ -0,0 +1,22 @@ +{ + "name": "word counter", + "id": "b99deb52-6955-43d2-a53b-a31540cd19a5", + "version": "0.3.0", + "description": "view word/character/sentence/block count & speaking/reading times in the side panel.", + "tags": ["extension", "panel"], + "authors": [ + { + "name": "dragonwocky", + "email": "thedragonring.bod@gmail.com", + "homepage": "https://dragonwocky.me/", + "avatar": "https://dragonwocky.me/avatar.jpg" + } + ], + "js": { + "client": ["client.mjs"] + }, + "css": { + "client": ["client.css"] + }, + "options": [] +}