/* Original styling throughout. No third-party art, icons, screenshots,
   logos or trademarked names. Type is Marcellus, the freely licensed
   stand-in for the glyphic serif a shipped MMO client uses for its
   interface; see fonts/LICENSE.md. */

@font-face {
  font-family: 'Marcellus';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/fonts/marcellus-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Marcellus';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/fonts/marcellus-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+1E00-1E9F, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Marcellus SC';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/fonts/marcellussc-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Cinzel';
  font-style: normal; font-weight: 500 700; font-display: swap;
  src: url('/static/fonts/cinzel-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}

:root {
  --gold:        #c9a227;
  --gold-lit:    #f0d98a;
  --gold-deep:   #6e5410;
  --ink:         #f1e5c8;
  --ink-soft:    #b9a986;
  --frame-1:     #2a2418;
  --frame-2:     #120f0a;
  --glass:       rgba(8, 10, 8, 0.72);
  --crimson:     #7c1f18;
  --crimson-lit: #b8392c;
  --ok:          #7ec46b;
  --bad:         #e0655a;
  --amber:       #e0b155;
  /* Sampled off the client, not chosen: the word "Back" in its own button
     reads (246, 201, 68) at the glyph centres and the High-Definition Models
     label reads (228, 194, 65). One colour, used for both. */
  --wow-gold:    #f6c944;
}

* { box-sizing: border-box; }

/* The `hidden` attribute is only a default `display: none`, so ANY display
   rule beats it. A `display: block` on the old stand-in kept it
   silhouette painted over the live 3D canvas even with hidden set. This is
   the third override of this shape in this file; make it non-negotiable
   once rather than remembering it at each call site. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: 'Marcellus', Georgia, 'Times New Roman', serif;
  color: var(--ink);
  overflow: hidden;
  /* The loading state, and the scene's own colour.

     rgb(0, 56, 57) is not picked: it is the exact value of the backdrop
     plane's texture, flat across all 128x128 texels, and the same value the
     renderer clears to. So the page before the scene arrives is the colour of
     the sky the scene ends up showing, and the 7.4MB it takes to get there
     passes without anything else appearing and being replaced.

     What used to be here was #071110 behind a vector silhouette and a CSS
     gradient. They existed so the committed demo had something without the
     extracted assets, and the cost was that every refresh showed them -- they
     are what the page IS until the real scene loads. */
  background: rgb(0, 56, 57);
}

.stage {
  position: relative;
  z-index: 1;
  height: 100%;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: 1rem 1.25rem 1.25rem;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- shared metal frame -------------------------------------------------

   A 9-slice border-image built from an inline SVG. Corner ornaments stay a
   fixed size while the edges repeat, which is what keeps the brackets crisp
   at any panel size -- a plain `border` cannot do corners at all, and a
   stretched background image distorts them. */

:root {
  --ornate-frame:
    url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='72' height='72'>\
<defs><linearGradient id='m' x1='0' y1='0' x2='0' y2='1'>\
<stop offset='0' stop-color='%23f4e0a4'/><stop offset='.38' stop-color='%23b08f36'/>\
<stop offset='.52' stop-color='%23604a13'/><stop offset='1' stop-color='%23cbae63'/>\
</linearGradient>\
<linearGradient id='v' x1='0' y1='0' x2='1' y2='0'>\
<stop offset='0' stop-color='%23f4e0a4'/><stop offset='.38' stop-color='%23b08f36'/>\
<stop offset='.52' stop-color='%23604a13'/><stop offset='1' stop-color='%23cbae63'/>\
</linearGradient></defs>\
<rect x='0' y='4' width='72' height='7' fill='url(%23m)'/>\
<rect x='0' y='61' width='72' height='7' fill='url(%23m)'/>\
<rect x='4' y='0' width='7' height='72' fill='url(%23v)'/>\
<rect x='61' y='0' width='7' height='72' fill='url(%23v)'/>\
<rect x='11' y='11' width='50' height='50' fill='none' stroke='%23120d06' stroke-width='2'/>\
<g fill='url(%23m)'>\
<path d='M1 1h30l-8 8H9v14l-8 8z'/>\
<path d='M71 1H41l8 8h14v14l8 8z'/>\
<path d='M1 71h30l-8-8H9V49l-8-8z'/>\
<path d='M71 71H41l8-8h14V49l8-8z'/>\
</g>\
<g fill='%23f4e0a4'>\
<circle cx='7.5' cy='7.5' r='2.6'/><circle cx='64.5' cy='7.5' r='2.6'/>\
<circle cx='7.5' cy='64.5' r='2.6'/><circle cx='64.5' cy='64.5' r='2.6'/>\
</g></svg>");
}

.ornate {
  border: 14px solid transparent;
  border-image: var(--ornate-frame) 24 round;
}


.framed {
  border: 1px solid var(--gold-deep);
  border-radius: 3px;
  background:
    linear-gradient(180deg, rgba(60, 52, 34, 0.9), rgba(18, 15, 10, 0.94));
  box-shadow:
    inset 0 1px 0 rgba(240, 217, 138, 0.18),
    inset 0 0 22px rgba(0, 0, 0, 0.6),
    0 2px 10px rgba(0, 0, 0, 0.5);
}

/* ---- view tools --------------------------------------------------------- */

/* The client groups these 3 + 2, not five evenly. */
.viewtools {
  position: absolute; top: 1rem; left: 1.25rem;
  display: flex; gap: 1.1rem; z-index: 4;
}
.viewtools__group { display: flex; gap: 5px; }

.viewtool {
  width: 30px; height: 28px;
  font: inherit; font-size: 0.8rem; line-height: 1;
  color: var(--gold-lit);
  border: 1px solid var(--gold-deep);
  border-radius: 3px;
  background: linear-gradient(180deg, #3b3320, #17130c);
  box-shadow: inset 0 1px 0 rgba(240, 217, 138, 0.25), 0 2px 5px rgba(0,0,0,0.5);
  opacity: 0.9;
}

/* ---- name plate --------------------------------------------------------- */

.nameplate {
  justify-self: center;
  width: min(38rem, 92%);
  text-align: center;
  z-index: 4;
}

/* The label sits ON the frame's top rail in the client, not floating
   above it as separate text. */
.nameplate__title {
  font-family: 'Marcellus', Georgia, serif;
  font-size: 0.86rem;
  letter-spacing: 0.06em;
  color: var(--ink);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95);
  margin: -6px 0 5px;
}

.nameplate__frame {
  /* A grid rather than a stack, so the title can be centred on the FIELDS
     instead of on the frame. The dice button sits to their left and the
     availability indicator to their right, so the fields' centre is not the
     frame's centre, and a title centred on the frame reads visibly left of
     the boxes it labels. Spanning the two field columns hands that
     arithmetic to the browser -- and keeps it right when the availability
     indicator appears and changes the width on the right. */
  display: grid;
  grid-template-columns: auto 1fr 1fr auto;
  align-items: center;
  column-gap: 0.5rem;
  /* The frame is what its corner brackets are positioned against, as the
     client's NameChoiceFrame is the parent its Frame_TL / Frame_TR anchor to.
     Say so explicitly. It used to happen only as a side effect of the
     drop-shadow filter -- any filter makes an element the containing block
     for absolute children -- and removing the shadow sent both brackets to
     the corners of the page. */
  position: relative;
  padding: 2px 8px 8px;
  border: 14px solid transparent;
  border-image: var(--ornate-frame) 24 round;
  background: linear-gradient(180deg, rgba(52, 44, 27, 0.95), rgba(14, 11, 7, 0.97));
  background-clip: padding-box;
  filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.6));
}

