diff --git a/src/article.py b/src/article.py index be39ab9..9a0b09f 100644 --- a/src/article.py +++ b/src/article.py @@ -153,7 +153,7 @@ class LexiconArticle: article_by_title[target].citedby.add(article.title) return list(article_by_title.values()) - def build_default_content(self): + def build_default_contentblock(self): """ Formats citations into the article content as normal HTML links and returns the result. @@ -164,23 +164,29 @@ class LexiconArticle: "" if cite_tuple[1] in self.wcites else " class=\"phantom\"") for format_id, cite_tuple in self.citations.items() } - return self.content.format(**format_map) + article_content = self.content.format(**format_map) + return "
\n

{}

\n{}
\n".format( + self.title, + article_content) def build_default_citeblock(self, prev_article, next_article): """ Builds the citeblock content HTML for use in regular article pages. For each defined target, links the target page as Previous or Next. """ - citeblock = "
\n" - # Prev/next links - if next_article is not None: - citeblock += "

Next →

\n".format( - next_article.title_filesafe, " class=\"phantom\"" if next_article.player is None else "") - if prev_article is not None: - citeblock += "

← Previous

\n".format( - prev_article.title_filesafe, " class=\"phantom\"" if prev_article.player is None else "") - if next_article is None and prev_article is None: - citeblock += "

 

\n" + citeblock = "
\n" + # Prev/next links: + if next_article is not None or prev_article is not None: + prev_link = ("← Previous".format( + prev_article.title_filesafe, + " class=\"phantom\"" if prev_article.player is None else "") + if prev_article is not None else "") + next_link = ("Next →".format( + next_article.title_filesafe, + " class=\"phantom\"" if next_article.player is None else "") + if next_article is not None else "") + citeblock += "\n\n\n
{}{}
\n".format( + prev_link, next_link) # Citations cites_links = [ "{0}".format( @@ -189,7 +195,7 @@ class LexiconArticle: for title in sorted( self.wcites | self.pcites, key=lambda t: utils.titlesort(t))] - cites_str = " | ".join(cites_links) + cites_str = " / ".join(cites_links) if len(cites_str) < 1: cites_str = "—" citeblock += "

Citations: {}

\n".format(cites_str) # Citedby @@ -199,7 +205,7 @@ class LexiconArticle: for title in sorted( self.citedby, key=lambda t: utils.titlesort(t))] - citedby_str = " | ".join(citedby_links) + citedby_str = " / ".join(citedby_links) if len(citedby_str) < 1: citedby_str = "—" citeblock += "

Cited by: {}

\n
\n".format(citedby_str) return citeblock diff --git a/src/build.py b/src/build.py index 456f4bf..bdab5ca 100644 --- a/src/build.py +++ b/src/build.py @@ -8,17 +8,37 @@ from collections import defaultdict # For rank inversion in statistics from src import utils from src.article import LexiconArticle -def build_contents_page(articles, config): +class LexiconPage: + """ + An abstraction layer around formatting a Lexicon page skeleton with kwargs + so that kwargs that are constant across pages aren't repeated. + """ + + def __init__(self, skeleton=None, page=None): + self.kwargs = {} + self.skeleton = skeleton + if page is not None: + self.skeleton = page.skeleton + self.kwargs = dict(page.kwargs) + + def add_kwargs(self, **kwargs): + self.kwargs.update(kwargs) + + def format(self, **kwargs): + total_kwargs = {**self.kwargs, **kwargs} + return self.skeleton.format(**total_kwargs) + +def build_contents_page(page, articles, index_list): """ Builds the full HTML of the contents page. """ - content = "" + content = "
" # Head the contents page with counts of written and phantom articles phantom_count = len([article for article in articles if article.player is None]) if phantom_count == 0: - content = "

There are {0} entries in this lexicon.

\n".format(len(articles)) + content += "

There are {0} entries in this lexicon.

\n".format(len(articles)) else: - content = "

There are {0} entries, {1} written and {2} phantom.

\n".format( + content += "

There are {0} entries, {1} written and {2} phantom.

\n".format( len(articles), len(articles) - phantom_count, phantom_count) # Prepare article links link_by_title = {article.title : "{0}".format( @@ -28,7 +48,7 @@ def build_contents_page(articles, config): # Write the articles in alphabetical order content += utils.load_resource("contents.html") content += "
\n
    \n" - indices = config["INDEX_LIST"].split("\n") + indices = index_list.split("\n") alphabetical_order = sorted( articles, key=lambda a: utils.titlesort(a.title)) @@ -64,70 +84,31 @@ def build_contents_page(articles, config): content += "
  • {}
  • \n".format(link_by_title[article.title]) content += "
