diff options
author | cyfraeviolae <cyfraeviolae> | 2022-02-25 16:26:02 -0500 |
---|---|---|
committer | cyfraeviolae <cyfraeviolae> | 2022-02-25 16:26:02 -0500 |
commit | 36af38bb79f2310138853f59f4aff18908ad5abc (patch) | |
tree | d396acef4e08d038727ae93701c56b15ef69dd65 |
init
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | index.html | 64 | ||||
-rw-r--r-- | static/EBGaramond-Italic-VariableFont_wght.ttf | bin | 0 -> 829752 bytes | |||
-rw-r--r-- | static/EBGaramond-VariableFont_wght.ttf | bin | 0 -> 897728 bytes | |||
-rw-r--r-- | static/OFL.txt | 93 | ||||
-rw-r--r-- | static/favicon.ico | bin | 0 -> 318 bytes | |||
-rw-r--r-- | static/poems.js | 46 | ||||
-rw-r--r-- | static/script.js | 192 | ||||
-rw-r--r-- | static/styles.css | 141 |
9 files changed, 537 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bc1b69 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Prosodyle diff --git a/index.html b/index.html new file mode 100644 index 0000000..0ede797 --- /dev/null +++ b/index.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html> + <head> + <title>Prosodyle</title> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" type="text/css" href="/static/styles.css"> + <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico"> + </head> + <body> + <div class="container"> + <div> + <a href="/" class="title">Prosodyle</a><span>@</span><a href="https://cyfraeviolae.org">cyfraeviolae.org</a> + <span class="sep">|</span> + <a href="https://cyfraeviolae.org/git/prosodyle">source code</a> + </div> + <p> + The dithyrambic sorcerer Roseacrucis has breached the Library’s security measures and encrypted its + treasured poetry collections. As our newest acolyte, your task is to decode one secret line of poetry each day. + </p> + <p> + For the ransom price of one new line with the same metrical and syllabic structure as today’s secret + line, Roseacrucis will reveal which letters are in the right place 🟩, in the right word but in a different + place 🟨, or not in the word at all ⬜. + </p> + <noscript>Sorry, JavaScript is required to play Prosodyle.</noscript> + <form id="game" action="javascript:void(0);" method="none"> + <p> + Write a line of poetry. Syllables in dark boxes should be stressed. + </p> + <div id="entry"></div> + <div id="btns"> + <button id="check" type="submit">Check</button> + <button id="clear">Clear</button> + </div> + <hr> + <div id="win" style="display: none;"> + <p> + Victory is yours! Today’s secret line was in + <strong id="meter"></strong>: + </p> + <pre id="ctx" class="poetry"></pre> + <p> + But our troubles are not over. Most of the Library’s poetry + remains encrypted, and we need your help to decode a new line + tomorrow. + </p> + <p> + In the meantime, you may share your results with your fellow acolytes, + adding punctuation as desired. + </p> + <div> + <textarea cols="80" rows="6" id="share"></textarea> + </div> + <button id="copy">Copy</button> + <hr> + </div> + <p id="guesses"></p> + </form> + </div> + <script src="/static/poems.js"></script> + <script src="/static/script.js"></script> + </body> +</html> diff --git a/static/EBGaramond-Italic-VariableFont_wght.ttf b/static/EBGaramond-Italic-VariableFont_wght.ttf Binary files differnew file mode 100644 index 0000000..fa31b71 --- /dev/null +++ b/static/EBGaramond-Italic-VariableFont_wght.ttf diff --git a/static/EBGaramond-VariableFont_wght.ttf b/static/EBGaramond-VariableFont_wght.ttf Binary files differnew file mode 100644 index 0000000..123d5dd --- /dev/null +++ b/static/EBGaramond-VariableFont_wght.ttf diff --git a/static/OFL.txt b/static/OFL.txt new file mode 100644 index 0000000..1ba1596 --- /dev/null +++ b/static/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2012-2013, The Mozilla Corporation and Telefonica S.A.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/static/favicon.ico b/static/favicon.ico Binary files differnew file mode 100644 index 0000000..bb88dc0 --- /dev/null +++ b/static/favicon.ico diff --git a/static/poems.js b/static/poems.js new file mode 100644 index 0000000..fd5d0bd --- /dev/null +++ b/static/poems.js @@ -0,0 +1,46 @@ +var challenges = [ + { + "line": [["In", "/fin", "ite"], ["/wrath"], ["and"], ["/in", "fin", "/ite"], ["des", "/pair"]], + "title": "Book IV", + "collection": "Paradise Lost", + "author": "John Milton", + "meter": "iambic pentameter", + "ctx": `Me miserable! which way shall I flie +Infinite wrauth, and infinite despaire? +Which way I flie is Hell; my self am Hell; +And in the lowest deep a lower deep +Still threatning to devour me opens wide, +To which the Hell I suffer seems a Heav'n.`, + "link": "https://milton.host.dartmouth.edu/reading_room/pl/book_4/text.shtml", + }, + { + "line": [["For"], ["the"], ["/An", "gel"], ["of"], ["/Death"], ["spread"], ["his"], ["/wings"], ["on"], ["the"], ["/blast"]], + "title": "The Destruction of Sennacherib", + "collection": "Hebrew Melodies", + "author": "Lord Byron", + "meter": "anapestic tetrameter", + "ctx": `For the Angel of Death spread his wings on the blast, +And breathed in the face of the foe as he passed; +And the eyes of the sleepers waxed deadly and chill, +And their hearts but once heaved, and for ever grew still!`, + "link": "https://www.poetryfoundation.org/poems/43827/the-destruction-of-sennacherib", + }, + { + "line": [["A"], ["/pre", "sence"], ["/that"], ["dis", "/turbs"], ["me"], ["/with"], ["the"], ["/joy"]], + "title": "Lines written a few miles above Tintern Abbey", + "collection": "Lyrical Ballads", + "author": "William Wordsworth", + "meter": "iambic pentameter", + "ctx": `And I have felt +A presence that disturbs me with the joy +Of elevated thoughts; a sense sublime +Of something far more deeply interfused, +Whose dwelling is the light of setting suns, +And the round ocean, and the living air, +And the blue sky, and in the mind of man, +A motion and a spirit, that impels +All thinking things, all objects of all thought, +And rolls through all things.`, + "link": "https://www.gutenberg.org/files/9622/9622-h/9622-h.htm#poem23", + }, +] diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..552da72 --- /dev/null +++ b/static/script.js @@ -0,0 +1,192 @@ +"use strict"; + +var entryEl = document.getElementById("entry") +var guessesEl = document.getElementById("guesses") +var winEl = document.getElementById("win") + +function renderWord(word, wordIdx, guess, score, offset) { + var els = [] + var c = 0; + for (var syllable of word) { + var sylEls = [] + + var shouldStress = syllable[0] == "/"; + if (shouldStress) { + syllable = syllable.slice(1) + } + var stress = shouldStress ? "stress" : "" + for (var _ in syllable) { + var charclass = '' + var able = '' + if (score) { + if (score.greens[c]) { + charclass += 'green' + } else if (score.yellows[c]) { + charclass += 'yellow' + } else if (score.greys[c]) { + charclass += 'grey' + } + able = 'disabled' + } + var val = guess ? guess[wordIdx][c] : '' + var typ = guess ? 'solbox' : 'entrybox' + sylEls.push(`<input data-word="${wordIdx}" data-character="${c}" data-offset="${c+offset}" class="box ${typ} ${charclass}" type="text" maxlength="1" required title="A-Z only" value="${val}" pattern="[A-Za-z]" ${able}></input>`) + c++; + } + + els.push(`<div class="syllable ` + stress + `">` + sylEls.join('') + `</div>`) + } + + return `<div class="word">` + els.join(`<div class="syllable-sep">·</div>`) + `</div>` +} + +function renderLine(line, guess, scores) { + var els = [] + var offset = 0; + for (var wordIdx in line) { + var word = line[wordIdx] + els.push(renderWord(word, wordIdx, guess, scores && scores[wordIdx], offset)) + offset += word.join('').replaceAll('/', '').length + console.log(word, word.join('').replaceAll('/', '').length) + } + return `<div class="line">` + els.join('') + `</div>` +} + +function consumeInput() { + var els = Array.from(document.getElementsByClassName("entrybox")); + els.sort((x, y) => { + var xword = x.getAttribute('data-word') + var xchar = x.getAttribute('data-character') + var yword = y.getAttribute('data-word') + var ychar = y.getAttribute('data-character') + + if (xword != yword) { + return xword - yword; + } + return xchar - ychar; + }) + var words = {} + for (var el of els) { + words[el.getAttribute('data-word')] = (words[el.getAttribute('data-word')] || '') + + el.value.toUpperCase() + } + return words +} + +function scoreLine(guess, answer) { + var scores = [] + for (var idx in guess) { + var guessword = guess[idx] + var answerword = answer[idx].join('').replaceAll('/', '').toUpperCase() + scores.push(scoreWord(guessword, answerword)) + } + return scores +} + +function scoreWord(guess, answer) { + var greens = {} + var yellows = {} + var greys = {} + var counts = {} + var placed = {} + for (var idx in guess) { + if (guess[idx] == answer[idx]) { + greens[idx] = 1 + placed[answer[idx]] = (placed[answer[idx]] || 0) + 1 + } + counts[answer[idx]] = (counts[answer[idx]] || 0) + 1 + } + for (var idx in guess) { + if (greens[idx]) { + continue + } + if ((placed[guess[idx]] || 0) < counts[guess[idx]]) { + yellows[idx] = 1 + placed[guess[idx]] = (placed[guess[idx]] || 0) + 1; + } else { + greys[idx] = 1 + } + } + return {greens: greens, yellows: yellows, greys: greys} +} + +document.getElementById('clear').onclick = function(e) { + e.preventDefault(); + for (var el of document.getElementsByClassName('entrybox')) { + el.value = '' + } + document.querySelector(`[data-offset="0"]`).focus(); +} + +document.getElementById('game').addEventListener('submit', function(e) { + var guess = consumeInput() + guesses.push(guess) + var scores = scoreLine(guess, challenge.line) + var linehtml = renderLine(challenge.line, guess, scores) + guessesEl.innerHTML = linehtml + guessesEl.innerHTML + var win = true; + for (var score of scores) { + if (Object.keys(score.yellows).length || Object.keys(score.greys).length) { + win = false; + } + } + if (win) { + entryEl.innerHTML = linehtml + winGame(challenge) + } +}) + +function winGame(challenge) { + winEl.style = 'display: block;' + document.getElementById('btns').style = 'display: none;' + document.getElementById('meter').innerText = challenge.meter + document.getElementById('ctx').innerHTML = challenge.ctx.replaceAll(/^(.*)/gm, '\t$1') + `\n\t<span class="byline">—<a href="${challenge.link}">${challenge.title}</a>, ${challenge.collection}, ${challenge.author}</span>` + var date = new Date().toISOString().slice(0, 10) + var firstguess = Object.values(guesses[0]).join(' ') + document.getElementById('share').value = `I solved the ${date} Prosodyle at cyfraeviolae.org/prosodyle. My first guess was: "${firstguess}".` +} + +document.getElementById('entry').addEventListener('keydown', function(e) { + if (!Array.from(e.target.classList).includes('entrybox')) { + return; + } + if (e.ctrlKey || e.altKey || (e.key.length != 1 && e.key != 'Backspace')) { + return; + } + e.preventDefault(); + var offset = parseInt(e.target.getAttribute('data-offset')) + var el; + if (e.key == 'Backspace') { + e.target.value = ''; + el = document.querySelector(`[data-offset="${offset-1}"]`); + } else { + e.target.value = e.key; + el = document.querySelector(`[data-offset="${offset+1}"]`); + } + if (el) { + el.focus(); + } +}) + +function getDailyChallenge() { + // https://stackoverflow.com/a/8619946 + var now = new Date(); + var start = new Date(now.getFullYear(), 0, 0); + var diff = now - start; + var oneDay = 1000 * 60 * 60 * 24; + var day = Math.floor(diff / oneDay); + // end snippet + return challenges[day-56] +} + +document.getElementById('copy').addEventListener('click', function(e) { + e.preventDefault(); + document.getElementById('share').select() + document.execCommand('copy'); +}) + + +var guesses = [] +var challenge = getDailyChallenge() +entryEl.innerHTML = renderLine(challenge.line) +document.querySelector(`[data-offset="0"]`).focus(); diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..96b9109 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,141 @@ +@font-face { + font-family: EBGaramond; + src: url(/static/EBGaramond-VariableFont_wght.ttf) format('woff2-variations'); + font-style: normal; +} + +@font-face { + font-family: EBGaramond; + src: url(/static/EBGaramond-Italic-VariableFont_wght.ttf) format('woff2-variations'); + font-style: italic; +} + +body { + background: #fdf3f3; + color: DarkSlateGrey; + font-family: EBGaramond, serif; + font-size: large; +} + +a { + color: #1a97bf; +} + +a:hover { + color: #075d77; +} + +.container { + margin: 1em; + max-width: 40em; +} + +.title { + letter-spacing: -0.5px; + font-weight: bold; +} + +div { + margin-bottom: 10px; +} + +form { + border: 1px DarkSlateGrey solid; + padding: 10px; + padding-left: 25px; +} + +.sep { + margin-left: 6px; + margin-right: 6px; +} + +.ingredient { + display: inline-block; + margin-right: 5px; + margin-bottom: 5px; +} + +button { + margin-right: 10px; +} + +.box { + width: 35px; + height: 35px; + text-align: center; + font-weight: bold; + font-size: 22px; + border: 2px lightgrey solid; + margin-right: 0px; + margin-left: 0px; + text-transform: uppercase; + color: black; + box-sizing: border-box; + caret-color: transparent; + outline:0px none transparent; + /* appearance: none; */ + /* -moz-appearance: none; */ +} +.box:focus { + background-color: #fff079; +} +.box::selection { + color: none; + background: none; +} +.syllable .box:not(:first-child) { + margin-left: -1px; +} +.syllable .box:not(:last-child) { + margin-right: -1px; +} + +.syllable { + display: inline; +} +.syllable-sep { + display: inline; + font-size: 30px; + margin: 1px; +} +.word { + display: inline-block; + margin-right: 40px; + margin-bottom: 7px; +} + +.stress>.box { + border-color: darkslategrey; + /* background-color: lightcyan; */ +} + +.green { + background-color: rgb(106, 170, 100); + color: white; + border: 0; +} +.yellow { + background-color: rgb(201, 180, 88); + color: white; + border: 0; +} +.grey { + background-color: rgb(120, 124, 126); + color: white; + border: 0; +} + +hr { + border: .1px DarkSlateGrey dashed; + margin-top: 20px; +} + +.poetry { + font-family: EBGaramond; +} + +.byline { + font-style: italic; + font-size: 75%; +} |