/* The wrapper stays in the markup for readability and drops out of layout,
   so its children are the grid's own items. The visually-hidden labels are
   absolutely positioned and so create no tracks. */
.nameplate__inputs { display: contents; }

.nameplate__title { grid-row: 1; grid-column: 2 / 4; }
.nameplate__dice { grid-row: 2; grid-column: 1; }
.nameplate__field { grid-row: 2; }
.availability { grid-row: 2; grid-column: 4; }

.nameplate input {
  flex: 1 1 0;
  min-width: 0;
  padding: 0.5rem 0.7rem;
  font-family: inherit;
  font-size: 1.05rem;
  text-align: center;
  color: var(--ink);
  background: linear-gradient(180deg, #0a0c09, #14170f);
  border: 1px solid #443a1e;
  border-radius: 3px;
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.8);
  transition: box-shadow 140ms ease, border-color 140ms ease;
}

.nameplate input::placeholder { color: rgba(185, 169, 134, 0.42); }

.nameplate input:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow:
    inset 0 2px 6px rgba(0, 0, 0, 0.8),
    0 0 0 1px var(--gold),
    0 0 14px rgba(201, 162, 39, 0.55);
}

.nameplate__dice,
.nameplate__clear {
  flex: none;
  width: 30px; height: 30px;
  font: inherit; font-size: 1rem; line-height: 1;
  border: 1px solid var(--gold-deep);
  border-radius: 3px;
  background: linear-gradient(180deg, #3b3320, #17130c);
  color: var(--gold-lit);
  box-shadow: inset 0 1px 0 rgba(240, 217, 138, 0.2);
}
.nameplate__clear { color: var(--crimson-lit); }
.nameplate__dice:hover,
.nameplate__clear:hover { filter: brightness(1.3); }

.nameplate__locale {
  margin-top: 0.4rem;
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
.nameplate__locale code {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 0.92em;
  color: var(--gold-lit);
}

/* ---- the placeholder model ----------------------------------------------- */

.model {
  position: absolute;
  left: 0; right: 0;
  top: 7rem; bottom: 4.6rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  text-align: center;
  z-index: 1;
  pointer-events: none;
}


/* The 3D viewer occupies the same slot as the silhouette. It hides itself
   when no local asset is present, so the committed demo is unaffected. */
.model__canvas {
  width: min(48vw, 560px);
  height: 100%;
  pointer-events: auto;
}
.model__canvas canvas { display: block; width: 100%; height: 100%; }

.model__caption {
  margin: 0.4rem 0 0;
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(185, 169, 134, 0.45);
}

/* ---- appearance panel ------------------------------------------------------ */

.panel {
  position: absolute;
  top: 6rem; right: 1.1rem;
  /* The reference panel is about 19% of the screen width; at 16.5rem this was
     nearer 26%, which read as a web sidebar rather than the client's panel.
     14rem is 15% at this viewport -- a rem wider than the rows needed, because
     the verdict has to be readable in it and "Name Unavailable" over two lines
     with the reason broken over three is not a result anyone reads. */
  width: 14rem;
  /* No min-height. The frame is the message now, so it is exactly as tall as
     what it has to say -- a fixed floor left a 384px tile around 273px of
     text. The width stays fixed: the client's panel has one, and a tile that
     also changed width would jump sideways every time a reason code came back
     a different length. */
  padding: 0.5rem 0.7rem 0.7rem;
  z-index: 3;
  border: 14px solid transparent;
  border-image: var(--ornate-frame) 24 round;
  /* The client's panel is warm brown, not neutral black. */
  background: linear-gradient(180deg, rgba(48, 38, 24, 0.94), rgba(22, 17, 10, 0.96));
  background-clip: padding-box;
  filter: drop-shadow(0 8px 22px rgba(0, 0, 0, 0.65));
}

/* The verdict IS the frame: same element, so .panel above carries the box and
   this carries only what pass and fail change. No inner card and no coloured
   left edge -- the edge was a border, and this element's border is already the
   nine-sliced frame art. The mark and the heading colour do that job. */



.verdict__head { display: flex; align-items: center; gap: 0.5rem; }

.verdict__mark {
  display: grid; place-items: center;
  flex: none; width: 1.15rem; height: 1.15rem;
  font-size: 0.72rem; color: #0d100c;
  border-radius: 50%;
}
.verdict--ok  .verdict__mark { background: var(--ok); }
.verdict--bad .verdict__mark { background: var(--bad); }

.verdict__text {
  font-family: 'Marcellus SC', 'Marcellus', Georgia, serif;
  font-size: 0.9rem;
  letter-spacing: 0.04em;
  line-height: 1.2;
}
.verdict--ok  .verdict__text { color: var(--ok); }
.verdict--bad .verdict__text { color: var(--bad); }

.verdict__detail {
  margin: 0.45rem 0 0;
  font-size: 0.8rem;
  line-height: 1.35;
  color: var(--ink);
}

/* Stacked, not side by side. The row was a flex pair with a fixed-width term,
   which is fine across 23rem and leaves about 110px for the value inside the
   panel -- every reason code then breaks over four lines. */
.why { margin: 0.6rem 0 0; display: grid; gap: 0.45rem; font-size: 0.72rem; }
.why__row { display: block; }
.why dt {
  font-size: 0.6rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: rgba(185, 169, 134, 0.7);
  margin-bottom: 0.1rem;
}
.why dd { margin: 0; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.why code {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 0.84em;
  padding: 0.05em 0.3em; border-radius: 2px;
  background: rgba(201, 162, 39, 0.14); color: var(--gold-lit);
}
.why__note {
  margin: 0.55rem 0 0;
  font-size: 0.66rem; font-style: italic; line-height: 1.35;
  color: rgba(185, 169, 134, 0.5);
}

.hdmodels {
  position: absolute;
  right: 1.25rem; bottom: 3.6rem;
  z-index: 4;
  display: flex; align-items: center; gap: 0.4rem;
  font-size: 0.78rem;
  color: var(--ink);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95);
}
.hdmodels__box {
  display: grid; place-items: center;
  width: 15px; height: 15px;
  font-size: 0.7rem; line-height: 1;
  color: var(--gold-lit);
  border: 1px solid var(--gold-deep);
  background: rgba(10, 12, 9, 0.85);
}

/* ---- back / finish ------------------------------------------------------------ */

.chrome {
  position: absolute; bottom: 1.25rem;
  min-width: 10.5rem;
  padding: 0.62rem 1.4rem;
  z-index: 3;
  /* Centred by the box, not by text-align.

     Back and Finish had the same padding and font and came out 46px and 43px
     tall, with Back's text 14.5px from the top against 13.5px from the bottom.
     The cause is the arrow: U+25C0 is not in Marcellus SC, so it renders from
     a fallback whose taller line box inflates the button and shifts the
     baseline inside it. A flex row with an explicit line-height takes the
     decision away from whichever font the arrow lands in. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.45em;
  line-height: 1.25;
  /* letter-spacing puts its gap after the LAST letter as well, so the measured
     run is glyphs + one trailing space and centring it leaves the glyphs half
     a space to the left. Widening the left padding by a whole space moves the
     content box's centre half a space right, which cancels it exactly. */
  padding-left: calc(1.4rem + 0.09em);
  font-family: 'Marcellus SC', 'Marcellus', Georgia, serif;
  font-size: 0.9rem; letter-spacing: 0.09em;
  border-radius: 4px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
  transition: filter 120ms ease, transform 80ms ease;
}

.chrome__arrow {
  font-size: 0.72em;
  line-height: 1;
}

.chrome--back {
  left: 1.25rem;
  color: var(--ink);
  border: 1px solid #4d1512;
  background: linear-gradient(180deg, var(--crimson-lit), var(--crimson) 48%, #55130f);
  box-shadow: inset 0 1px 0 rgba(255, 210, 190, 0.25), 0 2px 0 #300d0a,
              0 5px 14px rgba(0, 0, 0, 0.5);
}

.chrome--finish {
  right: 1.25rem;
  color: var(--gold-lit);
  border: 1px solid var(--gold-deep);
  background: linear-gradient(180deg, #4a4026, #221c10 52%, #14100a);
  box-shadow: inset 0 1px 0 rgba(240, 217, 138, 0.22), 0 2px 0 #0c0a06,
              0 5px 14px rgba(0, 0, 0, 0.5);
}

.chrome:hover { filter: brightness(1.18); }
.chrome:active { transform: translateY(2px); }
.chrome:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--gold), 0 0 16px rgba(201, 162, 39, 0.7);
}
/* Empty fields leave Finish DISABLED in the client, which is a state the
   first pass of this mock simply did not have. */
.chrome[disabled] {
  color: rgba(185, 169, 134, 0.45);
  border-color: #3a3524;
  background: linear-gradient(180deg, #2b2a22, #191812 55%, #121109);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 2px 0 #0a0906;
  text-shadow: none;
}
.chrome[disabled]:hover { filter: none; }
.chrome[disabled]:active { transform: none; }

/* The panel used to be hidden below 880px, which is fine for seven decorative
   rows and not fine for the one thing on the page that reports a result. It
   stays, narrower. */
@media (max-width: 880px) {
  .panel { width: 11rem; top: 5rem; }
}

@media (max-width: 720px) {
  .panel { width: auto; left: 1rem; right: 1rem; top: auto; bottom: 5.4rem; }
}

@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/* ---- full-bleed backdrop ---------------------------------------------------
   With a real 3D scene behind the character the canvas cannot stay a column:
   a graveyard rendered inside 560px is not a backdrop. The class goes on
   <body> only once a scene has actually loaded, so the committed demo -- which
   ships no scene -- keeps the vector art and the column exactly as they are. */
body.has-backdrop .model {
  top: 0; bottom: 0; left: 0; right: 0;
}
body.has-backdrop .model__canvas {
  width: 100%; height: 100%;
}

/* ---- cursor ----------------------------------------------------------------
   Original art, not Blizzard's: a stylised armoured hand, bold enough to read
   at the 32px browsers reliably honour. It lives behind a custom property so
   a local build running against extracted assets can swap in the real one --
   interface/cursor/point.blp, converted to PNG -- with a single line:

     :root { --cursor-point: url('/static/cursor/point.png'); }

   The hotspot is the fingertip. `default` at the end covers a browser that
   refuses the image, so the pointer never disappears. */
:root {
  --cursor-hotspot: 13 2;
  --cursor-point: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cg%20stroke%3D%22%2312100a%22%20stroke-width%3D%221.5%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M7.2%2020.5%20L22.4%2019.6%20L23.4%2027.4%20L7.6%2028.4%20Z%22%20fill%3D%22%236b727c%22%2F%3E%3Cpath%20d%3D%22M7.2%2020.5%20L22.4%2019.6%20L22.75%2022.4%20L7.45%2023.4%20Z%22%20fill%3D%22%23c9a227%22%2F%3E%3Crect%20x%3D%228.4%22%20y%3D%2211.6%22%20width%3D%2213.2%22%20height%3D%229.2%22%20rx%3D%222.6%22%20fill%3D%22%238d959f%22%2F%3E%3Cpath%20d%3D%22M8.8%2014.2%20q-3.1%200.6-2.7%203.4%20q0.35%202.4%203%202.2%20z%22%20fill%3D%22%237d858f%22%2F%3E%3Cpath%20d%3D%22M11.4%203.4%20q2.3-2.1%204.5%200%20L16.5%2012.4%20L10.9%2012.4%20Z%22%20fill%3D%22%23aab3bd%22%2F%3E%3C%2Fg%3E%3Cpath%20d%3D%22M12.4%2015.4%20h7.2%20M12.6%2018.1%20h7.0%22%20stroke%3D%22%233a3f47%22%20stroke-width%3D%221.1%22%20stroke-linecap%3D%22round%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E");
}
body { cursor: var(--cursor-point) var(--cursor-hotspot), default; }

/* One cursor, everywhere.

   The client does not swap the pointer for a hand over a button, an I-beam
   over the name fields or a grab hand over the model -- it draws its own
   gauntlet and keeps it, and every native swap is a tell that this is a web
   page. `cursor` inherits, so letting these inherit from <body> is all it
   takes; the rules that set pointer / default / grab / grabbing individually
   are gone, so there is one place to change it rather than six. */
button, input, select, textarea, a, label, [role="button"] { cursor: inherit; }

/* The client keeps one cursor across the whole screen, including while
   turning the character, so these do not swap to grab/grabbing. */
.model__canvas,
.model__canvas:active,
.chrome,
.nameplate input { cursor: var(--cursor-point) var(--cursor-hotspot), default; }

/* ---- converted client art --------------------------------------------------
   Everything here is scoped to .has-ui-art, which app.js sets only after
   finding the converted textures. The repo ships none of them, so by default
   none of this applies and the CSS-only styling above stands unchanged.
   Run scripts/build_ui_assets.py against your own export to turn it on. */

/* The glue button, at the client's own proportions.

   The art is the v2 set, 493x128, and all three states share one geometry read
   off the pixels: a 4px soft edge, then the chamfered corner over 10px, the
   steel band at rows 7-11, and flat body from row 18 to 109. So the slice is
   18 on every side, which is the smallest cap that still contains the whole
   chamfer.

   Cap WIDTH is not the slice, though. The client draws these caps at about
   1.3x the texture -- its button chamfers over 13 rows against the texture's
   10 -- and at half its size that is 12px against a slice of 18. Checked
   rather than assumed: 12/18 of a 10px chamfer is 6.7px on a 46px button, or
   14.5% of the height, against 14% measured on the client's own. The previous
   slice was 11 on all four sides of the WRONG texture, which put the bottom
   bevel inside the stretched middle and smeared it down the button.

   Size comes from the client too. Measured on its own screenshots, the Back
   button is 399x92 around a 26px cap height and Finish is 413x91 around 25, so
   height is 3.54x the cap and width is 4.34x the height. Ours was 168x42.9
   around a cap of 10.2 -- ratio 4.22, a button too big for its text and too
   small for the screen. At a 1.15rem cap of 13.0 those ratios give a drawn
   200x46, plus the 2.7px the soft edge scales to on each side.

   The old size was not an accident: the comment here used to say it was held
   down so the box would not swallow the checkbox above it. The checkbox moved
   instead. */
.has-ui-art .chrome {
  box-sizing: border-box;
  width: 205px;
  height: 51px;
  border-width: 12px;
  border-style: solid;
  border-color: transparent;
  border-image: url('/static/ui/button-up.png') 18 fill stretch;
  background: none;
  box-shadow: none;
  /* The content box and the drawn button share a centre to within half a
     pixel, so flex centring needs no offset -- only the letter-spacing
     compensation the base rule explains. */
  padding: 0 0 0 0.09em;
  font-size: 1.15rem;
  color: var(--wow-gold);
}
.has-ui-art .chrome:hover { filter: brightness(1.22); }
.has-ui-art .chrome:active {
  border-image: url('/static/ui/button-down.png') 18 fill stretch;
  transform: translateY(1px);
}
.has-ui-art .chrome[disabled] {
  border-image: url('/static/ui/button-disabled.png') 18 fill stretch;
  filter: none;
  /* The client's disabled label is flat neutral grey -- (128, 128, 128)
     sampled off "Finish" in its greyed state -- not a faded gold. */
  color: #808080;
}
/* The client draws every glue button from one texture and letters them all in
   the same gold, so neither the texture nor the colour differs between Back
   and Finish. The cream on Back was ours. */

/* The name fields use charactercreate-labelframe, not glues-editbox-background.

   The editbox texture is the heavy one: a 156x36 panel with a ragged silver
   lip across its whole top edge, which is what put a chunky white bar over
   both name boxes. The client's own character-create fields are a thin drawn
   outline around a near-black well -- two hairlines and a soft shadow, nothing
   more -- and that is charactercreate-labelframe, 107x38.

   Its structure, read off the pixels: shadow in rows 0-5, edge lines at rows
   6-7 and 9, flat interior to row 25, the lower lines at rows 26 and 28,
   shadow from 31. Columns run 8/9/11 in and 97/99/100 out. So the slice is
   10/10/12/12 with the widths matching, and the 13px of vertical shadow sits
   outside the drawn field.

   Height follows the client as well: its field is 549x63 against a name bar
   whose frame art we already share, which is 0.115 of the width. At our 239px
   field that is 27.4px of drawn field, plus the shadow, so the box stays
   almost exactly where it was at 40px -- what changes is that 13px of it is
   now shadow instead of silver lip. */
.has-ui-art .nameplate input {
  box-sizing: border-box;
  height: 40px;
  border-width: 10px 10px 12px 12px;
  border-style: solid;
  border-color: transparent;
  border-image: url('/static/ui/labelframe.png') 10 10 12 12 fill stretch;
  background: none;
  /* The drawn well is already dark and flat; the CSS-only rule's inset shadow
     on top of it reads as a second, smaller box inside the frame. */
  box-shadow: none;
  padding: 0 0.55rem;
}


/* The High-Definition Models line, at the client's own colour and art.

   Three things were ours rather than the client's: the label was cream, the
   box was a CSS border, and the tick was the text glyph U+2713. In the client
   the label is the same gold as the button lettering -- (228, 194, 65) sampled
   against (246, 201, 68) for "Back" -- and the control is two textures,
   interface/buttons/ui-checkbox-up and -check. Neither is in the character
   create export, because they live under Interface/Buttons; the build script
   pulls them from CASC by FileDataID.

   They are drawn as two layers at ONE size, which is how the client stacks
   them: the check is its own texture, gold, and overhangs its box to the upper
   right rather than sitting inside it. 20px is where the client puts it --
   the drawn check is 1.14x the label's cap height there, and 20px of a texture
   whose content is 20 of 32 rows gives exactly that.

   The line also moves up, to clear the taller button below it: that button's
   drawn top edge now sits 68.7px off the bottom. */
.has-ui-art .hdmodels {
  bottom: 4.6rem;
  font-size: 0.93rem;
  gap: 0.32rem;
  color: var(--wow-gold);
}
.has-ui-art .hdmodels__box {
  width: 20px; height: 20px;
  border: none;
  color: transparent;
  background:
    url('/static/ui/checkbox-check.png') center / contain no-repeat,
    url('/static/ui/checkbox-up.png') center / contain no-repeat;
}

/* ---- the client's own cursor -----------------------------------------------
   The hotspot travels with the image rather than being fixed: the drawn
   stand-in points from the middle of a 32px box, while the client's gauntlet
   has its tip in the very first pixel, so the same offset would have put the
   click point out in the cuff. */
.has-ui-art {
  --cursor-point: url('/static/ui/cursor-point.png');
  --cursor-hotspot: 0 0;
}

/* The dice is gold in the client, not bone-white. */
.nameplate__dice { color: var(--gold-lit); }

/* ---- name availability indicator -------------------------------------------
   The client's NameAvailabilityState: 20x20 for the red X, 23x20 for the
   checkmark, sitting 10px to the right of the surname box. Drawn rather than
   taken from an atlas, because common-icon-redx and common-icon-checkmark are
   atlas members addressed through a DB2 and are not exportable by path. */
.availability {
  width: 23px; height: 20px;
  margin-left: 10px;
  flex: 0 0 auto;
  align-self: center;
  background: center / contain no-repeat;
  cursor: var(--cursor-point) var(--cursor-hotspot), default;
}
.availability--ok {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 23 20'%3E%3Cpath d='M2.5 10.5l6 6 12-13' fill='none' stroke='%231eff00' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.availability--bad {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M4 4l12 12M16 4L4 16' fill='none' stroke='%23ff2020' stroke-width='4' stroke-linecap='round'/%3E%3C/svg%3E");
}
/* Checking: the client shows nothing during its one second delay, but a
   demo benefits from saying that something is happening. */
.availability--busy {
  background-image: none;
  border: 2px solid rgba(201, 162, 39, 0.35);
  border-top-color: var(--gold-lit);
  border-radius: 50%;
  width: 14px; height: 14px;
  animation: availability-spin 700ms linear infinite;
}
@keyframes availability-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .availability--busy { animation: none; }
}

/* ---- scene vignette --------------------------------------------------------
   charactercreate-vignette-top / -bottom / -sides, cut from the atlas at the
   rects UiTextureAtlasMember gives. Each is pure black with a gradient alpha,
   one pixel wide in the direction it stretches. The client sizes them 451,
   577 and 703 against its own layout; those are carried as percentages so
   the framing holds at any viewport. The right side is the same texture with
   TexCoords left=1 right=0, i.e. mirrored.

   That layout is 1920x1200, NOT 1024x768. The appearance panel is
   Size x="390" in the client's template and measures 20.3% of the width
   here, and 390/1920 = 20.3% -- so its numbers divide by 1920. Assuming 1024
   made every overlay nearly twice its proper size, which is why the first
   attempt buried the scene instead of framing it. */
.vignette {
  /* Fixed, so the stage's padding does not inset it, and z-index 2 so it
     lands above the scene and the character but below the UI frames at 3 --
     which is the layer the client draws it on. */
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.vignette i {
  position: absolute;
  display: block;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  /* The texture holds flat at 209/255 -- 82% black -- across its first ~12%
     before it starts to fall, and that shelf is what reads as a hard edge.
     The client draws it over a scene its own renderer has already darkened;
     ours is unlit at full texture brightness, so the same overlay lands
     harder. Carrying it at 60% restores the relationship rather than
     redrawing the art. */
  /* The client's own number, not a taste: UpdateBackgroundOverlays sets
     every BGTex overlay to factionBGAlphaValues.Horde = 0.6 for an undead
     (DK 0.8, DH 0.7, Pandaren 0.75 would override it). */
  opacity: 0.6;
}
/* No mask here any more. One was added to soften what looked like a shoulder
   in the gradient; it was really a mis-cut texture -- the crop used one
   build's rect against another build's sheet and started with 17 transparent
   pixels before stepping to 82% black. With the crop correct the falloff is
   smooth and needs no help. */
.vignette__top {
  top: 0; left: 0; right: 0; height: 37.6%;
  background-image: url('/static/ui/charactercreate-vignette-top.png');
}
.vignette__bottom {
  bottom: 0; left: 0; right: 0; height: 48.1%;
  background-image: url('/static/ui/charactercreate-vignette-bottom.png');
  /* Faded OUT on this screen. SetMode(CHAR_CREATE_MODE_CUSTOMIZE) plays
     BottomBackgroundOverlay.FadeOut, and the name entry is in customize mode;
     it only fades back in on returning to race and class. At 0.6 it was
     darkening the whole lower half -- 49% black at the bottom edge -- which
     is most of "so dark in the front". */
  opacity: 0;
}
/* Ultrawide only: CharacterCreateMixin:OnDisplaySizeChanged shows these 89 px
   edges when the screen is wider than 16:9 (by more than 0.001). */
.vignette__wide-left, .vignette__wide-right {
  display: none;
  top: 0; bottom: 0; width: 4.64%;
  background-image: url('/static/ui/charactercreate-vignette-sides-widescreen.png');
}
.vignette__wide-left { left: 0; }
.vignette__wide-right { right: 0; transform: scaleX(-1); }
@media (min-aspect-ratio: 1601/900) {
  .vignette__wide-left, .vignette__wide-right { display: block; }
}
.vignette__left {
  top: 0; bottom: 0; left: 0; width: 36.6%;
  background-image: url('/static/ui/charactercreate-vignette-sides.png');
}
.vignette__right {
  top: 0; bottom: 0; right: 0; width: 36.6%;
  background-image: url('/static/ui/charactercreate-vignette-sides.png');
  transform: scaleX(-1);
}
/* Without the converted art there is nothing to show, and a black box would
   be worse than no vignette at all. */
body:not(.has-ui-art) .vignette { display: none; }

/* ---- the client's own icons ------------------------------------------------ */
.has-ui-art .nameplate__dice {
  color: transparent;
  background: url('/static/ui/charactercreate-icon-dice.png') center / contain no-repeat;
}
.has-ui-art .availability--ok {
  background-image: url('/static/ui/common-icon-checkmark.png');
}
.has-ui-art .availability--bad {
  background-image: url('/static/ui/common-icon-redx.png');
}

/* The view controls use the client's icons: common-icon-undo, -zoomout,
   -zoomin, -rotateleft and -rotateright, all members of one atlas sheet. The
   glyphs underneath were stand-ins and are hidden rather than removed, so the
   buttons still read without the converted art. */
.has-ui-art .viewtool {
  background-repeat: no-repeat;
  background-position: center;
  background-size: 62%;
}
/* color:transparent goes per-button, not on all of them: the rotate pair has
   no art in this build, and blanking their glyphs too would leave two empty
   squares. */
.has-ui-art .viewtool--undo,
/* ---- the view controls, in their own frame ---------------------------------

   The five buttons sit in a bronze tray in the client, and every piece of it
   is in this build's atlas and not in live: diffing the two member tables,
   the whole heavybronze family and common-button-square-gray-*-c60 exist only
   in the client's own. So the tray is the SAME frame art the name bar and the
   result panel already use, and each button is its own octagonal frame --
   "square" in the atlas name, octagonal in the file.

   Every number is measured off the client's own tray. Visible tray 428x93,
   buttons 55 square, gap 18 inside a group and 66 between the two groups,
   9px from the frame's inner edge to the first button, and a 34px icon on a
   55px button. The button texture carries 13px of transparent margin per
   side, so a 30px element draws a 26.9px frame -- scale 0.489 -- and every
   spacing below is that measurement times 0.489, less the margin where the
   gap is between two elements rather than two drawn frames. The icon works
   out at 55% of the element, which is why it is written as a percentage.

   The tray's border-width is 11px for the same reason: the frame's band and
   bevel are rows 14-28 of a 34px slice, so 0.44 of whatever width it is
   drawn at, and 0.44 x 11 is the 4.9px the client's tray shows. */
.has-ui-art .viewtools {
  gap: 29px;
  padding: 3px;
  border: 11px solid transparent;
  border-image: url('/static/ui/heavybronze-frame-basic-c60-2x.png') 34 fill stretch;
  background:
    url('/static/ui/heavybronze-frame-background-c60-2x.png') center / cover no-repeat;
  background-clip: padding-box;
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, 0.6));
}
.has-ui-art .viewtools__group { gap: 6px; }

.has-ui-art .viewtool {
  position: relative;
  width: 30px; height: 30px;
  border: none;
  border-radius: 0;
  box-shadow: none;
  opacity: 1;
  color: transparent;
  background: url('/static/ui/common-button-square-gray-up-c60.png')
              center / contain no-repeat;
}
.has-ui-art .viewtool:active {
  background-image: url('/static/ui/common-button-square-gray-down-c60.png');
}
/* The icon rides on top of the frame rather than replacing it, so the two are
   separate layers the way the client's Icon texture is separate from its
   NormalTexture. */
.has-ui-art .viewtool::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--viewtool-icon) center / 55% no-repeat;
}
.has-ui-art .viewtool--undo { --viewtool-icon: url('/static/ui/common-icon-undo.png'); }
.has-ui-art .viewtool--zoomout { --viewtool-icon: url('/static/ui/common-icon-zoomout.png'); }
.has-ui-art .viewtool--zoomin { --viewtool-icon: url('/static/ui/common-icon-zoomin.png'); }
.has-ui-art .viewtool--rotateleft { --viewtool-icon: url('/static/ui/common-icon-rotateleft.png'); }
.has-ui-art .viewtool--rotateright { --viewtool-icon: url('/static/ui/common-icon-rotateright.png'); }