\n
\n" # Fill in the page skeleton - entry_skeleton = utils.load_resource("entry-page.html") - css = utils.load_resource("lexicon.css") - return entry_skeleton.format( - title="Index of " + config["LEXICON_TITLE"], - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - prompt=config["PROMPT"], - sort=config["DEFAULT_SORT"], - content=content, - citeblock="") + return page.format(title="Index", content=content) -def build_rules_page(config): +def build_rules_page(page): """ Builds the full HTML of the rules page. """ content = utils.load_resource("rules.html") # Fill in the entry skeleton - entry_skeleton = utils.load_resource("entry-page.html") - css = utils.load_resource("lexicon.css") - return entry_skeleton.format( - title="Rules", - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - prompt=config["PROMPT"], - sort=config["DEFAULT_SORT"], - content=content, - citeblock="") + return page.format(title="Rules", content=content) -def build_formatting_page(config): +def build_formatting_page(page): """ Builds the full HTML of the formatting page. """ content = utils.load_resource("formatting.html") # Fill in the entry skeleton - entry_skeleton = utils.load_resource("entry-page.html") - css = utils.load_resource("lexicon.css") - return entry_skeleton.format( - title="Formatting", - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - prompt=config["PROMPT"], - sort=config["DEFAULT_SORT"], - content=content, - citeblock="") + return page.format(title="Formatting", content=content) -def build_session_page(config): +def build_session_page(page, session_content): """ Builds the full HTML of the session page. """ # Fill in the entry skeleton - entry_skeleton = utils.load_resource("entry-page.html") - css = utils.load_resource("lexicon.css") - return entry_skeleton.format( - title=config["LEXICON_TITLE"], - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - prompt=config["PROMPT"], - sort=config["DEFAULT_SORT"], - content=config["SESSION_PAGE"], - citeblock="") + content = "
{}
".format(session_content) + return page.format(title="Session", content=content) def reverse_statistics_dict(stats, reverse=True): """ @@ -147,7 +128,7 @@ def reverse_statistics_dict(stats, reverse=True): def itemize(stats_list): return map(lambda x: "{0} – {1}".format(x[0], "; ".join(x[1])), stats_list) -def build_statistics_page(articles, config): +def build_statistics_page(page, articles): """ Builds the full HTML of the statistics page. """ @@ -174,19 +155,19 @@ def build_statistics_page(articles, config): # Format the ranks into strings top_ranked_items = itemize(top_ranked) # Write the statistics to the page - content += "
\n" - content += "

Top 10 pages by page rank:
\n" + content += "

\n" + content += "Top 10 pages by page rank:
\n" content += "
\n".join(top_ranked_items) - content += "

\n
\n" + content += "
\n" # Top number of citations made citations_made = { title : len(cites) for title, cites in cite_map.items() } top_citations = reverse_statistics_dict(citations_made)[:3] top_citations_items = itemize(top_citations) - content += "
\n" - content += "

Most citations made from:
\n" + content += "

\n" + content += "Most citations made from:
\n" content += "
\n".join(top_citations_items) - content += "

\n
\n" + content += "
\n" # Top number of times cited # Build a map of what cites each article @@ -201,10 +182,10 @@ def build_statistics_page(articles, config): citations_to = { title : len(cites) for title, cites in cited_by_map.items() } top_cited = reverse_statistics_dict(citations_to)[:3] top_cited_items = itemize(top_cited) - content += "
\n" - content += "

Most citations made to:
\n" + content += "

\n" + content += "Most citations made to:
\n" content += "
\n".join(top_cited_items) - content += "

\n
\n" + content += "
\n" # Top article length, roughly by words article_length = {} @@ -218,16 +199,16 @@ def build_statistics_page(articles, config): article_length[article.title] = wordcount top_length = reverse_statistics_dict(article_length)[:3] top_length_items = itemize(top_length) - content += "
\n" - content += "

Longest article:
\n" + content += "

\n" + content += "Longest article:
\n" content += "
\n".join(top_length_items) - content += "

\n
\n" + content += "
\n" # Total word count - content += "
\n" - content += "

Total word count:
\n" - content += str(sum(article_length.values())) + "

" - content += "

