Front-end generation for A2, trained on 202 examples
of Claude Opus web design from
glamour-opus.
Given a design brief, A2 with this adapter writes a complete, self-contained component — semantic
HTML, a full stylesheet, and any JavaScript it needs — instead of the thin sketch the base model
produces.
This is the 2-epoch checkpoint, not the final one. See Which checkpoint below; the 4-epoch
weights are worse and the 3-epoch weights fail in a specific way.
Measured
22 held-out briefs, never seen in training. Everything below is scored by parsing and executing —
tags balanced by a real HTML parser, CSS through tinycss2, JavaScript through node --check —
never by asking a model whether the page looks good.
base A2
+ Glanz
Opus (ground truth)
structure + balanced, of completed generations
19/20
18/18
22/22
classes with a matching CSS rule
0.776
0.944
0.964
self-contained (no external assets)
21/22
22/22
22/22
CSS rules, mean
10.5
43.0
—
output size, mean chars
4,343
14,564
12,911
repetition collapses
2/22
0/22
0/22
The class-coverage number is the one that matters most. A model that has learned the shape of
these pages without the substance emits rich markup styled by nothing; at 0.944 against a 0.964
ceiling, the extra markup is genuinely styled. Output length lands on the training distribution
rather than overshooting it.
It also fixes a base failure mode. Two of the 22 base generations degenerate into a repeated
line — <div class="rule"></div> emitted until the token cap, a 0.05 unique-line ratio. The
adapter has none, and its worst case is 0.51. This was not a training target; it is a side effect.
Nothing broke
A behavioural probe of the capabilities A2 is actually for — tool calling, abstention, identity,
Luna persona gating — run at temperature 0 before and after:
base A2
+ Glanz
behavioural checks passed
7/9
9/9
The two gained cases are the same one: asked "Where is shipment TRK-88213 right now?", base A2
replies "if you provide the tracking number, I can give you details" — it will not lift the
argument out of the prompt, with or without thinking enabled. With the adapter it calls
lookup_shipment(tracking="TRK-88213") correctly.
This is reported as observed, not claimed as a feature. It is two cases, and there is no
obvious mechanism by which web-design data teaches argument extraction beyond r=32 touching all
seven projections. Do not plan around it.
Tool calling, parallel calls, abstention, the Schneewolf Labs identity under adversarial pressure,
and Luna's gating behind her system prompt are all unchanged.
Usage
You must disable thinking. A2's template ends its generation prompt with <think>\n. Training
used enable_thinking=False, which makes the template emit a pre-closed <think>\n\n</think>\n\n
block instead, so the model was never trained to write HTML from inside an open think block.
Generating with thinking on puts it in a state this adapter has never seen.
python
1from peft import PeftModel
2from transformers import AutoModelForCausalLM, AutoTokenizer
34tok = AutoTokenizer.from_pretrained("schneewolflabs/A2")5model = AutoModelForCausalLM.from_pretrained("schneewolflabs/A2", dtype="bfloat16",6 device_map="auto")7model = PeftModel.from_pretrained(model,"schneewolflabs/Glanz-A2-LoRA")89SYSTEM =(10"You are a front-end designer. Given a design brief, produce a single self-contained web "11"component: semantic HTML, a complete stylesheet, and any JavaScript it needs. Output three "12"fenced code blocks in order — html, css, js — and nothing else. Omit the js block if the "13"component needs no script. Use no external assets, frameworks, or network requests."14)15enc = tok.apply_chat_template(16[{"role":"system","content": SYSTEM},17{"role":"user","content":"Design a pricing table for a small SaaS. Dark theme."}],18 add_generation_prompt=True, enable_thinking=False,# <- required19 return_tensors="pt", return_dict=True).to(model.device)20print(tok.decode(model.generate(**enc, max_new_tokens=16000, do_sample=False)[0]))
Allow a large token budget. These generations average 14,564 characters. At n_predict=7000,
6 of 22 briefs were cut off mid-page; at 16,000, 4 still were. A truncated page looks exactly like
a malformed one — the last fence never closes — so budget generously or you will measure your own
cap.
Which checkpoint
Four epochs were trained and all four evaluated. Held-out loss on 22 rows against loss on an
equal-sized slice of training rows:
checkpoint
held-out
train
gap
base
0.7012
0.7065
−0.005
epoch 1
0.5632
0.5269
0.036
epoch 2 (this one)
0.5339
0.4406
0.093
epoch 3
0.5329
0.3821
0.151
epoch 4
0.5457
0.3686
0.177
Held-out loss bottoms at epoch 3 and rises at epoch 4 while training loss keeps falling — the
4-epoch weights are overfit, and they are what a naive save_model at the end of training would
have shipped.
Between epochs 2 and 3 the loss difference is 0.001, which is nothing, and the memorisation gap
differs by 60%, which is not. The design eval broke the tie decisively: epoch 3 scores 0.966 on
class coverage — the best of any variant — but collapses into repetition on 2 of 22 briefs, the
same failure the base model has and epoch 2 eliminates. Buying 0.02 of coverage with two
catastrophic loops is a bad trade.
Training loss fell 0.762 → 0.357 with no rebound. max_length is 8192 because the longest
rendered sample is 6,672 tokens and 4096 would have truncated 180 of 224 — teaching the model to
stop mid-page would have been worse than not training.
6 of 200 source rows had syntactically invalid JavaScript, truncated mid-token by whatever cap
the original Glamour capture ran under. Those blocks were dropped after a node --check pass
rather than trained on. The block was dropped rather than the row, because js is the last field
generated, so a truncated row's HTML and CSS are complete and still worth learning from.
Limitations
Not Opus. Coverage and structure reach the ceiling; the design does not. This model
produces well-formed, densely styled, plausible pages, not award-winning ones.
Single provenance. 224 briefs from one generator in one aesthetic register. Expect the
house style of that corpus.
The system prompt matters. Trained under one specific framing; the behaviour does carry to
bare prompts (a no-system-prompt design request grew from 435 to 2,996 characters) but the
measured numbers are all under the prompt shown above.
Scale untested. Everything here is at --lora-scaled 1.0. Lower scales were not swept.