/* ---- heavybronze frames ----------------------------------------------------
   The client's own frame art, resolved through UiTextureAtlasMember and cut
   from sheet 8203427. The slice is 34, taken from
   UiTextureAtlasElementSliceData rather than measured: it is the number this
   project guessed at for weeks. The texture is the -2x (high-DPI) variant, so
   the border-width is half the slice and the art scales down into it.

   The -c60 suffix is why an earlier lookup for a bare "heavybronze-frame-basic"
   found nothing: the members are named per canvas, and only the client's own
   table carries them at all. */
/* The frame art is drawn --frame-art wide, but the LAYOUT border is only half
   that, so the fill reaches under it.

   Across each 34px band of the art: 12 rows of transparent margin, a faint
   anti-aliased edge, 16 rows of solid bronze, then four rows of soft inner
   shadow fading out (alpha 182 -> 46) that are meant to darken the fill
   beneath them. With the border as wide as the art and the fill clipped to
   the padding box, nothing was beneath them but the scene: a thin see-through
   line inside the bronze on every side. Half the art width lands inside the
   solid bronze (41-88% of the band), so the fill starts out of sight under
   the bronze, runs under the shadow, and never reaches the transparent outer
   margin. Padding takes up the difference so nothing inside moves. */
.has-ui-art .nameplate__frame,
.has-ui-art .panel {
  --frame-art: 17px;
  --frame-edge: calc(var(--frame-art) / 2);
  border: var(--frame-edge) solid transparent;
  border-image: url('/static/ui/heavybronze-frame-basic-c60-2x.png') 34 fill stretch;
  border-image-width: var(--frame-art);
  background:
    url('/static/ui/heavybronze-frame-background-c60-2x.png') center / cover no-repeat;
  background-clip: padding-box;
  /* No drop shadow. One at 0 8px 22px / 0.6 darkened the scene beneath the
     frame by up to 24 levels, fading over ~30px from the bronze -- a soft
     dark rectangle hanging under the bar, with its own edge where it ran
     out. The client draws these frames straight onto the scene; the art's
     anti-aliased outer edge is all the separation it has or needs.
     (The filter was also, silently, what anchored the name bar's brackets;
     .nameplate__frame now says position: relative itself.) */
  filter: none;
}