\n
\n" + content += "
\n" + content += "Total word count:
\n" + content += str(sum(article_length.values())) + content += "
\n" # Player pageranks players = sorted(set([article.player for article in articles if article.player is not None])) @@ -247,10 +228,10 @@ def build_statistics_page(articles, config): in articles_by_player.items()} player_rank = reverse_statistics_dict(pagerank_by_player) player_rank_items = itemize(player_rank) - content += "
\n" - content += "

Player total page rank:
\n" + content += "

\n" + content += "Player total page rank:
\n" content += "
\n".join(player_rank_items) - content += "

\n
\n" + content += "
\n" # Player citations made player_cite_count = { @@ -258,10 +239,10 @@ def build_statistics_page(articles, config): for player, articles in articles_by_player.items()} player_cites_made_ranks = reverse_statistics_dict(player_cite_count) player_cites_made_items = itemize(player_cites_made_ranks) - content += "
\n" - content += "

Citations made by player
\n" + content += "

\n" + content += "Citations made by player
\n" content += "
\n".join(player_cites_made_items) - content += "

\n
\n" + content += "
\n" # Player cited count cited_times = {player : 0 for player in players} @@ -270,23 +251,13 @@ def build_statistics_page(articles, config): cited_times[article.player] += len(article.citedby) cited_times_ranked = reverse_statistics_dict(cited_times) cited_times_items = itemize(cited_times_ranked) - content += "
\n" - content += "

Citations made to player
\n" + content += "

\n" + content += "Citations made to player
\n" content += "
\n".join(cited_times_items) - content += "

\n
\n" + content += "
\n" # Fill in the entry skeleton - entry_skeleton = utils.load_resource("entry-page.html") - css = utils.load_resource("lexicon.css") - return entry_skeleton.format( - title="Statistics", - lexicon=config["LEXICON_TITLE"], - css=css, - logo=config["LOGO_FILENAME"], - prompt=config["PROMPT"], - sort=config["DEFAULT_SORT"], - content=content, - citeblock="") + return page.format(title="Statistics", content=content) def build_graphviz_file(cite_map): """ @@ -367,26 +338,29 @@ def build_all(path_prefix, lexicon_name): lex_path = os.path.join(path_prefix, lexicon_name) # Load the Lexicon's peripherals config = utils.load_config(lexicon_name) - entry_skeleton = utils.load_resource("entry-page.html") - css = utils.load_resource("lexicon.css") + page_skeleton = utils.load_resource("page-skeleton.html") + page = LexiconPage(skeleton=page_skeleton) + page.add_kwargs( + lexicon=config["LEXICON_TITLE"], + logo=config["LOGO_FILENAME"], + prompt=config["PROMPT"], + sort=config["DEFAULT_SORT"]) # Parse the written articles articles = LexiconArticle.parse_from_directory(os.path.join(lex_path, "src")) - # At this point, the articles haven't been cross-populated, - # so we can derive the written titles from this list - #written_titles = [article.title for article in articles] # Once they've been populated, the articles list has the titles of all articles # Sort this by turn before title so prev/next links run in turn order articles = sorted( LexiconArticle.populate(articles), key=lambda a: (a.turn, utils.titlesort(a.title))) - #phantom_titles = [article.title for article in articles if article.title not in written_titles] + def pathto(*els): return os.path.join(lex_path, *els) # Write the redirect page print("Writing redirect page...") with open(pathto("index.html"), "w", encoding="utf8") as f: - f.write(utils.load_resource("redirect.html").format(lexicon=config["LEXICON_TITLE"], sort=config["DEFAULT_SORT"])) + f.write(utils.load_resource("redirect.html").format( + lexicon=config["LEXICON_TITLE"], sort=config["DEFAULT_SORT"])) # Write the article pages print("Deleting old article pages...") @@ -398,38 +372,32 @@ def build_all(path_prefix, lexicon_name): for idx in range(l): article = articles[idx] with open(pathto("article", article.title_filesafe + ".html"), "w", encoding="utf-8") as f: - content = article.build_default_content() + contentblock = article.build_default_contentblock() citeblock = article.build_default_citeblock( None if idx == 0 else articles[idx - 1], None if idx == l-1 else articles[idx + 1]) - article_html = entry_skeleton.format( + article_html = page.format( title = article.title, - lexicon = config["LEXICON_TITLE"], - css = css, - logo = config["LOGO_FILENAME"], - prompt = config["PROMPT"], - sort = config["DEFAULT_SORT"], - content = content, - citeblock = citeblock) + content = contentblock + citeblock) f.write(article_html) print(" Wrote " + article.title) # Write default pages print("Writing default pages...") with open(pathto("contents", "index.html"), "w", encoding="utf-8") as f: - f.write(build_contents_page(articles, config)) + f.write(build_contents_page(page, articles, config["INDEX_LIST"])) print(" Wrote Contents") with open(pathto("rules", "index.html"), "w", encoding="utf-8") as f: - f.write(build_rules_page(config)) + f.write(build_rules_page(page)) print(" Wrote Rules") with open(pathto("formatting", "index.html"), "w", encoding="utf-8") as f: - f.write(build_formatting_page(config)) + f.write(build_formatting_page(page)) print(" Wrote Formatting") with open(pathto("session", "index.html"), "w", encoding="utf-8") as f: - f.write(build_session_page(config)) + f.write(build_session_page(page, config["SESSION_PAGE"])) print(" Wrote Session") with open(pathto("statistics", "index.html"), "w", encoding="utf-8") as f: - f.write(build_statistics_page(articles, config)) + f.write(build_statistics_page(page, articles)) print(" Wrote Statistics") # Write auxiliary pages diff --git a/src/resources/formatting.html b/src/resources/formatting.html index b2b3341..0976810 100644 --- a/src/resources/formatting.html +++ b/src/resources/formatting.html @@ -1,3 +1,4 @@ +

