Compare commits

..

No commits in common. "main" and "e19a2a3df1ec29a6921cf526f828e1bf3591ae34" have entirely different histories.

75 changed files with 810 additions and 3911 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
*.pyc
.DS_Store
Pipfile.lock

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "static/flot"]
path = static/flot
url = https://github.com/flot/flot.git

13
Pipfile
View File

@ -1,13 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
jinja2 = "*"
markdown = "*"
[dev-packages]
[requires]
python_version = "3.9"

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# This is my server! It's not much, but it's mine.
It runs on flask. It's kinda messy! This is one project I'm working on strictly for results - I just want to post stuff as easily as possible, with as little hassle as possible (but avoiding having to use PHP). There might be some slightly-more-complicated stuff in here using blueprints or subapplications, so that I can extend things! It's not done, though.
Poke. Poke.
I made a service file for use with systemd finally! It'll make it so that it'll automatically start when the server starts. That lives in `auriga.service`. It also has the `gunicorn` command for starting the server. I've removed the old hacky start script that kept it; if you want to run it locally, you should be able to just run `pure_flask.py`. If you need to run it manually, just copy and paste it from `auriga.service`. But don't do that. Just use `systemd`.

108
articles/__init__.py Normal file
View File

@ -0,0 +1,108 @@
"""Route requests for articles according to shoofle's rules.
This is a simple module which is basically entirely here to provide access, in a sensible location,
to the `bloop` object. It's a blueprint which describes how to route requests for articles on my website
(possibly located at http://shoofle.net, possibly not located there). Most of the interesting stuff is in
`render_article`; Go team!"""
import os
from flask import Blueprint, render_template, send_from_directory, abort
folder = "articles"
article_base_template = os.path.join(folder, "article.template.html")
article_small_template = os.path.join(folder, "article.zip.template.html")
bloop = Blueprint("articles", __name__, template_folder="")
@bloop.route("/")
def main_page():
"""Renders the list of articles/projects."""
return render_template("project_list.html")
@bloop.route("/.zip/")
def main_page_small():
return render_template("project_list.zip.html")
@bloop.route("/<article_name>/")
def render_article(article_name):
"""Renders a requested article! This should always be @routed last, because it catches a
wide variety of requests. As a result, other things need to be @routed first, because they
might never get called if this catches them first."""
# Arguably, the various options for how to render (templates, articles, flat html) could be stuck into various
# subdirectories. Ultimately I don't want to do this because I want this to be lightweight - this __init__.py file
# can be chucked into any folder and start showing pages correctly. But whatever!
# In the examples, let's think about a request for "example.com/some-article".
# First, we convert the important part of the requested page into a filename
# "example.com/Some-Article/" => folder="Some-Article" => file_name = "articles/some_article"
file_name = os.path.join(folder, article_name.replace("-", "_").lower())
# Here's the priority list for file rendering!
if os.path.isfile(file_name + ".template.html"):
# If the file "articles/some_article.template.html" exists, then there's a specific {template} written
# for this path. Specific page {templates} take priority.
return render_template(file_name + ".template.html")
if os.path.isfile(file_name + ".article.html"):
# If "articles/some_article.article.html" exists but there's no template, then we should render that
# {article fragment}, but using the {article base template}. In the future, this should possibly also
# extract the title from the {article fragment} and feed it into the {article base template} as well.
return render_template(article_base_template, target=file_name + ".article.html")
if file_name.endswith(".zip") and os.path.isfile(file_name[:-4] + ".article.html"):
return render_template(article_small_template, target=file_name[:-4] + ".article.html")
if os.path.isfile(file_name + ".html"):
# If we haven't found any results yet, check to see if "articles/some_article.html" exists. If it does,
# just display it plain. This also provides a clean way to access the raw form of files like
# "articles/some_article.template.html" - just make a request for "example.com/some-article.template/"
# and it will be caught by this rule and rendered.
return render_template(file_name + ".html")
if os.path.isfile(file_name):
# If it didn't match any other rules, then just render the file that has precisely the requested name.
return render_template(file_name);
# I *believe* there's one instance that can't be accessed by this kind of routing in any way:
# If the files "articles/some_article" and "articles/some_article.html" both exist, then no request will
# convince this blueprint to return the former. However, as sacrifices go, I don't think it's too bad, and
# that should be the only case when this happens.
# It also can't find files with hyphens in their name, because they get replaced with underscores.
# If we didn't find any files, throw up a 404.
abort(404)
@bloop.route("/<article_name>/<path:file_path>")
def supplementary(article_name, file_path):
"""Sends a file such that articles can have supplementary content.
The article at "example.com/great-articles" will have its article fragment HTML defined at
"articles/great_articles.article.html" and its supplementary content will be found in the folder
"articles/great_articles/some_image.jpg".
Oh, and one last thought - if we want more complicated supplementary content behaviors, it might be
necessary to make a blueprint for it. Then, it's necessary to include the blueprint in this file. Oh well.
"""
# Put the article name into the standard form, and concatenate them together.
article_name = article_name.replace("-", "_").lower()
# You could make a strong argument that this is unnecessary, and we could just shove in a static file handler.
# But I like this solution more. It better separates out the supplementary content from the article itself.
# Things that don't fit into this framework might not belong as articles, anyway!
# Important! This didn't work right for a long time until I finally made it join `folder` to the beginning.
# Without that, it tries to load the file relative to wherever the server's running - which is /not/ the
# right thing. The server's running somewhere, but this needs to be in the articles folder, yadda yadda,
# you can figure it out.
path = os.path.join(folder, article_name, file_path)
# If the path exists and is a file, then we should send it.
if os.path.isfile(path):
directory, file_name = os.path.split(path)
print("sending %(fn)s from directory %(d)s" % {"fn": file_name, "d": directory})
return send_from_directory(directory, file_name)
# If that file wasn't found, then, well, whoops.
abort(404)
bloop.route("/<article_name>.zip/<path:file_path>")(supplementary)
"""Th-th-th-that's all, folks!"""

View File

@ -1,5 +1,4 @@
<!doctype html>
<!-- see generate.ignore.py for how this is used with .article.html files! -->
<html>
<head>
{% block head %}

View File

@ -1,21 +0,0 @@
<!doctype html>
<!-- see generate.ignore.py for how this is used with .article.md files! -->
<html>
<head>
{% block head %}
<meta charset="utf-8">
<title>{% block title %}Writings by Shoofle{% endblock %}</title>
<script src="/static/jquery.min.js" type="text/javascript"></script>
<link href="/static/bootstrap/css/bootstrap.css" rel="stylesheet" type="text/css">
<link href="/static/bootstrap/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
<link href="/static/shoofle.css" rel="stylesheet" type="text/css">
{% endblock %}
</head>
<body>
{% block body -%}
<article>
{{ contents }}
</article>
{%- endblock %}
</body>
</html>

View File

@ -1,16 +0,0 @@
<style>
</style>
<article>
<p>In January and February of 2024, I made <a href="/static/atelier/ateli8.html">a game!</a> Its a bite-size fangame for the <a href="https://en.wikipedia.org/wiki/Atelier_(video_game_series)">Atelier franchise</a>, and it takes 15-20 minutes to play. I made it in <a href="https://www.lexaloffle.com/pico-8.php">Pico-8</a>, a fantasy console themed around the 8-bit consoles of eld. In Atelier Phoebe you play as Phoebe, a young alchemist who has inherited her grandfathers workshop in a town down on its luck; can you bring back the fireworks festival that used to light up the skies?</p>
<figure>
<img src="screenshot.png" alt="a screenshot showing Phoebe saying 'when i craft, i pick which of each ingredient to use with left and right,' above a workbench-like crafting interface">
<figcaption>It's even got tutorials!</figcaption>
</figure>
<p>I feel like its almost overstatement to call this a whole entire game sometimes. The whole “story” is about eight quests long and has maybe two major beats. Nevertheless, Im really proud of it, because Im not sure Ive ever made a software project that feels as finished as this on my own. Ive made a lot of things, and some of them have been pretty useful, but it was rewarding to actually put this through multiple rounds of bug fixing, finalize a title screen, and so on. And as short as it is, Atelier Phoebe does have a beginning, middle, and end it has everything a “real” game should have. The parts are just extremely short.</p>
<p>One of my goals with Atelier Phoebe was to make a game somewhat incongruous with the Pico-8 environment. Pico-8 really lends itself to simple platformers and shoot-em-ups, and doesnt usually make people think of story-based RPGs. I really wanted to include talk sprites for dialogue in this game, specifically because Pico-8 is so poorly suited to them, with its fixed 16-color palette and limited sprite sheet. Im proud of them they were fun to draw and really contributed to the feeling of using everything available to me. See…</p>
<p>One of the constraints for Pico-8 games is that they have to fit within 16 kilobytes, and your space for visual assets (there are built-in tools for drawing, designing maps, and arranging music and sound effects) is limited to a 128×128 bitmap field. The bottom half of that is also shared with the tile map data (also 128×128) so in practice one of your graphics data or your tile map data is limited to 128×64. Thats pretty constrained! Most of the projects and demos Ive done in Pico-8 havent come close to using all of either one, but Atelier Phoebe really maxed out the sprite data, in large part due to those precious talk sprites for dialog. In theory I think they could have been compressed somehow, but that would have meant interfacing with external tools, which I decided early on I was going to avoid.</p>
<p>I say “avoid”, but I did use one external tool a compression tool called <a href="https://github.com/thisismypassport/shrinko8">shrinko8</a>. See, Pico-8 has a simple limit on graphics data, but the overall package of your game “cartridge” is limited to 16 kilobytes (after built-in compression), with a supplemental limit of 8192 tokens and 65k characters (pre-compression). By the time I was finishing up Atelier Phoebe I was damn close to all of those limits, and I was over the character and compressed data limits. Fortunately, Im not the first person to have made an ambitious project in the limits of pico-8, and shrinko8 will supply some more intelligent static analysis-based compression. This allowed me to export the game in its finished state. The limits put the kibosh on some further expansions I had planned. I would have liked to write more dialogue and a more complicated and full-featured alchemy system, but I have the ultimate excuse there was no room for any more!</p>
<p>The whole project was split over 16 different files, the bulk of which were separate files for each characters scripts and separate files for each screens behavior. I did edit code and text in Sublime Text rather than Pico-8s built-in editor as much as I love the vibe of the environment and dedication to the bit, I cant stare at 3×5 pixel fonts for more than an hour before my brain melts out my ears.</p>
<p>I pushed myself to get this to a finished state, in the mind that finishing projects is itself a skill that I want to get better at. However, the artificial time pressure meant that I discarded plans to make music, which was okay by me because music is by far the component I am least familiar with. Maybe Atelier Phoebe 2 will have music. I dont know.</p>
</article>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -1,255 +0,0 @@
<article>
<style type="text/css">
#consonant-grid, #vowel-grid {
margin-left: auto;
margin-right: auto;
}
td {
padding: 2em;
}
.vowel-example {
padding: 1em;
}
td, .vowel-sequence {
border: 2px solid gray;
}
table > img {
width: 50%;
height: 50%;
}
.hero-example {
width: 50%;
display: block;
margin-left: auto;
margin-right: auto;
}
.example-glyph {
width: 20%;
display: block;
margin-left: auto;
margin-right: auto;
}
#vowel-grid {
display: block;
width: 60%;
}
.vowel-sequence {
display: block;
}
.vowel-example {
display: inline-block;
}
.vowel-example * {
vertical-align: middle;
}
.vowel-example > svg {
width: 3.5em;
height: 3.5em;
}
.unverified {
color: lightgray;
}
</style>
<h3>An introduction to Circle Script (Version... 3?)</h3>
<p>Circle script is a writing system that I made up. The fundamental gimmick of it is that words and sentences in it form circles.</p>
<img src="splash-example.svg" alt="A large glyph composed of nested circles." class="hero-example" />
<p>It's largely decorative, has vague intentions at being phonetic, and I like using it in art! The other primary use case is writing cute notes to people I like.</p>
<h4>Here's the overview of how to read circle script:</h5>
<p>To read circle script, you start at whatever spot is marked with a little line segment outside the circle, parallel to it. If that's not present, you can usually start at the bottom. Walk around the circle widdershins (counterclockwise) and decode each shape and glyph you encounter, turning them into sounds.</p>
<img src="zagreus.svg" alt="A circle script glyph showing a single word-circle, with a small line outside the circle for a starting-point indicator." class="example-glyph"/>
<p>If you're reading a whole sentence, you walk around the circle and read each word as you come across it. For each word, you read it starting from the point where it touches the circle that contains it, so you kind of reorient as you go.</p>
<p>Also, you might have noticed that some of the shapes inside word-circles have lines connecting them. Those are decorative! A lot of the component parts of the circle script writing system have places for lines to come off. These can be connected as you like, but the... proper? I guess? fancy, perhaps? way to use them is to draw connecting lines between words which are conceptually linked. You might use those lines to connect an adjective with the noun it modifies, or the names of two people who care about each other. There's a lot of room for poetic embellishment in drawing lines between words!</p>
<p>Of course, you can also just draw the lines out in a way that looks cool. :)</p>
<h4>Okay, now the meat of circle script: How to read words!</h5>
<p>To read circle script, you need to understand consonants and vowels. Consonants are easier, so we'll start with those. Consonants are drawn with marks that float above (inside) the continuous line of the circle. Each consonant is composed of a shape, which corresponds to what kind of sound it is, and a diacritic, which indicates where in the mouth it is made. Additionally, if the consonant is <a href="https://en.wikipedia.org/wiki/Voice_(phonetics)">voiced</a>, the shape will generally be drawn with a doubled line.</p>
<p><strong>Phew!</strong> That sounded complicated. It's not so bad, really - I'm going to give you a grid, and you can just find the sound you want, and it'll tell you how to draw it. All of that business about kinds of sounds, places in mouths, and voice just means this is a featural script, so that similar sounds are indicated by similar-looking letters.</p>
<aside><p>In this version of circle script, consonants do not distort the overall shape, and vowels do. In versions 1 and 2, it was the other way around!</p></aside>
<table id="consonant-grid">
<thead>
<tr class="header">
<th></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-1.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-2.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-3.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-4.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-5.svg" /></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-mound-1.svg" /></th>
<td>[m] <strong>m</strong>ap</td>
<td>[n] <strong>n</strong>ope</td>
<td class="unverified">[ñ] pi<strong>ñ</strong>ata (unverified)</td>
<td>[ŋ] ha<strong>ng</strong></td>
<td></td>
<th scope="row" class="consonant-shape"><img src="consonant-mound-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-ohm-1.svg" /></th>
<td>[p] <strong>p</strong>ear, [b] <strong>b</strong>at</td>
<td>[t] <strong>t</strong>alk, [d] <strong>d</strong>eal</td>
<td></td>
<td>[k] <strong>k</strong>ale, [g] <strong>g</strong>ulp</td>
<td class="unverified">[q], [ɢ] (unverified)</td>
<th scope="row" class="consonant-shape"><img src="consonant-ohm-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-angle-1.svg" /></th>
<td>[f] <strong>f</strong>ace, [v] <strong>v</strong>eer</td>
<td>[s] <strong>s</strong>ight, [z] <strong>z</strong>oot</td>
<td>[ʃ] <strong>sh</strong>ort, [ʒ] mea<strong>s</strong>ure</td>
<td></td>
<td class="unverified">[ʔ], [ʡ] (unverified)</td>
<th scope="row" class="consonant-shape"><img src="consonant-angle-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-loop-1.svg" /></th>
<td>[θ] <strong>th</strong>in, [ð] <strong>th</strong>at</td>
<td class="unverified">[ts] <strong>ts</strong>ar? (unverified)</td>
<td>[tʃ] <strong>ch</strong>alk, [dʒ] <strong>j</strong>u<strong>dg</strong>e</td>
<td>[x~χ] lo<strong>ch</strong>, <span class="unverified">[ʁ] french r</span></td>
<td></td>
<th scope="row" class="consonant-shape"><img src="consonant-loop-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-wave-1.svg" /></th>
<td>[w] <strong>w</strong>alk</td>
<td>[ɹ] <strong>r</strong>eal, [l] <strong>l</strong>atent</td>
<td>[ɾ~r] pe<strong>rr</strong>o, <span class="unverified">[ɬ] welsh ll</span></td>
<td>[j] <strong>y</strong>eet</td>
<td>[h] <strong>h</strong>edonism</td>
<th scope="row" class="consonant-shape"><img src="consonant-wave-2.svg" /></th>
</tr>
</tbody>
</table>
<p>If you're familiar with the <a href="https://en.wikipedia.org/wiki/International_Phonetic_Alphabet">international phonetic alphabet</a> this might look somewhat familiar! (Although I've taken some heavy liberties to make this fit into a grid and match with my American understanding of what sounds are similar) Each square in the grid shows the two consonants (one unvoiced, one unvoiced) indicated by that combination of diacritic (above) and shape (from the side). The unvoiced version (on the left, in a grid square) is indicated by the single-line version of the shape, seen on the left side of the grid. The voiced version uses the "heavy" version, seen on the right side of the grid and originally drawn with a doubled line. I've included the IPA transcription for the consonants, along with an example of that sound in my dialect, which is probably roughly <a href="https://en.wikipedia.org/wiki/General_American_English">General American English</a>.</p>
<p>Vowels are a little more haphazard. Have you ever really thought about vowels? They're pretty messed up, especially in English. We were taught the five vowels are a, e, i, o, u, and sometimes y. It turns out that while we have five and a half <em>letters</em> we call vowels, in English, they correspond to anywhere between twelve and fifteen different <em>sounds</em>. And those sounds aren't even necessarily the ones you're thinking of - a lot of the things we think of as individual vowels, like the /i/ in "r<strong>i</strong>ght", are actually diphthongs, composed of two vowels that we run together.</p>
<p>Anyway, circle script draws vowels as modifications to the outer circle of a word - as you're drawing the circle, you take a detour to take either a small bite out of it, a big bite out of it, or a loop-the-loop. It is then decorated with a circle, placed either above the detour shape (on the inside of the overall word circle), below the detour shape (often inside the detour shape, towards the outside of the word circle), or on the line of the detour circle. Then, you add a line for emphasis if necessary.</p>
<p>The vowels are organized in a way close to the five vowel system seen in many languages. Consider the Japanese ka ki ku ke ko; these are considered the "strong" versions of the five basic vowels. They also come in "weak" versions, common in English. The pairing between strong and weak versions is roughly based on whatever I thought made the most sense to my ears. They're listed in the vowel chart. A strong vowel will have a line extending off it, which can be used freely as with the connectors on the consonant glyphs, and a weak vowel will have no such line.</p>
<p>There's one vowel that I didn't quite fit into a correspondence with another "stronger" or "weaker" vowel, /æ/, which you might know from the word "bat" or "back" (in American English). There are also three common diphthongs provided as vowel glyphs. </p>
<p>Hopefully, if this system needs to be extended for other vowels, they can be defined by relation to a "similar" sounding vowel, and adding a diacritic of some sort. But that hasn't been standardized yet.</p>
<p>The examples I give are based on my accent (some variation of general American English), so if there's any confusion, consult a resource like <a href="https://ipachart.com">ipachart.com</a> for recordings of vowels. Several common diphthongs are available as vowel shapes.</p>
<object data="./vowel-example.svg" type="image/svg+xml" style="display: none"></object>
<div id="vowel-grid">
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use xlink:href="./vowel-example.svg#base-omega" ></use>
<use xlink:href="./vowel-example.svg#dot-outer"></use>
<use xlink:href="./vowel-example.svg#emphasis-outer"></use>
</svg>
[ɑ, a], box or bah
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use href="vowel-example.svg#base-omega" ></use>
<use href="vowel-example.svg#dot-outer"></use>
</svg>
[ʌ, ə], but or schwa (unstressed)
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<use href="vowel-example.svg#base-mound" ></use>
<use href="vowel-example.svg#dot-inner"></use>
<use href="vowel-example.svg#emphasis-inner"></use>
</svg>
[i], beat
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<use href="vowel-example.svg#base-mound" ></use>
<use href="vowel-example.svg#dot-inner"></use>
</svg>
[ɪ], bit
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<use href="vowel-example.svg#base-mound" ></use>
<use href="vowel-example.svg#triangle-inner"></use>
<use href="vowel-example.svg#emphasis-inner"></use>
</svg>
[u], boot
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<use href="vowel-example.svg#base-mound" ></use>
<use href="vowel-example.svg#triangle-inner"></use>
</svg>
[ʊ], book
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<use href="vowel-example.svg#base-mound" ></use>
<use href="vowel-example.svg#dot-outer"></use>
<use href="vowel-example.svg#emphasis-outer"></use>
</svg>
[ɛi, e], bait
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<use href="vowel-example.svg#base-mound" ></use>
<use href="vowel-example.svg#dot-outer"></use>
</svg>
[ɛ], bet
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use href="vowel-example.svg#base-omega" ></use>
<use href="vowel-example.svg#dot-inner"></use>
<use href="vowel-example.svg#emphasis-inner"></use>
</svg>
[o, ɔu], won't, boat
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use href="vowel-example.svg#base-omega" ></use>
<use href="vowel-example.svg#dot-inner"></use>
</svg>
[ɔ], bawk
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use href="vowel-example.svg#base-omega" ></use>
<use href="vowel-example.svg#dot-on"></use>
</svg>
[æ], bat
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use href="vowel-example.svg#base-loop" ></use>
<use href="vowel-example.svg#dot-outer"></use>
</svg>
[au], cow
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use href="vowel-example.svg#base-loop" ></use>
<use href="vowel-example.svg#dot-on"></use>
</svg>
[ai, aɪ], by
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<use href="vowel-example.svg#base-loop" ></use>
<use href="vowel-example.svg#dot-inner"></use>
</svg>
[ɔi, ɔɪ], boy
</div>
</div>
</div>
<p>If you merge caught-cot, it is preferred to use the strong vowel /a~ɑ/ rather than the weak /ɔ/ vowel.</p>
</article>

View File