/* Corner brackets are fixed-size stamps, not nine-sliced -- which is why they
   carry no slice row. The XML anchors them at exactly 0,0 with
   useAtlasSize="true", and the two frames use DIFFERENT sets:

     name bar : heavybronze-horz-cornerbracket TL + TR   (both top corners)
     panel    : heavybronze-vert-cornerbracket TR + BR   (both right corners)

   An earlier pass put the horz pair on both and offset them -6px, which is
   why the panel's accents sat in the wrong corners with the wrong art. The
   -2x members are 64px, so they draw at 32. */
.has-ui-art .panel { position: absolute; }
.has-ui-art .panel::before,
.has-ui-art .panel::after,
.has-ui-art .nameplate__frame::before,
.has-ui-art .nameplate__frame::after {
  content: "";
  position: absolute;
  /* 32px on a 17px frame: the 64px art at the same half scale as the frame's
     34px slice. Thinner frames scale it with them (see the name bar). */
  width: calc(var(--frame-art) * 32 / 17);
  height: calc(var(--frame-art) * 32 / 17);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  pointer-events: none;
}
/* Offsets are the negative LAYOUT border, not 0: an absolutely positioned
   child is placed against the PADDING box, and the XML anchors these to the
   frame's outer corner, so they come back out by --frame-edge. */
