diff --git a/.jekyll-metadata b/.jekyll-metadata
index 4a38cc3..087df6f 100644
Binary files a/.jekyll-metadata and b/.jekyll-metadata differ
diff --git a/_plugins/video_tag.rb b/_plugins/video_tag.rb
new file mode 100644
index 0000000..2d35fac
--- /dev/null
+++ b/_plugins/video_tag.rb
@@ -0,0 +1,61 @@
+# Title: Simple Video tag for Jekyll
+# Author: Brandon Mathis http://brandonmathis.com
+# Description: Easily output MPEG4 HTML5 video with a flash backup.
+#
+# Syntax {% video url/to/video [width height] [url/to/poster] %}
+#
+# Example:
+# {% video http://site.com/video.mp4 720 480 http://site.com/poster-frame.jpg %}
+#
+# Output:
+#
+#
+
+module Jekyll
+
+ class VideoTag < Liquid::Tag
+ @video = nil
+ @poster = ''
+ @height = ''
+ @width = ''
+
+ def initialize(tag_name, markup, tokens)
+ @videos = markup.scan(/((https?:\/\/|\/)\S+\.(webm|ogv|mp4)\S*)/i).map(&:first).compact
+ @poster = markup.scan(/((https?:\/\/|\/)\S+\.(png|gif|jpe?g)\S*)/i).map(&:first).compact.first
+ @sizes = markup.scan(/\s(\d\S+)/i).map(&:first).compact
+ super
+ end
+
+ def render(context)
+ output = super
+ types = {
+ '.mp4' => "type='video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"'",
+ '.ogv' => "type='video/ogg; codecs=theora, vorbis'",
+ '.webm' => "type='video/webm; codecs=vp8, vorbis'"
+ }
+ if @videos.size > 0
+ video = ""
+ else
+ "Error processing input, expected syntax: {% video url/to/video [url/to/video] [url/to/video] [width height] [url/to/poster] %}"
+ end
+ end
+
+ def poster
+ "poster='#{@poster}'" if @poster
+ end
+
+ def sizes
+ attrs = "width='#{@sizes[0]}'" if @sizes[0]
+ attrs += " height='#{@sizes[1]}'" if @sizes[1]
+ attrs
+ end
+ end
+end
+
+Liquid::Template.register_tag('video', Jekyll::VideoTag)
diff --git a/_posts/2024/2024-05-17-refactoring-with-purpose.md b/_posts/2024/2024-05-17-refactoring-with-purpose.md
new file mode 100644
index 0000000..85802a1
--- /dev/null
+++ b/_posts/2024/2024-05-17-refactoring-with-purpose.md
@@ -0,0 +1,229 @@
+---
+title: "Improving JsonSchema.Net (Part 2)"
+date: 2024-05-17 09:00:00 +1200
+tags: [json-schema, architecture, performance, learning]
+toc: true
+pin: false
+---
+
+Over the last few posts, I've gone over some recent changes to my libraries that work toward better performance by way of reducing memory allocations.
+
+In this post, I'd like to review some changes I made internally to _JsonSchema.Net_ that helped the code make more sense while also providing some of the performance increase.
+
+## The sad state of things
+
+In version 6 and prior, analysis of schemas was performed and stored in code that was strewn about in many different places.
+
+- `JsonSchema` would assess and store a lot of its own data, like base URI, dialect, and anchors.
+- There were extension methods for various lookups that I had to do a lot, and the static class that defined the methods had private static dictionaries to cache the data.
+ - Keyword `Type` and instance to keyword name (e.g. `TitleKeyword` -> "title")
+ - Whether a keyword supported a given JSON Schema version (e.g. `prefixItems` is only 2020-12)
+ - Keyword priority calculation and lookup (e.g. `properties` needs to run before `additionalProperties`)
+ - Whether a keyword produced annotations that another keyword needed (e.g. `unevaluatedProperties` depends on annotations from `properties`, even nested ones)
+- The code to determine which keywords to evaluate was in `EvaluationOptions`.
+- But the code to determine which keywords were supported by the schema's declared meta-schema was in `EvaluationContext`.
+
+Yeah, a lot of code in places it didn't need to be. Moreover, a lot of this was performed at evaluation time.
+
+It was time to fix this.
+
+## A better way
+
+About a month ago, I ran through an experiment to see if I could make a JSON Schema library (from scratch) that didn't have an object model. This came out of [reworking my JSON Logic library](/posts/logic-without-models) to do the same.
+
+> The results of this experiment can be found in the [`schema/experiment-modelless-schema`](https://github.com/gregsdennis/json-everything/tree/schema/experiment-modelless-schema) branch, if you want to have a look. There's a new static `JsonSchema.Evaluate()` method that calls each keyword via a new `IKeywordHandler` interface. While the single run performance is great, it can't compete at scale with the [static analysis](/posts/new-json-schema-net) that was introduced a few versions ago.
+{: .prompt-info }
+
+In building the experiment, I had to rebuild things like the schema and keyword registries, and I discovered that I could do a lot of the analysis that yielded the above information at registration time. This meant that I wasn't trying to get this data during evaluation, which is what lead to the stark increase in performance for single evaluations.
+
+I had decided not to pursue the experiment further, but I had learned a lot by doing it, so it wasn't a waste.
+
+> Sometimes rebuilding something from scratch can give you better results, even if it just teaches you things.
+{: .prompt-tip }
+
+So let's get refactoring!
+
+
+{% video /assets//video/matrix-we-got-a-lot-to-do.mp4 798 %}
+
We got a lot to do. We gotta get to it. - The Matrix, 1999
+
+
+## Managing keyword data
+
+I started with the keyword registry. I wanted to get rid of all of those extensions and just precalculate everything as keywords were registered.
+
+In its current state, `SchemaKeywordRegistry` contained three different dictionaries:
+
+- keyword name → keyword type
+- keyword type → instance (for keywords that need to support null values, like `const`; this resolves some serializer problems)
+- keyword type → keyword `TypeInfoResolver` (supports Native AOT)
+
+In the keyword extensions, I then had more dictionaries:
+
+- keyword type → keyword name (reverse of what's in the registry)
+- keyword type → evaluation group (supporting priority and keyword evaluation order)
+- keyword type → specification versions
+
+That's a lot of dictionaries! And I needed them all to be concurrent!
+
+### Consolidation
+
+First, I need to consolidate all of this into a "keyword meta-data" type. This is what I came up with:
+
+```c#
+class KeywordMetaData
+{
+ public string Name { get; }
+ public Type Type { get; }
+ public long Priority { get; set; }
+ public bool ProducesDependentAnnotations { get; set; }
+ public IJsonSchemaKeyword? NullValue { get; set; }
+ public SpecVersion SupportedVersions { get; set; }
+ public JsonSerializerContext? SerializerContext { get; }
+
+ // constructor contains most of the keyword inspection as well.
+}
+```
+
+This single type stores all of the information for a single keyword that was stored in the various dictionaries listed above.
+
+### Access
+
+Second, I need a way to store these so that I can access them in multiple ways. What I'd really like is a current dictionary that allows access to items using multiple keys. There are probably (definitely) a number of ways to do this.
+
+My [approach](https://github.com/gregsdennis/json-everything/blob/master/src/JsonSchema/MultiLookupConcurrentDictionary.cs) was to wrap a `ConcurrentDictionary