Lexipython provides support for a limited amount of Markdown-esque formatting.

 # Player: PN
@@ -7,17 +8,50 @@
 This is an example page.
 Some words are //italicized//,
 and some words are **bolded**.
-All of these sentences are part of the same paragraph.
+All of these sentences are part of the same
+paragraph.
 
 This is a new paragraph.\\
-Unlike the last paragraph, this line will be after a line break within the paragraph.
+Unlike the last paragraph, this line will be after a
+line break within the paragraph.
 
-This is an [[example citation|Phantom page]]. You can also cite a [[phantom page]] with just the title.
+This is an [[example citation|Phantom page]]. You can
+also cite a [[phantom page]] with just the title.
 
 ~Dr. X. Amplepage
 
-

Each turn, fill out the header with your player information, the current turn, and the title of your entry. The Player field can be anything as long as it's the same for all articles you write (even when they're by different characters). Using your initials is recommended.

-

Two line breaks begins a new paragraph. A single line break does nothing, unless the line is ended by a double backslash (\\).

-

Text bounded by ** will be bolded: **bold** produces bold. Text bounded by // will be italicized: //italics// produces italics.

-

To cite another Lexicon entry, use double brackets. Text in double brackets will cite and link to the entry of the same name: [[Example page]] produces Example page. Text in double brackets split with a | will alias the link as the left text and link to the entry with the name of the right text: [[this text|Example page]] produces this text. You must be precise in the entry title you cite to. Citations to "Example" vs. "The Example" will point to different entries and create different phantoms, and your GM will probably have to clean up after you.

-

Beginning a paragraph with ~ will right-align it and place a horizontal line above it. Use this for signing your entry with your scholar's name.

+

Each turn, fill out the header with your player information, the current + turn, and the title of your entry. The Player field can be anything + as long as it's the same for all articles you write (even when they're by + different characters). Using your initials is recommended.

+

Two line breaks begins a new paragraph. A single line break does nothing, + unless the line is ended by a double backslash (\\).

+

Text bounded by ** will be bolded: **bold** produces bold. Text + bounded by // will be italicized: //italics// produces italics.

+

To cite another Lexicon entry, use double brackets. Text in double brackets + will cite and link to the entry of the same name: [[Example page]] produces + Example page. Text in + double brackets split with a | will alias the link as the left text and + link to the entry with the name of the right text: [[this text|Example page]] + produces this text. You + must be precise in the entry title you cite to. Citations to + "Example" vs. "The Example" will point to different entries and create + different phantoms, and your GM will probably have to clean up after + you.

+

Beginning a paragraph with ~ will right-align it and place a horizontal line + above it. Use this for signing your entry with your scholar's name.

+
+
+

Example page

+

This is an example page. +Some words are italicized, +and some words are bolded. +All of these sentences are part of the same +paragraph.

+

This is a new paragraph.
+Unlike the last paragraph, this line will be after a +line break within the paragraph.

+

This is an example citation. You can + also cite a phantom page with just the title.

+

Dr. X. Amplepage