/* The name bar's border is thinner (13px, set below) than the panel's 17, and
   its brackets have to follow it in two ways.

   Position: they come out by their own frame's border, not the panel's.
   Placed at -17 on a 13px border they sat 4px too far up and out.

   Scale: the 32px bracket is its 64px art at half size, which matches a
   17px border -- the frame art's 34px edge at the same half size. On a 13px
   border that edge draws at 13/17 of that scale while the bracket did not,
   so each bracket's vertical arm was thicker than the side border it runs
   into: a step on both sides just below the corners. Scaled by the same
   13/17, arm and border are the same width and meet flush.

   --frame-art and --frame-edge (shared with the panel) drive the art, the
   offsets and the bracket size. */
.has-ui-art .nameplate__frame::before {
  top: calc(-1 * var(--frame-edge)); left: calc(-1 * var(--frame-edge));
  background-image: url('/static/ui/heavybronze-horz-cornerbracket-tl-c60-2x.png');
}
.has-ui-art .nameplate__frame::after {
  top: calc(-1 * var(--frame-edge)); right: calc(-1 * var(--frame-edge));
  background-image: url('/static/ui/heavybronze-horz-cornerbracket-tr-c60-2x.png');
}
.has-ui-art .panel::before {
  top: calc(-1 * var(--frame-edge)); right: calc(-1 * var(--frame-edge));
  background-image: url('/static/ui/heavybronze-vert-cornerbracket-tr-c60-2x.png');
}
.has-ui-art .panel::after {
  bottom: calc(-1 * var(--frame-edge)); right: calc(-1 * var(--frame-edge));
  background-image: url('/static/ui/heavybronze-vert-cornerbracket-br-c60-2x.png');
}

