diff --git a/CLAUDE.md b/CLAUDE.md index 468b7d0..a783135 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,6 +73,26 @@ Two properties of the card header are less obvious than they look: Only the 10th-edition and 11th-edition renderers show lore. Leave the 9th-edition renderer in `src/9th/` alone. It lays out its header differently. +## The header has five layers + +The card header stacks the coloured accent bar (1), the model image (2), the +name and the stat line (3) and the two lore elements (4, 5). The order comes +from the official cards, where a wide image passes *behind* the text. Every +layer therefore carries an explicit `z-index`, and the header sets +`isolation: isolate` so those five numbers never meet the rest of the card. A +new absolutely positioned element in the header needs a number from this scale; +without one it lands under the image. + +The name and the stat line are wide, mostly empty boxes, and they now lie over +the image. `pointer-events: none` on the box with `auto` on the text inside +keeps the drag and the wheel that place the image (`ImgEditor`) reaching it. A +new element on layer 3 needs the same treatment, or it takes the pointer away +from the image behind it. + +The image box also carries a `mask-image` (`imageFade` in +[`src/10th/Roster.jsx`](src/10th/Roster.jsx)). A picture wider than its box +would otherwise end at a hard vertical edge in the middle of the stat line. + ## Tests The transforms began as three standalone Python scripts diff --git a/src/10th/Roster.jsx b/src/10th/Roster.jsx index f9920ca..350fc41 100644 --- a/src/10th/Roster.jsx +++ b/src/10th/Roster.jsx @@ -26,6 +26,12 @@ import { import { useLore } from "../helpers/useLore"; import { HIDE_UNIT_COMPOSITION, ShortSummaryTable } from "../fork"; +// Soft left edge for the model image in the card header. Opaque over the right +// two thirds of the image box, so only the part that reaches across the stat +// line is faded. +const imageFade = + "linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.55) 16%, rgba(0,0,0,1) 36%)"; + const getShortSummarySubtitle = (force) => { const details = []; if (force.forceDisposition) { @@ -388,6 +394,9 @@ const Unit = ({ backgroundSize: "cover", color: "#fff", position: "relative", + // Keeps the header's five layers to itself, so their z-indices + // never have to be compared against the rest of the card. + isolation: "isolate", }} >
- {name} + {name}
-
+
{modelStats.map((model, index) => ( @@ -489,7 +510,7 @@ const Unit = ({ top: 0, bottom: 0, width: "40%", - zIndex: 101, + zIndex: 4, pointerEvents: "none", background: "linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.55) 40%, rgba(0,0,0,.7) 100%)", @@ -502,7 +523,7 @@ const Unit = ({ top: 0, bottom: 0, width: "29%", - zIndex: 102, + zIndex: 5, display: "flex", alignItems: "center", padding: "30px 14px 14px 6px", @@ -547,8 +568,14 @@ const Unit = ({ height: "100%", bottom: 0, width: hasLore ? "40%" : "60%", - zIndex: 100, + zIndex: 2, overflow: "hidden", + // A picture wider than its box would otherwise end at a hard + // vertical edge over the stat line. The official cards let it + // fade into the dark background instead. The mask fades the + // image itself, so the background stays untouched. + maskImage: imageFade, + WebkitMaskImage: imageFade, }} > {hasImage && } diff --git a/src/index.css b/src/index.css index 3db28ca..c79c215 100644 --- a/src/index.css +++ b/src/index.css @@ -208,6 +208,18 @@ } @media print { + /* The cards get cut apart, so the white border around a cut card comes + from two different places: the page margin on the outer edges, and + half of the gap between two cards on the edges in between. The gap is + therefore twice the page margin, and every edge ends up the same. */ + @page { + margin: 8mm; + } + + .avoid-page-break { + margin-bottom: 16mm !important; + } + .print-display-none, .print-display-none * { display: none !important;