diff --git a/.gitignore b/.gitignore
index fb7a88e1..63bcf3f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,9 @@ build-iPhoneSimulator/
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
.DS_Store
+
+# SimpleCov files
+coverage/
+
+# Claude files
+.claude/
diff --git a/.rspec b/.rspec
new file mode 100644
index 00000000..5be63fcb
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,2 @@
+--require spec_helper
+--format documentation
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 00000000..fcdb2e10
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+4.0.0
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 00000000..e0314a74
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,156 @@
+# Solution Design
+
+Extract a Google knowledge-graph paintings carousel from a saved results page into a JSON array. The HTML file is parsed directly — **no additional HTTP requests**.
+
+## Approach summary
+
+The carousel is parsed **structurally, not by CSS class**, because Google rotates its obfuscated class names (verified: van-gogh's classes are 100% absent from the newer Picasso page, where a class-based parser returns **0** items). Three small objects do the work:
+
+- **`Parser`** — finds each item as a `stick=` search link that wraps an ``, then reads `name` (the img `alt`), `extensions` (caption rows after the title), and `link` (the absolutized `href`).
+- **`ImageResolver`** — supplies the image without any network call: inline base64 (injected by `_setImagesSrc` scripts, keyed by img `id`) or the lazy `data-src` thumbnail URL.
+- **`Artwork` / `SearchResult`** — render the `{ "artworks": [...] }` output.
+
+Validated against three real pages: van-gogh (47 items, exact match to the SerpApi-provided example), Picasso (45 items), and Leonardo da Vinci (47 items) — the latter two via generated, spot-verified snapshots plus independent counts.
+
+## 1. Output schema
+
+```json
+{
+ "artworks": [
+ {
+ "name": "The Starry Night",
+ "link": "https://www.google.com/search?...",
+ "image": "data:image/jpeg;base64,...", // or an https URL, or null
+ "extensions": ["1889"] // optional — omitted when there is no date
+ }
+ ]
+}
+```
+
+- `name` — String. The painting title.
+- `link` — String. Absolute Google search URL.
+- `image` — String or null. Inline base64 data URI, a `gstatic` thumbnail URL, or `null` if neither is present in the file.
+- `extensions` — Array of String. Models SerpApi's field; on this page it holds at most the date (e.g. `"1889"`). **The key is omitted entirely when no date is present** (4 of 47 items), matching `van-gogh-expected-array.json`.
+
+## 2. How an item is recognized
+
+Each painting is one carousel cell — a single `` anchor, annotated below. The parser matches on its **structure**, never on class names, which change over time (Section 4):
+
+```html
+
+ ; alt = name -->
+ id="_L_FkZ...63"
+ data-src="https://encrypted-tbn0.gstatic..."
+ src="data:image/gif;base64,...1x1 placeholder..."/>
+
+
+```
+
+These four signals — a `stick=` link wrapping an ``, the `alt`, the caption leaf rows, and the img `id`/`data-src` — appear on every capture. Only the class names differ between pages, which is exactly why we don't match on them:
+
+| Structural role | van-gogh class | picasso class | leonardo class |
+| --------------- | -------------- | ------------- | -------------- |
+| item wrapper | `iELo6` | `TILZre` | `TILZre` |
+| thumbnail img | `taFZJe` | `pHjwVc` | `pHjwVc` |
+| caption box | `KHK6lb` | `Y5eSNd` | `Y5eSNd` |
+| title row | `pgNMRc` | `yfEcJe` | `yfEcJe` |
+| date row | `cxzHyb` | `DWyOHb` | `DWyOHb` |
+
+Picasso and Leonardo share one capture generation (identical classes); van-gogh is an older one. The structural parser handles all three unchanged.
+
+### Field sources
+
+| Field | Source | Handling |
+| ------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------ |
+| `name` | `img@alt` | Collapse internal whitespace/newlines. |
+| `extensions` | caption leaf divs after the title (e.g. `1937`) | Array of the remaining rows; `[]` when absent or blank (key omitted at render time). |
+| `link` | `a@href` | Relative `/search?...` → prepend `https://www.google.com`; decode HTML entities. |
+| `image` | see Section 3 (ImageResolver) | The img's own `src` is a throwaway 1×1 gif — always ignored. |
+
+### Image delivery: two mechanisms
+
+1. **Inline base64.** The `` has an `id` and no `data-src`. The real JPEG is injected by a script block elsewhere in the document:
+
+```js
+(function () {
+ var s = "data:image/jpeg;base64,/9j/4AAQ...";
+ var ii = ["_L_FkZ4qlAtyDwbkP49Pj0QU_63"];
+ var r = "";
+ _setImagesSrc(ii, s, r);
+})();
+```
+
+Each `_setImagesSrc` block pairs one base64 string (`s`) with a single image id (the sole `ii` entry); the resolver maps that id to the base64. The base64 lives inside a JS string literal where the `=` padding is hex-escaped as `\x3d` (the only escape seen across captures); it must be unescaped to match the expected output.
+
+2. **Lazy-loaded URL.** The `` carries `data-src="https://encrypted-tbn{0..3}.gstatic.com/images?q=tbn:..."`. We record the URL string as-is (no fetch).
+
+Counts confirm the model against `files/van-gogh-expected-array.json`: 47 carousel items = 8 base64 + 39 `gstatic` URLs = 47 artworks.
+
+## 3. Entities
+
+1. **`ImageResolver`** — Pre-scans the document's `
+
+
+
+
+
+ Leonardo da Vinci · Mona Lisa · The
+ Virgin and Child with St Anne · The Virgin and
+ Child with Saint Anne and the Infant Saint
+ John the Baptist · Virgin of the ...Read more
+
+ Leonardo di ser
+ Piero da Vinci was
+ an Italian
+ polymath of the
+ High Renaissance
+ who was active as
+ a painter,
+ draughtsman,
+ engineer,
+ scientist,
+ theorist,
+ sculptor, and
+ architect.Wikipedia
+
+ https://www.britannica.com
+ › Visual Arts › Painting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mona Lisa (c. 1503–19) · Last Supper
+ (c. 1495–98) · Vitruvian Man (c. 1498) · Head
+ of a Woman (c. 1492–1501) · Self Portrait (c.
+ 1517–18) · Salvator Mundi (c. 1500).Read more
+
+ TIL despite being one of the most famous
+ painters in history,
+ only 15 paintings of Leonardo da Vinci are
+ known to exist. The small number of ...Read more
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Images
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10 Famous Artworks by
+ Leonardo da Vinci |
+ Britannica
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Britannica
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Leonardo Da Vinci
+ Paintings: A Brief Guide To
+ 8 Famous Works ...
+
+ TIL only eight major
+ works that exist today
+ are universally
+ attributed to Leonardo
+ Da Vinci.
+
+
+
+
+
+
+
+
+
+
+ Reddit ·
+
+
+
+
+ r/todayilearned
+
+ · 100+
+ comments
+
+
+
+ · 3 months
+ ago
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the Mona
+ Lisa · The
+ Last Supper
+ · Virgin of
+ the Rocks
+ (the Louvre)
+ · The
+ Adoration of
+ the Magi ·
+ Saint Jerome
+ in the
+ Wilderness ·
+ The Virgin
+ an...More
+
+ https://cenacolovinciano.org
+ › ... › Museum › The works
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The Last Supper, painted between 1494 and
+ the beginning of 1498, is considered perhaps the most important
+ mural painting in the world.Read more
+
+ 31 works of art Artwork. Wreath of
+ Laurel, Palm, and Juniper a Young Lady
+ Portrait. Gift of Dian Woodner, 2022.84.1. The
+ Armand Hammer Collection,
+
+ Mona Lisa's enduring mystery. Users obsess over hidden codes,
+ secret messages, identity theories,
+ and the enduring mystery behind the
+ world's most famous painting.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The Last Supper secrets. Social media reveals hidden music
+ notes, AI-decoded symbols, and the
+ painting's dramatic history of
+ deterioration and restoration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Salvator Mundi price debate. The $450m painting sparks debate
+ over authenticity, its mysterious
+ disappearance, and whether art is
+ just for money laundering.
+
+ his visual art ranges from
+ calligraphic designs to politically charged
+ works such as Iraq Typography and
+ Immortal Martyr, 4 mythological
+ painting
+