/* The rotate pair needed your client's rect: the live build still points at
   L259 for these two and resolves to empty space. The icons themselves are
   set with the rest of the group above. */

/* The client's LeftBlackBar / RightBlackBar: the backdrop covers a fixed
   angle, and past it there is no world to draw. Width is computed from the
   camera, so these are zero-width whenever the view fits inside the scene. */

/* ---- name bar scale --------------------------------------------------------
   The inputs were carrying more height and weight than the client's, which
   made the bar sit heavier over the scene than it should.

   The border-width override that used to live here is gone. It forced 7px on
   all four sides of the field, which cut across the slice the art actually
   needs; with charactercreate-labelframe and its own 10/10/12/12 the geometry
   comes from the texture instead of from a number picked to make the old
   editbox art look thinner.

   The text size comes from the client too: its placeholder cap is 25px in a
   63px field, so 0.397 of the drawn height. Ours draws 27, which wants a cap
   of 10.7 and a 0.9rem font. */
.nameplate__inputs input {
  font-size: 0.9rem;
}
.nameplate__title {
  font-size: 0.72rem;
}
/* Clear of the bronze. Measured at this size, the lettering's top sat 1.4px
   INSIDE the frame's inner shadow (ink at 27.6, shadow ending at 29) with
   7.9px spare beneath it before the fields: centring it in the 15px between
   the two wants it 4.65px lower. The margins trade 4.5px -- off the negative
   top, off the bottom -- so the title's row keeps its height: the fields do
   not move and the frame does not grow. 4.5 CSS px is 9 whole device pixels
   on a 2x screen, so the text stays on the pixel grid. */