@ -1,421 +0,0 @@
<html>
<head>
<style type="text/css">
#consonant-grid, #vowel-grid {
margin-left: auto;
margin-right: auto;
}
td {
padding: 2em;
}
.vowel-example {
padding: 1em;
}
td, .vowel-sequence {
border: 2px solid gray;
}
table > img {
width: 50%;
height: 50%;
}
.hero-example {
width: 50%;
display: block;
margin-left: auto;
margin-right: auto;
}
.example-glyph {
width: 20%;
display: block;
margin-left: auto;
margin-right: auto;
}
#vowel-grid {
display: block;
width: 60%;
}
.vowel-sequence {
display: block;
}
.vowel-example {
display: inline-block;
}
.vowel-example * {
vertical-align: middle;
}
.vowel-example > svg {
width: 3.5em;
height: 3.5em;
}
.unverified {
color: lightgray;
}
</style>
</head>
<body>
<h3>An introduction to Circle Script (Version... 3?)</h3>
<p>Circle script is a writing system that I made up. The fundamental gimmick of it is that words and sentences in it form circles.</p>
<img src="splash-example.svg" alt="A large glyph composed of nested circles." class="hero-example" />
<p>It's largely decorative, has vague intentions at being phonetic, and I like using it in art! The other primary use case is writing cute notes to people I like.</p>
<h4>Here's the overview of how to read circle script:</h4>
<p>To read circle script, you start at whatever spot is marked with a little line segment outside the circle, parallel to it. If that's not present, you can usually start at the bottom. Walk around the circle widdershins (counterclockwise) and decode each shape and glyph you encounter, turning them into sounds.</p>
<img src="zagreus.svg" alt="A circle script glyph showing a single word-circle, with a small line outside the circle for a starting-point indicator." class="example-glyph"/>
<p>If you're reading a whole sentence, you walk around the circle and read each word as you come across it. For each word, you read it starting from the point where it touches the circle that contains it, so you kind of reorient as you go.</p>
<p>Also, you might have noticed that some of the shapes inside word-circles have lines connecting them. Those are decorative! A lot of the component parts of the circle script writing system have places for lines to come off. These can be connected as you like, but the... proper? I guess? fancy, perhaps? way to use them is to draw connecting lines between words which are conceptually linked. You might use those lines to connect an adjective with the noun it modifies, or the names of two people who care about each other. There's a lot of room for poetic embellishment in drawing lines between words!</p>
<p>Of course, you can also just draw the lines out in a way that looks cool. :)</p>
<h4>Okay, now the meat of circle script: How to read words!</h5>
<p>To read circle script, you need to understand consonants and vowels. Consonants are easier, so we'll start with those. Consonants are drawn with marks that float above (inside) the continuous line of the circle. Each consonant is composed of a shape, which corresponds to what kind of sound it is, and a radical, which indicates where in the mouth it is made. Additionally, if the consonant is <a href="https://en.wikipedia.org/wiki/Voice_(phonetics)">voiced</a>, the shape will generally be drawn with a doubled line.</p>
<p><strong>Phew!</strong> That sounded complicated. It's not so bad, really - I'm going to give you a grid, and you can just find the sound you want, and it'll tell you how to draw it. All of that business about kinds of sounds, places in mouths, and voice just means this is a featural script, so that similar sounds are indicated by similar-looking letters.</p>
<aside><p>In this version of circle script, consonants do not distort the overall shape, and vowels do. In versions 1 and 2, it was the other way around!</p></aside>
<table id="consonant-grid">
<thead>
<tr class="header">
<th></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-1.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-2.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-3.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-4.svg" /></th>
<th scope="col" class="diacritic-header"><img src="example-diacritic-5.svg" /></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-mound-1.svg" /></th>
<td>[m] <strong>m</strong>ap</td>
<td>[n] <strong>n</strong>ope</td>
<td class="unverified">[ñ] pi<strong>ñ</strong>ata (unverified)</td>
<td>[ŋ] ha<strong>ng</strong></td>
<td></td>
<th scope="row" class="consonant-shape"><img src="consonant-mound-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-ohm-1.svg" /></th>
<td>[p] <strong>p</strong>ear, [b] <strong>b</strong>at</td>
<td>[t] <strong>t</strong>alk, [d] <strong>d</strong>eal</td>
<td></td>
<td>[k] <strong>k</strong>ale, [g] <strong>g</strong>ulp</td>
<td class="unverified">[q], [ɢ] (unverified)</td>
<th scope="row" class="consonant-shape"><img src="consonant-ohm-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-angle-1.svg" /></th>
<td>[f] <strong>f</strong>ace, [v] <strong>v</strong>eer</td>
<td>[s] <strong>s</strong>ight, [z] <strong>z</strong>oot</td>
<td>[ʃ] <strong>sh</strong>ort, [ʒ] mea<strong>s</strong>ure</td>
<td></td>
<td class="unverified">[ʔ], [ʡ] (unverified)</td>
<th scope="row" class="consonant-shape"><img src="consonant-angle-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-loop-1.svg" /></th>
<td>[θ] <strong>th</strong>in, [ð] <strong>th</strong>at</td>
<td class="unverified">[ts] <strong>ts</strong>ar? (unverified)</td>
<td>[tʃ] <strong>ch</strong>alk, [dʒ] <strong>j</strong>u<strong>dg</strong>e</td>
<td>[x~χ] lo<strong>ch</strong>, <span class="unverified">[ʁ] french r</span></td>
<td></td>
<th scope="row" class="consonant-shape"><img src="consonant-loop-2.svg" /></th>
</tr>
<tr>
<th scope="row" class="consonant-shape"><img src="consonant-wave-1.svg" /></th>
<td>[w] <strong>w</strong>alk</td>
<td>[ɹ] <strong>r</strong>eal, [l] <strong>l</strong>atent</td>
<td>[ɾ~r] pe<strong>rr</strong>o, <span class="unverified">[ɬ] welsh ll</span></td>
<td>[j] <strong>y</strong>eet</td>
<td>[h] <strong>h</strong>edonism</td>
<th scope="row" class="consonant-shape"><img src="consonant-wave-2.svg" /></th>
</tr>
</tbody>
</table>
<p>If you're familiar with the <a href="https://en.wikipedia.org/wiki/International_Phonetic_Alphabet">international phonetic alphabet</a> this might look somewhat familiar! (Although I've taken some heavy liberties to make this fit into a grid and match with my American understanding of what sounds are similar) Each square in the grid shows the two consonants (one unvoiced, one unvoiced) indicated by that combination of radical (above) and shape (from the side). The unvoiced version (on the left, in a grid square) is indicated by the single-line version of the shape, seen on the left side of the grid. The voiced version uses the "heavy" version, seen on the right side of the grid and originally drawn with a doubled line. I've included the IPA transcription for the consonants, along with an example of that sound in my dialect, which is probably roughly <a href="https://en.wikipedia.org/wiki/General_American_English">General American English</a>.</p>
<p>Vowels are a little more haphazard. Have you ever really thought about vowels? They're pretty messed up, especially in English. We were taught the five vowels are a, e, i, o, u, and sometimes y. It turns out that while we have five and a half <em>letters</em> we call vowels, in English, they correspond to anywhere between twelve and fifteen different <em>sounds</em>. And those sounds aren't even necessarily the ones you're thinking of - a lot of the things we think of as individual vowels, like the /i/ in "r<strong>i</strong>ght", are actually diphthongs, composed of two vowels that we run together.</p>
<p>Anyway, circle script draws vowels as modifications to the outer circle of a word - as you're drawing the circle, you take a detour to take either a small bite out of it, a big bite out of it, or a loop-the-loop. It is then decorated with a circle, placed either above the detour shape (on the inside of the overall word circle), below the detour shape (often inside the detour shape, towards the outside of the word circle), or on the line of the detour circle. Then, you add a line for emphasis if necessary.</p>
<p>The vowels are organized in a way close to the five vowel system seen in many languages. Consider the Japanese ka ki ku ke ko; these are considered the "strong" versions of the five basic vowels. They also come in "weak" versions, common in English. The pairing between strong and weak versions is roughly based on whatever I thought made the most sense to my ears. They're listed in the vowel chart. A strong vowel will have a line extending off it, which can be used freely as with the connectors on the consonant glyphs, and a weak vowel will have no such line.</p>
<p>There's one vowel that I didn't quite fit into a correspondence with another "stronger" or "weaker" vowel, /æ/, which you might know from the word "bat" or "back" (in American English). There are also three common diphthongs provided as vowel glyphs. </p>
<p>Hopefully, if this system needs to be extended for other vowels, they can be defined by relation to a "similar" sounding vowel, and adding a radical of some sort. But that hasn't been standardized yet.</p>
<p>The examples I give are based on my accent (some variation of general American English), so if there's any confusion, consult a resource like <a href="https://ipachart.com">ipachart.com</a> for recordings of vowels. Several common diphthongs are available as vowel shapes.</p>
<div id="vowel-grid">
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 h 21.9728073 c 0,0 -8.7107683,-5.308618 -8.6254273,-18.536589 0.08534,-13.227971 11.8971468,-19.957943 22.88067,-20.100079 10.983523,-0.142135 24.132914,6.070091 24.270193,19.58669 0.137279,13.516599 -8.372985,19.018148 -8.372985,19.018148 l 21.57823,-0.142137"
id="base-omega"
sodipodi:nodetypes="cczzzcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-outer"
cx="12.504731"
cy="50.600185"
r="7.9595795" />
<path
style="fill:none;stroke:#000000;stroke-width:3.49271999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 12.50473,58.701897 c 0,15.634887 0,15.634887 0,15.634887"
id="emphasis-outer" />
</svg>
[ɑ, a], box or bah
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 h 21.9728073 c 0,0 -8.7107683,-5.308618 -8.6254273,-18.536589 0.08534,-13.227971 11.8971468,-19.957943 22.88067,-20.100079 10.983523,-0.142135 24.132914,6.070091 24.270193,19.58669 0.137279,13.516599 -8.372985,19.018148 -8.372985,19.018148 l 21.57823,-0.142137"
id="base-omega"
sodipodi:nodetypes="cczzzcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-outer"
cx="12.504731"
cy="50.600185"
r="7.9595795" />
</svg>
[ʌ, ə], but or schwa (unstressed)
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.402715,49.335233 h 12.307604 c 0,0 7.3545431,-18.311313 24.915392,-18.311313 17.560849,0 23.714653,18.137348 23.714653,18.137348 l 12.907974,-0.142135"
id="base-mound"
sodipodi:nodetypes="cczcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-inner"
cx="12.50473"
cy="14.924212"
r="7.9595795" />
<path
style="fill:none;stroke:#000000;stroke-width:3.49271999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 12.646867,6.8224964 C 12.504729,-8.9545266 12.504729,-1.2792181 12.504729,-1.2792181"
id="emphasis-inner" />
</svg>
[i], beat
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.402715,49.335233 h 12.307604 c 0,0 7.3545431,-18.311313 24.915392,-18.311313 17.560849,0 23.714653,18.137348 23.714653,18.137348 l 12.907974,-0.142135"
id="base-mound"
sodipodi:nodetypes="cczcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-inner"
cx="12.50473"
cy="14.924212"
r="7.9595795" />
</svg>
[ɪ], bit
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.402715,49.335233 h 12.307604 c 0,0 7.3545431,-18.311313 24.915392,-18.311313 17.560849,0 23.714653,18.137348 23.714653,18.137348 l 12.907974,-0.142135"
id="base-mound"
sodipodi:nodetypes="cczcc" />
<path
sodipodi:type="star"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="triangle-inner"
sodipodi:sides="3"
sodipodi:cx="12.602679"
sodipodi:cy="15.606641"
sodipodi:r1="8.7842556"
sodipodi:r2="8.4311752"
sodipodi:arg1="-1.565766"
sodipodi:arg2="-0.51856839"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 12.646867,6.822496 20.187878,20.036981 4.9732929,19.960446 Z"
inkscape:transform-center-x="0.11044414"
inkscape:transform-center-y="-0.12140727" />
<path
style="fill:none;stroke:#000000;stroke-width:3.49271999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 12.646867,6.8224964 C 12.504729,-8.9545266 12.504729,-1.2792181 12.504729,-1.2792181"
id="emphasis-inner" />
</svg>
[u], boot
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.402715,49.335233 h 12.307604 c 0,0 7.3545431,-18.311313 24.915392,-18.311313 17.560849,0 23.714653,18.137348 23.714653,18.137348 l 12.907974,-0.142135"
id="base-mound"
sodipodi:nodetypes="cczcc" />
<path
sodipodi:type="star"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="triangle-inner"
sodipodi:sides="3"
sodipodi:cx="12.602679"
sodipodi:cy="15.606641"
sodipodi:r1="8.7842556"
sodipodi:r2="8.4311752"
sodipodi:arg1="-1.565766"
sodipodi:arg2="-0.51856839"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 12.646867,6.822496 20.187878,20.036981 4.9732929,19.960446 Z"
inkscape:transform-center-x="0.11044414"
inkscape:transform-center-y="-0.12140727" />
</svg>
[ʊ], book
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.402715,49.335233 h 12.307604 c 0,0 7.3545431,-18.311313 24.915392,-18.311313 17.560849,0 23.714653,18.137348 23.714653,18.137348 l 12.907974,-0.142135"
id="base-mound"
sodipodi:nodetypes="cczcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-outer"
cx="12.504731"
cy="50.600185"
r="7.9595795" />
<path
style="fill:none;stroke:#000000;stroke-width:3.49271999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 12.50473,58.701897 c 0,15.634887 0,15.634887 0,15.634887"
id="emphasis-outer" />
</svg>
[ɛi, e], bait
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48084;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.402715,49.335233 h 12.307604 c 0,0 7.3545431,-18.311313 24.915392,-18.311313 17.560849,0 23.714653,18.137348 23.714653,18.137348 l 12.907974,-0.142135"
id="base-mound"
sodipodi:nodetypes="cczcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-outer"
cx="12.504731"
cy="50.600185"
r="7.9595795" />
</svg>
[ɛ], bet
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 h 21.9728073 c 0,0 -8.7107683,-5.308618 -8.6254273,-18.536589 0.08534,-13.227971 11.8971468,-19.957943 22.88067,-20.100079 10.983523,-0.142135 24.132914,6.070091 24.270193,19.58669 0.137279,13.516599 -8.372985,19.018148 -8.372985,19.018148 l 21.57823,-0.142137"
id="base-omega"
sodipodi:nodetypes="cczzzcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-inner"
cx="12.50473"
cy="14.924212"
r="7.9595795" />
<path
style="fill:none;stroke:#000000;stroke-width:3.49271999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 12.646867,6.8224964 C 12.504729,-8.9545266 12.504729,-1.2792181 12.504729,-1.2792181"
id="emphasis-inner" />
</svg>
[o, ɔu], won't, boat
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 h 21.9728073 c 0,0 -8.7107683,-5.308618 -8.6254273,-18.536589 0.08534,-13.227971 11.8971468,-19.957943 22.88067,-20.100079 10.983523,-0.142135 24.132914,6.070091 24.270193,19.58669 0.137279,13.516599 -8.372985,19.018148 -8.372985,19.018148 l 21.57823,-0.142137"
id="base-omega"
sodipodi:nodetypes="cczzzcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-inner"
cx="12.50473"
cy="14.924212"
r="7.9595795" />
</svg>
[ɔ], bawk
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 h 21.9728073 c 0,0 -8.7107683,-5.308618 -8.6254273,-18.536589 0.08534,-13.227971 11.8971468,-19.957943 22.88067,-20.100079 10.983523,-0.142135 24.132914,6.070091 24.270193,19.58669 0.137279,13.516599 -8.372985,19.018148 -8.372985,19.018148 l 21.57823,-0.142137"
id="base-omega"
sodipodi:nodetypes="cczzzcc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-on"
cx="12.646866"
cy="31.411909"
r="7.9595795" />
</svg>
[æ], bat
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 c 21.9728073,0 54.541558,-0.600371 58.120012,-22.089973 C 35.199043,37.798618 21.290897,32.244199 12.511064,32.347864 3.742928,32.451391 -10.546513,37.771707 -8.5599175,48.096898 -4.5769567,68.798058 27.981866,69.673483 49.560097,69.531345"
id="base-loop"
sodipodi:nodetypes="ccssc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-outer"
cx="12.504731"
cy="50.600185"
r="7.9595795" />
</svg>
[au], cow
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 c 21.9728073,0 54.541558,-0.600371 58.120012,-22.089973 C 35.199043,37.798618 21.290897,32.244199 12.511064,32.347864 3.742928,32.451391 -10.546513,37.771707 -8.5599175,48.096898 -4.5769567,68.798058 27.981866,69.673483 49.560097,69.531345"
id="base-loop"
sodipodi:nodetypes="ccssc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-on"
cx="12.646866"
cy="31.411909"
r="7.9595795" />
</svg>
[ai, aɪ], by
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >
<path
style="fill:none;stroke:#000000;stroke-width:3.48083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -24.143392,69.705313 c 21.9728073,0 54.541558,-0.600371 58.120012,-22.089973 C 35.199043,37.798618 21.290897,32.244199 12.511064,32.347864 3.742928,32.451391 -10.546513,37.771707 -8.5599175,48.096898 -4.5769567,68.798058 27.981866,69.673483 49.560097,69.531345"
id="base-loop"
sodipodi:nodetypes="ccssc" />
<circle
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.49272;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="dot-inner"
cx="12.50473"
cy="14.924212"
r="7.9595795" />
</svg>
[ɔi, ɔɪ], boy
</div>
</div>
</div>
<p>If you merge caught-cot, it is preferred to use the strong vowel /a~ɑ/ rather than the weak /ɔ/ vowel.</p>
</body>
</head>

View File

