/* THE ITEM TOOLTIP, DRAWN THE WAY THE GAME DRAWS IT.
   ===========================================================================
   Owner, 11 Aug 2026, with seven screenshots of infinitybuilds.gg's item panels:
   *"look at these, how perfect they are, graphically. I want ours to look just
   like this… I want this done before anything else."* Their panel is itself a
   rendition of the game's, so this is built from the GAME'S OWN SPEC and checked
   against theirs, rather than copied off a screenshot.

   THE SPEC IS `base/meta/UI/ItemTooltipTemplate.ui.json`, read by
   tools/ui-map-item-tooltip.mjs into docs/item-tooltip-ui-map.txt. It gives the
   ORDER of every section, the font and size of every line, and the colours:

     ItemName          ExocetMedium 28, ALL CAPS, recoloured per rarity
     ItemType          SansSerif 18  #595858 at alpha 180
     UpgradeLevel      SansSerif 18  #7f7f7f          (the masterwork line)
     ItemAttributes    #c8c8c8, with AttributeText2 #808080 for the roll range
     ItemSkill*        name #b69e7a, description #cbc7c1   (the runeword block)
     SocketText        #b3a99b
     RunewordName      #c6bcad,  RunewordDescription #a1a1a1
     ItemFlavor        SansSerif_Italic 17 #808080
     LevelRequirement  Serif 16, ALL CAPS, #e3e0d9

   and the section order the owner listed by name: header, divider, the four
   rolled affixes, a divider, the TEMPERED line with the smith's mark, a divider,
   the aspect, a divider for a TRANSFIGURE with the Horadric cube beside it, a
   divider, the gems and runes with what they do, then the flavour text.

   THE FONTS ARE THE GAME'S. `tools/extract-fonts.mjs` pulled ExocetMedium,
   SansSerif and SansSerif_Italic out of d4data months ago and nothing had ever
   shipped them. Exocet is why the game's item names have that crossed O.

   THE MARKER ICONS ARE THE GAME'S, and which frame is which was MEASURED rather
   than eyeballed — see docs/tooltip-icons.json for the scores and the one row
   (the smith's mark) that is reasoned rather than proven.

   THE FRAME IS THE GAME'S NINE-SLICE. Each rarity's frame is a TiledStyle with
   four corners, four borders and a background, 128x128 each; they are drawn here
   as nine background layers on one element, which is what the game does and what
   `border-image` could not do without compositing them into one sprite offline.

   TWO RULES FROM ReadErrors THAT SHAPE THE CSS:
   - **No `filter` on art with an alpha channel** (8h). Every marker is drawn at
     its own colour and dimmed with `opacity`, never tinted with a filter — a
     filter un-premultiplies, and in Firefox that blows small bright art out to
     white while Chromium rounds it away.
   - **Both engines.** The owner browses in Firefox and the preview pane is
     Chromium, so nothing here uses a Chromium-only property. */

/* ---- the game's own faces ------------------------------------------------ */
@font-face {
  font-family: "D4Exocet";
  src: url("/assets/fonts/ExocetMedium.ttf") format("truetype");
  font-weight: 400; font-style: normal; font-display: swap;
}
/* ⚠ D4Sans IS WOFF2 AND MUST STAY THAT WAY — THE .ttf FILES WERE BROKEN.
   2 Sep 2026. All three PT Serif faces carried a legacy format-0 `kern` table
   larger than the uint16 length field describing it (125,766 / 126,486 /
   124,398 bytes), and Firefox logged two errors per face and threw the table
   away the moment a tooltip drew:

       downloadable font: kern: Too large subtable (font-family: "D4Sans")
       downloadable font: Table discarded (font-family: "D4Sans")

   Six errors, on every page that draws an item tooltip, in the browser the
   owner actually uses — live from 11 Aug to 2 Sep. Chromium said nothing,
   which is the asymmetry CLAUDE.md's both-engines rule exists for; this was
   found by `check-firefox.mjs` while adding D4Serif, not by looking.

   Dropping the table costs NO KERNING: all three also carry a GPOS `kern`
   feature, which is the table a browser uses for an OpenType font anyway. That
   was not assumed — the strings below were measured in both engines before and
   after, and every width is identical to three decimal places.

   `python tools/build-webfont.py --all` rebuilds these from
   tools/art-source/fonts and asserts glyph count, codepoint count, family name
   and the GPOS kern feature all survive. 1,037,932 -> 254,828 bytes. */
