Compare commits

..

2 Commits

Author SHA1 Message Date
luxick 0623ba903b Update AdMech background 2026-08-03 17:32:52 +02:00
luxick 96bcc975a7 Adjust Image rendering in header
there are now layers in the card header so that the image does not
overlap any readable text
2026-08-03 17:15:03 +02:00
4 changed files with 65 additions and 6 deletions
+20
View File
@@ -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 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. 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 ## Tests
The transforms began as three standalone Python scripts The transforms began as three standalone Python scripts
+33 -6
View File
@@ -26,6 +26,12 @@ import {
import { useLore } from "../helpers/useLore"; import { useLore } from "../helpers/useLore";
import { HIDE_UNIT_COMPOSITION, ShortSummaryTable } from "../fork"; 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 getShortSummarySubtitle = (force) => {
const details = []; const details = [];
if (force.forceDisposition) { if (force.forceDisposition) {
@@ -388,6 +394,9 @@ const Unit = ({
backgroundSize: "cover", backgroundSize: "cover",
color: "#fff", color: "#fff",
position: "relative", 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",
}} }}
> >
<div <div
@@ -406,6 +415,11 @@ const Unit = ({
top: 0, top: 0,
width: "100%", width: "100%",
display: "flex", display: "flex",
// The model image sits between this accent bar and the text,
// as it does on the official cards. Every layer of the header
// therefore needs an explicit z-index: bar 1, image 2, text 3,
// lore 4 and 5.
zIndex: 1,
}} }}
> >
<div <div
@@ -446,22 +460,29 @@ const Unit = ({
lineHeight: "1", lineHeight: "1",
fontWeight: 800, fontWeight: 800,
textTransform: "uppercase", textTransform: "uppercase",
zIndex: 1, zIndex: 3,
position: "relative", position: "relative",
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
marginBottom: 2, marginBottom: 2,
// This box is as wide as the card and now lies over the image,
// where it would swallow the drag and the wheel that place the
// image. Only the glyphs need the pointer.
pointerEvents: "none",
}} }}
> >
{name} <span style={{ pointerEvents: "auto" }}>{name}</span>
</div> </div>
<div className="relative flex gap-4"> <div className="pointer-events-none relative z-[3] flex gap-4">
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: 6, gap: 6,
// See the note on the name above. The stat boxes are narrow,
// but the box around them reaches the far edge of the card.
pointerEvents: "auto",
}} }}
> >
{modelStats.map((model, index) => ( {modelStats.map((model, index) => (
@@ -489,7 +510,7 @@ const Unit = ({
top: 0, top: 0,
bottom: 0, bottom: 0,
width: "40%", width: "40%",
zIndex: 101, zIndex: 4,
pointerEvents: "none", pointerEvents: "none",
background: background:
"linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.55) 40%, rgba(0,0,0,.7) 100%)", "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, top: 0,
bottom: 0, bottom: 0,
width: "29%", width: "29%",
zIndex: 102, zIndex: 5,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
padding: "30px 14px 14px 6px", padding: "30px 14px 14px 6px",
@@ -547,8 +568,14 @@ const Unit = ({
height: "100%", height: "100%",
bottom: 0, bottom: 0,
width: hasLore ? "40%" : "60%", width: hasLore ? "40%" : "60%",
zIndex: 100, zIndex: 2,
overflow: "hidden", 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 && <ImgEditor image={image} name={name} />} {hasImage && <ImgEditor image={image} name={name} />}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

After

Width:  |  Height:  |  Size: 928 KiB

+12
View File
@@ -208,6 +208,18 @@
} }
@media print { @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,
.print-display-none * { .print-display-none * {
display: none !important; display: none !important;