.has-ui-art .nameplate__title {
  margin: -1.5px 0 0.5px;
}
.has-ui-art .nameplate__frame {
  --frame-art: 13px;
  /* The base padding, plus what the half-width layout border gave back. */
  padding: calc(2px + var(--frame-art) - var(--frame-edge))
           calc(8px + var(--frame-art) - var(--frame-edge))
           calc(8px + var(--frame-art) - var(--frame-edge));
}
.has-ui-art .panel {
  padding: calc(0.5rem + var(--frame-art) - var(--frame-edge))
           calc(0.7rem + var(--frame-art) - var(--frame-edge))
           calc(0.7rem + var(--frame-art) - var(--frame-edge));
}

/* ---- phones ------------------------------------------------------------------
   A phone gets the name bar and the verdict, nothing else. The view tools,
   the High-Definition Models line and Back / Finish are chrome from the
   client's screen that has nothing to do on a 375px one: the tools sat behind
   the name bar, and the two buttons overlapped each other. Nothing is lost by
   hiding Finish -- the name is checked a second after typing stops, and Enter
   still submits the form.

   The fields get the room back from the frame. At 92% of the stage and the
   desktop gaps, each field had 66px for its text against 146px of "Type
   Secondary Name", so both placeholders were cut mid-word. The bar now spans
   the stage, the gaps and paddings tighten, and what is left is about
   50vw - 85px of text per field. The placeholder is sized to that: "Type
   Secondary Name" measures 10.1 em in this face, so 1 / 10.6 of the space
   fits it with a margin, held between 9px and the desktop 0.9rem.

   Typed text is 16px, not the placeholder's size: iOS zooms the whole page
   onto any field set smaller than that when it takes focus. A name is at most
   12 letters, and a typical one fits at 16px.

   A phone on its side is 844x390: wide enough to miss the width test, and
   too short for the desktop layout, which put the view tools behind the name
   bar and Back / Finish over the character. The height test gives it the same
   simple screen. */
@media (max-width: 720px), (max-height: 500px) {
  .viewtools,
  .hdmodels,
  .chrome { display: none; }

  .nameplate { width: min(38rem, 100%); }
  .nameplate__frame { column-gap: 0.35rem; }
  .has-ui-art .nameplate__frame {
    padding-left: calc(4px + var(--frame-art) - var(--frame-edge));
    padding-right: calc(4px + var(--frame-art) - var(--frame-edge));
  }
  .has-ui-art .nameplate__dice { width: 26px; height: 26px; }
  .nameplate__inputs input,
  .has-ui-art .nameplate__inputs input {
    padding-left: 0.2rem;
    padding-right: 0.2rem;
    font-size: 16px;
  }
  .nameplate__inputs input::placeholder {
    font-size: clamp(9px, calc((50vw - 85px) / 10.6), 0.9rem);
  }

  /* The verdict sits centred at the foot of the screen, where the buttons
     were: auto margins between a zero left and right centre an absolutely
     positioned box of fixed width. */
  .panel,
  .has-ui-art .panel {
    top: auto;
    left: 0; right: 0;
    bottom: calc(1rem + env(safe-area-inset-bottom));
    margin: 0 auto;
    width: min(24rem, calc(100% - 2rem));
  }
}