@font-face {
  font-family: "D4Sans";
  src: url("/assets/fonts/SansSerif.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "D4Sans";
  src: url("/assets/fonts/SansSerif_Medium.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "D4Sans";
  src: url("/assets/fonts/SansSerif_Italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
/* The face the game draws a CHARACTER NAME in — `base/meta/Font/Serif.fnt`,
   which is really **Old Fenris** (URW). ⚠ The .fnt names describe nothing:
   `SansSerif.fnt` above is **PT Serif**, a serif, and `SansSerif_Medium` is its
   BOLD, not a medium. The file-to-face mapping is by byte length against
   d4data's `dataSize` and `tools/extract-fonts.mjs` asserts it per file
   (Serif.fnt dataSize 650192 = tools/art-source/fonts/Serif.otf, exactly).
   WOFF2, and it had the same oversize `kern` table as the D4Sans group above,
   dropped the same way for the same reason: 650,192 -> 200,408 bytes with no
   glyphs lost (all 1141, all 680 codepoints, name table intact). D4Exocet is
   the one face still served raw — its kern table is 23,124 bytes, well inside
   the uint16 field, so it is not broken and converting it would be payload
   saving rather than a fix. `build-webfont.py --all` includes it when someone
   decides to take the ~120 KB.
   ⚠ NO BOLD EXISTS. Serif.fnt.json declares snoBoldStyle, snoItalicStyle and
   snoBoldItalicStyle all null and the face is usWeightClass 400, so any rule
   asking for 700 gets SYNTHETIC bold on a CFF display face. Declare 400 only,
   and let callers set font-weight:400 rather than inheriting a heading's 700. */
@font-face {
  font-family: "D4Serif";
  src: url("/assets/fonts/Serif.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}

/* THE PALETTE VARIABLES ARE DECLARED AT :root AS WELL AS ON .d4tip — 29 Aug
   2026, for the gear editor's aspect and unique pickers, which colour power
   sentences with the same .d4n/.d4r/.d4s/.d4k classes OUTSIDE a .d4tip card.
   Custom properties at :root change nothing by themselves; .d4tip re-declares
   the same values so the card is unaffected. One palette, still one place. */
:root, .d4tip {
  /* The card is 340 across with 308 of content, measured off the reference. The
     game authors the same panel at 450/271, i.e. a wider gutter; 340 is the size
     a web tooltip wants and the proportions are kept. */
  --tip-w: 340px;
  --tip-pad-x: 16px;
  --tip-pad-y: 18px;
  /* The nine-slice corner. The pieces are authored 128px and the game scales
     them 0.6 (0.7 for legendary); at our width that lands near 52. */
  --tip-corner: 52px;
  /* The bullet column and the rail the text starts on — the game insets an
     attribute's text 32px from a 10px-padded icon, the same 3:1 the reference
     uses at 14/19. */
  --tip-marker: 14px;
  --tip-rail: 19px;

  --tip-ink: #c2b49e;          /* ItemAttributes body */
  --tip-ink-bright: #e3e0d9;   /* Style_Header */
  --tip-ink-dim: #8a7f70;      /* bullets, flavour */
  --tip-range: #8a8a8a;        /* AttributeText2, the [min - max] */
  --tip-value: #dcd9d3;        /* a plain rolled value */
  --tip-value-mw: #7b7cf5;     /* the same value once the item is masterworked */
  --tip-value-mwmax: #f77f05;  /* the affix the last masterwork rank landed on */
  /* MEASURED, not picked: tools/measure-tooltip-colours.mjs clusters the
     saturated pixels of the owner's screenshots.
       #f77b04  the aspect sentence (14% and 37% of the two Ward shots)
       #7776f3  a value on an item at quality 25 — within five points of the
                --tip-value-mw already here, so that one is left alone
       #e9ea08  a value on an item ABOVE quality 25 (their Jade Relic, 40)
       #d897ec  a mythic's power sentence (their Drognan's Anguish) */
  --tip-aspect: #f77b04;       /* an aspect or transfigure sentence */
  --tip-value-hq: #e9ea08;     /* THE OWNER, 14 Aug 2026: "any quality over 25 is yellow" */
  /* A STATIC NUMBER INSIDE A POWER SENTENCE — one that does not roll. Measured
     at #d7a45e in BOTH places it appears: the 10% in their Overheating aspect
     and every number in their mythic ring's power. One colour, and the first
     version of this had it as #e9ea08, which is a different yellow entirely —
     that is the QUALITY-40 affix value off their Jade Relic, and conflating the
     two put a bright lemon where the game prints a warm gold. */
  --tip-power-static: #d7a45e;
  --tip-power-text-mythic: #d897ec;
  --tip-value-temper: #b3a99b;
  --tip-value-transfig: #ddc155;
  --tip-rule: rgba(140, 120, 90, .25);
  --tip-accent: #e8c06a;       /* d4-important, the words the game colours */
  /* THE NUMBER INSIDE A POWER SENTENCE, and it is BLUE — measured on the
     owner's own amulet, whose aspect reads "deal 45%[x]" with the 45% in the
     SAME blue as the item's affix values. It was #d4ccba, a warm cream that
     read as part of the sentence rather than as a value.

     It points at --tip-value-mw rather than carrying its own #7776f3: the
     measurement and that variable are within five points a channel, and one
     blue that two rules share cannot drift into two blues nobody meant. */
  --tip-number: var(--tip-value-mw);

  /* THE BODY IS DARKENED AND THE TOP IS NOT, and that is a rendering decision
     worth writing down. The game's nine-slice pieces each carry their own
     top-lit gradient, authored for a panel around 450x600. Ours is 340 wide and
     a hover panel is often taller, so every piece stretches its fade over more
     pixels than it was drawn for and the body comes out washed in the rarity
     colour — where the reference, and the game, are near-black below the header
     with the colour only at the top and along the edges.

     It goes over the WHOLE card rather than over the centre tile alone. Centre
     only was tried first and drew a visible rectangle: the centre went dark
     while the border strips stayed lit, and the seam between them read as a box
     inside the panel. A full-width wash that starts at zero keeps the top
     corners' filigree exactly as bright as the game drew it. */
  --tip-wash: linear-gradient(180deg,
    rgba(8, 6, 6, 0) 0%, rgba(8, 6, 6, .30) 14%, rgba(8, 6, 6, .58) 38%,
    rgba(8, 6, 6, .70) 66%, rgba(8, 6, 6, .74) 100%);
}
/* ⚠ THE VARIABLES ABOVE AND THE STYLES BELOW ARE SEPARATE BLOCKS ON PURPOSE.
   The var block is `:root, .d4tip` so the palette resolves outside a card;
   these declarations are the CARD's — the first cut prefixed the ONE combined
   block with `:root` and put `width: 340px` on the html element itself, which
   shoved every floating pop-out and right-flush control on the planner
   sideways (probe-planner-edit caught it at -206px). Styles never ride the
   var block. */
.d4tip {
  width: var(--tip-w);
  max-width: calc(100vw - 20px);
  font-family: "D4Sans", Georgia, "Times New Roman", serif;
  font-size: 16px;
  line-height: 1.4;
  color: var(--tip-ink);
  text-align: left;
}
/* THE DROP SHADOW IS A `box-shadow` ON THE CARD, NOT A `filter` ON THE PANEL,
   and the difference is not cosmetic. The reference uses
   `filter: drop-shadow(...)` on the whole tooltip, which would put a filter over
   every piece of alpha art inside it — the nine-slice frame and eight marker
   icons. A filter un-premultiplies before it runs, and where alpha is small that
   division overflows and clips to WHITE in Firefox while Chromium rounds it
   away: this project already shipped 46 icons and every cluster gate as white
   silhouettes that way, visible only on the owner's own machine (ReadErrors 8h).
   The card is a rectangle, so a box-shadow is the same picture with none of it. */
.d4tipCard { box-shadow: 0 10px 28px rgba(0, 0, 0, .88) }

/* ---- the frame ----------------------------------------------------------- */
/* Nine layers in one element, top-most first: four corners pinned, four borders
   stretched between them, the background last and inset. `fInsetBackground` is
   true on all eight rarities, which is why the background is sized to the gap
   rather than to the whole box. */
.d4tipCard {
  position: relative;
  box-sizing: border-box;
  padding: var(--tip-pad-y) var(--tip-pad-x);
  background-repeat: no-repeat;
  background-position:
    center center,
    left top, right top, left bottom, right bottom,
    var(--tip-corner) top, var(--tip-corner) bottom,
    left var(--tip-corner), right var(--tip-corner),
    center center;
  background-size:
    100% 100%,
    var(--tip-corner) var(--tip-corner), var(--tip-corner) var(--tip-corner),
    var(--tip-corner) var(--tip-corner), var(--tip-corner) var(--tip-corner),
    calc(100% - var(--tip-corner) * 2) var(--tip-corner),
    calc(100% - var(--tip-corner) * 2) var(--tip-corner),
    var(--tip-corner) calc(100% - var(--tip-corner) * 2),
    var(--tip-corner) calc(100% - var(--tip-corner) * 2),
    calc(100% - var(--tip-corner) * 2 + 2px) calc(100% - var(--tip-corner) * 2 + 2px);
  background-image:
    var(--tip-wash),
    var(--f-tl), var(--f-tr), var(--f-bl), var(--f-br),
    var(--f-top), var(--f-bottom), var(--f-left), var(--f-right), var(--f-bg);
  /* Under everything: the game's plain tooltip panel, so a short item whose
     nine-slice background does not reach still sits on the right texture. */
}
.d4tipCard::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: #0b0908 url("/assets/ui/tooltip-base.webp") center/100% 100% no-repeat;
}

/* One line per rarity, and the same eight names the game's TiledStyle files use.
   `season` is here because a seasonal item draws it INSTEAD of its rarity frame. */
.d4tip[data-rarity="common"],
.d4frame[data-rarity="common"] { --f-tl:url("/assets/tooltip/frame/common/tl.webp");    --f-tr:url("/assets/tooltip/frame/common/tr.webp");    --f-bl:url("/assets/tooltip/frame/common/bl.webp");    --f-br:url("/assets/tooltip/frame/common/br.webp");    --f-top:url("/assets/tooltip/frame/common/top.webp");    --f-bottom:url("/assets/tooltip/frame/common/bottom.webp");    --f-left:url("/assets/tooltip/frame/common/left.webp");    --f-right:url("/assets/tooltip/frame/common/right.webp");    --f-bg:url("/assets/tooltip/frame/common/bg.webp"); }
.d4tip[data-rarity="magic"],
.d4frame[data-rarity="magic"] { --f-tl:url("/assets/tooltip/frame/magic/tl.webp");     --f-tr:url("/assets/tooltip/frame/magic/tr.webp");     --f-bl:url("/assets/tooltip/frame/magic/bl.webp");     --f-br:url("/assets/tooltip/frame/magic/br.webp");     --f-top:url("/assets/tooltip/frame/magic/top.webp");     --f-bottom:url("/assets/tooltip/frame/magic/bottom.webp");     --f-left:url("/assets/tooltip/frame/magic/left.webp");     --f-right:url("/assets/tooltip/frame/magic/right.webp");     --f-bg:url("/assets/tooltip/frame/magic/bg.webp"); }
.d4tip[data-rarity="rare"],
.d4frame[data-rarity="rare"] { --f-tl:url("/assets/tooltip/frame/rare/tl.webp");      --f-tr:url("/assets/tooltip/frame/rare/tr.webp");      --f-bl:url("/assets/tooltip/frame/rare/bl.webp");      --f-br:url("/assets/tooltip/frame/rare/br.webp");      --f-top:url("/assets/tooltip/frame/rare/top.webp");      --f-bottom:url("/assets/tooltip/frame/rare/bottom.webp");      --f-left:url("/assets/tooltip/frame/rare/left.webp");      --f-right:url("/assets/tooltip/frame/rare/right.webp");      --f-bg:url("/assets/tooltip/frame/rare/bg.webp"); }
.d4tip[data-rarity="legendary"],
.d4frame[data-rarity="legendary"] { --f-tl:url("/assets/tooltip/frame/legendary/tl.webp"); --f-tr:url("/assets/tooltip/frame/legendary/tr.webp"); --f-bl:url("/assets/tooltip/frame/legendary/bl.webp"); --f-br:url("/assets/tooltip/frame/legendary/br.webp"); --f-top:url("/assets/tooltip/frame/legendary/top.webp"); --f-bottom:url("/assets/tooltip/frame/legendary/bottom.webp"); --f-left:url("/assets/tooltip/frame/legendary/left.webp"); --f-right:url("/assets/tooltip/frame/legendary/right.webp"); --f-bg:url("/assets/tooltip/frame/legendary/bg.webp"); }
.d4tip[data-rarity="unique"],
.d4frame[data-rarity="unique"] { --f-tl:url("/assets/tooltip/frame/unique/tl.webp");    --f-tr:url("/assets/tooltip/frame/unique/tr.webp");    --f-bl:url("/assets/tooltip/frame/unique/bl.webp");    --f-br:url("/assets/tooltip/frame/unique/br.webp");    --f-top:url("/assets/tooltip/frame/unique/top.webp");    --f-bottom:url("/assets/tooltip/frame/unique/bottom.webp");    --f-left:url("/assets/tooltip/frame/unique/left.webp");    --f-right:url("/assets/tooltip/frame/unique/right.webp");    --f-bg:url("/assets/tooltip/frame/unique/bg.webp"); }
.d4tip[data-rarity="mythic"],
.d4frame[data-rarity="mythic"] { --f-tl:url("/assets/tooltip/frame/mythic/tl.webp");    --f-tr:url("/assets/tooltip/frame/mythic/tr.webp");    --f-bl:url("/assets/tooltip/frame/mythic/bl.webp");    --f-br:url("/assets/tooltip/frame/mythic/br.webp");    --f-top:url("/assets/tooltip/frame/mythic/top.webp");    --f-bottom:url("/assets/tooltip/frame/mythic/bottom.webp");    --f-left:url("/assets/tooltip/frame/mythic/left.webp");    --f-right:url("/assets/tooltip/frame/mythic/right.webp");    --f-bg:url("/assets/tooltip/frame/mythic/bg.webp"); }
.d4tip[data-rarity="set"],
.d4frame[data-rarity="set"] { --f-tl:url("/assets/tooltip/frame/set/tl.webp");       --f-tr:url("/assets/tooltip/frame/set/tr.webp");       --f-bl:url("/assets/tooltip/frame/set/bl.webp");       --f-br:url("/assets/tooltip/frame/set/br.webp");       --f-top:url("/assets/tooltip/frame/set/top.webp");       --f-bottom:url("/assets/tooltip/frame/set/bottom.webp");       --f-left:url("/assets/tooltip/frame/set/left.webp");       --f-right:url("/assets/tooltip/frame/set/right.webp");       --f-bg:url("/assets/tooltip/frame/set/bg.webp"); }
.d4tip[data-season="1"]         { --f-tl:url("/assets/tooltip/frame/season/tl.webp");    --f-tr:url("/assets/tooltip/frame/season/tr.webp");    --f-bl:url("/assets/tooltip/frame/season/bl.webp");    --f-br:url("/assets/tooltip/frame/season/br.webp");    --f-top:url("/assets/tooltip/frame/season/top.webp");    --f-bottom:url("/assets/tooltip/frame/season/bottom.webp");    --f-left:url("/assets/tooltip/frame/season/left.webp");    --f-right:url("/assets/tooltip/frame/season/right.webp");    --f-bg:url("/assets/tooltip/frame/season/bg.webp"); }

/* ---- THE SAME FRAME ON A GEAR SQUARE ------------------------------------
   Owner, 12 Aug 2026, zoomed into one tile: "ours doesnt have flair around the
   edges, ours isnt purple showing mythic unique".

   It is the SAME nine-slice the panel wears. infinitybuilds do exactly this —
   one `frame-<rarity>.webp` served to both their tooltip and their gear tile —
   and the game`s TooltipBackgroundRarity_* styles are already pulled for the
   panel, so this is not new art, it is the art we have at a smaller corner.
   Anything carrying `.d4frame` and a `data-rarity` gets the corners, the
   borders and the rarity-tinted background; an element with no data-rarity
   paints none of it, which is what an empty square should look like. */
.d4frame {
  --tip-corner: 22px;
  background-repeat: no-repeat;
  background-position:
    left top, right top, left bottom, right bottom,
    var(--tip-corner) top, var(--tip-corner) bottom,
    left var(--tip-corner), right var(--tip-corner),
    center center;
  background-size:
    var(--tip-corner) var(--tip-corner), var(--tip-corner) var(--tip-corner),
    var(--tip-corner) var(--tip-corner), var(--tip-corner) var(--tip-corner),
    calc(100% - var(--tip-corner) * 2) var(--tip-corner),
    calc(100% - var(--tip-corner) * 2) var(--tip-corner),
    var(--tip-corner) calc(100% - var(--tip-corner) * 2),
    var(--tip-corner) calc(100% - var(--tip-corner) * 2),
    calc(100% - var(--tip-corner) * 2 + 2px) calc(100% - var(--tip-corner) * 2 + 2px);
  background-image:
    var(--f-tl), var(--f-tr), var(--f-bl), var(--f-br),
    var(--f-top), var(--f-bottom), var(--f-left), var(--f-right), var(--f-bg);
}

/* ---- the header ---------------------------------------------------------- */
.d4tipHead { display: grid; grid-template-columns: 1fr 60px; gap: 12px;
  align-items: start; margin-bottom: 4px; }
.d4tipNameBlock { min-width: 0; }
.d4tipName {
  font-family: "D4Exocet", Georgia, serif;
  font-size: 21.6px; line-height: 1.1; letter-spacing: .015em;
  text-transform: uppercase; color: var(--tip-ink-bright);
  /* The game sets its item names over a busy panel; the shadow is what keeps a
     pale name legible on the light part of a mythic frame. */
  text-shadow: 0 1px 4px rgba(0,0,0,.95), 0 0 12px rgba(0,0,0,.6);
  margin: 0; padding: 0 0 5px 1px;
}
.d4tip[data-rarity="common"]    .d4tipName { color: #d5cfb6 }
.d4tip[data-rarity="magic"]     .d4tipName { color: #6e9eff }
.d4tip[data-rarity="rare"]      .d4tipName { color: #f8f066 }
/* MEASURED OFF THE OWNER'S OWN TOOLTIPS, 13 Aug 2026, by
   tools/measure-tooltip-colours.mjs over a 3x crop of each name — never by eye
   (ReadErrors 48b).

   ⚠ A NAME IN A SCREENSHOT IS NOT NECESSARILY ITS RARITY COLOUR. The first
   version of this block took #25d6f2 off Drognan's Anguish and shipped it as
   "the mythic colour", with a note that a legendary ring measured the same cyan
   and that the cause was unknown. The owner answered in one line: *"The game
   has Loot Filters and you can color the name of items if the item matches
   certain criteria set by your Loot Filters."* Both cyan names are FILTER HITS
   — a setting of theirs, not a property of the item — and a rule derived from
   two of them would have been a rule about their loot filter.

   So the samples were re-chosen to exclude filtered ones. Their other three
   mythics — Vision of the Firestorm, Raiment of the Infinite, Tibault's Will —
   all read #d9a474 over the violet banner, and that agreement across three
   items is what makes it the default rather than another filter.

     legendary  #f77b04  Jade Relic of Inner Calm, 55.8% of its name's pixels.
                         The same orange the aspect sentence already uses.
     mythic     #d9a474  three mythics, 22.1% and 16.2% of their names' pixels.
     unique     #d9a474  Vision of the Firestorm reads #d3aa84 — but it is the
                         only regular unique anywhere in the screenshots and it
                         lives in gearonly.png, a COLLAGE. Controlled for: a
                         legendary name that measures #f77b04 full-size measures
                         #e88518 in that same collage, so the collage lightens
                         and desaturates, and #d3aa84 is what #d9a474 looks like
                         after that shift. The full-size value is used for both.
                         One full-size regular-unique tooltip would confirm it.

   THE CYAN IS DELIBERATELY NOT MODELLED. A loot filter is the player's own
   setting and nothing in a build document can know it. */
.d4tip[data-rarity="legendary"] .d4tipName { color: #f77b04 }
.d4tip[data-rarity="unique"]    .d4tipName { color: #d9a474 }
/* ⚠ MYTHIC IS LAVENDER, NOT THE UNIQUE TAN. This read #d9a474, the same hex as unique,
   so a mythic and a unique were indistinguishable by name anywhere on the site. Sampled
   #ba9bd1 off the owner's own Hellfire Torch capture (17 Sep 2026), which lands a hair from
   --tip-power-text-mythic below, arrived at separately. Fixed on the s15 cards first and
   here on their word: "Yeah fix it." */
.d4tip[data-rarity="mythic"]    .d4tipName { color: #ba9bd1 }
.d4tip[data-rarity="set"]       .d4tipName { color: #52c26a }

/* THE GREATER AFFIX STARS UNDER THE NAME — the owner's one addition to the
   reference: *"The only thing I'd want added from ours is the GA symbols under
   the item name."* One star per Greater Affix, the game's own icon, and never a
   "0" on an item that has none. */
.d4tipGaRow { display: flex; gap: 3px; align-items: center; margin: 1px 0 0 2px; height: 13px }
.d4tipGaRow img { width: 13px; height: 13px; display: block }

.d4tipSub, .d4tipPower { font-size: 13px; line-height: 1.25; margin-top: 1px; padding-left: 3px }
.d4tipSub { color: #ede8dc; opacity: .7 }
.d4tipPower { color: #d5cfb6 }
.d4tipMwOf { color: #7da4ff; font-weight: 700; margin-left: 2px }
/* The masterwork line carries the game's own marker. `inline-flex` so the icon
   sits on the text's baseline row rather than starting a line of its own, and
   `opacity` is the only dimmer ever used on marker art (ReadErrors 8h). */
/* `gap` would apply between EVERY child of the flex box, including the text node
   and the `/25` span — which printed "Masterwork: 25 /25" with a space before
   the slash. The spacing belongs to the icon alone. */
/* The Transfigured line is the masterwork line's twin — an icon and a word on
   ONE row. Without the inline-flex it inherited `.d4tipPower`'s block display
   and the cube sat on a line of its own above the word. */
.d4tipMw { display: inline-flex; align-items: center }
/* …but the Transfigured line is a ROW OF ITS OWN, under the quality line. As
   inline-flex it flowed up beside it and read as "25 (✷ +25) Quality 🎲
   Transfigured" on one line, which is not what the game prints. */
.d4tipTransfigLine { display: flex; align-items: center }
.d4tipTransfigLine .d4tipMwIcon { width: 15px; height: 15px; object-fit: contain;
  flex: none; display: block; margin-right: 5px }
.d4tipMw .d4tipMwIcon { width: 13px; height: 13px; object-fit: contain; flex: none;
  display: block; margin-right: 5px }
/* THE TOTAL IS THE VIOLET THE GAME PRINTS IT IN, and it is a different fact
   from the masterwork rank beside it — an Item Quality transfigure moves this
   number and not the other. Measured on the Jade Relic's `40 (✷ +25) Quality`
   and the Demon Emblem's `13 (◆ +13/25)`, both of which print the word and the
   leading number in the same violet as the `/25`. */
.d4tipMwTotal { color: #7da4ff; font-weight: 700; margin-right: 3px }
/* An item still being masterworked shows a DIAMOND where a finished one shows
   the starburst, and we have only the starburst. A blank of the same size holds
   the line's rhythm without asserting the wrong icon; the moment
   AttributeBullet_Masterworking turns up in the extraction this becomes an img
   like its sibling above. */
.d4tipMw .d4tipMwIcon.is-partial { display: inline-block; margin-right: 5px }

.d4tipIcon { width: 60px; height: 60px; flex-shrink: 0; overflow: hidden; position: relative }
.d4tipIcon img { width: 100%; height: 100%; object-fit: contain; display: block }

/* ---- rules --------------------------------------------------------------- */
.d4tipRule { height: 1px; background: var(--tip-rule); margin: 8px 0 }
.d4tipRule.is-muted { opacity: .6 }

/* ---- the item's own base lines (ItemFixed) -------------------------------
   Armour, damage per second, all-resistance. Larger and paler than an affix and
   with NO marker, because nothing about them rolled — the game's own
   ItemArmorText is #f4f4ee at 21 where an attribute is #c8c8c8 at 18. */
.d4tipFixed { margin: 6px 0 }
.d4tipFixedLine { font-size: 15px; line-height: 1.35; color: #f4f4ee; margin: 1px 0 }
.d4tipFixedVal { font-weight: 600 }

/* ---- affix rows ---------------------------------------------------------- */
.d4tipAffixes { list-style: none; margin: 6px 0; padding: 0 }
.d4tipAffix {
  display: grid;
  grid-template-columns: var(--tip-marker) minmax(0, 1fr) auto;
  column-gap: calc(var(--tip-rail) - var(--tip-marker));
  align-items: start; margin: 2px 0;
  font-size: 13.6px; line-height: 1.4; color: var(--tip-ink);
}
.d4tipBullet, .d4tipTrail {
  display: inline-flex; align-items: center; min-height: 1.45em; line-height: 1;
}
.d4tipBullet { width: var(--tip-marker); justify-content: flex-start; color: var(--tip-ink-dim) }
.d4tipBullet img { width: 100%; height: 100%; object-fit: contain; display: block }
.d4tipBullet > span { display: inline-flex; width: .95em; height: .95em; flex-shrink: 0; align-self: center }
.d4tipBullet.is-ga > span { width: .86em; height: .86em }
.d4tipTrail { justify-self: end; margin-left: 6px }
.d4tipTrail img { width: .95em; height: .95em; object-fit: contain; display: block }
.d4tipAffixText { display: flex; flex-direction: column; flex: 1; min-width: 0 }
.d4tipVal { color: var(--tip-value); font-weight: 600 }
/* the dimmed label the game puts before a value: "Requires:", "Cooldown:" */
.d4tipRowLabel { color: var(--tip-ink-dim) }
.d4tipRange { color: var(--tip-range) }
.d4tipAffix.is-mw .d4tipVal { color: var(--tip-value-mw) }
.d4tipAffix.is-mwmax .d4tipVal { color: var(--tip-value-mwmax) }
/* A TEMPERED LINE'S VALUE IS COLOURED BY THE SAME RULE AS ANY OTHER — see
   toneOf. `--tip-value-temper` stays defined for the row's own tone; it is no
   longer applied to the number, because the owner's own temper is blue. */
.d4tipAffix.is-temper:not(.is-mw) .d4tipVal { color: var(--tip-value-temper) }
.d4tipAffix.is-transfig .d4tipVal { color: var(--tip-value-transfig) }
/* ⚠ THERE IS NO "MULTIPLIERS ARE ORANGE" RULE, AND I SHIPPED ONE FOR AN HOUR.
   I read three of the owner's amulet's values as orange off the screenshot by
   eye and built a rule on it. Measured PER LINE afterwards — which is what the
   owner meant by "check against the whole folder" — only ONE of them is:

     Contracurse Ward (quality 25)   x56% ORANGE   x90% blue   x21% blue   9.4% blue
     Drognan's Anguish (quality 25)  x53% ORANGE   x15% blue   +151 blue   9.0% blue
     Jade Relic (quality 40)         x65% VIOLET   x14% YELLOW  +3,021 YELLOW  11.2% YELLOW

   THE COLOUR IS THE MASTERWORK UPGRADE LADDER, not the stat and not the affix.
   Every value on a quality-25 item is blue except one; every value on the
   quality-40 focus is yellow except one. That is the game's own progression for
   how many times an affix has been upgraded — blue, then yellow, then orange —
   and the odd one out is the affix the last upgrade landed on, which is exactly
   what `is-mwmax` has meant here all along.

   So this file goes back to what it said before I touched it, and the real rule
   is recorded in CLAUDE.md as an open item: drawing it properly needs a
   per-affix upgrade count that no build document carries. `is-mult` is still
   emitted by the row builder because the multiplier fact is worth having, and
   nothing paints with it. */
/* A line the reader could not make out. The game never prints this, so it must
   read as a warning rather than as an affix. */
.d4tipAffix.is-bad { color: #d99a5c }
.d4tipNote { color: #8a8175; font-size: 11.5px; line-height: 1.35; margin-top: 6px }

/* ---- the aspect, and the transfigure ------------------------------------- */
.d4tipPowerLine {
  position: relative; padding-left: var(--tip-rail); margin: 0;
  font-size: 13.6px; line-height: 1.5; color: #d79942;
}
.d4tipPowerLine .d4tipPowerIcon {
  position: absolute; left: 0; top: .18em; width: 1.05em; height: 1.05em;
  display: inline-flex;
}
.d4tipPowerLine .d4tipPowerIcon img { width: 100%; height: 100%; object-fit: contain; display: block }
/* ⚠ THE ASPECT IS ORANGE, NOT GOLD. This was #e2b770 and the owner read it
   straight off a screenshot: "The color of the first aspect should be orange,
   it is yellow text." Their own panel measures #f77b04 for the aspect sentence
   — the same orange as the transfigure line below, which is why that one never
   drew a complaint. Both aspects on their amulet are this colour.
   Measured by tools/measure-tooltip-colours.mjs, not sampled by eye. */
.d4tip[data-rarity="legendary"] .d4tipPowerLine { color: var(--tip-aspect) }
.d4tip[data-rarity="unique"]    .d4tipPowerLine { color: #c98c4c }
.d4tip[data-rarity="mythic"]    .d4tipPowerLine { color: #c5a1d3 }
/* A TRANSFIGURED power is its own colour whatever the item is, because the whole
   point of the marker is that this line did not come with the item. */
.d4tip .d4tipPowerLine.is-transfig { color: #f77f05 }
/* THE LIVE READOUT under a power — Esu's Heirloom's "Current Bonus: 52.0% [26.0 - 52.0]%".
   No colour of its own and no rule above it: in the game it is the same description string
   as the power, separated from it by a blank line, and it inherits the power's colour for
   the rarity. The rail padding is the power line's, so it aligns under the sentence rather
   than under the icon. */
.d4tip .d4tipPowerLine.is-readout { margin-top: .75em }

/* The words the game colours inside a power sentence. */
.d4k { color: var(--tip-accent) }
.d4n { color: var(--tip-number) }
.d4r { color: var(--tip-range) }
/* ⚠ THE NUMBER INSIDE AN ASPECT SENTENCE IS BLUE, and inside a MYTHIC's unique
   power it is YELLOW on light purple. The owner, 14 Aug 2026: "make the aspect
   value blue… And Mythic uniques unique power yellow instead of blue, with
   light purple text."

   Both are on their own items. Their Contracurse Ward's aspect reads "deal 45%
   [x] [30 - 45]% increased damage" with the 45% blue inside an orange sentence;
   their Drognan's Anguish reads "Burns you for 5% of your Maximum Life over 10
   seconds… 325%[x]" with every number yellow inside a light purple one. The
   yellow is measured at #e9ea08 and the purple at #d897ec, which is why the
   mythic text colour below is not the #c5a1d3 that was here.

   It is the RARITY that decides, not the quality: Drognan's is quality 25 and
   its affix values are blue while its power numbers are yellow. */
/* THE ROLLED NUMBER IS BLUE AND A STATIC ONE IS GOLD. The owner, 14 Aug 2026:
   "We only color blue the number that can have multiple roll ranges on aspects,
   not the static ones." Their Overheating aspect carries both in one sentence —
   `10%` gold, `30.0%` blue — and `powerText` decides which is which by whether
   the range is attached to it. */
.d4tipPowerLine .d4n { color: var(--tip-number) }
.d4s { color: var(--tip-power-static) }
/* A MYTHIC'S POWER IS GOLD THROUGHOUT, its rolled number included: their
   Drognan's Anguish prints 325% [260 - 325]% in the same gold as its two static
   numbers, against light purple prose. */
.d4tip[data-rarity="mythic"] .d4tipPowerLine { color: var(--tip-power-text-mythic) }
.d4tip[data-rarity="mythic"] .d4tipPowerLine .d4n { color: var(--tip-power-static) }

/* ⚠ ANY QUALITY OVER 25 TURNS THE AFFIX VALUES YELLOW — the owner, in exactly
   those words, explaining the Jade Relic: "That is when a transfigure gives
   +x Item Quality that turns the affixes yellow." Their focus reads quality 40
   and every value on it is yellow where the same lines on a 25/25 item are
   blue.

   THE FLAG IS THE ITEM'S QUALITY, WHICH DOCUMENTS ALREADY CARRY, so this is a
   join rather than a judgement — item-realism.js reads the same field and
   already records their Raiment at masterwork 25/25 and quality 32. The odd
   line out on any item is still `is-mwmax`, the affix the last upgrade landed
   on; it is orange at quality 25 and violet on their 40, which is one step
   further along the same ladder and is NOT drawn yet — see CLAUDE.md. */
.d4tip[data-quality="high"] .d4tipAffix.is-mw .d4tipVal { color: var(--tip-value-hq) }

/* ---- sockets: gems, and a rune pair -------------------------------------- */
.d4tipSockets { list-style: none; margin: 4px 0 0; padding: 0; display: flex;
  flex-direction: column; gap: 6px }
/* ⚠ THE SOCKET ROW WAS PROPORTIONALLY SMALLER THAN THE GAME'S, and the gem was
   the casualty. The owner: "I do not see the gem icon in the socket." It was
   drawn, it had loaded, and a DOM read said so — at TEN BY SIXTEEN REAL PIXELS.

   MEASURED ON BOTH SIDES, the same way, by the script in this commit's message:
   in their own screenshot the emerald is 20x37 against text rows 29 tall, so
   the gem is 1.28 line-heights; ours was 10x16 against 12, which is 1.33. The
   RATIO was already right — what was wrong is that this row's text was 12.5px
   where the affix lines above it are 13.6px, so the whole assembly rendered a
   third smaller than the game's and took the gem down with it.

   So the row is sized from the affix text and the icon is expressed in `em`,
   which keeps the game's measured 1.28 whatever the panel scales to. The gem
   artwork is 128x128 with the ink filling 99% of the height and 56% of the
   width — a tall hexagon with side padding — so `contain` in a square box is
   what decides its height, and the height is the number that was measured. */
.d4tipSocket { display: flex; align-items: flex-start;
  gap: max(4px, calc(var(--tip-rail) - 26px)); font-size: 13.6px; line-height: 1.5 }
.d4tipSocketIcon { width: 2.05em; height: 2.05em; flex-shrink: 0; display: inline-flex;
  align-items: center; justify-content: center; position: relative;
  background: url("/assets/tooltip/socket.webp") center/contain no-repeat }
.d4tipSocketIcon img { width: 1.9em; height: 1.9em; object-fit: contain; display: block }
.d4tipSocketIcon.is-empty { opacity: .7 }
.d4tipSocketText { display: flex; flex-direction: column; gap: 1px; min-width: 0 }
.d4tipSocketLabel { color: #ece7dc; font-size: 13.6px; letter-spacing: .01em }
.d4tipSocketName { color: #8a8680; font-size: 11.2px; font-style: italic; margin-top: -1px }

.d4tipRuneword { display: flex; gap: 8px; margin: 4px 0 }
.d4tipRunewordChain { display: flex; flex-direction: column; align-items: center; flex-shrink: 0 }
/* THE SECOND ICON HAS TO LAND ON THE SECOND ROW. The game puts the ritual icon
   beside "YulMot (50/150) - Pugilistic" and the invocation icon beside
   "Shadow", one per title row. Ours drifted two rows down into the sentences,
   because the icons were unsized and the link added a fixed 10px on top.
   Sizing each icon to ONE title row (13.6px x 1.35) and closing the link up
   makes the stack track those two rows whatever the font does. */
/* ⚠ SIZING THE <img> ALONE DID NOTHING VISIBLE, because each icon is wrapped in
   a `.d4tipSocketIcon` span that carries the 28px socket ring — so the SPAN
   kept the stack two rows tall while the picture inside it shrank. Both. */
.d4tipRunewordChain .d4tipSocketIcon { width: 18px; height: 18px; background: none;
  display: block; flex: none }
.d4tipRunewordChain img { width: 18px; height: 18px; object-fit: contain; display: block }
.d4tipRunewordLink { width: 0; height: 0 }
.d4tipRunewordBody { flex: 1; min-width: 0 }
.d4tipRunewordName { color: #c6bcad; font-size: 13.6px; line-height: 1.35; margin-bottom: 2px }
.d4tipRunewordDesc { color: #a1a1a1; font-size: 12.5px; line-height: 1.35; margin-top: 1px }
/* THE GAME'S OWN RUNEWORD BLOCK, off the owner's Raiment of the Infinite in
   gearonly.png — the only complete one in any screenshot. The RITUAL half is
   amber and the INVOCATION half is the mythic purple, which is how the trigger
   and the payload are told apart at a glance; the counts, the trigger count and
   the overflow line are all the same grey the range brackets use.

   ⚠ THESE HEXES ARE COLLAGE-DERIVED AND SO ARE APPROXIMATE. Measured at
   #e9ba94 for the name row and #b99538 for the ritual sentence — but the
   control on that file says it lightens (a true #f77b04 reads #e88518 there),
   so these are set to the nearest colour the panel already uses rather than to
   a shifted sample presented as exact. One full-size runeword screenshot
   settles all three. */
.d4tipRunewordCount { color: var(--tip-range) }
.d4tipRuneTitle { color: #d79942 }
.d4tipRunewordDesc.is-ritual { color: #c8922e }
.d4tipRunewordDesc.is-invocation { color: #c5a1d3 }
.d4tipRunewordDesc.is-overflow { color: var(--tip-range) }
/* The two halves of a runeword are named in the game's own two colours — the
   condition rune reads as the trigger, the effect rune as what it buys. */
.d4tipRuneCond { color: #f8f066 }
.d4tipRuneEff  { color: #6e9eff }

/* ---- flavour and footer -------------------------------------------------- */
.d4tipFlavour { color: var(--tip-ink-dim); font-style: italic; font-size: 13px;
  line-height: 1.45; margin: 4px 0 }
.d4tipReqLevel { color: #c4b496; text-align: right; margin-top: 6px;
  font-family: Georgia, "Times New Roman", serif; font-size: 12.8px }
/* THE GAME'S FOOTER LINES, right-aligned under everything: "Cannot Salvage", a green
   "Seasonal Item", then a rule and "Sell Value: ...". Sampled off the owner's own capture of
   Liquid Rainbow (17 Sep 2026); the green is the same one docs/s15/game-text-colours.json
   holds for the game's seasonal leaf. */
.d4tipFoot { color: var(--tip-ink-bright); text-align: right; margin-top: 4px;
  font-family: Georgia, "Times New Roman", serif; font-size: 13px }
.d4tipFoot.is-season { color: #3ec98c }
.d4tipFootLabel { color: var(--tip-ink-dim) }
/* a description line is a paragraph, not a stat: no bullet, and no bullet-sized indent */
.d4tipAffix.is-plain { display: block; padding-left: 0 }
.d4tipAffix.is-plain .d4tipAffixText { display: block }
.d4tipRequires { color: #c55757; opacity: .95; display: flex; flex-wrap: wrap;
  justify-content: flex-end; align-items: center; gap: 4px; margin-top: 2px;
  font-family: Georgia, "Times New Roman", serif; font-size: 12.5px }
.d4tipClass { display: inline-flex; align-items: center; gap: 3px }
.d4tipClass img { width: 11px; height: 11px; object-fit: contain; display: block }
.d4tipOnly { opacity: .85; font-style: italic }

/* WHAT TO CHASE ON THIS PIECE — two affixes at Greater, the temper, the gem.
   OURS, NOT THE GAME'S, so it is visibly a build instruction rather than
   something the item says about itself: a gold rule above it, the star the game
   uses for a Greater Affix, and the quieter type the provenance line uses.
   Drawn as plain colour and borders — no CSS filter and no var() layering on
   art, both of which have broken Gecko on this project before. */
.d4tipChase { margin-top: 7px }
.d4tipChaseHead { font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: #c8a24a; display: flex; align-items: baseline; gap: 7px }
.d4tipChaseWhy { font-size: 9px; letter-spacing: .08em; color: #6f675c;
  border: 1px solid #3a352c; border-radius: 2px; padding: 0 3px }
.d4tipChaseRow { display: flex; align-items: baseline; gap: 6px; margin-top: 3px;
  font-size: 12px; color: #d8d2c4 }
.d4tipChaseStar { color: #e0a03c; font-size: 11px; line-height: 1 }
.d4tipChaseText { flex: 1 1 auto }
.d4tipChaseGain { font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 10px; color: #7fa87f }
/* a target the piece already carries at Greater: the row still says what to chase,
   in the grey the temper and gem rows use, because there is nothing left to gain */
.d4tipChaseGain.is-have { color: #6f675c }
/* the line a suggestion would replace, under it and quieter than it */
.d4tipChaseSwap { display: block; font-size: 10.5px; color: #8b8175; margin-top: 1px }
.d4tipChaseRow.is-temper .d4tipChaseStar { color: #9fb4d8 }
.d4tipChaseRow.is-temper .d4tipChaseGain,
.d4tipChaseRow.is-gem .d4tipChaseGain { color: #6f675c }
.d4tipChaseRow.is-gem .d4tipChaseStar { color: #8fb6c8 }
/* the game's own {icon:bullet} inside a power sentence: a bullet, on its own line,
   the way the game lists what a Conjuration grants (9 Sep 2026) */
.d4tipTextBullet { color: #8b8175; margin: 0 4px 0 2px }
/* the masterwork capstone, in the violet the game draws its own bullet in
   (measured [122,120,241] on the owner's Zealot ring, 8 Sep 2026) */
.d4tipChaseRow.is-capstone .d4tipChaseStar { color: #7a78f1 }
.d4tipChaseRow.is-capstone .d4tipChaseGain { color: #8b8175 }
.d4tipChaseNote { margin-top: 4px; font-size: 10px; color: #6f675c;
  font-family: ui-monospace, Menlo, Consolas, monospace }

/* Provenance, ours and not the game's, so it is quiet and at the bottom. */
.d4tipProv { font-size: 10.5px; line-height: 1.45; color: #6f675c; margin-top: 8px;
  font-family: ui-monospace, Menlo, Consolas, monospace }
.d4tipProv.is-warn { color: #e0a03c }

.d4tipEmpty { color: #8a8680; font-size: 12.5px }