+
\ No newline at end of file diff --git a/src/resources/lexicon.css b/src/resources/lexicon.css index 2babead..dee13b1 100644 --- a/src/resources/lexicon.css +++ b/src/resources/lexicon.css @@ -1,10 +1,35 @@ -body { background-color: #eeeeee; margin: 10px; } -div#header { background-color: #ffffff; margin: 10px 0; box-shadow: 2px 2px 10px #888888; overflow: hidden; } -img#logo { float:left; margin:8px; max-width: 140px; } -div#header p { margin:10px; } -div.content { margin-top: 10px; background-color: #ffffff; padding: 10px; box-shadow: 2px 2px 10px #888888; overflow: hidden; } +body { background-color: #eeeeee; line-height: 1.4; font-size: 16px; } +div#wrapper { max-width: 800px; position: absolute; left: 0; right: 0; + margin: 0 auto; } +div#header { padding: 5px; margin: 5px; background-color: #ffffff; + box-shadow: 2px 2px 10px #888888; border-radius: 5px; } +div#header p, div#header h2 { margin: 5px; } +div#sidebar { width: 200px; float:left; margin:5px; padding: 8px; + text-align: center; background-color: #ffffff; + box-shadow: 2px 2px 10px #888888; border-radius: 5px; } +img#logo { max-width: 200px; } +table { table-layout: fixed; width: 100%; } +div#sidebar table { border-collapse: collapse; } +div.citeblock table td:first-child + td a { justify-content: flex-end; } +table a { display: flex; padding: 3px; background-color: #dddddd; + border-radius: 5px; text-decoration: none; } +div#sidebar table a { justify-content: center; } +table a:hover { background-color: #cccccc; } +div#sidebar table td { padding: 0px; margin: 3px 0; + border-bottom: 8px solid transparent; } +div#content { position: absolute; right: 0px; left: 226px; max-width: 564px; + margin: 5px; } +div.contentblock { background-color: #ffffff; box-shadow: 2px 2px 10px #888888; + margin-bottom: 5px; padding: 10px; width: auto; border-radius: 5px; } a.phantom { color: #cc2200; } div.citeblock a.phantom { font-style: italic; } span.signature { text-align: right; } -div.moveable { float: left; margin: 8px; } -div.moveable p { margin: 0px; } \ No newline at end of file +@media only screen and (max-width: 816px) { + div#wrapper { padding: 5px; } + div#header { max-width: 554; margin: 0 auto; } + div#sidebar { max-width: 548; width: inherit; float: inherit; + margin: 5px auto; } + div#content { max-width: 564; position: static; right: inherit; + margin: 5px auto; } + img#logo { max-width: inherit; width: 100%; } +} \ No newline at end of file diff --git a/src/resources/page-skeleton.html b/src/resources/page-skeleton.html new file mode 100644 index 0000000..6136ff8 --- /dev/null +++ b/src/resources/page-skeleton.html @@ -0,0 +1,29 @@ + + +{title} | {lexicon} + + + + + +
+ + +
+{content} +
+
+ + diff --git a/src/resources/rules.html b/src/resources/rules.html index c6c7892..2f8cf5a 100644 --- a/src/resources/rules.html +++ b/src/resources/rules.html @@ -1,18 +1,67 @@ +
    -
  1. At the beginning of the game, you will be provided with a topic statement that sets the tone for the game. Use it for inspiration and a stepping-stone into shaping the world of the Lexicon.
  2. -
  3. Each round, you will be assigned an index, a grouping of letters. Your entry must alphabetize under that index.
      -
    1. Each index has a number of open slots equal to the number of players, which are taken up by article titles when an article is written in that index or a citation is made to an unwritten article, or phantom. If there are no open slots in your index, you must write the article for a phantom in that index.
    2. -
    3. "The" and "A" aren't counted in indexing.
  4. -
  5. Once you've picked an article title, write your article on that subject.
      -
    1. There are no hard and fast rules about style. Try to sound like an encyclopedia entry or the overview section at the top of a wiki article.
    2. -
    3. You must respect and not contradict any factual content of any posted articles. You may introduce new facts that place things in a new light, provide alternative interpretations, or flesh out unexplained details in unexpected ways; but you must not contradict what has been previously established as fact.
    4. -
    5. Aim for around 200-300 words. Going over is okay, but be reasonable.
  6. -
  7. Your article must cite other articles in the Lexicon. Sometimes these citations will be to phantoms, articles that have not been written yet.
      -
    1. On the first turn, your article must cite exactly two phantom articles.
    2. -
    3. On subsequent turns, your article must cite exactly two phantom articles, either already-cited phantoms or new ones. Your article must also cite at least one written article.
    4. -
    5. On the penultimate turn, you must cite exactly one phantom article and at least two written articles.
    6. -
    7. On the final turn, you must cite at least three written articles.
    8. -
    9. You may not cite an entry you wrote. You may cite phantoms you have cited before.
    10. -
    11. Once you cite a phantom, you cannot choose to write it if you write an article for that index later.
  8. - -

    Ersatz Scrivener. In the course of the game, it may come to pass that a scholar is assigned an index in which no slots are available to them, because they have cited all the available phantoms in their previous articles. When this happens, the player instead writes their article as Ersatz Scrivener, radical skeptic. Ersatz does not believe in the existence of whatever he is writing about, no matter how obvious it seems to others or how central it is in the developing history of the world. All references, testimony, etc. with regard to its existence are tragic delusion at best or malicious lies at worst. Unlike the other scholars, Ersatz does not treat the research of his peers as fact, because he does not believe he has peers. Players writing articles as Ersatz are encouraged to name and shame the work of the misguided amateurs collaborating with him.

    +
  9. Each Lexicon has a topic statement that sets the tone for the game. + It provides a starting point for shaping the developing world of the + Lexicon. As it is a starting point, don't feel contrained to write only + about the topics mentioned directly in it.
  10. +
  11. Articles are sorted under an index, a grouping of letters. An article is + in an index if its first letter is in that group of letters. "The", "A", + and "An" aren't counted in indexing. Example: One of the indices is JKL. + An article titled 'The Jabberwock' would index under JKL, not T's index.
    1. +
    2. Until the game is over, some of the articles will have been cited, but not + yet written. These are called phantom articles. A phantom article has a + title, which is defined by the first citation to it, but no content.
    3. +
    4. Generally, an index has a number of "slots" equal to the number of players. + When an article is first written or cited, it takes up one slot in its + corresponding index.
    +
  12. Each turn, you will be assigned to write in an index.
    1. +
    2. Your articles should be written from the perspective of your character. + Your character should be a scholar collaborating with the other + scholars on the production of the Lexicon. You should play the same + character for the duration of the game.
    3. +
    4. If the index has open slots, you may come up with a new article title + and write an article under that title. If all unwritten slots in your + index are filled by phantom articles, you must choose one of them and + write it.
    5. +
    6. There are no hard and fast rules about style, but it is recommended + that players imitate an encyclopedic style to stay true to the game's + conceit.
    7. +
    8. There are no hard and fast rules about length, but it is recommended + that the Editor enforce a maximum word limit. In general, aiming for + 200-300 words is ideal.
    9. +
    10. You must respect and not contradict the factual content of all written + articles. You may introduce new facts that put things in a new light, + provide alternative interpretations, or flesh out unexplained details + in unexpected ways; but you must not contradict what has been + previously established as fact. Use the "yes, and" rule from improv + acting: accept what your fellow scholars have written and add to it in + new ways, rather than trying to undo their work. This rule includes + facts that have been established in written articles about the topics + of phantom articles.
    +
  13. Each article will cite other articles in the Lexicon.
    1. +
    2. You may not cite an entry that you have written. When you write an + article, you may not cite it in later articles.
    3. +
    4. As a corollary, you may not write phantom articles that you have cited. + If you cite an article and then write it later, your former article + now cites you, which is forbidden per the above.
    5. +
    6. On the first turn, there are no written articles. Your first article + must cite exactly two phantom articles.
    7. +
    8. On subsequent turns, your article must cite exactly two phantoms, + but you can cite phantoms that already exist. Your article must also + cite at least one written article. You can cite more than one.
    9. +
    10. On the penultimate turn, you must cite exactly one phantom + article and at least two written articles.
    11. +
    12. On the final turn, you must cite at least three written articles.
    +
  14. As the game goes on, it may come to pass that a player must write an + article in an index, but that index is full, and that player has already + cited all the phantoms in it. When this happens, the player instead writes + their article as **Ersatz Scrivener**, radical skeptic. Ersatz does not + believe in the existence of whatever he is writing about, no matter how + obvious it seems to others or how central it is in the developing history + of the world. For Ersatz, all references, testimony, etc. with regard to + its existence are tragic delusion at best or malicious lies at worst. + Unlike the other scholars, Ersatz does not treat the research of his peers + as fact, because he does not believe he has peers. Players writing articles + as Ersatz are encouraged to lambast the amateur work of his misguided + "collaborators".
  15. +
\ No newline at end of file