@ -1,409 +0,0 @@
<html>
<head>
<script src="../../static/jquery.min.js" type="text/javascript"></script>
<link href="../../static/bootstrap/css/bootstrap.css" rel="stylesheet" type="text/css">
<link href="../../static/bootstrap/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
<link href="../../static/shoofle.css" rel="stylesheet" type="text/css">
</head>
<body>
<style type="text/css">
svg circle, svg path { fill: none; }
svg .glyph { stroke: #600; fill: none; }
svg .vowel { stroke: #044; }
svg .radical { stroke: #004; }
svg circle.radical { fill: black; }
svg .enclosure { stroke: #000; }
</style>
<div class="row-fluid">
<div class="span6 offset3">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="400px" preserveAspectRatio="xMidYMax" id="arena" viewBox="-105 -105 210 210">
<defs>
<path id="mound" d="M-10,0 H-8 A9,8 0,0,1 8,0 H10" class="glyph"></path>
<g id="mound2">
<path d="M-10,0 H-8 A9,8 0 0,1 8,0 H10" class="glyph"></path>
<path d="M-6,0 A7,6 0,0,1 6,0" class="glyph"></path>
</g>
<path id="omega" d="M-10,0 H-4 A7,7 0,1,1 4,0 H10" class="glyph"></path>
<g id="omega2">
<path d="M-10,0 H-4 A7,7 0,1,1 4,0 H10" class="glyph"></path>
<path d="M-1,-1 A4.5,4.5 0,1,1 1,-1" class="glyph"></path>
</g>
<path id="angle" d="M-10,0 H-6 L0,-8 L6,0 H10" class="glyph"></path>
<g id="angle2">
<path d="M-10,0 H-6 L0,-8 L6,0 H10" class="glyph"></path>
<path d="M-4,0 L0,-5.3333 L4,0" class="glyph"></path>
</g>
<path id="chalice" d="M-10,0 H-6 A15,15 0,0,1 -10,-6 A15,10 0,0,0 10,-6 A15,15 0,0,1 6,0 H10" class="glyph"></path>
<g id="chalice2">
<path d="M-10,0 H-6 A15,15 0,0,1 -10,-6 A15,10 0,0,0 10,-6 A15,15 0,0,1 6,0 H10" class="glyph"></path>
<path d="M-8.5,-3 A 14,9 0,0,0 8.5,-3" class="glyph"></path>
</g>
<path id="loop" d="M-10,0 H-8 C6,0 6,-10 0,-10 C-6,-10 -6,0 6,0 H10" class="glyph"></path>
<g id="loop2">
<path d="M-10,0 H-8 C6,0 6,-10 0,-10 C-6,-10 -6,0 6,0 H10" class="glyph"></path>
<circle r="2" cx="0" cy="-6" class="glyph"></circle>
</g>
<path id="weird_up" d="M -4,-4 A 4,6 0,0,0 0,-8 A 4,6 0,0,0 4,-4" class="vowel"></path>
<path id="weird_down" d="M -4,-8 A 4,6 0,0,0 0,-4 A 4,6 0,0,0 4,-8" class="vowel"></path>
</defs>
</svg>
</div>
</div>
<hr>
<input type="text" value="shoof~l ihs ~ bihg dohrk" />
<h3>Available Sounds:</h3>
<p id="sounds_list"></p>
<script type="text/javascript">
var svg_ns = "http://www.w3.org/2000/svg";
var xlink_ns = "http://www.w3.org/1999/xlink";
function cos_deg (angle) { return Math.cos(angle * Math.PI / 180); }
function sin_deg (angle) { return Math.sin(angle * Math.PI / 180); }
function norm (vector) {
var norm = Math.sqrt(vector.x*vector.x + vector.y*vector.y);
return {x: vector.x / norm, y: vector.y / norm};
}
function sub (a, b) { return {x: a.x-b.x, y: a.y-b.y}; }
function add (a, b) { return {x: a.x+b.x, y: a.y+b.y}; }
function mul (a, b) {
if ("x" in a) { return {x: a.x*b, y: a.y*b}; }
return a*b;
}
// Table is the table of what letters mean what things!
// Each entry in the table is a list of columnar cells.
// Each column corresponds to a particular radical configuration.
// Each cell is a list of strings which will be recognized in parsing.
var table = {};
// Main consonants!
table["mound"] = [ ["m"], ["n"], [], ["ng"]];
table["omega"] = [ ["p"], ["t"], [], ["k", "c", "q"]];
table["angle"] = [ ["f", "ph"], ["s"], ["sh"], []];
table["loop"] = [ ["th"], [], ["ch"], []];
table["chalice"] = [["w"], ["r"], ["y"], ["h"]];
// Alternate (mostly, voiced) consonants!
table["mound2"] = [ [], [], [], []];
table["omega2"] = [ ["b"], ["d"], [], ["g"]];
table["angle2"] = [ ["v"], ["z"], ["zh"], []];
table["loop2"] = [ ["dh"], [], ["j"], []];
table["chalice2"] = [[], ["l"], [], []];
// Vowels in the eat/ate/at and oh/ow/aw tracks!
table["above double"] = [["ee"], ["ay"], ["a"]];
table["above single"] = [["ih"], ["eh", "e"], ["ah"]];
table["below"] = [["oh", "o"], ["ow", "ou"], ["aw"]];
// Vowels on the line!
table["on the line double"] = [["uh"], ["oo"]];
table["on the line single"] = [["~"], ["u"]];
// The weird ones!
table["weird up"] = [["i"]];
table["weird down"] = [["oy", "oi"]];
// These lists simply list out which radicals should be shown.
// four_rad_options contains the radicals array that should be passed to make glyphs in rows with four columns.
// three_rad_options contains the radicals array that will be passed to make glyphs in rows with three columns.
// And so on.
// These are consumed in make_new_glyph().
// Basically, each glyph has a number of locations that radicals are placed.
// If a shape is in the third column of a four-column row, then we look at four_rad_options, and see that the
// third element is [null, "dot", "dot"]. So that shape will have no radical in the first position, and dots in
// both the second and third positions.
var four_rad_options = [];
four_rad_options.push(["dot", null, null]);
four_rad_options.push(["line", null, null]);
four_rad_options.push([null, "dot", "dot"]);
four_rad_options.push([null, "line", "line"]);
var three_rad_options = [];
three_rad_options.push(["line", null]);
three_rad_options.push([null, null]);
three_rad_options.push([null, "line"]);
var two_rad_options = [];
two_rad_options.push([null, null]);
two_rad_options.push(["line", "line"]);
var one_rad_options = [];
one_rad_options.push([]);
var rad_options = [one_rad_options, two_rad_options, three_rad_options, four_rad_options];
// Invert the table of shapes, and the lists of radical options, into a lookup table.
// The keys are phrases recognized as coding a sound, and the values are objects describing the appearance of the glyph that should result.
var lookup = {};
$.each(table, function(shape_name, row) {
var radical_options = rad_options[row.length - 1];
$.each(row, function(radical_index, valid_sequences) {
var current_radicals = radical_options[radical_index];
$.each(valid_sequences, function(i, phrase) {
lookup[phrase] = {name: shape_name, radicals: current_radicals};
});
});
});
var consonant_shapes = "mound omega angle chalice loop ";
consonant_shapes = consonant_shapes + consonant_shapes.replace(/ /gi, "2 ");
consonant_shapes = consonant_shapes.split(" ");
console.log(consonant_shapes);
var vowel_shapes = "above double,above single,below,on the line double,on the line single,weird up,weird down".split(",");
// Defines the positions of the radicals for glyphs. Should perhaps be moved to live in the XML?
// Maybe each symbol could be a group, and then have radical shapes identified by their classes,
// and then dynamically shown/hidden?
var consonant_radical_positions = {
"mound": [{x: 0, y: -8}, {x: -3, y: -7}, {x: 3, y: -7}],
"omega": [{x: 0, y: -16}, {x: -3, y: -15}, {x: 3, y: -15}],
"angle": [{x: 0, y: -12}, {x: -3, y: -10}, {x: 3, y: -10}],
"loop": [{x: 0, y: -14}, {x: -3, y: -14}, {x: 3, y: -14}],
"chalice": [{x: 0, y: -7}, {x: -3, y: -7}, {x: 3, y: -7}]
};
// Ensure that all the doubled glyphs have the same radical positions as their singled counterparts.
$.each(consonant_shapes, function(i, name) {
if (name.slice(0, -1) in consonant_radical_positions) {
consonant_radical_positions[name] = consonant_radical_positions[name.slice(0, -1)];
}
});
var radical_backup = 3.5;
var radical_length = 3;
var radical_length_vowel = 6;
var vowel_radius = 4;
var inset = 5;
var word_radius = [14, 14, 14, 16, 20, 24, 28, 32];
var sentence_radius = 80;
var the_big_word;
function new_glyph(symbol_name, radicals) {
var glyph;
if ($.inArray(symbol_name, consonant_shapes) != -1) {
glyph = new_consonant(symbol_name, radicals);
}
if ($.inArray(symbol_name, vowel_shapes) != -1) {
glyph = new_vowel(symbol_name, radicals);
}
glyph.name = symbol_name;
glyph.radicals = radicals;
return glyph;
}
function new_vowel(symbol_name, radicals) {
var s = {handles: {}, vectors: []};
s.handles.circle_in = {x: 0, y: 0};
s.handles.circle_out = {x: 0, y: 0};
s.handles.radicals = [];
if (symbol_name == "weird up" || symbol_name == "weird down") {
s.e = ns_elem('use', svg_ns, {'xlink:href': '#' + symbol_name.replace(" ", "_")}, xlink_ns);
s.handles.center = {x: 0, y: -6};
}
else {
s.e = ns_elem('g', svg_ns);
if (symbol_name.indexOf("above") != -1) { s.handles.center = {x: 0, y: -8}; }
if (symbol_name.indexOf("below") != -1) { s.handles.center = {x: 0, y: 8}; }
if (symbol_name.indexOf("on the line") != -1) { s.handles.center = {x: 0, y: 0}; }
s.handles.radicals.push({x: 0, y: s.handles.center.y - vowel_radius});
s.handles.radicals.push({x: 0, y: s.handles.center.y + vowel_radius});
var vowel_out = $(ns_elem('circle', svg_ns));
vowel_out.attr({r: vowel_radius, cx: s.handles.center.x, cy: s.handles.center.y}).attr('class', 'vowel');
$(s.e).append(vowel_out);
if (symbol_name.indexOf("double") != -1) {
var vowel_in = $(ns_elem('circle', svg_ns));
vowel_in.attr({r: vowel_radius/3, cx: s.handles.center.x, cy: s.handles.center.y}).attr('class', 'vowel');
$(s.e).append(vowel_in);
}
$.each(radicals, function (index, value) {
if (value != "line") { return true; }
var start = s.handles.radicals[index]; var d = norm(sub(start, s.handles.center));
var end = { x: start.x + d.x * radical_length_vowel, y: start.y + d.y * radical_length_vowel};
var line = ns_elem('path', svg_ns);
$(line).attr('d', 'M ' + start.x + ', ' + start.y + ' L ' + end.x + ', ' + end.y);
$(line).attr('class', 'radical')
$(s.e).append(line);
});
}
$.each(s.handles, function(index, value) { if (index != "radicals") { s.vectors.push(value); } });
$.each(s.handles.radicals, function(index, value) { s.vectors.push(value); });
return s;
}
function new_consonant(symbol_name, radicals) {
var s = {e: ns_elem('g', svg_ns), handles: {}, vectors: []};
s.handles.center = {x: 0, y: 0};
s.handles.circle_in = {x: -10, y: 0};
s.handles.circle_out = {x: 10, y: 0};
s.handles.radicals = {};
$.extend(true, s.handles.radicals, consonant_radical_positions[symbol_name]);
$(s.e).append(ns_elem('use', svg_ns, {'xlink:href': '#' + symbol_name}, xlink_ns));
$.each(radicals, function(index, value) {
var radical_position = s.handles.radicals[index];
if (value == "dot") {
var circle = $(ns_elem('circle', svg_ns));
circle.attr({r: 1, cx: radical_position.x, cy: radical_position.y});
circle.attr('class', 'radical');
$(s.e).append(circle);
}
if (value == "line") {
var d = norm(sub(radical_position, s.handles.center));
var start = sub(radical_position, mul(d, radical_backup));
var end = add(radical_position, mul(d, radical_length));
var line = $(ns_elem('path', svg_ns));
line.attr('d', 'M' + start.x + ',' + start.y + ' L' + end.x + ',' + end.y);
line.attr('class', 'radical');
$(s.e).append(line);
}
});
$.each(s.handles, function(index, value) { if (index != "radicals") { s.vectors.push(value); } });
$.each(s.handles.radicals, function(i, vector) { s.vectors.push(vector); });
return s;
}
function transform_for_circle(radius, angle, glyph) {
var group = ns_elem('g', svg_ns);
$(group).attr('transform', 'rotate(' + (-1*angle) + ') translate(0 ' + radius + ')');
$(group).append(glyph.e);
$.each(glyph.vectors, function (name, value) {
// Translate the vector.
var translated = {};
translated.x = value.x;
translated.y = value.y + radius;
// Rotate the vector.
var rotated = {};
rotated.x = translated.x * cos_deg(-angle) - translated.y * sin_deg(-angle);
rotated.y = translated.x * sin_deg(-angle) + translated.y * cos_deg(-angle);
// Change the vector in-place.
value.x = rotated.x;
value.y = rotated.y;
});
glyph.e = group;
return glyph;
}
function enclose(radius, list_of_glyphs) {
var length = list_of_glyphs.length;
angle_step = 360 / length;
var enclosure = {e: ns_elem('g', svg_ns), children: [], handles: {}, vectors: [] };
$.each(list_of_glyphs, function (index, glyph) {
var the_glyph = transform_for_circle(radius, angle_step*index, glyph);
enclosure.children.push(the_glyph);
$(enclosure.e).append(the_glyph.e);
$.each(the_glyph.vectors, function (i, vector) { enclosure.vectors.push(vector); });
});
$.each(enclosure.children, function (index) {
var current = enclosure.children[index];
var next = enclosure.children[(index+1) % length];
var npd = "M " + current.handles.circle_out.x + " " + current.handles.circle_out.y;
var arc = "A" + radius + "," + radius + " 0,0,0 ";
if (length != 1) {
npd = npd + arc + next.handles.circle_in.x + "," + next.handles.circle_in.y + " ";
}
else {
npd = npd + arc + "0," + (next.handles.circle_in.y - radius - radius) + " ";
npd = npd + arc + next.handles.circle_in.x + "," + next.handles.circle_in.y + " ";
}
var arc = $(ns_elem('path', svg_ns)).attr('d', npd).attr('class', 'enclosure');
$(enclosure.e).append(arc);
});
$(enclosure.e).attr('transform', 'translate(0 ' + (-radius - inset) + ')');
$.each(enclosure.vectors, function (i, vector) {
vector.y = vector.y - radius - inset;
});
enclosure.handles.circle_in = {x: 0, y: 0};
enclosure.handles.circle_out = {x: 0, y: 0};
enclosure.vectors.push(enclosure.handles.circle_in);
enclosure.vectors.push(enclosure.handles.circle_out);
return enclosure;
}
function letters_from_string(str) {
str = str.toLowerCase();
str = str.replace(/x/g, "ks");
str = str.replace(/qu/g, "kw");
str = str.replace(/q/g, "k");
var tokens = [];
while (str.length != 0) {
var results = consume_token(str);
str = results.str;
if (results.success) {
tokens.push(results.token);
}
}
return tokens;
}
function consume_token(str) {
if (str[0] == " ") {
return consume_token(str.slice(1));
}
var out = {success: false};
var the_tip = str.slice(0,2);
if (the_tip in lookup) {
out.success = true;
out.str = str.slice(2);
out.token = lookup[the_tip];
console.log(out);
console.log(the_tip, out.token);
}
else if (the_tip[0] in lookup) {
out.success = true;
out.str = str.slice(1);
out.token = lookup[the_tip[0]];
}
return out;
}
function interpret_input(str) {
var words = [];
$.each(str.split(" "), function (i, word) {
var tk = letters_from_string(word);
var glyphs = [];
$.each(tk, function (i, letter_glyph) {
glyphs.push(new_glyph(letter_glyph.name, letter_glyph.radicals));
});
var r = word_radius[word_radius.length-1];
if (glyphs.length < word_radius.length) {
r = word_radius[glyphs.length];
}
words.push(enclose(r, glyphs));
});
var out = enclose(sentence_radius, words);
out = transform_for_circle(sentence_radius+inset, 0, out);
return out;
}
$(document).ready(function () {
function change_word(str) {
if (the_big_word && the_big_word.e) { $(the_big_word.e).remove(); }
the_big_word = interpret_input(str);
$('svg').append(the_big_word.e);
}
change_word($('input').val());
$('input').change(function() { change_word($(this).val()); });
$('input').keyup(function() { change_word($(this).val()); });
$('svg').append(the_big_word.e);
$('#sounds_list').text(Object.keys(lookup).join(", "));
});
</script>
<script type="text/javascript">
function ns_elem () { if (typeof arguments[0] === "undefined") { console.log('There was an error in the element assist function. Called with no valid tag name!');} var elem; if (typeof arguments[1] === "string") { elem = document.createElementNS(arguments[1], arguments[0]); for (var i=2; i<arguments.length; i+=2) { if (typeof arguments[i+1] === "undefined") { for (var key in arguments[i]) { elem.setAttribute(key, arguments[i][key]); } break; } else { for (var key in arguments[i]) { elem.setAttributeNS(arguments[i+1], key, arguments[i][key]); } } } } else { elem = document.createElement(arguments[0]); for (var i=1; i<arguments.length; i+=2) { if (typeof arguments[i+1] === "undefined") { for (var key in arguments[i]) { elem.setAttribute(key, arguments[i][key]); } break; } else { for (var key in arguments[i]) { elem.setAttributeNS(arguments[i+1], key, arguments[i][key]); } } } } return elem;}
</script>
</body>
</html>

View File

@ -1,440 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="../../static/jquery.min.js" type="text/javascript"></script>
<link href="../../static/bootstrap/css/bootstrap.css" rel="stylesheet" type="text/css">
<link href="../../static/bootstrap/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
<link href="../../static/shoofle.css" rel="stylesheet" type="text/css">
<script src="circle_setter_utils.js" type="text/javascript"></script>
<script src="circle_setter.js" type="text/javascript"></script>
</head>
<body>
<style type="text/css">
#consonant-grid, #vowel-grid {
margin-left: auto;
margin-right: auto;
}
/* consonants */
td svg {
width: 3.5em;
height: 3.5em;
}
th svg {
display: none;
width:3.5em;
height:3.5em;
}
td {
padding: 0.25em;
}
td, .vowel-sequence {
border: 2px solid gray;
}
/* vowels */
#vowel-grid {
display: block;
}
.vowel-sequence {
display: block;
}
.vowel-example {
display: inline-block;
padding: 1em;
}
.vowel-example * {
vertical-align: middle;
}
.vowel-example > svg {
width: 3.5em;
height: 3.5em;
}
/* svg stuff */
svg { draggable: false; }
svg path, svg circle {
fill: none;
stroke: red;
stroke-width: 4px;
}
#word_circle { stroke: transparent; }
.vowel-base, .glyph, .line_1, .line_2, .line_3, .dot_1, .dot_2, .dot_3{
stroke: #000;
stroke-width: 2px;
}
.handle { display: none; }
.click_target { fill:transparent; }
#delete { fill:pink; }
/* this is styling rather than layout, not sure where it should go */
.unverified {
color: lightgray;
}
.megaglyph { display: none; }
.intervowel { stroke-width: 1px; stroke: #000; }
</style>
<div class="row-fluid">
<div class="span4"><!-- consonants -->
<form id="consonant_form">
<table id="consonant-grid">
<tbody>
<tr>
<th scope="row" class="consonant-shape">
<svg >
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
class="mound-1 megaglyph"
transform="translate(25,0)"><path
d="M -22.108869,113.14142 C -14.825778,68.277574 38.195123,67.792033 47.420373,112.9472"
class="glyph"
sodipodi:nodetypes="cc" /><path
d="m 12.100818,78.237112 c 0.463923,-33.247812 0,-74.2276752 0,-74.2276752"
class="line_1" /><path
d="M 3.5161518,80.328763 C 3.9800748,47.08095 -2.3602059,3.9361133 -2.3602059,3.9361133"
class="line_2"
sodipodi:nodetypes="cc" /><path
d="M 20.887406,80.249008 C 21.351329,47.001196 26.763764,4.1656416 26.763764,4.1656416"
class="line_3"
sodipodi:nodetypes="cc" /><path
d="M -6.9323217,68.201226 C 6.3668024,51.190717 8.8410588,52.582486 12.397801,55.829947"
class="dot_2" /><path
d="M 4.0618415,47.554015 C 17.360965,30.543506 19.835221,31.935275 23.391964,35.182736"
class="dot_1" /><path
d="M 15.195992,69.203753 C 28.495116,52.193245 30.969373,53.585014 34.526115,56.832475"
class="dot_3" /></g>
</svg>
</th>
<td>[m]<svg/></td>
<td>[n]<svg/></td>
<td class="unverified">[ñ]</td>
<td>[ŋ]<svg/></td>
<td></td>
<th scope="row" class="consonant-shape">
<img src="consonant-mound-2.svg" />
</th>
</tr>
<tr>
<th scope="row" class="consonant-shape">
<svg>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
class="omega-1 megaglyph"
transform="translate(25,0)">
<path
d="m -20.755057,77.343458 c 0,0 0,30.618622 0.380829,46.994252 C -23.040029,95.204323 -6.4362494,84.506425 8.6829945,92.576601 23.802242,100.64678 20.107856,123.61413 -0.07606316,123.80455 H 44.10006"
class="glyph"
sodipodi:nodetypes="cczcc" />
<path
d="m -1.7099346,89.222829 c 0.46392,-33.24781 0,-74.22767 0,-74.22767"
class="line_1" />
<path
d="M 23.690919,125.15467 C 24.154842,91.90686 17.814561,48.76203 17.814561,48.76203"
class="line_2"
sodipodi:nodetypes="cc" />
<path
d="m 41.062172,125.07492 c 0.46392,-33.24781 5.87636,-76.08337 5.87636,-76.08337"
class="line_3"
sodipodi:nodetypes="cc" />
<path
d="M -12.402587,73.540483 C -5.9076651,66.117713 20.07202,31.787413 34.91756,47.251513"
class="dot_1"
inkscape:label="dot_1" />
<path
d="M 0.413012,80.253857 C 13.712136,63.243348 16.186393,64.635117 19.743135,67.882578"
class="dot_2"
inkscape:label="dot_2" />
<path
d="M 22.541326,81.256384 C 35.84045,64.245876 38.314707,65.637645 41.871449,68.885106"
id="path1720-1"
class="dot_3" />
</g>
</svg>
</th>
<td>[p], [b]<svg/></td>
<td>[t], [d]<svg/></td>
<td></td>
<td>[k], [g]<svg/></td>
<td class="unverified">[q], [ɢ]</td>
<th scope="row" class="consonant-shape">
<img src="consonant-ohm-2.svg" />
</th>
</tr>
<tr>
<th scope="row" class="consonant-shape">
<svg>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
class="angle-1 megaglyph"
transform="translate(25,0)">
<path
style="opacity:1;fill:none;stroke:#000000;stroke-width:2.88527;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -14.06223,75.917879 H 39.968325 L 39.843832,126.71158"
class="glyph" />
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 5.6174494,75.22561 C 6.0813724,41.977797 -0.2589083,-1.1670398 -0.2589083,-1.1670398"
class="line_2"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 22.988704,75.145855 C 23.452627,41.898043 28.865062,-0.93751155 28.865062,-0.93751155"
class="line_3"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 14.502302,74.634887 c 0.463923,-33.247813 0,-74.22767555 0,-74.22767555"
class="line_1" />
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -6.0317653,62.94798 C 7.2673588,45.937471 9.7416152,47.32924 13.298357,50.576701"
id="path1720" class="dot_2"/>
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 4.9623979,42.300769 C 18.261522,25.29026 20.735778,26.682029 24.292521,29.92949"
id="path1720-7" class="dot_1"/>
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 16.096549,63.950507 C 29.395673,46.939999 31.86993,48.331768 35.426672,51.579229"
id="path1720-1" class="dot_3"/>
</g>
</svg>
</th>
<td>[f], [v]<svg></svg></td>
<td>[s], [z]<svg></svg></td>
<td>[ʃ], [ʒ]<svg></svg></td>
<td></td>
<td class="unverified">[ʔ], [ʡ]<svg></svg></td>
<th scope="row" class="consonant-shape">
<img src="consonant-angle-2.svg" />
</th>
</tr>
<tr>
<th scope="row" class="consonant-shape">
<svg>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
class="loop-1 megaglyph"
transform="translate(25,0)"><path
style="fill:none;stroke:#000000;stroke-width:3.47853;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -17.664457,111.5189 c 17.71094207,0 50.4325,2.83413 56.735052,-9.45585 C 43.573377,93.28263 38.467691,81.072874 30.440263,79.305245 19.108262,76.809953 6.7206019,82.850589 6.8006582,96.359531 c 0.093399,15.760439 0.600371,39.624479 0.600371,39.624479"
class="path1628"
sodipodi:nodetypes="csssc" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.808051,77.786834 c 0.463923,-33.247812 0,-74.2276754 0,-74.2276754"
class="line_1" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 15.223385,80.178675 C 15.687308,46.930858 9.347027,3.7860214 9.347027,3.7860214"
class="line_2"
sodipodi:nodetypes="cc" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 32.594639,80.098915 C 33.058562,46.851104 38.470997,4.0155494 38.470997,4.0155494"
class="line_3"
sodipodi:nodetypes="cc" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 4.774911,69.552061 C 18.074035,52.541552 20.548292,53.933321 24.105034,57.180782"
class="dot_2" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 15.769074,48.90485 C 29.068198,31.894341 31.542454,33.28611 35.099197,36.533571"
class="dot_1" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 26.903225,70.554588 C 40.202349,53.54408 42.676606,54.935849 46.233348,58.18331"
class="dot_3" /></g>
</svg>
</th>
<td>[θ], [ð]<svg/></td>
<td class="unverified">[ts]<svg/></td>
<td>[tʃ], [dʒ]<svg/></td>
<td>[x~χ], <span class="unverified">[ʁ]</span><svg/></td>
<td></td>
<th scope="row" class="consonant-shape">
<img src="consonant-loop-2.svg" />
</th>
</tr>
<tr>
<th scope="row" class="consonant-shape">
<svg>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
class="wave-1 megaglyph"
transform="translate(25,0)"><path
style="fill:none;stroke:#000000;stroke-width:3.47853;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -22.617516,106.41574 C -10.15982,87.804245 -3.4056477,76.397198 -3.4056477,76.397198 c 0,0 -3.1519473,26.266232 11.2569551,26.116132 C 22.260209,102.36324 26.913083,76.847476 26.913083,76.847476 l 23.414466,29.868454"
class="glyph"
sodipodi:nodetypes="ccscc" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8.1984075,103.1525 c 0.463923,-33.247807 0,-74.22767 0,-74.22767"
class="line_1" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -3.3881142,77.627094 C -2.9241912,44.379281 -9.2644719,1.2344442 -9.2644719,1.2344442"
class="line_2"
sodipodi:nodetypes="cc" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 26.891115,78.748081 C 27.355038,45.500269 32.767473,2.6647142 32.767473,2.6647142"
class="line_3"
sodipodi:nodetypes="cc" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -7.8328782,68.651504 C 5.4662459,51.640995 7.9405023,53.032764 11.497244,56.280225"
class="dot_2" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 3.161285,48.004293 C 16.460409,30.993784 18.934665,32.385553 22.491408,35.633014"
class="dot_1" /><path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 14.295436,69.654031 C 27.59456,52.643523 30.068817,54.035292 33.625559,57.282753"
class="dot_3" /></g>
</svg>
</th>
<td>[w]<svg/></td>
<td>[ɹ], [l]<svg/></td>
<td>[ɾ~r], <span class="unverified">[ɬ]</span><svg/></td>
<td>[j]<svg/></td>
<td>[h]<svg/></td>
<th scope="row" class="consonant-shape">
<img src="consonant-wave-2.svg" />
</th>
</tr>
</tbody>
</table>
</form>
</div>
<div class="span4 ">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="400px" preserveAspectRatio="xMidYMax" id="arena" viewBox="-75 -105 150 210" draggable="false" >
<g transform="translate(-300,0)" id="consonant_place_group">
<g id="consonant_copy_from_here">
<rect class="click_target" x="-15" y="-35" width="30" height="35"/>
<g id="consonant_put_it_in_here" transform="translate(-12,-45) scale(0.3)"></g>
</g>
</g>
<circle cx="0" cy="0" r="100" id="word_circle" />
<rect x="80" id="delete" y="80" width="40" height="40"/>
<g transform="translate(300,0)" id="vowel_place_group">
<g id="vowel_copy_from_here">
<rect class="click_target" x="-15" y="-35" width="30" height="35"/>
<g id="vowel_put_it_in_here" transform="translate(-6,-30) scale(0.5)"></g>
</g>
</g>
<g id="connections" />
</svg>
</div>
<div class="span4"><!-- vowels -->
<div id="vowel-grid">
<div class="vowel megaglyph">
<svg>
<path
d="m -24.143392,69.705313 h 21.9728073 c 0,0 -8.7107683,-5.308618 -8.6254273,-18.536589 0.08534,-13.227971 11.8971468,-19.957943 22.88067,-20.100079 10.983523,-0.142135 24.132914,6.070091 24.270193,19.58669 0.137279,13.516599 -8.372985,19.018148 -8.372985,19.018148 l 21.57823,-0.142137"
class="base-omega vowel-base" />
<path
d="m -24.402715,49.335233 h 12.307604 c 0,0 7.3545431,-18.311313 24.915392,-18.311313 17.560849,0 23.714653,18.137348 23.714653,18.137348 l 12.907974,-0.142135"
class="base-mound vowel-base" />
<path
d="m -24.143392,69.705313 c 21.9728073,0 54.541558,-0.600371 58.120012,-22.089973 C 35.199043,37.798618 21.290897,32.244199 12.511064,32.347864 3.742928,32.451391 -10.546513,37.771707 -8.5599175,48.096898 -4.5769567,68.798058 27.981866,69.673483 49.560097,69.531345"
class="base-loop vowel-base" />
<circle
class="dot-outer"
cx="12.504731"
cy="50.600185"
r="7.9595795" />
<path
d="m 12.50473,58.701897 c 0,15.634887 0,15.634887 0,15.634887"
class="emphasis-outer" />
<circle
class="dot-inner"
cx="12.50473"
cy="14.924212"
r="7.9595795" />
<path
d="M 12.646867,6.8224964 C 12.504729,-8.9545266 12.504729,-1.2792181 12.504729,-1.2792181"
class="emphasis-inner" />
<path
class="triangle-inner"
d="M 12.646867,6.822496 20.187878,20.036981 4.9732929,19.960446 Z" />
<path
d="M 12.646867,6.8224964 C 12.504729,-8.9545266 12.504729,-1.2792181 12.504729,-1.2792181"
class="emphasis-inner" />
<circle
class="dot-on"
cx="12.646866"
cy="31.411909"
r="7.9595795" />
</svg>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-omega dot-outer emphasis-outer</svg> [ɑ, a], box or bah
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-omega dot-outer</svg> [ʌ, ə], but or schwa
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >base-mound dot-inner emphasis-inner</svg> [i], beat
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >base-mound dot-inner</svg> [ɪ], bit
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >base-mound triangle-inner emphasis-inner</svg> [u], boot
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >base-mound triangle-inner</svg> [ʊ], book
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >base-mound dot-outer emphasis-outer</svg> [ɛi, e], bait
</div>
<div class="vowel-example">
<svg viewBox="0 -12 24 80" >base-mound dot-outer</svg> [ɛ], bet
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-omega dot-inner emphasis-inner</svg> [o, ɔu], won't, boat
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-omega dot-inner</svg> [ɔ], bawk
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-omega dot-on</svg> [æ], bat
</div>
</div>
<div class="vowel-sequence">
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-loop dot-outer</svg> [au], cow
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-loop dot-on</svg> [ai, aɪ], by
</div>
<div class="vowel-example">
<svg viewBox="0 0 24 80" >base-loop dot-inner</svg> [ɔi, ɔɪ], boy
</div>
</div>
</div>
</div>
</div>
<hr>
</body>
</html>

View File

@ -1,290 +0,0 @@
var currently_dragging = null; // if this is null, we have nothing being dragged. if it's not null, then it will represent the glyph object (as goes in the glyphs array) which is currently being dragged
var major_radius;
const glyphs = [];
const vowels = [];
const consonants = [];
var svg;
var connections_container;
$(document).ready(function() {
svg = $('svg#arena')[0];
connections_container = $('svg#arena #connections');
major_radius = parseFloat($('circle').attr('r'));
$(document).on("mouseup", on_mouse_up);
$('td > svg').on("mousedown", function(event) {
let receptacle = $("#consonant_put_it_in_here");
let selected = $(this);
receptacle.html(selected.html());
const mouse_position = svg_from_dom({x:event.clientX, y:event.clientY}, svg);
var g = make_new_consonant();
set_location(g, mouse_position);
currently_dragging = g;
})
$('.vowel-example > svg').on("mousedown", function(event) {
let receptacle = $('#vowel_put_it_in_here');
let selected = $(this);
receptacle.html(selected.html());
var g = make_new_vowel();
const mouse_position = svg_from_dom({x:event.clientX, y:event.clientY}, svg);
set_location(g, mouse_position);
currently_dragging = g;
});
$('td > svg').each(function() { this.setAttribute('viewBox',"0 30 40 100");});
populate_consonant_grid();
populate_vowels();
});
function populate_consonant_grid() {
// this function expects that there's a #consonant-grid table, where each row corresponds to a glyph set
// the cells of the row need to be filled with the various possible configurations of that glyph
// so like, the <th> in a given row should contain a .megaglyph svg group, which contains the base glyph plus all possible radicals.
// then we use the split_megaglyph function to generate the five possible configurations out of that megaglyph
// and insert the five configured glyphs into their corresponding (td) cells of the table
$("#consonant-grid tbody tr").each(function (asdf, row) { // iterate over the rows in the table
// row will now be something like <tr><th/><td/><td/><td/><td/><td/><th/></tr>
// that is, a tr containing one th on either end plus a td for each consonant in this row
var head = $(row).find("th:first-child svg"); // this is the header for the row, which should contain the .megaglyph object
var megaglyph = head.find(".megaglyph");
var glyphs = split_megaglyph(megaglyph); // now this is a list of the configured consonant symbols
$(row).find("td").each(function(idx, cell) { // iterate over the cells in the row
// cell will now be a <td> cell which contains the IPA for that cell's consonants, plus the <svg> canvas into which to insert the configured consonant symbol
$(cell).find("svg").append(glyphs[idx]);
});
});
}
function split_megaglyph(megaglyph) {
// megaglyph is an svg object which contains all the possible radicals at once.
// this should return a list of new glyphs, cloned frmo megaglyph, each of which contains only the radicals it wants.
var one = megaglyph.clone();
one.find('.line_1,.line_2,.line_3,.dot_2,.dot_3').remove(); // only .dot_1
remove_class(one, "megaglyph");
var two = megaglyph.clone();
two.find('.line_2,.line_3,.dot_1,.dot_2,.dot_3').remove(); // line 1 only
remove_class(two, "megaglyph");
var three = megaglyph.clone();
three.find('.line_1,.line_2,.line_3,.dot_1').remove(); // only dots 2 and 3
remove_class(three, "megaglyph");
var four = megaglyph.clone();
four.find('.line_1,.dot_1,.dot_2,.dot_3').remove(); //lines 2 and 3
remove_class(four, "megaglyph");
var five = megaglyph.clone();
five.find('.line_1,.line_2,.line_3').remove(); // all three dots
remove_class(five, "megaglyph");
return [one, two, three, four, five];
}
function populate_vowels() {
megaglyph = $(".vowel.megaglyph");
$(".vowel-example svg").each((i, ex) => {
let glyphs = $(ex).text().replace(/(\s|\n)+/g,",").split(",").filter((a) => a!="")
$(ex).text("");
$.each(glyphs, (i, e) => {
let wanted_class = "." + e;
let glyph_original = megaglyph.find(wanted_class);
let new_glyph = glyph_original.clone();
$(ex).append(new_glyph);
});
});
}
function make_new_consonant(source) {
// create a new glyph object by copying the elements pointed to by source
// this will add it to the svg and to the glyphs list
var x = {};
x.angle = 0;
x.element = $("#consonant_copy_from_here").clone().attr("id","").attr("onclick", "").attr("onmousedown", "");
x.element.find("#consonant_put_it_in_here").attr("id","");
x.position = {x:0, y:0};
x.element.on("mousedown", x, (event) => { currently_dragging = event.data });
$(svg).append(x.element);
x.handles = [];
x.handles.push($(ns_elem("circle", svg_ns)))
x.handles.push($(ns_elem("circle", svg_ns)))
$.each(x.handles, function() {
$(svg).append(this)
this.attr("r", 2);
add_class(this, "handle");
});
update_handles(x);
glyphs.push(x);
x.vowel = false;
return x;
}
function make_new_vowel(source) {
// create a new glyph object by copying the elements pointed to by source
// this will add it to the svg and to the glyphs list
var x = {};
x.angle = 0;
x.element = $("#vowel_copy_from_here").clone().attr("id","").attr("onclick", "").attr("onmousedown", "");
x.element.find("#vowel_put_it_in_here").attr("id","");
x.position = {x:0, y:0};
x.element.on("mousedown", x, (event) => { currently_dragging = event.data });
$(svg).append(x.element);
x.handles = [];
x.handles.push($(ns_elem("circle", svg_ns)));
x.handles.push($(ns_elem("circle", svg_ns)));
$.each(x.handles, function() {
$(svg).append(this)
this.attr("r", 2);
add_class(this, "handle");
});
update_handles(x);
glyphs.push(x);
x.vowel = true;
return x;
}
function on_mouse_up(event) {
// this is called on the whole document when the mouse is released on it. event.data is meaningless. this should handle releasing the current dragged element, if it exists.
if (currently_dragging != null && is_in_delete_region(currently_dragging.position)) {
remove(glyphs, currently_dragging);
$.each(currently_dragging.handles, function() { $(this).remove(); });
currently_dragging.element.remove();
currently_dragging = null;
connect_vowels();
}
if (currently_dragging != null) {
set_location(currently_dragging, mul(norm(currently_dragging.position), major_radius));
}
currently_dragging = null;
}
function is_in_delete_region(p) {
r = $("#delete");
first = {x:parseFloat(r.attr("x")), y:parseFloat(r.attr("y"))};
second = {x:parseFloat(r.attr("width"))+first.x, y:parseFloat(r.attr("height"))+first.y};
if (p.x > first.x && p.x < second.x && p.y > first.y && p.y < second.y) {
return true;
}
return false;
}
$(document).mousemove(function(event) {
const mouse_position = svg_from_dom({x:event.clientX, y:event.clientY}, svg);
const mouse_move = sub(mouse_position, previous_mouse_position);
previous_mouse_position = mouse_position;
var s_position = mouse_position;
// get the position inside the svg tag for the mouse
if (currently_dragging != null) {
set_location(currently_dragging, add(currently_dragging.position, mouse_move));
update_handles(currently_dragging);
}
});
function update_handles(g) {
if (g.element.find('path.glyph,path.vowel-base').length != 0) {
set_loc(g.handles[0], get_global_point_at_length(svg, g.element.find('path.glyph,path.vowel-base')[0], 0));
set_loc(g.handles[1], get_global_point_at_length(svg, g.element.find('path.glyph,path.vowel-base')[0], 1000));
}
connect_vowels();
}
function set_location(glyph, point) {
glyph.position = point;
glyph.angle = Math.atan2(point.y, point.x);
set_transform(glyph.element, glyph.position, appropriate_angle(point));
update_handles(glyph);
}
function connect_vowels() {
let vowels = $.grep(glyphs, (v) => v.vowel);
vowels.sort((a, b) => b.angle - a.angle);
console.log($.map(vowels, (a)=>a.angle));
// clear oout the old connections
connections_container.html("");
if (vowels.length == 0) {
// if there's no vowels, we should have just a bare circle
/*} else if (vowels.length == 1) {
// make a fake point on the other side to connect
let connection = ns_elem("path", svg_ns);
add_class(connection, "intervowel");
let path = `M ${start_x} ${start_y} `;
path += `A ${}`
$(connection).attr("d", `M ${start_x} ${start_y} A ${major_radius} ${major_radius} 0 ${sweep} ${large} ${end_x} ${end_y}`);
connections_container.append(connection);*/
} else {
// otherwise iterate over them and connect one to the next
for (let i=0; i < vowels.length; i++) {
//connections_container.append($(ns_elem("circle", svg_ns)).attr("r",1).attr("cx",Math.cos(vowels[i].angle)*major_radius).attr("cy",major_radius*Math.sin(vowels[i].angle)));
let connection = ns_elem("path", svg_ns);
add_class(connection, "intervowel");
let first = vowels[i];
let second = vowels[(i+1)%vowels.length];
let start_x = parseFloat(first.handles[1].attr("cx"));
let start_y = parseFloat(first.handles[1].attr("cy"));
/* abandoned quadratic approach
let roundness = 0.2*Math.abs(first.angle - second.angle);
roundness = first.angle > second.angle ? 1 : 0;
let s_dx = start_x + (Math.sin(first.angle) * major_radius * roundness);
let s_dy = start_y - (Math.cos(first.angle) * major_radius * roundness);
*/
let end_x = parseFloat(second.handles[0].attr("cx"));
let end_y = parseFloat(second.handles[0].attr("cy"));
/*
let e_dx = end_x - (Math.sin(second.angle) * major_radius * roundness);
let e_dy = end_y + (Math.cos(second.angle) * major_radius * roundness);
// $(connection).attr("d", `M ${start_x} ${start_y} C ${s_dx} ${s_dy} ${e_dx} ${e_dy} ${end_x} ${end_y}`);
*/
let distance = -(second.angle - first.angle);
if (vowels.length == 1) {
distance = 1.5*Math.PI;
}
let midpoint;
let sweep = "0";
let large = "0";
if (distance < 0) {
distance += 2*Math.PI;
}
if (distance < Math.PI) {
sweep = "0"
large = "0"
} else if (distance < 2*Math.PI) {
sweep = "1"
large = "0"
}
$(connection).attr("d", `M ${start_x} ${start_y} A ${major_radius} ${major_radius} 0 ${sweep} ${large} ${end_x} ${end_y}`);
connections_container.append(connection);
}
}
}

View File

@ -1,87 +0,0 @@
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) {
return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
function ns_elem () { if (typeof arguments[0] === "undefined") { console.log('There was an error in the element assist function. Called with no valid tag name!');} var elem; if (typeof arguments[1] === "string") { elem = document.createElementNS(arguments[1], arguments[0]); for (var i=2; i<arguments.length; i+=2) { if (typeof arguments[i+1] === "undefined") { for (var key in arguments[i]) { elem.setAttribute(key, arguments[i][key]); } break; } else { for (var key in arguments[i]) { elem.setAttributeNS(arguments[i+1], key, arguments[i][key]); } } } } else { elem = document.createElement(arguments[0]); for (var i=1; i<arguments.length; i+=2) { if (typeof arguments[i+1] === "undefined") { for (var key in arguments[i]) { elem.setAttribute(key, arguments[i][key]); } break; } else { for (var key in arguments[i]) { elem.setAttributeNS(arguments[i+1], key, arguments[i][key]); } } } } return elem;}
var svg_ns = "http://www.w3.org/2000/svg";
var xlink_ns = "http://www.w3.org/1999/xlink";
function norm (vector) {
var norm = Math.sqrt(vector.x*vector.x + vector.y*vector.y);
return {x: vector.x / norm, y: vector.y / norm};
}
function len (vector) {
return Math.sqrt(vector.x*vector.x + vector.y*vector.y);
}
function sub (a, b) { return {x: a.x-b.x, y: a.y-b.y}; }
function add (a, b) { return {x: a.x+b.x, y: a.y+b.y}; }
function mul (a, b) {
if ("x" in a) { return {x: a.x*b, y: a.y*b}; }
return a*b;
}
function remove_class(element, classname) {
var classlist = $(element).attr("class");
if (classlist) {
classlist = classlist.split(" ");
}
var newclasslist = "";
for (const x in classlist) {
newclasslist += (classname != classlist[x]) ? " " + classlist[x] : "";
}
$(element).attr("class", newclasslist);
}
function add_class(element, classname) {
const classlist = $(element).attr("class");
$(element).attr("class", classlist + " " + classname);
}
function svg_from_dom(point, svg) {
const pt = svg.createSVGPoint();
pt.x = point.x;
pt.y = point.y;
return pt.matrixTransform(svg.getScreenCTM().inverse());
}
function dom_from_svg(point, svg) {
const pt = svg.createSVGPoint();
pt.x = point.x;
pt.y = point.y;
return pt.matrixTransform(svg.getScreenCTM());
}
function point_from_circle(circle) {
return {x:parseFloat(circle.attr('cx')), y:parseFloat(circle.attr('cy'))};
}
var previous_mouse_position = {x:0,y:0};
function set_transform(element, point, rotation) {
const transform_string = `translate(${point.x},${point.y}) rotate(${180*rotation/Math.PI})`;
return $(element).attr("transform", transform_string);
}
function remove(list, element) {
const index = list.indexOf(element);
if (index > -1) { // only splice array when item is found
list.splice(index, 1); // 2nd parameter means remove one item only
}
}
function appropriate_angle(point) {
return -Math.atan2(point.x, point.y);
}
function newsvg(element) {
document.createElementNS(svg_ns, element);
}
function set_loc(elem, point) { return $(elem).attr("cx", point.x).attr("cy",point.y)}
function get_global_point_at_length(svg, path, distance) {
return path.getPointAtLength(distance).matrixTransform(path.getTransformToElement(svg));
}

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-angle-1.svg"
inkscape:version="1.2.2 (b0a8486, 2022-12-01)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
id="defs2"><linearGradient
id="linearGradient837"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" /></linearGradient></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="23.206893"
inkscape:cy="1.8145307"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
units="px"
width="96px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><metadata
id="metadata5"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="opacity:1;fill:none;stroke:#000000;stroke-width:2.88527;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -14.06223,13.052879 H 39.968325 L 39.843832,63.84658"
id="glyph" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 5.6174494,12.36061 c 0.463923,-33.247813 -5.8763577,-76.39265 -5.8763577,-76.39265"
id="line_2"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 22.988704,12.280855 c 0.463923,-33.247812 5.876358,-76.083367 5.876358,-76.083367"
id="line_3"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 14.502302,11.769887 c 0.463923,-33.247813 0,-74.227676 0,-74.227676"
id="line_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -6.0317653,0.08298 C 7.2673588,-16.927529 9.7416152,-15.53576 13.298357,-12.288299"
id="path1720" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 4.9623979,-20.564231 C 18.261522,-37.57474 20.735778,-36.182971 24.292521,-32.93551"
id="path1720-7"
inkscape:label="path1720-7" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 16.096549,1.085507 C 29.395673,-15.925001 31.86993,-14.533232 35.426672,-11.285771"
id="path1720-1" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,668 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg15"
sodipodi:docname="consonant-grid.svg"
inkscape:version="1.2.2 (b0a8486, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview17"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.84096521"
inkscape:cx="366.24583"
inkscape:cy="233.06553"
inkscape:window-width="1440"
inkscape:window-height="758"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs12" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 9.7229639,53.06885 c 2.9816401,0 8.4903201,0.477127 9.5513561,-1.591892 0.758044,-1.478186 -0.101499,-3.533701 -1.452918,-3.831281 -1.907744,-0.420083 -3.993209,0.596859 -3.979732,2.871092 0.01572,2.653272 0.101073,6.670788 0.101073,6.670788"
id="path1628"
sodipodi:nodetypes="csssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 30.535204,52.770771 c 1.49082,0 4.898604,0.009 6.78458,-0.0706 1.873007,-0.07905 2.015092,-1.96225 1.832016,-2.961609 -0.227784,-1.243402 -1.754854,-1.749986 -2.752713,-0.814537 -0.91786,0.860453 -0.729652,2.493078 0.417849,2.762684 0.979201,0.230063 2.660526,0.0052 3.269624,-1.265874 0.595811,-1.243368 -0.303644,-2.800925 -1.452918,-3.073237 -1.900818,-0.450386 -3.993209,0.268373 -3.979731,2.871092 0.01374,2.653283 0.101072,6.670788 0.101072,6.670788"
id="path1628-9"
sodipodi:nodetypes="csssssssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 10.508582,80.680208 c 1.22611,-7.552835 10.152187,-7.634576 11.70526,-0.0327"
id="path879"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 30.014938,80.030009 c 1.22611,-7.552835 10.152187,-7.634576 11.70526,-0.0327 m -1.062628,0.01635 c -1.6808,-6.161123 -8.085041,-6.137978 -9.678092,0.0327"
id="path879-7"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.569512;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.84484,99.28405 c 0,0 0,5.15465 0.06411,7.91149 -0.448788,-4.90461 2.346461,-6.7056 4.891788,-5.34699 2.545328,1.35862 1.923377,5.22518 -1.474589,5.25723 h 7.437058"
id="path10"
sodipodi:nodetypes="cczcc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.545345;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 30.318715,99.115629 c 0,0 -0.05102,5.140001 0.01037,7.779851 -0.429744,-4.69648 2.297911,-6.62514 4.735228,-5.32417 2.437317,1.30096 1.841759,5.00344 -1.412015,5.03414 h 7.796778 m -10.091811,0.002 c -0.246143,-1.8626 1.015569,-4.86936 2.907095,-4.11641 1.891525,0.75296 0.714007,3.28454 -0.168828,3.96932"
id="path10-0"
sodipodi:nodetypes="cczccczc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 10.855693,135.25488 c 2.097255,-3.13325 3.234322,-5.05363 3.234322,-5.05363 0,0 -0.530631,4.42193 1.89511,4.39666 2.425741,-0.0253 3.209053,-4.32085 3.209053,-4.32085 l 3.941829,5.02836"
id="path1666"
sodipodi:nodetypes="ccscc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 30.571231,134.82058 c 2.097255,-3.13325 3.234322,-5.05363 3.234322,-5.05363 0,0 -0.530631,4.42193 1.89511,4.39666 2.425741,-0.0253 3.209053,-4.32085 3.209053,-4.32085 0,0 -0.06547,5.59699 -0.0758,7.79522 -0.01033,2.19822 3.613344,0.6317 1.743502,-1.25077 -1.869842,-1.88248 -3.316329,-0.30899 -3.335394,0.98545 -0.03352,2.27611 -2.071987,2.02145 -2.071987,2.02145"
id="path1666-3"
sodipodi:nodetypes="ccscszsc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 53.593812,53.041233 c 2.98164,0 8.49032,0.477127 9.551356,-1.591892 0.758044,-1.478186 -0.101499,-3.533701 -1.452918,-3.831281 -1.907744,-0.420083 -3.993209,0.596859 -3.979732,2.871092 0.01572,2.653272 0.101073,6.670788 0.101073,6.670788"
id="path1628-98"
sodipodi:nodetypes="csssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 74.406052,52.743154 c 1.49082,0 4.898604,0.009 6.78458,-0.0706 1.873007,-0.07905 2.015092,-1.96225 1.832016,-2.961609 -0.227784,-1.243402 -1.754854,-1.749986 -2.752713,-0.814537 -0.91786,0.860453 -0.729652,2.493078 0.417849,2.762684 0.979201,0.230063 2.660526,0.0052 3.269624,-1.265874 0.595811,-1.243368 -0.303644,-2.800925 -1.452918,-3.073237 -1.900818,-0.450386 -3.993209,0.268373 -3.979731,2.871092 0.01374,2.653283 0.101072,6.670787 0.101072,6.670787"
id="path1628-9-6"
sodipodi:nodetypes="csssssssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 54.37943,80.652589 c 1.22611,-7.55283 10.152187,-7.63457 11.70526,-0.0327"
id="path879-5"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 73.885786,80.002389 c 1.22611,-7.55283 10.152187,-7.63457 11.70526,-0.0327 m -1.062628,0.0163 c -1.6808,-6.16112 -8.085041,-6.13798 -9.678092,0.0327"
id="path879-7-7"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.569512;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 55.715688,99.256432 c 0,0 0,5.154648 0.06411,7.911488 -0.448788,-4.90461 2.346461,-6.7056 4.891788,-5.34699 2.545328,1.35862 1.923377,5.22518 -1.474589,5.25723 h 7.437058"
id="path10-6"
sodipodi:nodetypes="cczcc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.545345;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 74.189563,99.088012 c 0,0 -0.05102,5.139998 0.01037,7.779848 -0.429744,-4.69648 2.297911,-6.62514 4.735228,-5.32417 2.437317,1.30096 1.841759,5.00344 -1.412015,5.03414 h 7.796778 m -10.091811,0.002 c -0.246143,-1.8626 1.015569,-4.86936 2.907095,-4.11641 1.891525,0.75296 0.714007,3.28454 -0.168828,3.96933"
id="path10-0-2"
sodipodi:nodetypes="cczccczc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 54.726541,135.22727 c 2.097255,-3.13325 3.234322,-5.05363 3.234322,-5.05363 0,0 -0.530631,4.42193 1.89511,4.39666 2.425741,-0.0253 3.209053,-4.32085 3.209053,-4.32085 l 3.941829,5.02836"
id="path1666-7"
sodipodi:nodetypes="ccscc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 74.442079,134.79297 c 2.097255,-3.13325 3.234322,-5.05363 3.234322,-5.05363 0,0 -0.530631,4.42193 1.89511,4.39666 2.425741,-0.0253 3.209053,-4.32085 3.209053,-4.32085 0,0 -0.06547,5.59699 -0.0758,7.79522 -0.01033,2.19822 3.613344,0.6317 1.743502,-1.25077 -1.869842,-1.88248 -3.316329,-0.30899 -3.335394,0.98545 -0.03352,2.27611 -2.071987,2.02145 -2.071987,2.02145"
id="path1666-3-0"
sodipodi:nodetypes="ccscszsc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 89.460337,52.097377 c 2.98164,0 8.49032,0.477127 9.551356,-1.591892 0.758044,-1.478186 -0.101499,-3.533701 -1.452918,-3.831281 -1.907744,-0.420083 -3.993209,0.596859 -3.979732,2.871092 0.01572,2.653272 0.101073,6.670788 0.101073,6.670788"
id="path1628-3"
sodipodi:nodetypes="csssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 110.27258,51.799298 c 1.49082,0 4.8986,0.009 6.78458,-0.0706 1.873,-0.07905 2.01509,-1.96225 1.83201,-2.961609 -0.22778,-1.243402 -1.75485,-1.749986 -2.75271,-0.814537 -0.91786,0.860453 -0.72965,2.493078 0.41785,2.762684 0.9792,0.230063 2.66053,0.0052 3.26962,-1.265874 0.59581,-1.243368 -0.30364,-2.800925 -1.45291,-3.073237 -1.90082,-0.450386 -3.99321,0.268373 -3.97974,2.871092 0.0137,2.653283 0.10108,6.670788 0.10108,6.670788"
id="path1628-9-9"
sodipodi:nodetypes="csssssssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 90.245955,79.708735 c 1.22611,-7.552835 10.152185,-7.634576 11.705265,-0.0327"
id="path879-9"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.75231,79.058536 c 1.22611,-7.552835 10.15219,-7.634576 11.70526,-0.0327 m -1.06262,0.01635 c -1.6808,-6.161123 -8.08504,-6.137978 -9.6781,0.0327"
id="path879-7-9"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.569512;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 91.582213,98.312577 c 0,0 0,5.154653 0.06411,7.911493 -0.448788,-4.90461 2.346461,-6.705607 4.891788,-5.34699 2.545328,1.35861 1.923377,5.22517 -1.474589,5.25723 h 7.437058"
id="path10-1"
sodipodi:nodetypes="cczcc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.545345;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 110.05609,98.144156 c 0,0 -0.051,5.140004 0.0104,7.779854 -0.42975,-4.69648 2.29791,-6.625139 4.73523,-5.32418 2.43731,1.30097 1.84176,5.00345 -1.41202,5.03414 h 7.79678 m -10.09181,0.002 c -0.24614,-1.86259 1.01557,-4.86936 2.90709,-4.1164 1.89153,0.75295 0.71401,3.28454 -0.16882,3.96932"
id="path10-0-7"
sodipodi:nodetypes="cczccczc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 90.593066,134.28341 c 2.097255,-3.13325 3.234322,-5.05363 3.234322,-5.05363 0,0 -0.530631,4.42193 1.89511,4.39666 2.425741,-0.0253 3.209053,-4.32085 3.209053,-4.32085 l 3.941829,5.02836"
id="path1666-2"
sodipodi:nodetypes="ccscc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 110.30861,133.84911 c 2.09725,-3.13325 3.23432,-5.05363 3.23432,-5.05363 0,0 -0.53063,4.42193 1.89511,4.39666 2.42574,-0.0253 3.20905,-4.32085 3.20905,-4.32085 0,0 -0.0655,5.59699 -0.0758,7.79522 -0.0103,2.19822 3.61335,0.6317 1.7435,-1.25077 -1.86984,-1.88248 -3.31632,-0.30899 -3.33539,0.98545 -0.0335,2.27611 -2.07199,2.02145 -2.07199,2.02145"
id="path1666-3-3"
sodipodi:nodetypes="ccscszsc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 124.06839,51.782758 c 2.98164,0 8.49032,0.477127 9.55136,-1.591892 0.75804,-1.478186 -0.1015,-3.533701 -1.45292,-3.831281 -1.90775,-0.420083 -3.99321,0.596859 -3.97973,2.871092 0.0157,2.653272 0.10107,6.670788 0.10107,6.670788"
id="path1628-6"
sodipodi:nodetypes="csssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 144.88063,51.484679 c 1.49082,0 4.8986,0.009 6.78458,-0.0706 1.87301,-0.07905 2.01509,-1.96225 1.83202,-2.961609 -0.22779,-1.243402 -1.75486,-1.749986 -2.75272,-0.814537 -0.91786,0.860453 -0.72965,2.493078 0.41785,2.762684 0.9792,0.230063 2.66053,0.0052 3.26963,-1.265874 0.59581,-1.243368 -0.30365,-2.800925 -1.45292,-3.073237 -1.90082,-0.450386 -3.99321,0.268373 -3.97973,2.871092 0.0137,2.653283 0.10107,6.670788 0.10107,6.670788"
id="path1628-9-5"
sodipodi:nodetypes="csssssssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 124.85401,79.394116 c 1.22611,-7.552835 10.15219,-7.634576 11.70526,-0.0327"
id="path879-58"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 144.36037,78.743917 c 1.22611,-7.552835 10.15218,-7.634576 11.70526,-0.0327 m -1.06263,0.01635 c -1.6808,-6.161123 -8.08504,-6.137978 -9.67809,0.0327"
id="path879-7-1"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.569512;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 126.19027,97.997958 c 0,0 0,5.154652 0.0641,7.911492 -0.44879,-4.90461 2.34646,-6.705606 4.89178,-5.34699 2.54533,1.35861 1.92338,5.22517 -1.47458,5.25723 h 7.43705"
id="path10-4"
sodipodi:nodetypes="cczcc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.545345;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 144.66414,97.829537 c 0,0 -0.051,5.140003 0.0104,7.779853 -0.42974,-4.69648 2.29791,-6.625138 4.73523,-5.32418 2.43732,1.30097 1.84176,5.00345 -1.41202,5.03414 h 7.79678 m -10.09181,0.002 c -0.24614,-1.86259 1.01557,-4.86935 2.9071,-4.1164 1.89152,0.75296 0.714,3.28454 -0.16883,3.96932"
id="path10-0-71"
sodipodi:nodetypes="cczccczc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 125.20112,133.96879 c 2.09726,-3.13325 3.23432,-5.05363 3.23432,-5.05363 0,0 -0.53063,4.42193 1.89511,4.39666 2.42574,-0.0253 3.20906,-4.32085 3.20906,-4.32085 l 3.94183,5.02836"
id="path1666-38"
sodipodi:nodetypes="ccscc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 144.91666,133.53449 c 2.09726,-3.13325 3.23432,-5.05363 3.23432,-5.05363 0,0 -0.53063,4.42193 1.89511,4.39666 2.42574,-0.0253 3.20905,-4.32085 3.20905,-4.32085 0,0 -0.0655,5.59699 -0.0758,7.79522 -0.0103,2.19822 3.61334,0.6317 1.7435,-1.25077 -1.86985,-1.88248 -3.31633,-0.30899 -3.3354,0.98545 -0.0335,2.27611 -2.07198,2.02145 -2.07198,2.02145"
id="path1666-3-4"
sodipodi:nodetypes="ccscszsc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 162.45185,53.355851 c 2.98164,0 8.49032,0.477127 9.55136,-1.591892 0.75804,-1.478186 -0.1015,-3.533701 -1.45292,-3.831281 -1.90774,-0.420083 -3.99321,0.596859 -3.97973,2.871092 0.0157,2.653272 0.10107,6.670788 0.10107,6.670788"
id="path1628-8"
sodipodi:nodetypes="csssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 182.94948,53.057772 c 1.49082,0 4.89861,0.009 6.78458,-0.0706 1.87301,-0.07905 2.0151,-1.96225 1.83202,-2.961609 -0.22778,-1.243402 -1.75485,-1.749986 -2.75271,-0.814537 -0.91786,0.860453 -0.72966,2.493078 0.41785,2.762684 0.9792,0.230063 2.66052,0.0052 3.26962,-1.265874 0.59581,-1.243368 -0.30364,-2.800925 -1.45292,-3.073237 -1.90082,-0.450386 -3.99321,0.268373 -3.97973,2.871092 0.0137,2.653283 0.10107,6.670788 0.10107,6.670788"
id="path1628-9-0"
sodipodi:nodetypes="csssssssc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 161.97901,78.135641 c 1.2261,-7.552835 10.15218,-7.634576 11.70525,-0.0327"
id="path879-4"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.570064;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 181.48536,77.485442 c 1.22611,-7.552835 10.15219,-7.634576 11.70526,-0.0327 m -1.06263,0.01635 c -1.6808,-6.161123 -8.08504,-6.137978 -9.67809,0.0327"
id="path879-7-6"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.569512;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 163.31526,96.739483 c 0,0 0,5.154647 0.0641,7.911487 -0.44879,-4.904608 2.34646,-6.705601 4.89179,-5.346985 2.54533,1.358615 1.92337,5.225175 -1.47459,5.257225 h 7.43706"
id="path10-03"
sodipodi:nodetypes="cczcc" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.545345;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 181.78913,96.571062 c 0,0 -0.051,5.139998 0.0104,7.779848 -0.42974,-4.696478 2.29792,-6.625133 4.73523,-5.32417 2.43732,1.30096 1.84176,5.00344 -1.41201,5.03414 h 7.79678 m -10.09182,0.002 c -0.24614,-1.8626 1.01557,-4.869359 2.9071,-4.116404 1.89152,0.752954 0.71401,3.284534 -0.16883,3.969324"
id="path10-0-26"
sodipodi:nodetypes="cczccczc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 162.32612,132.71032 c 2.09725,-3.13325 3.23432,-5.05363 3.23432,-5.05363 0,0 -0.53063,4.42193 1.89511,4.39666 2.42574,-0.0253 3.20905,-4.32085 3.20905,-4.32085 l 3.94183,5.02836"
id="path1666-9"
sodipodi:nodetypes="ccscc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.585611;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 182.04165,132.27602 c 2.09726,-3.13325 3.23433,-5.05363 3.23433,-5.05363 0,0 -0.53064,4.42193 1.89511,4.39666 2.42574,-0.0253 3.20905,-4.32085 3.20905,-4.32085 0,0 -0.0655,5.59699 -0.0758,7.79522 -0.0103,2.19822 3.61334,0.6317 1.7435,-1.25077 -1.86984,-1.88248 -3.31633,-0.30899 -3.33539,0.98545 -0.0335,2.27611 -2.07199,2.02145 -2.07199,2.02145"
id="path1666-3-41"
sodipodi:nodetypes="ccscszsc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.485736;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 9.8026943,19.441974 h 9.0960607 l -0.021,8.551131"
id="path997" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.43739,13.602138 c 1.093421,-1.249624 5.467106,-7.0291361 7.966354,-4.4257518"
id="path2316" />
<path
style="fill:none;stroke:#000000;stroke-width:0.586;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 30.326387,18.301203 h 8.89884 l -0.0205,8.365723 m -1.25076,-0.963698 -0.0615,-6.110259 h -6.7869"
id="path997-9"
sodipodi:nodetypes="cccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 31.572983,15.804468 c 1.093421,-1.249624 5.467106,-7.029136 7.966354,-4.425752"
id="path2316-3" />
<path
style="fill:none;stroke:#000000;stroke-width:0.485736;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 53.84931,17.554259 h 9.096053 l -0.02096,8.551128"
id="path997-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 58.501698,17.259415 c 0.0781,-5.597275 0,-12.4962421 0,-12.4962421"
id="path2336" />
<path
style="fill:none;stroke:#000000;stroke-width:0.586;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 71.856051,17.671966 h 8.89884 l -0.0205,8.365723 m -1.25076,-0.963698 -0.0615,-6.110259 h -6.7869"
id="path997-9-0"
sodipodi:nodetypes="cccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 76.434961,17.574033 c 0.0781,-5.597274 0,-12.4962418 0,-12.4962418"
id="path2336-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.485736;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 88.77198,17.868877 h 9.096053 l -0.02096,8.55113"
id="path997-27" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 90.282072,13.447936 c 2.23891,-2.863722 2.655452,-2.629417 3.25423,-2.082707"
id="path1720" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 94.007378,13.616712 c 2.23891,-2.863722 2.655451,-2.629417 3.254229,-2.082707"
id="path1720-1" />
<path
style="fill:none;stroke:#000000;stroke-width:0.586;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 106.77872,16.72811 h 8.89884 l -0.0205,8.365723 m -1.25076,-0.963698 -0.0615,-6.110259 h -6.7869"
id="path997-9-2"
sodipodi:nodetypes="cccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 108.52995,13.133318 c 2.23892,-2.863722 2.65546,-2.629417 3.25424,-2.082707"
id="path1720-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 112.25526,13.302094 c 2.23891,-2.863722 2.65546,-2.629417 3.25423,-2.082707"
id="path1720-1-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.485736;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 124.00927,17.554259 h 9.09605 l -0.021,8.551128"
id="path997-3" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 127.84567,17.145316 c 0.0781,-5.597275 -0.98929,-12.8607146 -0.98929,-12.8607146"
id="path2336-38"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 130.77012,17.13189 c 0.0781,-5.597275 0.98929,-12.8086476 0.98929,-12.8086476"
id="path2336-3"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.586;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 142.64524,17.357347 h 8.89884 l -0.0205,8.365723 m -1.25076,-0.963698 -0.0615,-6.110259 h -6.7869"
id="path997-9-3"
sodipodi:nodetypes="cccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 146.40817,17.145317 c 0.0781,-5.597275 -0.98929,-12.8607158 -0.98929,-12.8607158"
id="path2336-1"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 149.33262,17.13189 c 0.0781,-5.597275 0.98929,-12.8086477 0.98929,-12.8086477"
id="path2336-3-5"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.485736;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 160.50503,17.868877 h 9.09606 l -0.021,8.551129"
id="path997-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 162.64436,16.129773 c 2.23891,-2.863722 2.65545,-2.629418 3.25423,-2.082707"
id="path1720-3" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 164.49523,12.653811 c 2.23891,-2.8637217 2.65545,-2.629417 3.25423,-2.082707"
id="path1720-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 166.36967,16.298548 c 2.23891,-2.863721 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.586;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 181.97258,17.986585 h 8.89884 l -0.0205,8.365723 m -1.25076,-0.963698 -0.0615,-6.110259 h -6.7869"
id="path997-9-9"
sodipodi:nodetypes="cccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 183.40919,14.871299 c 2.23891,-2.863722 2.65545,-2.629418 3.25423,-2.082707"
id="path1720-4" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 185.26006,11.395337 c 2.23891,-2.8637222 2.65545,-2.6294172 3.25423,-2.0827072"
id="path1720-7-3" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 187.1345,15.040074 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-6" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.887232,46.422738 c 1.09342,-1.249624 5.46711,-7.029137 7.96635,-4.425752"
id="path2316-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 34.337442,45.7935 c 1.09342,-1.249624 5.46711,-7.029136 7.96636,-4.425752"
id="path2316-3-9" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 61.266162,47.248447 c 0.0781,-5.597275 0,-12.496242 0,-12.496242"
id="path2336-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 81.401753,47.248446 c 0.0781,-5.597274 0,-12.496242 0,-12.496242"
id="path2336-7-4" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 93.046532,43.436968 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082707"
id="path1720-9" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 96.771842,43.605744 c 2.23891,-2.863722 2.65545,-2.629417 3.254228,-2.082707"
id="path1720-1-1" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 113.49674,43.751587 c 2.23892,-2.863722 2.65546,-2.629417 3.25424,-2.082707"
id="path1720-8-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 117.22205,43.920363 c 2.23891,-2.863722 2.65546,-2.629417 3.25423,-2.082707"
id="path1720-1-8-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 129.98089,46.819729 c 0.0781,-5.597275 -0.98929,-12.860715 -0.98929,-12.860715"
id="path2336-38-5"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 132.90534,46.806303 c 0.0781,-5.597275 0.98929,-12.808648 0.98929,-12.808648"
id="path2336-3-4"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 150.4311,46.81973 c 0.0781,-5.597275 -0.98929,-12.860716 -0.98929,-12.860716"
id="path2336-1-1"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 153.35555,46.806303 c 0.0781,-5.597275 0.98929,-12.808648 0.98929,-12.808648"
id="path2336-3-5-8"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 165.40882,46.118805 c 2.23891,-2.863722 2.65545,-2.629418 3.25423,-2.082707"
id="path1720-3-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 167.25969,42.642843 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082707"
id="path1720-7-35" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 169.13413,46.28758 c 2.23891,-2.863721 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-5-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 186.17365,44.860331 c 2.23891,-2.863722 2.65545,-2.629418 3.25423,-2.082707"
id="path1720-4-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 188.02452,41.384369 c 2.23891,-2.863723 2.65545,-2.629418 3.25423,-2.082708"
id="path1720-7-3-6" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 189.89896,45.029106 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-6-6" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 12.943377,73.479941 c 1.093421,-1.249624 5.467106,-7.029136 7.966354,-4.425752"
id="path2316-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 33.393589,72.850703 c 1.093421,-1.249624 5.467106,-7.029136 7.966354,-4.425752"
id="path2316-3-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 60.322304,74.30565 c 0.0781,-5.597275 0,-12.496242 0,-12.496242"
id="path2336-4" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 79.199423,74.620268 c 0.0781,-5.597274 0,-12.496242 0,-12.496242"
id="path2336-7-1" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 92.102675,70.494171 c 2.23891,-2.863722 2.65546,-2.629417 3.25423,-2.082707"
id="path1720-71" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 95.827985,70.662947 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082707"
id="path1720-1-87" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 110.35056,70.179553 c 2.23892,-2.863722 2.65546,-2.629417 3.25424,-2.082707"
id="path1720-8-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 114.07587,70.348329 c 2.23891,-2.863722 2.65546,-2.629417 3.25423,-2.082707"
id="path1720-1-8-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 128.72242,73.876932 c 0.0781,-5.597275 -0.98929,-12.860715 -0.98929,-12.860715"
id="path2336-38-7"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 131.64687,73.863506 c 0.0781,-5.597275 0.98929,-12.808648 0.98929,-12.808648"
id="path2336-3-44"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 148.22878,73.562315 c 0.0781,-5.597275 -0.98929,-12.860716 -0.98929,-12.860716"
id="path2336-1-9"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 151.15323,73.548888 c 0.0781,-5.597275 0.98929,-12.808648 0.98929,-12.808648"
id="path2336-3-5-1"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 164.15035,70.659059 c 2.23891,-2.863722 2.65545,-2.629418 3.25423,-2.082707"
id="path1720-3-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 166.00122,67.183097 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082707"
id="path1720-7-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 167.87566,70.827834 c 2.23891,-2.863721 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-5-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 183.65671,70.344441 c 2.23891,-2.863722 2.65545,-2.629418 3.25423,-2.082707"
id="path1720-4-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 185.50758,66.868479 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082707"
id="path1720-7-3-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 187.38202,70.513216 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-6-9" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.055666,97.705577 c 1.093421,-1.249624 5.467106,-7.029136 7.966354,-4.425752"
id="path2316-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 31.505878,97.076339 c 1.093421,-1.249624 5.467106,-7.029136 7.966354,-4.425752"
id="path2316-3-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 58.434593,98.531286 c 0.0781,-5.597275 0,-12.496242 0,-12.496242"
id="path2336-0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 76.367856,98.845904 c 0.0781,-5.597274 0,-12.496242 0,-12.496242"
id="path2336-7-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 90.214967,94.719807 c 2.23891,-2.863722 2.655452,-2.629417 3.25423,-2.082707"
id="path1720-48" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 93.940273,94.888583 c 2.23891,-2.863722 2.655451,-2.629417 3.254229,-2.082707"
id="path1720-1-58" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 108.46284,94.405189 c 2.23892,-2.863722 2.65546,-2.629417 3.25424,-2.082707"
id="path1720-8-3" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 112.18815,94.573965 c 2.23891,-2.863722 2.65546,-2.629417 3.25423,-2.082707"
id="path1720-1-8-0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 127.77856,98.417187 c 0.0781,-5.597275 -0.98929,-12.860715 -0.98929,-12.860715"
id="path2336-38-6"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 130.70301,98.403761 c 0.0781,-5.597275 0.98929,-12.808648 0.98929,-12.808648"
id="path2336-3-2"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 146.34106,98.417188 c 0.0781,-5.597275 -0.98929,-12.860716 -0.98929,-12.860716"
id="path2336-1-2"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 149.26551,98.403761 c 0.0781,-5.597275 0.98929,-12.808648 0.98929,-12.808648"
id="path2336-3-5-5"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 162.57725,97.401644 c 2.23891,-2.863722 2.65545,-2.629418 3.25424,-2.082707"
id="path1720-3-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 164.42812,93.925682 c 2.23892,-2.863722 2.65546,-2.629417 3.25424,-2.082707"
id="path1720-7-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 166.30257,97.570419 c 2.23891,-2.863721 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-5-7" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 183.34209,96.14317 c 2.23891,-2.863722 2.65545,-2.629418 3.25423,-2.082707"
id="path1720-4-1" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 185.19296,92.667208 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082707"
id="path1720-7-3-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 187.0674,96.311945 c 2.23891,-2.863722 2.65545,-2.629417 3.25423,-2.082706"
id="path1720-1-6-2" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.684903,127.90897 c 1.093421,-1.24963 5.467106,-7.02914 7.966354,-4.42575"
id="path2316-1" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 32.135115,127.27973 c 1.093421,-1.24962 5.467106,-7.02914 7.966354,-4.42575"
id="path2316-3-1" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 59.06383,128.73468 c 0.0781,-5.59728 0,-12.49625 0,-12.49625"
id="path2336-01" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 76.997093,129.04929 c 0.0781,-5.59727 0,-12.49624 0,-12.49624"
id="path2336-7-8" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 90.844204,124.9232 c 2.23891,-2.86372 2.655452,-2.62942 3.25423,-2.08271"
id="path1720-76" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 94.56951,125.09197 c 2.23891,-2.86372 2.655451,-2.62941 3.254229,-2.0827"
id="path1720-1-0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 109.09209,124.60858 c 2.23892,-2.86372 2.65546,-2.62942 3.25424,-2.08271"
id="path1720-8-0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 112.8174,124.77736 c 2.23891,-2.86373 2.65546,-2.62942 3.25423,-2.08271"
id="path1720-1-8-9" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 128.40781,128.62058 c 0.0781,-5.59728 -0.98929,-12.86072 -0.98929,-12.86072"
id="path2336-38-56"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 131.33226,128.60715 c 0.0781,-5.59727 0.98929,-12.80865 0.98929,-12.80865"
id="path2336-3-28"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 146.97031,128.62058 c 0.0781,-5.59728 -0.98929,-12.86072 -0.98929,-12.86072"
id="path2336-1-5"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 149.89476,128.60715 c 0.0781,-5.59727 0.98929,-12.80865 0.98929,-12.80865"
id="path2336-3-5-9"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 163.2065,127.60503 c 2.23891,-2.86372 2.65545,-2.62941 3.25423,-2.0827"
id="path1720-3-4" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 165.05737,124.12907 c 2.23891,-2.86372 2.65545,-2.62941 3.25423,-2.0827"
id="path1720-7-39" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 166.93181,127.77381 c 2.23891,-2.86372 2.65545,-2.62942 3.25423,-2.08271"
id="path1720-1-5-0" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 183.97133,126.34656 c 2.23891,-2.86372 2.65545,-2.62942 3.25423,-2.08271"
id="path1720-4-5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 185.8222,122.8706 c 2.23891,-2.86372 2.65545,-2.62942 3.25423,-2.08271"
id="path1720-7-3-63" />
<path
style="fill:none;stroke:#000000;stroke-width:0.5455;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 187.69664,126.51534 c 2.23891,-2.86373 2.65545,-2.62942 3.25423,-2.08271"
id="path1720-1-6-63" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-loop-1.svg"
inkscape:version="1.2.2 (b0a8486, 2022-12-01)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
id="defs2"><linearGradient
id="linearGradient837"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" /></linearGradient></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="19.386828"
inkscape:cy="1.4325242"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
units="px"
width="96px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><metadata
id="metadata5"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="fill:none;stroke:#000000;stroke-width:3.47853;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -17.664457,42.067215 c 17.71094207,0 50.4325,2.83413 56.735052,-9.45585 C 43.573377,23.830945 38.467691,11.621189 30.440263,9.8535595 19.108262,7.3582675 6.7206019,13.398904 6.8006582,26.907846 c 0.093399,15.760439 0.600371,39.624479 0.600371,39.624479"
id="path1628"
sodipodi:nodetypes="csssc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.808051,8.3351485 c 0.463923,-33.2478115 0,-74.2276745 0,-74.2276745"
id="line_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 15.223385,10.72699 C 15.687308,-22.520827 9.347027,-65.665664 9.347027,-65.665664"
id="line_2"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 32.594639,10.64723 c 0.463923,-33.247811 5.876358,-76.083366 5.876358,-76.083366"
id="line_3"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 4.774911,0.10037555 C 18.074035,-16.910133 20.548292,-15.518364 24.105034,-12.270903"
id="dot_2" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 15.769074,-20.546835 c 13.299124,-17.010509 15.77338,-15.61874 19.330123,-12.371279"
id="dot_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 26.903225,1.1029025 C 40.202349,-15.907605 42.676606,-14.515836 46.233348,-11.268375"
id="dot_3" /></g></svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-mound-1.svg"
inkscape:version="1.2.2 (b0a8486, 2022-12-01)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"><defs
id="defs2"><linearGradient
id="linearGradient837"
osb:paint="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" /></linearGradient><linearGradient
id="linearGradient837-5"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835-5" /></linearGradient></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="27.217961"
inkscape:cy="-0.28650485"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
units="px"
width="96px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><metadata
id="metadata5"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="fill:none;stroke:#000000;stroke-width:3.38618;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -22.108869,56.56292 C -14.825778,11.699074 38.195123,11.213533 47.420373,56.3687"
id="path879"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 12.100818,21.658612 c 0.463923,-33.247812 0,-74.227675 0,-74.227675"
id="line_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 3.5161518,23.750263 c 0.463923,-33.247813 -5.8763577,-76.39265 -5.8763577,-76.39265"
id="line_2"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 20.887406,23.670508 c 0.463923,-33.247812 5.876358,-76.083366 5.876358,-76.083366"
id="line_3"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -6.9323217,11.622726 C 6.3668024,-5.387783 8.8410588,-3.996014 12.397801,-0.748553"
id="dot_2" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 4.0618415,-9.024485 C 17.360965,-26.034994 19.835221,-24.643225 23.391964,-21.395764"
id="dot_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 15.195992,12.625253 C 28.495116,-4.385255 30.969373,-2.993486 34.526115,0.253975"
id="dot_3" /></g></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,79 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
inkscape:version="1.2.2 (b0a8486, 2022-12-01)"
sodipodi:docname="consonant-ohm-1.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
id="defs2"><linearGradient
id="linearGradient837"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" /></linearGradient></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.4709904"
inkscape:cx="19.466311"
inkscape:cy="9.9616332"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
units="px"
width="96px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><metadata
id="metadata5"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.3829;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -20.755057,14.478458 c 0,0 0,30.618622 0.380829,46.994252 C -23.040029,32.339323 -6.4362494,21.641425 8.6829945,29.711601 23.802242,37.78178 20.107856,60.74913 -0.07606316,60.93955 H 44.10006"
id="path10"
sodipodi:nodetypes="cczcc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -1.7099346,26.357829 c 0.46392,-33.24781 0,-74.22767 0,-74.22767"
id="line_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 23.690919,62.28967 C 24.154842,29.04186 17.814561,-14.10297 17.814561,-14.10297"
id="line_2"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 41.062172,62.20992 c 0.46392,-33.24781 5.87636,-76.08337 5.87636,-76.08337"
id="line_3"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -12.402587,10.675483 c 6.4949219,-7.42277 32.474607,-41.75307 47.320147,-26.28897"
id="path2316"
inkscape:label="dot_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 0.413012,17.388857 C 13.712136,0.378348 16.186393,1.770117 19.743135,5.017578"
id="path1720"
inkscape:label="dot_2" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 22.541326,18.391384 C 35.84045,1.380876 38.314707,2.772645 41.871449,6.020106"
id="path1720-1"
inkscape:label="dot_3" /></g></svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-wave-1.svg"
inkscape:version="1.2.2 (b0a8486, 2022-12-01)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
id="defs2"><linearGradient
id="linearGradient837"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" /></linearGradient></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="19.004822"
inkscape:cy="6.207605"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
units="px"
width="96px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" /><metadata
id="metadata5"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="fill:none;stroke:#000000;stroke-width:3.47853;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -22.617516,52.98049 C -10.15982,34.368995 -3.4056477,22.961948 -3.4056477,22.961948 c 0,0 -3.1519473,26.266232 11.2569551,26.116132 C 22.260209,48.92799 26.913083,23.412226 26.913083,23.412226 L 50.327549,53.28068"
id="glyph"
sodipodi:nodetypes="ccscc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8.1984075,49.71725 c 0.463923,-33.247807 0,-74.22767 0,-74.22767"
id="line_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -3.3881142,24.191844 c 0.463923,-33.247813 -5.8763577,-76.39265 -5.8763577,-76.39265"
id="line_2"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 26.891115,25.312831 c 0.463923,-33.247812 5.876358,-76.083367 5.876358,-76.083367"
id="line_3"
sodipodi:nodetypes="cc" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -7.8328782,15.216254 C 5.4662459,-1.794255 7.9405023,-0.402486 11.497244,2.844975"
id="dot_2" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 3.161285,-5.430957 c 13.299124,-17.010509 15.77338,-15.61874 19.330123,-12.371279"
id="dot_1" /><path
style="display:none;fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 14.295436,16.218781 C 27.59456,-0.791727 30.068817,0.600042 33.625559,3.847503"
id="dot_3" /></g></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="example-diacritic-5.svg"
inkscape:version="1.2.2 (b0a8486, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<linearGradient
id="linearGradient837"
inkscape:swatch="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.0815278"
inkscape:cx="61.005275"
inkscape:cy="44.77"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
units="px"
width="96px"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -9.779535,53.505783 C 3.5195891,36.495274 5.9938455,37.887043 9.5505876,41.134504"
id="path1720" />
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 1.2146282,32.858572 C 14.513752,15.848063 16.988008,17.239832 20.544751,20.487293"
id="path1720-7" />
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 12.348779,54.50831 C 25.647903,37.497802 28.12216,38.889571 31.678902,42.137032"
id="path1720-1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -56,7 +56,7 @@ table > img {
<p>It's largely decorative, has vague intentions at being phonetic, and I like using it in art! The other primary use case is writing cute notes to people I like.</p>
<h4>Here's the overview of how to read circle script:</h5>
<p>To read circle script, you start at whatever spot is marked with a little line segment outside the circle, parallel to it. If that's not present, you can usually start at the bottom. Walk around the circle widdershins (counterclockwise) and decode each shape and glyph you encounter, turning them into sounds.</p>
<img src="zagreus.svg" alt="A circle script glyph showing a single word-circle, with a small line outside the circle for a starting-point indicator." class="example-glyph"/>
<img src="/circle_script/zagreus.svg" alt="A circle script glyph showing a single word-circle, with a small line outside the circle for a starting-point indicator." class="example-glyph"/>
<p>If you're reading a whole sentence, you walk around the circle and read each word as you come across it. For each word, you read it starting from the point where it touches the circle that contains it, so you kind of reorient as you go.</p>
<p>Also, you might have noticed that some of the shapes inside word-circles have lines connecting them. Those are decorative! A lot of the component parts of the circle script writing system have places for lines to come off. These can be connected as you like, but the... proper? I guess? fancy, perhaps? way to use them is to draw connecting lines between words which are conceptually linked. You might use those lines to connect an adjective with the noun it modifies, or the names of two people who care about each other. There's a lot of room for poetic embellishment in drawing lines between words!</p>
<p>Of course, you can also just draw the lines out in a way that looks cool. :)</p>

View File

Before

Width:  |  Height:  |  Size: 370 KiB

After

Width:  |  Height:  |  Size: 370 KiB

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-angle-1.svg"
inkscape:version="1.0.1 (c497b03c, 2020-09-10)">
<defs
id="defs2">
<linearGradient
id="linearGradient837"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="58.297225"
inkscape:cy="49.565338"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="23"
inkscape:window-maximized="0"
units="px"
width="96px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="opacity:1;fill:none;stroke:#000000;stroke-width:2.88527;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -14.362416,10.777636 H 39.668139 L 39.543646,61.571338"
id="path997" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-loop-1.svg"
inkscape:version="1.0.1 (c497b03c, 2020-09-10)">
<defs
id="defs2">
<linearGradient
id="linearGradient837"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="58.297225"
inkscape:cy="49.565338"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="23"
inkscape:window-maximized="0"
units="px"
width="96px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:3.47853;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m -19.165384,42.476241 c 17.7109418,0 50.4325,2.834134 56.735052,-9.455841 C 42.07245,24.239975 36.966764,12.030219 28.939336,10.26259 17.607335,7.7672985 5.2196746,13.807934 5.2997309,27.316876 c 0.093399,15.76044 0.600371,39.62448 0.600371,39.62448"
id="path1628"
sodipodi:nodetypes="csssc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-mound-1.svg"
inkscape:version="1.0.1 (c497b03c, 2020-09-10)">
<defs
id="defs2">
<linearGradient
id="linearGradient837"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="53.426642"
inkscape:cy="52.239384"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="23"
inkscape:window-maximized="0"
units="px"
width="96px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:3.38618;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -22.108869,56.106179 C -14.825778,11.242338 38.195123,10.756797 47.420373,55.911962"
id="path879"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
inkscape:version="1.0.1 (c497b03c, 2020-09-10)"
sodipodi:docname="consonant-ohm-1.svg">
<defs
id="defs2">
<linearGradient
id="linearGradient837"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.0809783"
inkscape:cx="90"
inkscape:cy="111.42857"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="23"
inkscape:window-maximized="0"
units="px"
width="96px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.3829;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -19.24459,13.148591 c 0,0 0,30.618621 0.380829,46.994251 C -21.529562,31.009456 -4.9257819,20.311558 10.193462,28.381734 25.312709,36.45191 21.618323,59.419268 1.4344043,59.609684 H 45.610527"
id="path10"
sodipodi:nodetypes="cczcc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="consonant-wave-1.svg"
inkscape:version="1.0.1 (c497b03c, 2020-09-10)">
<defs
id="defs2">
<linearGradient
id="linearGradient837"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.2355135"
inkscape:cx="58.297225"
inkscape:cy="49.565338"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="23"
inkscape:window-maximized="0"
units="px"
width="96px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:3.47853;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -23.818258,53.583103 c 12.457696,-18.611498 19.2118685,-30.018545 19.2118685,-30.018545 0,0 -3.1519473,26.266227 11.2569551,26.116134 C 21.059467,49.5306 25.712341,24.014836 25.712341,24.014836 l 23.414466,29.868453"
id="path1666"
sodipodi:nodetypes="ccscc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 25.4 75.438"
version="1.1"
id="svg8"
sodipodi:docname="example-diacritic-4.svg"
inkscape:version="1.0.1 (c497b03c, 2020-09-10)">
<defs
id="defs2">
<linearGradient
id="linearGradient837"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop835" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.0815278"
inkscape:cx="60.769414"
inkscape:cy="44.743954"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1252"
inkscape:window-height="755"
inkscape:window-x="0"
inkscape:window-y="23"
inkscape:window-maximized="0"
units="px"
width="96px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 1.1153276,75.619445 C 1.5792506,42.371632 -4.7610301,-0.77320496 -4.7610301,-0.77320496"
id="path2336"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:3.24027;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 18.486582,75.53969 C 18.950505,42.291878 24.36294,-0.54367674 24.36294,-0.54367674"
id="path2336-3"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -1,8 +0,0 @@
<article>
<p>I think there's an interesting conceptual link between wikis and MUDs. Specifically, MUDs that allow you to build the map from within the game. The world of a MUD and the pages of a wiki form a graph of connected pages the user can move between and edit. I think that's neat.</p>
<p>I spent some time in an early relationship out of college playing around in a MUD called <a href="https://en.wikipedia.org/wiki/LambdaMOO">LambdaMOO</a>. It's built around an object-oriented programming paradigm and users have a tremendous freedom to script and reshape the world from within. I had a lot of fun building a city, and then scripting an NPC to wander around the city recording things for export.</p>
<p>LambdaMOO has its own scripting language which is closely tied with the natural language parsing of the MUD-style interface. Its functions are all "verbs" which can be (generally) called through syntax like the classic "take lantern from chest" interface, and the natural-language-first design of the script really stuck with me. I have always found myself wondering what else its design insights could be applied to.</p>
<p>Wikis are really cool, and I think we're all familiar with them. I've always been interested in how wikis kind of fulfill the original dream of hypertext, of documents with easy-to-traverse links between them. And I think it's fascinating how wikis (with their reading and clicking on links and writing new pages) correspond to MUDs (with their looking and traveling and digging new rooms) and it's always been sitting in the back of my head wondering "could those be combined?"</p>
<p>So I'm embarking on the project of writing my very first wiki software. Please set aside for a moment the fact that I'm once again building the tools for creative work rather than doing the creative work itself.</p>
<p>I'm planning on doing this with react and libSQL, at least to start. Let's go!</p>
</article>

View File

@ -1,507 +0,0 @@
<article>
<h1 id="an-asynchronous-assembly-adventure-on-the-game-boy-" class="title">an asynchronous assembly adventure, on the game boy!</h1>
<p>As a preface: This article assumes some familiarity with assembly, or a willingness to pick it up on the fly. The super ultra quick crash course on game boy sm83 assembly is this: </p>
<ul>
<li><code>ld a, b</code> means &quot;copy the value from register <code>b</code> into register <code>a</code>&quot; (note the order!)</li>
<li><code>ld [vCurrentSelection], a</code> means &quot;copy the value from register <code>a</code> into RAM at memory address <code>vCurrentSelection</code>&quot;.</li>
<li>Terms such as <code>vCurrentSelection</code> are statically allocated memory addresses, so they get substituted with raw immediate numbers at assembly time.</li>
<li>A line beginning with, for ex., <code>MenuSetup:</code> is a label, which is essentially a constant that gets replaced at compile time by the memory address of the following line</li>
<li>There are a few registers: <code>a</code>, <code>b</code>, <code>c</code>, <code>d</code>, <code>e</code>, <code>h</code>, and <code>l</code> are all 8-bit registers that can be used for various operations. you can also sometimes use the pairs <code>bc</code>, <code>de</code>, and <code>hl</code> as 16-bit registers, in addition to occasionally using the special pseudo-register-pairs <code>af</code> (<code>a</code> plus the processor&#39;s flag bits), <code>sp</code> (stack pointer), and <code>pc</code> (program counter)</li>
<li>There&#39;s also a stack. The 16-bit register <code>sp</code> can be set and read, and interacts with the <code>push</code>, <code>pop</code>, <code>call</code>, and <code>ret</code> instructions.</li>
<li>Most of the time I stick to passing variables through registers - a subroutine call requires loading values into the appropriate registers, then executing a <code>call</code> instruction to jump to the subroutine. Then the subroutine uses the <code>ret</code> instruction to return to the call site.</li>
<li><code>; semicolons for comments</code></li>
</ul>
<p>Picture it: You&#39;re writing a program for the nintendo game boy, in raw assembly, as you do. You want to initialize the menu screen, by setting a variable and loading graphical data to the screen. You&#39;ve got two subroutines to use. <code>CopyTilesToMap</code> is used to copy a tile map (a list of tile IDs) from the ROM into the dedicated screen memory, so that the right tiles will be displayed on the screen. <code>CopyRange</code> is used to copy the pixel data from the ROM into the specific spot in VRAM so the game boy knows how to draw the tile IDs we copied before. They just walk over a range of bytes, copying them one by one to their specified destination. It&#39;s most important to know that these are functions we use to copy a range of data into VRAM.</p>
<p>So, you write this code:</p>
<pre><code>MenuSetup:
; <span class="hljs-built_in">set</span> up whatever variables <span class="hljs-keyword">and</span> memory <span class="hljs-keyword">the</span> screen needs
ld <span class="hljs-keyword">a</span>, <span class="hljs-number">0</span>
ld [vCurrentSelection], <span class="hljs-keyword">a</span>
; <span class="hljs-built_in">load</span> <span class="hljs-keyword">the</span> tile IDs <span class="hljs-keyword">into</span> <span class="hljs-keyword">the</span> background map
ld hl, Menu.UITileMap ; source tile map location <span class="hljs-keyword">in</span> rom
ld de, _SCRN0 ; destination is <span class="hljs-keyword">the</span> <span class="hljs-built_in">start</span> <span class="hljs-keyword">of</span> <span class="hljs-keyword">the</span> screen <span class="hljs-keyword">in</span> memory
ld b, <span class="hljs-number">18</span> ; height (<span class="hljs-keyword">in</span> tiles)
ld c, <span class="hljs-number">20</span> ; width (<span class="hljs-keyword">in</span> tiles) (takes up <span class="hljs-keyword">the</span> full screen)
call CopyTilesToMap
; <span class="hljs-built_in">load</span> <span class="hljs-keyword">the</span> data <span class="hljs-keyword">for</span> all <span class="hljs-keyword">the</span> tiles used <span class="hljs-keyword">for</span> drawing <span class="hljs-keyword">the</span> screen
ld hl, Menu.UITileData ; source
ld de, _VRAM + $<span class="hljs-number">1000</span> ; destination
ld bc, Menu.UITileDataEnd - Menu.UITileData ; <span class="hljs-built_in">length</span> <span class="hljs-keyword">of</span> data
call CopyRange
ret
</code></pre><p>It&#39;s simple enough. First you set up whatever variables you need for the screen, then you use <code>CopyTilesToMap</code> to load the menu&#39;s tilemap, then you use <code>CopyRange</code> to load the data for what those tiles should look like. Seems good, right?</p>
<p>Wrong. The problem comes up immediately: The game boy CPU can&#39;t write to or read from graphics memory while the screen is drawing. You have to wait for the v-blank period, an extra ten scanlines&#39; worth of processor time between every frame. Only during that time are you given access to load data into VRAM.</p>
<p>If you&#39;re like me, your first thought is &quot;Okay, I&#39;ll make new versions of <code>CopyTilesToMap</code> and <code>CopyRange</code> that will safely restrict their activity to v-blank.&quot; They&#39;ll check between each byte transfer whether it&#39;s safe to copy data to VRAM, and otherwise they&#39;ll spin their wheels.</p>
<p>So if <code>CopyRange</code> looks like this:</p>
<pre><code>CopyRange:
<span class="hljs-keyword">if</span> <span class="hljs-keyword">the</span> <span class="hljs-built_in">length</span> <span class="hljs-built_in">to</span> copy is <span class="hljs-literal">zero</span>, <span class="hljs-literal">return</span>
copy <span class="hljs-keyword">the</span> <span class="hljs-keyword">byte</span> <span class="hljs-keyword">at</span> <span class="hljs-keyword">the</span> source address <span class="hljs-built_in">to</span> <span class="hljs-keyword">the</span> destination
step <span class="hljs-keyword">the</span> source address forward
step <span class="hljs-keyword">the</span> destination address forward
decrease <span class="hljs-keyword">the</span> <span class="hljs-built_in">length</span> <span class="hljs-built_in">to</span> copy
<span class="hljs-keyword">and</span> jump <span class="hljs-built_in">to</span> CopyRange
</code></pre>
<p>Then your new <code>CopyRangeSafely</code> will look like this:</p>
<pre><code>CopyRangeSafely:
<span class="hljs-keyword">if</span> <span class="hljs-keyword">the</span> <span class="hljs-built_in">length</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">copy</span> <span class="hljs-keyword">is</span> zero, <span class="hljs-literal">return</span>
<span class="hljs-keyword">copy</span> <span class="hljs-keyword">the</span> byte <span class="hljs-keyword">at</span> <span class="hljs-keyword">the</span> source address <span class="hljs-keyword">to</span> <span class="hljs-keyword">the</span> destination
step <span class="hljs-keyword">the</span> source address forward
step <span class="hljs-keyword">the</span> destination address forward
decrease <span class="hljs-keyword">the</span> <span class="hljs-built_in">length</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">copy</span>
.checkIfDone:
check <span class="hljs-keyword">if</span> <span class="hljs-keyword">the</span> game boy <span class="hljs-keyword">is</span> <span class="hljs-keyword">in</span> v-blank.
<span class="hljs-keyword">if</span> <span class="hljs-keyword">it</span> <span class="hljs-keyword">is</span> <span class="hljs-keyword">in</span> v-blank, jump <span class="hljs-keyword">to</span> CopyRangeSafely
<span class="hljs-keyword">if</span> <span class="hljs-keyword">it</span>'s <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> v-blank, wait a few cycles <span class="hljs-keyword">and</span> jump <span class="hljs-keyword">to</span> .checkIfDone
</code></pre><p>Checking for v-blank ultimately increases the number of instructions by at least 30%, between juggling registers, making fetches, and making comparisons. Much worse, this solution freezes up the entire handheld in a busy loop until it&#39;s done copying! You struggle onwards for a bit before realizing this isn&#39;t tenable at all. So you sit and think.</p>
<p>If only there were some way to write the same code for v-blank-safe memory transfers as for other memory transfers. This <code>CopyRangeSafely</code> function is really eating at you. If there were some way to take any block of code, and guarantee that it only executes during v-blank, and pauses otherwise. What you&#39;d really like is to be able to run game update code every frame outside of v-blank, and let your transfer code run during every v-blank until it&#39;s complete. </p>
<p>But that sounds like having two different threads. Multi-threaded code is hard, right? And the game boy processor is famously weak, it doesn&#39;t even have a modulo instruction! And this is just a hobby project. And you have no experience writing OS-level code... </p>
<p>But there&#39;s no harm in trying, eh? Maybe you could start by chipping away at the efficiency at least. So, what have you got available to you?</p>
<h2 id="a-first-attempt">a first attempt</h2>
<p>The &quot;safe&quot; versions of the copy functions were slower, in part because they have to check - once for every byte copied - whether it&#39;s safe to do so. Is there any way you could get the processor to start and stop that behavior on its own, without having to do the checks yourself? Does the game boy processor even have a way to do that?</p>
<p>Well, the game boy has interrupts. Under certain conditions, as the processor is running, it will execute a <code>call</code> to a hard-coded address determined by the conditions - <code>$0060</code> if the joypad was pressed, <code>$0058</code> if a byte was received from the serial port, <code>$0050</code> if a timer rolled over... These addresses have just enough space to call or jump somewhere else to react to the interrupt. It just so happens that you can configure one of these interrupts to happen when the game boy enters v-blank, and another for when it starts drawing line zero and thus exits v-blank. It also supports a <code>halt</code> instruction, which suspends the CPU until an interrupt fires.</p>
<p>Maybe you can use these somehow.</p>
<p>For a start, you can use the interrupt to at least stop needing to check when the transfer is safe. Here&#39;s how it works: You <code>halt</code> execution. Then, configure the v-blank interrupt to wake up the processor and continue what it&#39;s doing. Then it&#39;ll run some of your code. Then another interrupt, when the device exits v-blank and it&#39;s no longer safe to copy, will execute another <code>halt</code> instruction, to put it to sleep until the next v-blank period.</p>
<p>Maybe that could work. Then you could at least get rid of the slow and ugly <code>CopyRangeSafe</code> functions. So how does this look?</p>
<p>We want to run the <code>MenuSetup</code> subroutine from above. The changes here are simple:</p>
<pre><code>MenuSetup:
; <span class="hljs-built_in">set</span> up some statically allocated screen variables
ld <span class="hljs-keyword">a</span>, <span class="hljs-number">0</span>
ld [vCurrentSelection], <span class="hljs-keyword">a</span>
+ call SetUpInterrupts ; turn <span class="hljs-keyword">on</span> <span class="hljs-title">interrupts</span> <span class="hljs-title">and</span> <span class="hljs-title">set</span> <span class="hljs-title">up</span> <span class="hljs-title">the</span> <span class="hljs-title">handler</span>
+ halt ; <span class="hljs-built_in">wait</span> <span class="hljs-keyword">until</span> <span class="hljs-keyword">the</span> next interrupt
ld hl, Menu.UITileMap ; tile map location <span class="hljs-keyword">in</span> rom
ld de, _SCRN0 ; draw <span class="hljs-keyword">it</span> starting <span class="hljs-keyword">at</span> <span class="hljs-number">0</span>,<span class="hljs-number">0</span> <span class="hljs-keyword">on</span> <span class="hljs-title">the</span> <span class="hljs-title">screen</span>
ld b, <span class="hljs-number">18</span> ; height (<span class="hljs-keyword">in</span> tiles)
ld c, <span class="hljs-number">20</span> ; width (<span class="hljs-keyword">in</span> tiles) (takes up <span class="hljs-keyword">the</span> full screen)
call CopyTilesToMap
; <span class="hljs-built_in">load</span> <span class="hljs-keyword">the</span> data <span class="hljs-keyword">for</span> all <span class="hljs-keyword">the</span> tiles used <span class="hljs-keyword">for</span> drawing <span class="hljs-keyword">the</span> screen
ld hl, Menu.UITileData ; source
ld de, _VRAM + $<span class="hljs-number">1000</span> ; destination
ld bc, Menu.UITileDataEnd - Menu.UITileData ; <span class="hljs-built_in">length</span> <span class="hljs-keyword">of</span> data
call CopyRange
+ call TearDownInterrupts
ret
</code></pre><p><code>SetUpInterrupts</code> will clear the interrupt flags, enable the specific interrupts we want, enable interrupts globally, set the STAT interrupt to fire on scanline zero, and do whatever busywork needs to happen to connect the interrupt vectors (those hardcoded ROM addresses for each interrupt) to the code that you&#39;re writing. The <a href="https://gbdev.io/pandocs/Interrupts.html">pandocs</a> will help you here. Likewise, <code>TearDownInterrupts</code> will disable the two interrupt handlers we&#39;re using to restore the regular flow of code.</p>
<p>Then you&#39;ll have the two interrupt handlers, one for when we&#39;re entering v-blank and one for when we&#39;re exiting, which get hooked up by <code>SetUpInterrupts</code>. After that <code>halt</code> instruction, the processor is going to wait until an interrupt happens. What should the interrupt handlers look like?</p>
<p>When the device enters the v-blank period, nothing needs to happen, because the interrupt firing at all will wake the processor up. So it just needs to enable interrupts again (the processor turns off interrupts globally when it starts handling an interrupt) and return the processor to its previous work.</p>
<pre><code>VBlankInterrupt:
ei ; enable interrupts
ret ; <span class="hljs-literal">return</span> <span class="hljs-built_in">to</span> wherever <span class="hljs-keyword">the</span> processor was <span class="hljs-keyword">before</span> <span class="hljs-keyword">the</span> interrupt fired
</code></pre><p>When it reaches scanline zero, the <a href="https://gbdev.io/pandocs/Interrupt_Sources.html#int-48--stat-interrupt">configurable STAT interrupt</a> (set up in <code>SetUpInterrupts</code>) fires. It should enable interrupts, then execute a <code>halt</code> instruction to put the CPU to sleep.</p>
<pre><code>STATInterrupt:
ei ; enable interrupts
halt ; sleep processor <span class="hljs-keyword">until</span> next interrupt
ret ; <span class="hljs-literal">return</span> <span class="hljs-built_in">to</span> wherever <span class="hljs-keyword">the</span> processor was <span class="hljs-keyword">before</span> <span class="hljs-keyword">the</span> interrupt fired
</code></pre><p>So the execution goes like this:</p>
<ol>
<li>Our <code>MenuSetup</code> routine runs the first bit, doing synchronous normal code, setting up variables, and such. Everything for which it doesn&#39;t need to touch VRAM.</li>
<li>It calls <code>SetUpInterrupts</code>, which does the busywork of setting and enabling the v-blank interrupt and STAT interrupt.</li>
<li>It then halts, which puts the processor to sleep until...</li>
<li>The v-blank interrupt we set up in step 2 fires, waking up the processor. It immediately returns...</li>
<li>And starts executing the code in <code>MenuSetup</code> that touches VRAM. That code runs for a bit until...</li>
<li>When the game boy starts drawing scanline zero of the screen, the STAT interrupt we also set up in step 2 fires, which executes a <code>halt</code> to put the processor to sleep, until...</li>
<li>The v-blank interrupt fires again, waking up the processor. It immediately returns...</li>
<li>Continuing execution of the <code>MenuSetup</code> code from where we left off in step 5, until...</li>
<li>The STAT interrupt fires, putting the processor to sleep until...</li>
<li>The v-blank interrupt fires again, waking up the processor. It returns...</li>
<li>Continuing execution of the <code>MenuSetup</code> code from where it left off in step 8, until...</li>
<li><em>And so on!</em></li>
</ol>
<p>So what did all this (a few helper functions and two interrupts) net you? Well, now you don&#39;t need to have special <code>CopyRangeSafely</code> functions, and it&#39;ll run much faster without the overhead of checking all the time whether it&#39;s safe. I think we can feel pretty good about that! </p>
<p>But most of all, you&#39;ve learned a bit about the idea of using interrupts to enter and exit a specific &quot;safe&quot; period in the game loop. We&#39;re using the v-blank interrupt to enable our code to run, and the STAT interrupt to take control away and stop it again, so that the code that needs to run exclusively in v-blank can look the same as code that can run whenever, without changes!</p>
<h2 id="a-second-attempt">a second attempt</h2>
<p>But that first attempt doesn&#39;t solve the problem of interleaving other code with the <code>CopyRange</code> operation. Your program will now sleep whenever it&#39;s not able to copy data. But you wonder: is it possible to use that time, when the program is sleeping, to run something else at the same time? To use the interrupts to switch between two simultaneous &quot;threads&quot; of code being executed?</p>
<p>Well, what&#39;s the state of the processor at any given moment? Ignore the RAM for now, which should be shared between threads.</p>
<ol>
<li>There&#39;s the registers it uses to pass information around - <code>af</code>, <code>bc</code>, <code>de</code>, and <code>hl</code>. </li>
<li>There&#39;s the program counter <code>pc</code>, which indicates the specific line being executed. </li>
<li>And there&#39;s the stack pointer <code>sp</code> which holds the address of the variable stack of data used to store call and return locations.
Could we somehow... keep two copies of all of those? You could certainly define dedicated memory locations to store all the registers. </li>
</ol>
<p>The issue is the stack and the program counter. The stack is used for holding some data (<code>push</code> and <code>pop</code> will put register pairs on and take them off) and for tracking the call stack - used to remember where to resume when you <code>ret</code>urn from a subroutine, or from an interrupt.</p>
<p>In my experiments (particular to my code) the call stack only got four or five calls deep, and I wasn&#39;t ever putting much data on it. So it&#39;d be easy enough to allocate space for a second call stack, and then freely set the stack pointer <code>sp</code> to whatever you want.</p>
<p>The program counter isn&#39;t generally manipulated directly, except by <code>jp</code> (jump), <code>call</code>, and <code>ret</code> instructions. Pretend we can just read and write from and to it.</p>
<p>Maybe you could do as before, and write a v-blank handler to swap all of that context out, and a STAT handler to swap back... That might work! First, some starting assumptions:</p>
<p>Your goal is to be able to say &quot;hey, processor, run this other subroutine whenever it&#39;s safe to do so&quot;, and then the processor will handle scheduling its execution while you can go on and continue doing other stuff. You&#39;ll have to write a function to set up the interrupts to execute our asynchronous code. The point of this is to be able to write normal-looking code, so we&#39;ll make a new function <code>RunInVBlank</code> that will execute a specified subroutine (passed in <code>hl</code>) in the &quot;safe&quot; part of each frame. </p>
<p>So your new <code>MenuSetup</code> subroutine would break up into a part that runs immediately:</p>
<pre><code>MenuSetup:
; <span class="hljs-built_in">do</span> whatever synchronous stuff we want <span class="hljs-built_in">to</span> <span class="hljs-built_in">do</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">the</span> setup
; like initializing variables <span class="hljs-keyword">for</span> this screen.
ld <span class="hljs-keyword">a</span>, <span class="hljs-number">0</span>
ld [vCurrentSelection], <span class="hljs-keyword">a</span> ; example!
+ ld hl, MenuSetupVRAMPart ; pass <span class="hljs-keyword">the</span> subroutine we want <span class="hljs-keyword">as</span> <span class="hljs-keyword">an</span> argument
+ call RunInVBlank
ret
</code></pre><p>And a second part, which gets scheduled to only be running when VRAM is safe to access:</p>
<pre><code><span class="hljs-symbol">MenuSetupVRAMPart:</span>
<span class="hljs-keyword">ld</span> hl, Menu.UITileMap <span class="hljs-comment">; tile map location in rom</span>
<span class="hljs-keyword">ld</span> de, _SCRN0 <span class="hljs-comment">; draw it starting at 0,0 on the screen</span>
<span class="hljs-keyword">ld</span> b, <span class="hljs-number">18</span> <span class="hljs-comment">; height (in tiles) </span>
<span class="hljs-keyword">ld</span> c, <span class="hljs-number">20</span> <span class="hljs-comment">; width (in tiles) (takes up the full screen)</span>
<span class="hljs-keyword">call</span> CopyTilesToMap
<span class="hljs-comment">; load the data for all the tiles used for drawing the screen</span>
<span class="hljs-keyword">ld</span> hl, Menu.UITileData <span class="hljs-comment">; source</span>
<span class="hljs-keyword">ld</span> de, _VRAM + $<span class="hljs-number">1000</span> <span class="hljs-comment">; destination</span>
<span class="hljs-keyword">ld</span> bc, Menu.UITileDataEnd - Menu.UITileData <span class="hljs-comment">; length of data</span>
<span class="hljs-keyword">call</span> CopyRange
<span class="hljs-keyword">ret</span>
</code></pre><p>Then the normal flow of execution is that <code>MenuSetup</code> does its stuff, updates variables, calls <code>RunInVBlank</code> to schedule its subroutine for execution in v-blank, and then returns to do whatever else the main game loop wants done. When the v-blank period arrives, an interrupt will fire and switch contexts to execute a bit of the <code>MenuSetupVRAMPart</code>. When the v-blank period ends, another interrupt fires and context switches back to the main game loop, and things continue in this way, switching back and forth between the &quot;main thread&quot; and the execution of <code>MenuSetupVRAMPart</code>.</p>
<p>Now it&#39;s a matter of figuring out what that mysterious <code>RunInVBlank</code> subroutine will do. First off, you need to keep a separate copy of our registers. Define some static memory addresses wherever you do that: <code>vAsyncAF</code>, <code>vAsyncBC</code>, <code>vAsyncDE</code>, <code>vAsyncHL</code>, <code>vAsyncSP</code>, <code>vAsyncPC</code>. Next up, the stack: by default, the stack grows down from <code>$FFFF</code>. If your async stack starts at <code>$FFBF</code>, that will leave 64 bytes out of the special HRAM memory region (<code>$FF80</code>-<code>$FFFF</code>) for each stack. (Note: If we wanted to, we could configure our stacks to be anywhere in RAM, which would enable them to grow much bigger. I opted not to do this, because I&#39;m a silly goose.)</p>
<p><code>RunInVBlank</code> needs to set up that parallel execution environment (give all those registers starting values), and then enable the handler for entering v-blank. (Note: I&#39;m also going to take some liberties and pretend there are a few extra instructions the game boy doesn&#39;t actually support, like using <code>ld</code> to put two-byte values into memory addresses. Rewriting this to use the available asm instructions is a pain but it&#39;s doable.)</p>
<pre><code><span class="hljs-symbol">RunInVBlank:</span>
<span class="hljs-comment">; store starting values for the registers</span>
<span class="hljs-keyword">ld</span> [vAsyncAF], af
<span class="hljs-keyword">ld</span> [vAsyncBC], bc
<span class="hljs-keyword">ld</span> [vAsyncDE], de
<span class="hljs-keyword">ld</span> [vAsyncHL], hl
<span class="hljs-comment">; store starting value for the stack pointer</span>
<span class="hljs-keyword">ld</span> [vAsyncSP], $FFBF
<span class="hljs-comment">; store starting value for the program counter, passed as arg in hl</span>
<span class="hljs-keyword">ld</span> [vAsyncPC], hl
<span class="hljs-comment">; enable v-blank interrupt</span>
<span class="hljs-keyword">ld</span> hl, rIE <span class="hljs-comment">; target the interrupt enable flag</span>
<span class="hljs-keyword">set</span> B_IE_VBLANK, [hl] <span class="hljs-comment">; set the bit to enable the v-blank interrupt</span>
ei <span class="hljs-comment">; enable interrupts globally</span>
<span class="hljs-keyword">ret</span>
</code></pre><p>So you store the starting values for the registers, you set the starting value for the stack pointer, and you store the program counter we want to start from. Ezpz! Then what&#39;s the v-blank handler look like? It&#39;s gotta stash all the context info from the main thread, and unstash all the context info for the async thread.</p>
<pre><code><span class="hljs-symbol">VBlankInterrupt:</span>
<span class="hljs-comment">; store current values of the registers</span>
<span class="hljs-keyword">ld</span> [vMainAF], af <span class="hljs-comment">; stash af registers</span>
<span class="hljs-keyword">ld</span> [vMainBC], bc
<span class="hljs-keyword">ld</span> [vMainDE], de
<span class="hljs-keyword">ld</span> [vMainHL], hl
<span class="hljs-comment">; store current value of the stack pointer</span>
<span class="hljs-keyword">ld</span> [vMainSP], sp
<span class="hljs-comment">; store the current program counter</span>
<span class="hljs-keyword">ld</span> [vMainPC], pc <span class="hljs-comment">; hmm....</span>
<span class="hljs-comment">; get last values of the async registers</span>
<span class="hljs-keyword">ld</span> af, [vAsyncAF]
<span class="hljs-keyword">ld</span> bc, [vAsyncBC]
<span class="hljs-keyword">ld</span> de, [vAsyncDE]
<span class="hljs-keyword">ld</span> hl, [vAsyncHL]
<span class="hljs-comment">; get last value of the stack pointer</span>
<span class="hljs-keyword">ld</span> sp, [vAsyncSP]
<span class="hljs-comment">; get last program counter</span>
<span class="hljs-keyword">ld</span> pc, [vAsyncPC] <span class="hljs-comment">; hmm...</span>
<span class="hljs-keyword">ret</span>
</code></pre><p>And then you can write a <code>STATInterrupt</code> that should do the inverse, storing the async registers and fetching the main registers. These are context-switching interrupts! When the interrupt fires to signal the game boy is in the &quot;safe&quot; period, it switches context from main to async, and when the interrupt fires to signal we&#39;re out of the safe period, it switches context back.</p>
<p>But there&#39;s a big problem: we&#39;ve been very cavalier with the program counter. On the line where I&#39;ve commented <code>hmm...</code> we read from the program counter to get the state of the main thread. If <code>VBlankInterrupt</code> tries to store the current address of execution, it&#39;s not going to be where to resume the main thread - it&#39;s going to be inside <code>VBlankInterrupt</code>! Ditto for the <code>hmm!</code> line - writing directly to the program counter would mess up all sorts of things! When you want to interact with the program counter, you really need to use <code>jp</code> or <code>call</code> or <code>ret</code> instructions.</p>
<p>One more try.</p>
<h2 id="a-third-attempt">a third attempt</h2>
<p>The problems with <code>pc</code> are big. The approach above falls apart completely and is a huge pain to implement. Fret not, though, for we are valiant. The issue is in getting and storing information about where the processor is currently executing - you can&#39;t just read and write <code>pc</code>willy-nilly. But how does the processor handle that information? Well, it puts it on the stack! It&#39;s time to talk about the call stack, how it interacts with interrupts, and you.</p>
<p>The call stack works like this: the stack pointer <code>sp</code> always contains a memory address. It&#39;s initialized to <code>$FFFE</code>, the second-to-last memory address, at processor start-up. Whenever a <code>push</code> instruction is executed (<code>push hl</code>), the stack pointer <code>sp</code> is decreased by two bytes, and the register pair is copied into the new location <code>sp</code> points to (like <code>ld [sp], hl</code>). When a <code>pop</code> instruction is executed (<code>pop hl</code>), the memory at <code>sp</code> is copied into the argument, (<code>ld hl, [sp]</code>) and <code>sp</code> is <em>increased</em> by two bytes. Similarly, the <code>call Subroutine</code> instruction effectively pushes the address of the next instruction to execute (after <code>call Subroutine</code>) onto the stack, and jumps to <code>Subroutine</code>; <code>ret</code> likewise pops an address off the stack and jumps to it. </p>
<p>Tragically, talking about the &quot;top&quot; and &quot;bottom&quot; of the stack, which is normally quite a sensible metaphor for a stack (you can only interact with the top of the stack, and change what the stack holds by putting things on or taking them off), is now hopelessly confusing due to the stack growing backwards, and thus confusion about whether we talk about the &quot;top&quot; as the end or the beginning of the region of memory, which has opposite sense from the end or beginning of the values placed on the stack, and before you know it you&#39;re going <code>@_@</code> and are totally lost. </p>
<p>I&#39;m going to adopt my own convention. When I talk about the stack, I&#39;ll try to refer to the &quot;earliest&quot; and &quot;latest&quot; values: the &quot;earliest&quot; value on the stack (as an organization of information) is the first value that was pushed there chronologically. The &quot;latest&quot; is the last value that was pushed there. If you pop data off the stack, you&#39;re getting the latest value, and the stack shortens; if you push data on the stack you&#39;re changing the latest and the stack grows. If you executed a <code>push $BEEF</code> and then <code>push $B0DE</code> and then <code>push $1337</code>, the stack would look like this, listed from earliest to latest:</p>
<table>
<thead>
<tr>
<th>stack</th>
</tr>
</thead>
<tbody>
<tr>
<td>[ whatever was on the stack previously ]</td>
</tr>
<tr>
<td><code>$BEEF</code></td>
</tr>
<tr>
<td><code>$B0DE</code></td>
</tr>
<tr>
<td><code>$1337</code></td>
</tr>
<tr>
<td>{ <strong>stack ends here</strong> }</td>
</tr>
</tbody>
</table>
<p>In this diagram I&#39;ve written in [ brackets ] to suggest some amount of data <em>previously</em> pushed onto the stack, and in { <strong>curly braces</strong> } a placeholder to indicate the end of the stack, the location <code>sp</code> points to, where new data will be pushed to. Most of the time, though, the stack is used as a call stack. When you execute a <code>call Subroutine</code> instruction, the address of the next line gets pushed onto the stack, and the processor jumps to <code>Subroutine</code>. When you execute a <code>ret</code> instruction, that address gets popped off the stack, and the processor jumps to it. So the stack stores a record of all the locations in memory it should return to!</p>
<p>So you have a stack pointer <code>sp</code> representing the end of a stack in memory. It holds data you put there, as well as the &quot;call stack&quot; featuring the locations successive <code>ret</code> instructions should return to. How does it interact with interrupts, though? Well, when an interrupt is handled, the processor effectively executes a <code>call InterruptHandler</code> instruction - it pushes the next address to execute onto the stack, and jumps to <code>InterruptHandler</code>. Then, when that code does a <code>ret</code>, it will restore computation from where we were before the interrupt.</p>
<p>Here&#39;s a theoretical interrupt we might write, and a marked line to pay attention to:</p>
<pre><code><span class="hljs-symbol">VBlankInterrupt:</span>
<span class="hljs-keyword">nop </span><span class="hljs-comment">; do nothing</span>
<span class="hljs-comment">;;;;;; What's the stack look like here?</span>
<span class="hljs-keyword">ei </span><span class="hljs-comment">; enable interrupts</span>
ret
</code></pre><p>At the marked line, the stack has the following stuff on it, from earliest to latest:</p>
<table>
<thead>
<tr>
<th>call stack</th>
</tr>
</thead>
<tbody>
<tr>
<td>[ ... ]</td>
</tr>
<tr>
<td>[ various stuff from before the interrupt fires ]</td>
</tr>
<tr>
<td>[ more of that stuff ... ]</td>
</tr>
<tr>
<td>the address that was being executed right before the interrupt fires, placed on the stack by the CPU when reacting to the interrupt.</td>
</tr>
<tr>
<td>{ <strong>stack ends here</strong> }</td>
</tr>
</tbody>
</table>
<p>Here I&#39;ve written in [ brackets ] some placeholder data, which could be <code>call</code> stack data, or could be data that was <code>push</code>ed onto the stack previously. But &quot;the address that was being executed&quot; got placed on the stack by the processor&#39;s interrupt handler.</p>
<p>When the <code>ret</code> is executed in the <code>VBlankInterrupt</code>, it pops the last value off the stack, and jumps execution to that address. But if, perchance, the last value on the stack was a different one than when this interrupt started, it would jump to a totally new spot...</p>
<p>Bear with me now: suppose you have two stacks. The &quot;main thread stack&quot; is currently in use, and, elsewhere in memory, there is an &quot;async thread stack&quot; which holds an address the async thread is executing. They look like this before the interrupt fires, from earliest to latest:</p>
<table>
<thead>
<tr>
<th>main thread stack</th>
<th>async thread stack</th>
</tr>
</thead>
<tbody>
<tr>
<td>[ various data ... ]</td>
<td>[ various data ... ]</td>
</tr>
<tr>
<td>{ <strong>stack ends here</strong> }</td>
<td>async thread program counter</td>
</tr>
</tbody>
</table>
<p>When the interrupt fires, it pushes the main thread&#39;s program counter onto the stack:</p>
<table>
<thead>
<tr>
<th>main thread stack</th>
<th>async thread stack</th>
</tr>
</thead>
<tbody>
<tr>
<td>[ various data ... ]</td>
<td>[ various data ... ]</td>
</tr>
<tr>
<td>main thread program counter</td>
<td>async thread program counter</td>
</tr>
<tr>
<td>{ <strong>stack ends here</strong> }</td>
</tr>
</tbody>
</table>
<p>What if the interrupt now swapped our stack pointer from the main stack to the async stack?</p>
<table>
<thead>
<tr>
<th>main thread stack</th>
<th>async thread stack</th>
</tr>
</thead>
<tbody>
<tr>
<td>[ various data ... ]</td>
<td>[ various data ... ]</td>
</tr>
<tr>
<td>main thread program counter</td>
<td>async thread program counter</td>
</tr>
<tr>
<td></td>
<td>{ <strong>stack ends here</strong> }</td>
</tr>
</tbody>
</table>
<p>Then at the end, it would <code>ret</code> and resume execution in the async thread.</p>
<table>
<thead>
<tr>
<th>main thread stack</th>
<th>async thread stack</th>
</tr>
</thead>
<tbody>
<tr>
<td>[ various data ... ]</td>
<td>[ various data ... ]</td>
</tr>
<tr>
<td>main thread program counter</td>
<td>{ <strong>stack ends here</strong> }</td>
</tr>
</tbody>
</table>
<p>That&#39;s very simple! All you need in your handler to achieve this is the following:</p>
<pre><code><span class="hljs-symbol">VBlankInterrupt:</span>
<span class="hljs-comment">; save main thread stack pointer</span>
ld [vMainSP], <span class="hljs-built_in">sp</span>
<span class="hljs-comment">; load side thread stack pointer</span>
ld <span class="hljs-built_in">sp</span>, [vAsyncSP]
<span class="hljs-keyword">ei</span>
ret
</code></pre><p>and then a matching STAT interrupt handler:</p>
<pre><code><span class="hljs-symbol">STATInterrupt:</span>
<span class="hljs-comment">; load side thread stack pointer</span>
ld <span class="hljs-built_in">sp</span>, [vAsyncSP]
<span class="hljs-comment">; save main thread stack pointer</span>
ld [vMainSP], <span class="hljs-built_in">sp</span>
<span class="hljs-keyword">ei</span>
ret
</code></pre><p>This switches the stack context beautifully and avoids having to do any difficult manipulation of the program counter <code>pc</code> - it&#39;s all handled by the call stacks! </p>
<p>But now you aren&#39;t holding onto the registers. In the last attempt, you had to write <code>ld [vAsyncAF], af</code> and the like, and I mentioned that those instructions don&#39;t actually exist and brushed over them. You can do it but it&#39;s slow and ugly. But! It turns out the stack can help you here as well! Just push all the registers onto the stack before switching, and then pop them off after. ggez!</p>
<p>Here&#39;s the new approach: the interrupt handler to switch contexts should do the following sequence:</p>
<ol>
<li>push all the registers onto the stack </li>
<li>save the stack pointer for the old context</li>
<li>fetch the stack pointer for the new context </li>
<li>and then pop all the registers off the stack.</li>
</ol>
<p>So here&#39;s what our &quot;enter async thread&quot; interrupt handler looks like now:</p>
<pre><code><span class="hljs-symbol">VBlankInterrupt</span>:
<span class="hljs-keyword">push </span>af
<span class="hljs-keyword">push </span><span class="hljs-keyword">bc</span>
<span class="hljs-keyword">push </span>de
<span class="hljs-keyword">push </span>hl
<span class="hljs-comment">; save main thread stack pointer</span>
ld [vMainSP], <span class="hljs-built_in">sp</span>
<span class="hljs-comment">; load async thread stack pointer</span>
ld <span class="hljs-built_in">sp</span>, [vAsyncSP]
<span class="hljs-keyword">pop </span>hl
<span class="hljs-keyword">pop </span>de
<span class="hljs-keyword">pop </span><span class="hljs-keyword">bc</span>
<span class="hljs-keyword">pop </span>af
ei
ret
</code></pre><p>And then a matching interrupt handler to fire on the STAT interrupt when we hit scanline zero:</p>
<pre><code><span class="hljs-symbol">STATInterrupt</span>:
<span class="hljs-keyword">push </span>af
<span class="hljs-keyword">push </span><span class="hljs-keyword">bc</span>
<span class="hljs-keyword">push </span>de
<span class="hljs-keyword">push </span>hl
<span class="hljs-comment">; save async thread stack pointer</span> :w
ld [vAsyncSP], <span class="hljs-built_in">sp</span>
<span class="hljs-comment">; load main thread stack pointer</span>
ld <span class="hljs-built_in">sp</span>, [vMainSP]
<span class="hljs-keyword">pop </span>hl
<span class="hljs-keyword">pop </span>de
<span class="hljs-keyword">pop </span><span class="hljs-keyword">bc</span>
<span class="hljs-keyword">pop </span>af
ei
ret
</code></pre><p>Pleasingly symmetric, no? This is quite close to the code I wrote in my project. There are two steps left: First, clean up one loose end, then, write a <code>RunInVBlank</code> subroutine to work with this stack-centric approach. Time for you to trim that loose end:</p>
<p>What happens when the subroutine in our thread returns the final time? At that point the stack pointer will be pointing past the stack, and you&#39;ll underflow the stack, but this is dang ol&#39; game boy assembly, so there&#39;s no error handling but what you write yourself. The solution to this is very simple: We write a handler for when the subroutine returns, and put that on the stack first! When the subroutine returns, it&#39;ll execute the &quot;early return&quot; handler, and that can clean up and turn off the interrupts itself.</p>
<p>This &quot;early return&quot; handler is pretty simple: it just needs to turn off the interrupts, and maybe have some places to put other bookkeeping we might add in the future.</p>
<pre><code><span class="hljs-symbol">EarlyReturn:</span>
<span class="hljs-keyword">di </span><span class="hljs-comment">; disable interrupts globally, because this would result in very strange </span>
<span class="hljs-comment">; behavior otherwise if an interrupt somehow fired during it</span>
<span class="hljs-comment">; turn off the specific interrupts we've been using</span>
ld hl, rIE <span class="hljs-comment">; target the hardware register controlling interrupts</span>
res <span class="hljs-keyword">B_IE_VBLANK, </span>[hl] <span class="hljs-comment">; reset the bit to turn off the v-blank interrupt</span>
res <span class="hljs-keyword">B_IE_STAT, </span>[hl] <span class="hljs-comment">; reset the bit to turn off the STAT interrupt</span>
<span class="hljs-comment">; [do any other bookkeeping necessary here]</span>
ld <span class="hljs-built_in">sp</span>, [vMainSP] <span class="hljs-comment">; restore the main thread's stack</span>
<span class="hljs-comment">; get all the registers off the stack, because there's no longer</span>
<span class="hljs-comment">; going to be a STAT interrupt to restore them</span>
pop hl
pop de
pop <span class="hljs-keyword">bc</span>
pop af
<span class="hljs-keyword">ei </span><span class="hljs-comment">; re-enable interrupts globally at the end</span>
ret <span class="hljs-comment">; return execution to the main thread context</span>
</code></pre>
<p>Now, to incorporate it. You&#39;ll put this on your stack first when you&#39;re preparing your <code>RunInVBlank</code> function. Then the subroutine you want to run goes on the stack next, and then the registers. Define a couple constants for the memory locations these live at. Let&#39;s write the final <code>RunInVBlank</code> function, fully using the stack and early return!</p>
<pre><code>def ASYNC_STACK_TOP = $FFBF <span class="hljs-comment">; the top of the stack will be at this address</span>
def ASYNC_STACK_EARLY_RETURN = ASYNC_STACK_TOP - <span class="hljs-number">2</span> <span class="hljs-comment">; allocate two bytes to hold the early return handle</span>
def ASYNC_STACK_FUNCTION = ASYNC_STACK_EARLY_RETURN - <span class="hljs-number">2</span> <span class="hljs-comment">; two more bytes for where the async thread should resume from when it's called for the first time</span>
<span class="hljs-symbol">RunInVBlank:</span>
ld [vMainSP], <span class="hljs-built_in">sp</span> <span class="hljs-comment">; store the stack pointer so we can restore after using it </span>
<span class="hljs-comment">; make sure we've got the early return handle at the base of the stack</span>
ld [ASYNC_STACK_EARLY_RETURN], EarlyReturn
<span class="hljs-comment">; now we want to build our stack. the first thing on it will be the function</span>
<span class="hljs-comment">; we're running in the thread, so it can resume. so point the stack pointer </span>
<span class="hljs-comment">; at it</span>
ld <span class="hljs-built_in">sp</span>, ASYNC_STACK_FUNCTION
push hl <span class="hljs-comment">; the argument to RunInVBlank is a subroutine address in hl.</span>
<span class="hljs-comment">; so it goes on the stack first, at the location we just set sp to</span>
push af <span class="hljs-comment">; then we put all the registers in the right order </span>
push <span class="hljs-keyword">bc </span><span class="hljs-comment">; so that when the program switches context into the async thread,</span>
push de <span class="hljs-comment">; it can get them out</span>
push hl
<span class="hljs-comment">; and now our async stack is set up! we just need to store it and </span>
<span class="hljs-comment">; restore the main thread stack</span>
ld [vAsyncSP], <span class="hljs-built_in">sp</span>
ld <span class="hljs-built_in">sp</span>, [vMainSP]
<span class="hljs-comment">; enable the interrupts</span>
ld hl, rIE <span class="hljs-comment">; target the interrupt enable flag</span>
set <span class="hljs-keyword">B_IE_VBLANK, </span>[hl] <span class="hljs-comment">; set the bit to enable the v-blank interrupt</span>
set <span class="hljs-keyword">B_IE_STAT, </span>[hl] <span class="hljs-comment">; set the bit to enable the STAT interrupt</span>
<span class="hljs-keyword">ei </span> <span class="hljs-comment">; enable interrupts globally</span>
ret
</code></pre>
<p>And that&#39;s more or less the same as the code I wrote! At any time, you can pass a subroutine address via <code>hl</code> to the <code>RunInVBlank</code> function, and it will then be executed in the background, only running between the v-blank and STAT interrupts. When it finishes by executing a <code>ret</code> instruction, it&#39;ll clean itself up, turn the interrupts off, and restore flow to the main thread. I think it&#39;s a pretty clean interface, and very usable. I&#39;ve used it extensively in my year-long game boy project, the Liquid Crystal Dreams tarot deck. (Look for it soon on kickstarter!) I use this async function whenever I want to load graphics data, so I don&#39;t ever have to worry about when there&#39;s time to do it safely. It&#39;s all scheduled by interrupts and a couple of assembly-time constants!</p>
<p>Thanks for coming on this little journey with me. It was really fun to invent the wheel like this, especially because OS-level code is such a black box to me most of the time, but here I am, writing the assembly for a context switching thread management system. </p>
<p>There&#39;s a handful of additional tasks which you might find interesting to think through, if you&#39;ve been following along and want some more:</p>
<ul>
<li>You don&#39;t actually have some of the instructions I used, like loading a constant into <code>sp</code>. Can you write performant replacements for them?</li>
<li>It&#39;s probably possible to combine the two interrupts into one.</li>
<li>Use the stat interrupt for both the &quot;switch from main context to async context&quot; and &quot;switch from async context to main context&quot; cases. This requires the handler code to reconfigure what handler code is being used! Self-modifying as heck!</li>
<li>What if the interrupts are needed for other functionality? Could you swap out interrupt handlers based on the state of the processor? How does this work with more interrupts?</li>
<li>Can you use the same technique to write code that executes during the h-blank period? Why not?</li>
<li>How would you pass information between the two threads? How would you store information about the state of the threaded code? How would you work with that information to make sure that all the code that needs to get executed does get executed?</li>
<li>What happens when the async code returns?</li>
<li>What do you do if you want to cancel the async thread?</li>
</ul>
<p>And finally, some disclaimers and warnings:</p>
<p>Variable names have been changed to protect the innocent. There&#39;s some layers of indirection I&#39;ve skipped, such as the interrupt vector jumping into a specified RAM address edited for configurable interrupts. This could have been avoided if I had known about the <code>jp hl</code> instruction, but it&#39;s probably faster. Concurrent access to RAM is the same headache as in modern code, which has caused some truly perplexing bugs - there&#39;s a very small chance that a context switch will happen between writing the first byte of an important memory address and the second byte, which can wreak havoc. I found it was usually sufficient to temporarily disable interrupts to make operations atomic - surround a memory access with <code>di</code> and <code>ei</code> to turn interrupts on and off, and they&#39;ll get handled afterwards if they happened in between. I am not an expert. In fact I know shockingly little about the conventional wisdom. This does not constitute legal or medical advice.</p>
</article>

View File

@ -1,80 +0,0 @@
## tarot deck for the game boy!
for the last year, i've been working on a tarot deck for the nintendo game boy.
it's a full 78 card deck of pixel art illustrations. the major arcana are fully animated and shaded, and the minor arcana are in a silhouette style. the cartridge also shows you a few meanings for each card, including reversed cards, and allows you to shuffle the deck and perform readings, with a small library of spreads to choose from. it also supports the game boy printer, allowing you to print out the cards so you can hold on to them!
## the beginning
it's been a journey to make this. i had the idea sometime, in the year before i started work, that it would be funny to make a tarot deck for the game boy. i had a vision that it could help you do readings, index the meanings of cards, and let you print them out on the game boy printer. i have a love of "useful" software, especially when it's on something like a game console. it's so silly! i love the idea of being able to accomplish something with a computer, rather than just sinking time into it and getting experiences out. and who (aside from nintendo) doesn't love some retro homebrew software?
i've done a lot of projects for a long time. i'm a project-oriented person. i love arts and crafts. but most of my projects tend to get far enough that i can show them to a friend and say "hey, look at this!" and it's just finished enough that you can get what i'm going for. i get things to the proof-of-concept stage. but as i've been slowly angling towards going back into the professional sector, and thinking about building my portfolio, i thought maybe i should try to get things to more of a finished stage - so i can show them to a stranger and the stranger will really get what i'm trying to accomplish. this year my goal has been to actually finish and polish things.
so when i started looking into one of my old obsessions (the nintendo game boy) this year, the tarot project seemed like a natural fit. i'd exercise my art skills, i'd learn a new programming environment and language, and i'd produce something cool that would impress people. initially i thought it would take me a month or two.
## drawing in aseprite and coding in rgbds-live
it was smooth sailing early on. the whole project was built inside [rgbds-live](https://gbdev.io/rgbds-live/), a web interface to the venerable rgbds toolchain for game boy homebrew. i didn't know assembly, but i'm a dab hand at picking things up. i started poking at it, moving things around, and before long i had started on the work of building my standard framework of screens, bookkeeping functions, update and draw calls, and so on.
i also found [an export script](https://github.com/redboyrave/Aseprite-GB-Exporter/tree/main) for pixel art software Aseprite which could be readily modified for my purposes. i built a tool for generating and editing lists of points for animation data, i built some python functions for manipulating data in asm formats, and all around i was having a ball.
and i started drawing cards.
i can say i've gotten a lot better at art over this project. some of the early versions of card art were cringe-inducing, and i've still got a list of changes i'd make if i had infinite time. but when i started i think i didn't really comprehend in my soul how many cards are in a tarot deck. doing a series of 22 careful illustrations, including animation, which has to be programmed in assembly, was just. i can't overstate how much work it was. and then later i would make the ludicrous decision to *increase* that to 78.
this deck is largely in the style of rider-waite-smith, although many of my cards deviate from the specific compositions and i eschew a lot of the esoteric details. my Death is much more stark. my Lovers was a right-out-the-gate decision and it wasn't until my consultants pointed out the strong parallel that I re-composed Devil to match. I'm particularly happy with my Chariot, which is a much more dynamic composition than rider-waite-smith, and i think communicates its ideas of power and control and speed quite well. I chose to replace Judgment with The Meteor (as in "that killed the dinosaurs", or other apocalyptic scenarios), both to give a little bit of abstracting flair, and to better pull together the final five cards as an Astral Sequence. if you look you will notice that the positions of the astral bodies in focus descend over their course towards the center.
The World is really my piece de resistance; as on the main title screen, I have once again opted to make some pseudo-3d graphics, for the heck of it. getting its animation to form an illusion of a curved shape was complex, given the limited facilities for arithmetic operations. once again data tables are my friend! it's achieved with a matrix of bit flag values that control how far to move each component of the world map for each zone it can be in, so it moves slowly at the edges, and quickly in the middle.
speaking of the main title screen, it was one of the earliest things I actually built. it's stored as a list of 255 y/x/size pairs, precalculated by a python script; every frame it simply draws three cards at three points in that list. unfortunately the code for drawing the cards is horrendous; i may go back and clean it up.
over the course of this project i've gone from having no experience with assembly to having a lot. i understand a lot of things now. i've become adept at using subroutine calls and jump tables, computing offsets into arrays, building simple data structures, using the assembler to define static data layouts in ram, and i've even built a system that uses the game boy's interrupts to allow parallel execution of code; i detailed this adventure in [the advent async article].
i really enjoy assembly programming now, although i'd certainly prefer something higher level if i were going to make anything this big (or larger) again. i describe assembly as writing code while simultaneously solving a puzzle, because you don't have variables to work with, there's such limits on what values you can compute with, you have limited space to worry about, and so on. it's great fun, but of course quite limiting!
i'm permanently indebted to the fine folks who made rgbds-live, which executes the rgbds toolchain and provides a web interface to it with a live emulator and some debugging facilities. i certainly could have made this with just the basic rgbds tools, writing build scripts myself and such, but it was so easy to simply work in the browser with the emulator live next to the code... and i only ran into issues once or twice with extreme edge cases.
for my code i decided to use the stack almost exclusively as a call stack, and not for passing data, and never reset it from its starting location of high HRAM. this was a silly decision, and meant i wasn't using some features of the processor, but what's done is done. using it only as a call stack meant it stayed short - never more than 16 or so bytes, i think - and meant i could pack two stacks into the space when i eventually branched for having parallel code. i think it is customary to pass extra arguments on the stack as well? but i essentially always pass arguments in registers, with a few that use static memory locations to hold important values. this wouldn't work if i ever thought that two calls to a function might collide, but happily i simply guarantee it doesn't. packing all the arguments into the available registers sometimes required me to make questionable decisions - for example, my metatile sprite drawing routine (allocates sprites corresponding to a grid of tiles) takes the height and width tile counts in the high and low nibbles of the same register. this means i can't draw sprites something larger than 16x16 tiles, but i can just avoid doing that. there aren't that many sprites available anyway!
one of the moonshot ideas in this project was to have it interface with the game boy printer, a forgotten peripheral which allowed players to print out victory certificates and other images on thermal paper. this project never would have existed without the dedicated and thorough game boy development community, which has meticulously documented the behavior of the printer. even with their help, though, debugging serial communication and an opaque black box device was a challenge! and that's setting aside the question of actually doing graphical manipulation on the images. the printing code takes the tiles drawn to the screen, scales them by two, and simulates in software the sprites being drawn over them (including replacing palette values all around as necessary!), and then sends them 16 lines at a time to the printer, waiting for an OK signal between each batch. writing this - and interfacing it with my already-substantial UI code on two different screens - was a beast! i'm proud to say that it all works, though, and i have shitty receipt paper printouts of my art littering my desk.
the game boy has a native 64kb available to it, divided into a number of segments. at minimum, a rom can hold 32kb of data, which are directly accessed in the lower half of the memory space. however! with memory mapper chips, you can expand that, which is good, because i quickly ran out of room. the way this functions is whenever you write a byte to a special set of addresses in the rom, it will swap in a different "bank" of 16 kb in the second half of the rom space. so memory addresses 0-16k always hold the base of the rom, and addresses 16k-32k can be swapped for any of a large number of replacements (maximum size depending on the chip). this ended up being a little bit of a headache when i started swapping contexts, and is one of the weak spots of my parallel computation engine - it doesn't track that as part of the context to swap! in any case, it largely didn't impede me, because most of my actual game code was stored in the base 16k, and (mostly) only card data and sound were stored in the banked memory. fitting 5-8 cards into each bank meant we still ended up with a fair number, and i believe the final size of the rom is now 128kb. not the 4mb maximum, but certainly chunky as gameboy cartridges go!
the cards are stored in their own `.inc` files and apportioned into banks by `CardLibrary.inc`. that card library file also holds the list of spreads with descriptions, and the definitive store of card addresses, an array of three-byte tuples of the card's address plus which bank they need loaded. all of this is used by the `CardHelper.inc` functions which encapsulate a lot of the guts of manipulating cards, initializing card animation states, drawing cards, updating cards, and so on.
one of the sillier "mistakes" i made in this project was that i didn't notice, until very late, that there is in fact a `jp hl` instruction for a jump to a register. so instead i populated a list of dummy functions in work ram, each of the form `Handle: call $00; ret`. since these are in work memory, they can be directly changed, by editing their raw bytes, to point them at something new. then you simply jump to the static `Handle` address, and it will call whatever function you specified. if i had known about `jp hl` this kind of indirection wouldn't be needed, but alas! i took the hard way for no reason. one thing that has me kicking myself here is that i could have used a lot more jump tables if i had known how to implement them. as it is i only use the technique a few times.
i wrote a snazzy little implementation of a pseudorandom number generator! `Random.inc` holds a linear feedback shift register, which can be processed one bit at a time, or one byte at a time, and which is used for, well, random number generation. it's sorta a shame that i didn't end up actually needing it more than a tiny bit!
oh in the beginning of this section i mentioned my standard layout of scenes and helper functions. this is simple. in all sorts of different situations i end up writing the same code to deal with a multi-state application: somewhere there is a global variable holding what the present "scene" is. a "scene" or "screen" refers to a structure holding pointers to functions corresponding to initialization, frame update, frame draw, and optionally teardown. then there's a helper function to change what the active scene is, and then the game's main loop gets to do its own bookkeeping and setting up application-level variables (such as a delta time, or checking and compiling input states) and then call "current scene's update function", or "current scene's draw function", at the appropriate times. this lets you separate the universal behavior of how the update loop needs to behave from separately encapsulated per-screen variables and behaviors. it's worked pretty well for me. this cartridge has five screens: displaying a card while doing a reading, displaying a card while browsing, the shuffle interface, the spread selecting interface, and the main screen.
i'm really proud of doing 3d graphics on the game boy. have i mentioned that it doesn't have a multiply instruction? it doesn't have a multiply instruction. it doesn't even have modulo! wild.
## tooling
i worked primarily in rgbds-live, but i did accrue some extra tooling. i wrote several python functions i could use in the REPL to manipulate strings of data and generate values i wanted. aseprite was the name of the game for drawing pixel art. it's the best art program i know for pushing pixels, and i was able to get an export script working, which i'd also like to distribute widely. that export script renders the tiles (of an aseprite tilemap layer) into the appropriate bitplaned 2bpp format the game boy uses, and outputs them as hex literals in an array along with an array of indices describing the map itself. I rewrote it a bit to make it output compatible assembly data instead of C-style arrays, and add an offset value so you can load the tile data at whatever point in vram you like.
working in aseprite wasn't too bad. i certainly think it would have been a much worse experience in other programs! it has a few idiosyncracies like problems with generating dithered gradients, but i really can't complain. it did the job admirably.
I went through several options for adding music, which is still in progress. originally i was planning to compose my own. in the end i gave up on that; i believe someday i may learn music better, but at the moment it's one of the few things i consider beyond me. i went through a number of options for format, though, ranging from lsdj, to writing my own playback engine for a bespoke tracker format, to specially formatted schism tracker files, to the carillon editor (whose `.sav` file can be imported as a binary blob by your assembly, and indexed into to call functions for start, update, and pause!), to hugetracker (the standard for gameboy composition these days), back to writing my own, then to a hybrid approach, and finally i gave up. i'm using hugetracker, now, which requires a relatively small import and will do all the hard stuff for me, and a friend graciously offered to compose the music for me.
one of the things that i'm the most unhappy with about the overall state of this project is the use of disorganized `.inc` files. I have had trouble getting rgbds-live to work kindly with subdirectories, and i don't know how to configure it to build multiple .asm files into a single final project, so i have opted to simply not do that. so everything is in a massive tree of `.inc` files, there's a single `main.asm` file which is the entry point, and it's all in one directory, including 78 files for each card.
## the late: long slog, motivation, the experience
like i wrote earlier, one of the motivations for this project was to get better at finishing things, rather than just letting my projects linger in a half-finished state for eternity. i'm trying to get this done, not just play around until i get bored! so managing my motivation and enthusiasm levels was a huge part of this project's story. i'm deeply indebted to my husband for keeping me honest about working hours - they are strict in the face of my puppy dog eyes when i want to keep working after they get out of their job for the day. when they're out of work, i'm not allowed to work. i've been treating this as my job, but trying to make that a healthy thing instead of an anxiety-and-burnout-inducing thing. and i'm proud that i've managed consistent work on this for a year! there was one break for a couple of months after june when i wasn't feeling it, and it's been a little spotty as i wrap up loose ends and check things off my to-do list, but all in all i think i've done really well at sticking to my goals.
in particular this was a test of my art skills. i've always been a little bit of an artist, but this is far beyond the scope of anything i've done before; normally i just doodle and sketch, but liquid crystal dreams tarot required i make 78 finished illustrations (daunting), in pixel art (unfamiliar), with animations for some (never really done that). that was a lot! i'm really proud of myself, though, for just taking it a bit at a time. that's a struggle, given my tendency to catastrophize and drag myself down. i had to exercise nonjudgmental attitudes towards myself, avoiding the despair i'm inclined to feel when my art doesn't measure up to my quality standards. it was tough! in the end i'm really proud of what i made, even if it's not quite as good as i wanted.
when i started i was so used to sketching that i thought of my pixel art in terms of lines delineating shapes. but now, having drawn a lot more, i see how much pixel art is more like painting, with which i'm unfamiliar: you block out shapes, you don't draw their edges. you can still see traces of this sketchy approach in the early cards, before it gave way to the painterly style that i think works better.
i suppose if i wanted to put into words the core mysterious goal of this project, it's that i want to make something that wows people, even if they're not my friends. i've made so many things that my friends love and are impressed by, but which don't hold up to the scrutiny of an "impartial" observer; it's a part of my philosophy that i don't usually worry about that kind of thing. i keep working on things for me and my friends, and say screw the observer, that's too high of a standard to keep! and this has served me well. it's part of how i engage in so many arts and activities, to discard concern for how the outside world would see me and drag me down.
liquid crystal dreams has been an exercise in making something that can impress people even if they're not already on my side. i want something i can sell to people outside my friends and family, that people will see and *want*. that's the goal i'm striving to reach.
## finishing: publishing and packaging?
of course, you can't sell something without concern to the marketing, right? so i've been dipping my toes into the matter of publishing too. this has involved figuring out how to write cartridges, how to package and sell things, shipping costs, graphic design of marketing materials, writing copy (hi, that's what i'm doing right now! you're reading it!), and generally making everything ancillary needed for the process of getting a product out there and making sure people actually see and want and get it. i wrote an article about my parallel execution library that i'm proud of. i'm planning to write an essay about tarot cards and their meanings (which may become a companion book). i need to start writing a manual - and that's going to require gathering and formatting samples of the interface and art from the program itself! i'm gathering screenshots and recordings of art that i want to share to pique peoples' interest. marketing is a lot of work. normally it's work i eschew - i don't believe in letting the "outside world" influence me, and i make what i want to make, not what a nebulous populace wants! i don't want to sell my work, i want to make work that stands on its own and accomplishes its goal! but this is a project that really requires it, and it's a natural extension of this project, so i'm dipping my toes in. i might even make a kickstarter!
as for the particulars, i'm planning to sell the cartridges through homebrew factory, a possible deck of cards through the game crafter, and i got some stickers made from diecutstickers.com. i'm still looking for a printer for manuals at this time.
## final thoughts
this has been a lot of fun. i'm proud of it.

View File

@ -1,12 +0,0 @@
<article>
<p>I brought a piece of conceptual video art to an art festival!</p>
<p>Its a test piece for a better installation next year, but the basic concept is a mirror that shows you yesterday. Furthermore, it had a selector switch on it so you could pick the delay it showed, from five seconds, thirty seconds, five minutes, 12 hours, or 24 hours. I really like the 24 hour delay conceptually, and the five second delay is deeply disorienting. Your brain tries to parse it simultaneously as a live feed and a recording of you. You start slowing down all your actions waiting to see them on the video.</p>
<p>It was extremely gratifying to bring something effective at catching people off-guard, provoking double-takes, and generally delighting people.</p>
<p>The project itself ran on a raspberry pi 5, a razer kiyo pro webcam, and the cheapest screen we could get for $70. Combine that with a cheap flatpack screen holder and a thrifted chest of drawers and we had some kind of facsimile of a bedroom vanity.</p>
<figure>
<img src="mirror.jpg" alt="A monitor over a chest of drawers, at night.">
</figure>
<p>I threw together <a href="https://git.shoofle.net/shoofle/mirror-to-yesterday">the software</a> in about a week. Its a really simple python script that uses opencv to continuously record one-minute segments of video, as well as holding a buffer of 30 seconds of frames for the shorter delays. Im sure there are more efficient ways of working it than keeping three live video file decoding streams for the long-term delays, but it seems like the system is more bottlenecked by getting the video feed from the webcam than anything else. Its possible efficiency and speed gains could be had by switching up the codecs were using, which I plan to experiment on before we deploy next years version. I thought about trying to get more in the weeds to avoid using the not-really-for-this-purpose opencv library, but like, if it works, it works. If I find something more appropriate I might rewrite it, but in the meantime Im going to keep it running as it runs.</p>
<p>Unfortunately it doesnt necessarily run. Over the course of the week it encountered a number of problems. First, I had issues where it wouldnt load any videos because I left some paths relative and then changed the execution environment. Then there were permissions problems after being set up on a fresh device. Then the biggest problem was the screen not being bright enough to be visible at all during the day. Then white letterbox bars showed up and I still dont know why. And finally, to cap it all off, it stopped running towards the end of the week. Again, no idea.</p>
<p>All told though, Im pleased as punch that I brought a weird object to the weird object party.</p>
</article>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

13
auriga.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=The Auriga Webserver
After=network.target
[Service]
User=mrserver
WorkingDirectory=/home/shoofle/auriga
ExecStart=authbind gunicorn -b shoofle.net:80 pure_flask:app
Restart=always
[Install]
WantedBy=multi-user.target

View File

@ -1,102 +0,0 @@
#!python3
from jinja2 import Environment, FileSystemLoader, select_autoescape
import os
from pathlib import Path
import shutil
import argparse
import markdown
#TODO: make this find templates first and save them, rather than hardcoding all of them
#TODO: build some kind of functionality for listing and categorizing and otherwise having metadata for each file
parser = argparse.ArgumentParser(prog="generate.ignore.py",
description="shoofle's static site generator for shoofle.net")
parser.add_argument("-i", "--input", type=Path, help="source materials")
parser.add_argument("-o", "--output", type=Path, help="where to put the rendered site")
parser.add_argument("-v", "--verbose", action="store_true", help="show verbose output")
args = parser.parse_args()
source = args.input
finals = args.output
env = Environment(
loader=FileSystemLoader(source),
autoescape=select_autoescape())
article_template = env.get_template("articles/article.template.html")
md = markdown.Markdown(extensions=["tables"])
template_for_markdown = env.get_template("articles/article.template.md")
def process_file(path_to_source, source, finals):
rel_path = path_to_source.relative_to(source)
path_to_output = finals / rel_path
if ".ignore" in str(rel_path): return
if ".git" in str(rel_path): return
if path_to_source.is_dir(): pass
elif ".template.html" in str(path_to_source): pass
elif ".template.md" in str(path_to_source): pass
elif ".renderme" in str(path_to_source):
path_to_output = path_to_output.parent / path_to_output.name.replace(".renderme","")
with open(path_to_output, "w") as output_file:
os.makedirs(path_to_output.parent, exist_ok=True)
output_file.write(env.get_template(rel_path).render())
elif ".article.html" in str(path_to_source):
# all .article.html files are transformed into a folder of the same name and then rendered with the at index.html
folder_name = path_to_output.parent / path_to_output.name.replace(".article.html","")
os.makedirs(folder_name, exist_ok=True) # make the folder /var/www/shoofle.net/articles/circle_script
path_to_output = folder_name / "index.html"
with open(path_to_output, "w") as output_file:
output_file.write(article_template.render(title="Article", target=str(rel_path)))
elif ".article.md" in str(path_to_source):
# all .article.md files are rendered, then the same thing done as for article.html files
text = f"something must have gone wrong in the parsing of {path_to_source} for you to see this"
with open(path_to_source, "r") as input_file:
text = input_file.read()
html_source = md.convert(text)
folder_name = path_to_output.parent / path_to_output.name.replace(".article.md", "")
os.makedirs(folder_name, exist_ok=True)
path_to_output = folder_name / "index.html"
with open(path_to_output, "w") as output_file:
output_file.write(template_for_markdown.render(title="Article", contents=html_source))
else:
os.makedirs(path_to_output.parent, exist_ok=True)
shutil.copy(path_to_source, path_to_output)
if __name__ == "__main__":
for directory_path, directory_names, file_names in os.walk(source, topdown=True):
#filter out all .ignore and .git directories
for i in reversed(range(len(directory_names))):
if ".ignore" in directory_names[i] or ".git" in directory_names[i]: del directory_names[i]
for file in file_names:
process_file(
Path(directory_path) / Path(file),
source,
finals
)
if args.verbose: print(f"processing {file} in {directory_path}")

View File

@ -1,79 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Projects, by Shoofle</title>
<link href="./static/bootstrap/css/bootstrap.css" rel="stylesheet" type="text/css">
<link href="./static/bootstrap/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
<link href="./static/shoofle.css" rel="stylesheet" type="text/css">
<style>
body {
padding: 1em;
}
</style>
</head>
<body>
<div class="row-fluid">
<div class="span8">
<div class="row-fluid">
<div class="span4">
<div class="small_project coding">
<p class="description"><a href="articles/lcdt/async/">LCDT: Async Assembly Adventure</a></p>
<p class="name">I wrote about developing asynchronous execution in assembly on the nintendo game boy.</p>
</div>
</div>
<div class="span4">
<div class="small_project game">
<p class="description"><a href="articles/forest">The Forest</a></p>
<p class="name">The Forest, a MUD with wiki characteristics.</p>
</div>
</div>
<div class="span4">
<div class="small_project web">
<p class="description">The shoofle.net constellation!</p>
<p class="name"> I run a gitea server on <a href="https://git.shoofle.net">git.shoofle.net</a> and this server at <a href="https://shoofle.net">shoofle.net</a>.</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span4">
<div class="small_project art">
<p class="description"><a href="articles/circle-script/">Circle Script</a></p>
<p class="name">I created an artsy phonetic writing system for making intricate circles.</p>
</div>
</div>
<div class="span4">
<div class="small_project game">
<p class="description"><a href="articles/atelier-phoebe">Atelier Phoebe</a></p>
<p class="name">An Atelier fangame made for the <a href="https://www.lexaloffle.com/pico-8.php">Pico-8</a>.</p>
</div>
</div>
<div class="span4">
<div class="small_project game">
<p class="description"><a href="articles/mindjail-engine">A 2D Physics-Based Game Engine in Python with OpenGL</a></p>
<p class="name">The Mindjail Engine, my hand-crafted 2D game engine!</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<div class="small_project art">
<p class="description"><a href="articles/mirror-to-yesterday">the Mirror to Yesterday</a></p>
<p class="name">A mirror that shows you yesterday!</p>
</div>
</div>
</div>
</div>
<div class="span4">
<div class="about">
<h1>Hi! I'm Shoofle. I'm a lot of person.</h1>
<p>I'm a housewife, an artist, a woodworker, and a programmer. I generally find myself in the New York Capital District or Boston, but I like to couchsurf around and visit friends all over.</p>
<p>I like to think of myself as a creative person, and I have a voracious appetite for learning. It's been a while since I graduated from college but I like to think of myself as a perpetual student.</p>
<p>I think we have twin responsibilities to make the world better for those around us, and to enjoy what time we have on this earth.</p>
<p>You might know me as Shoofle, Ada, or Amazing.</p>
</div>
</div>
</div>
</body>
</html>

104
project_list.html Normal file
View File

@ -0,0 +1,104 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Projects, by Shoofle</title>
<link href="/static/bootstrap/css/bootstrap.css" rel="stylesheet" type="text/css">
<link href="/static/bootstrap/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
<link href="/static/shoofle.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="row-fluid">
<div class="span8">
<div class="row-fluid">
<div class="span3">
<div class="small_project web">
<p class="description"><a href="miscellany#auriga">HTML/CSS/JS Showcase on a Flask Server!</a></p>
<p class="name">Auriga! (<a href="http://li60-203.members.linode.com">you're lookin' at it!</a>)</p>
</div>
</div>
<div class="span3">
<div class="small_project writing">
<p class="description"><a href="miscellany#silver-asterism">Blog about Games as Art</a></p>
<p class="name">Some friend sand I occasionally write at <a href="http://silverasterism.blogspot.com/">Silver Asterism</a></p>
</div>
</div>
<div class="span6">
<div class="small_project game">
<p class="description"><a href="mindjail-engine">A 2D Physics-Based Game Engine in Python with OpenGL</a></p>
<p class="name">The Mindjail Engine, my hand-crafted 2D game engine!</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span4">
<div class="small_project art">
<p class="description"><a href="miscellany#spinning">Performance Art: Skilled Manipulation of Inertia Props</a></p>
<p class="name">I <a href="spinning">spin Poi and Staff</a> in my spare time</p>
</div>
</div>
<div class="span4">
<div class="small_project game">
<p class="description"><a href="shoof-shoof-revolution">DDR Clone in Javascript and SVG</a></p>
<p class="name">Shoof Shoof Revolution?</p>
</div>
</div>
<div class="span4">
<div class="small_project writing">
<p class="description"><a href="miscellany#lambdamoo">Worldbuilding is Fun, Or: Adventures in LambdaMOO</a></p>
<p class="name"><a href="city-on-the-river">City on the River</a>, and environs</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span6">
<div class="small_project web">
<p class="description"><a href="play-for-x">An Alternative to Rolling Dice in Tabletop RPGs</a></p>
<p class="name">Play for X</p>
</div>
</div>
<div class="span6">
<div class="small_project coding">
<p class="description"><a href="distributed-speakers">Free Ad-Hoc Sound Systems</a></p>
<p class="name">Project idea: Distributed Speakers</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<div class="small_project writing">
<p class="description"><a href="tamari">Ever heard of a Tamari Lattice?</a></p>
</div>
</div>
<div class="span6">
<div class="small_project writing">
<p class="description"><a href="language-for-games">If you were writing a language for game dev, what unorthodox features would you include?</a></p>
</div>
</div>
<div class="span3">
<div class="small_project writing">
<p class="description"><a href="oauth">I wrote about how I use oauth to make tumblr bots.</a></p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span4">
<div class="small_project art">
<p class="description"><a href="miscellany#guitar">I try to strum a guitar now and then. Spoiler: there are no recordings.</a></p>
</div>
</div>
</div>
</div>
<div class="span4">
<div class="about">
<h1>Hi! I'm Shoofle. I'm a lot of person.</h1>
<p>I graduated from the University of Virginia in 2012, majoring in math and physics with a minor in computer science. I'm really good at math. I'm pretty good at programming.</p>
<p>I've always loved to draw, but never focused enough time on it. I'm an intensely creative person and I like to think that this comes out in everything I do - and I'm trying harder every day.</p>
<p>I'm a passionate advocate for feminism, transgender rights, queer rights, and social justice in general.</p>
<p><span class="label label-important"><i class="icon-warning-sign"></i>Warning!</span> This website is still very much under construction. I'm in the process of figuring out how I want this website to look and function.</p>
</div>
</div>
</div>
</body>
</html>

105
project_list.zip.html Normal file
View File

@ -0,0 +1,105 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Projects, by Shoofle</title>
<link href="/static/bootstrap/css/bootstrap.css" rel="stylesheet" type="text/css">
<link href="/static/bootstrap/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
<link href="/static/shoofle.css" rel="stylesheet" type="text/css">
<style type="text/css"> body { transform: scale(0.5); transform-origin: 0% 0%; } </style>
</head>
<body>
<div class="row-fluid">
<div class="span8">
<div class="row-fluid">
<div class="span3">
<div class="small_project web">
<p class="description"><a href="miscellany#auriga">HTML/CSS/JS Showcase on a Flask Server!</a></p>
<p class="name">Auriga! (<a href="http://li60-203.members.linode.com">you're lookin' at it!</a>)</p>
</div>
</div>
<div class="span3">
<div class="small_project writing">
<p class="description"><a href="miscellany#silver-asterism">Blog about Games as Art</a></p>
<p class="name">Some friend sand I occasionally write at <a href="http://silverasterism.blogspot.com/">Silver Asterism</a></p>
</div>
</div>
<div class="span6">
<div class="small_project game">
<p class="description"><a href="mindjail-engine">A 2D Physics-Based Game Engine in Python with OpenGL</a></p>
<p class="name">The Mindjail Engine, my hand-crafted 2D game engine!</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span4">
<div class="small_project art">
<p class="description"><a href="miscellany#spinning">Performance Art: Skilled Manipulation of Inertia Props</a></p>
<p class="name">I <a href="spinning">spin Poi and Staff</a> in my spare time</p>
</div>
</div>
<div class="span4">
<div class="small_project game">
<p class="description"><a href="shoof-shoof-revolution">DDR Clone in Javascript and SVG</a></p>
<p class="name">Shoof Shoof Revolution?</p>
</div>
</div>
<div class="span4">
<div class="small_project writing">
<p class="description"><a href="miscellany#lambdamoo">Worldbuilding is Fun, Or: Adventures in LambdaMOO</a></p>
<p class="name"><a href="city-on-the-river">City on the River</a>, and environs</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span6">
<div class="small_project web">
<p class="description"><a href="play-for-x">An Alternative to Rolling Dice in Tabletop RPGs</a></p>
<p class="name">Play for X</p>
</div>
</div>
<div class="span6">
<div class="small_project coding">
<p class="description"><a href="distributed-speakers">Free Ad-Hoc Sound Systems</a></p>
<p class="name">Project idea: Distributed Speakers</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<div class="small_project writing">
<p class="description"><a href="tamari">Ever heard of a Tamari Lattice?</a></p>
</div>
</div>
<div class="span6">
<div class="small_project writing">
<p class="description"><a href="language-for-games">If you were writing a language for game dev, what unorthodox features would you include?</a></p>
</div>
</div>
<div class="span3">
<div class="small_project writing">
<p class="description"><a href="oauth">I wrote about how I use oauth to make tumblr bots.</a></p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span4">
<div class="small_project art">
<p class="description"><a href="miscellany#guitar">I try to strum a guitar now and then. Spoiler: there are no recordings.</a></p>
</div>
</div>
</div>
</div>
<div class="span4">
<div class="about">
<h1>Hi! I'm Shoofle. I'm a lot of person.</h1>
<p>I graduated from the University of Virginia in 2012, majoring in math and physics with a minor in computer science. I'm really good at math. I'm pretty good at programming.</p>
<p>I've always loved to draw, but never focused enough time on it. I'm an intensely creative person and I like to think that this comes out in everything I do - and I'm trying harder every day.</p>
<p>I'm a passionate advocate for feminism, transgender rights, queer rights, and social justice in general.</p>
<p><span class="label label-important"><i class="icon-warning-sign"></i>Warning!</span> This website is still very much under construction. I'm in the process of figuring out how I want this website to look and function.</p>
</div>
</div>
</div>
</body>
</html>

32
pure_flask.py Normal file
View File

@ -0,0 +1,32 @@
import os
from os.path import join, isfile
from flask import Flask, render_template, url_for, redirect, send_from_directory
from articles import bloop
app = Flask(__name__)
app.template_folder = ""
#server_directory = "/home/shoofle/auriga/"
#project_directory = join(server_directory, "pages")
#guitar_directory = join(server_directory, "guitar")
@app.route("/favicon.<extension>")
def favicon(extension=None):
return send_from_directory(join(app.root_path, "static"), "favicon.png", mimetype="image/png")
@app.route("/guitar_tab/")
@app.route("/guitar_tab/<file_name>")
def guitar_display(file_name=None):
guitar_files = [ f for f in os.listdir(guitar_directory) if isfile(join(guitar_directory, f)) and "html" not in f ]
if file_name in guitar_files:
with open(join(guitar_directory, file_name)) as tab:
c = unicode(tab.read(), "utf-8")
return render_template("guitar/render_tab.html", contents=c)
return render_template("guitar/default.html", guitar_files=guitar_files)
app.register_blueprint(bloop)
if __name__ == "__main__":
app.debug = True
app.run()

BIN
pure_flask.pyc Normal file

Binary file not shown.

View File

@ -1,16 +0,0 @@
# This is my server! It's not much, but it's mine.
It used to run on flask, but now it is just static html to be served through nginx. Who needs to actually write server code, right? :(
It's still in progress, and completely broken at the moment.
Okay this has been updated towards being statically generated and served by nginx (or whatever stat
ic web server you like). I have made a fancy schmancy STATIC SITE GENERATOR in `generate.ignore.py`
, which basically just renders jinja templates for appropriate files! it's pretty self-explanatory,
use -h to get help on the args, it's a great time.
shooflenet.service is no longer necessary because this is all run through nginx
nginx needs to be updated to handle -/_ inconsistencies in file names
no! the generator needs to be updated to output the - folders. or both - and _ folders. or symlink it. oor i dno't evne know
from this folder: `pipenv run python3 generate.ignore.py --input . --output /var/www/shoofle.net/` i think will generate it to where we want

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 KiB

1
static/flot Submodule

@ -0,0 +1 @@
Subproject commit 0360316eb5026abcff7e8172e0825e700fb1d804

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 KiB

1
static/html.vim Symbolic link
View File

@ -0,0 +1 @@
/home/shoofle/.vim/indent/html.vim

View File

@ -62,20 +62,6 @@ figcaption {
.file-tree, .file-tree ul { list-style: none; }
/* default styling for tables: 1em corner-radius on the whole table, plus 1px solid black between them? */
table {
border-collapse: separate;
border: 1px solid black;
border-radius: 1em;
border-spacing: 0;
max-width: 70%;
}
table td { padding: 0.5em 1.5em 0.5em 1.5em; }
thead th { border-bottom: 1px solid black; }
tr td { border-bottom: 1px solid black; }
tr:last-child td { border-bottom: none; }
td { border-right: 1px solid black; }
td:last-child { border-right: none; }
/* background and color coding to differentiate URLs and filepaths from surrounding text. */
.filename, .url {