Compare commits
3 Commits
e7ecbcabc0
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 836a53e95e | |||
| 0623ba903b | |||
| 96bcc975a7 |
@@ -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
|
||||||
|
|||||||
+34
-7
@@ -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} />}
|
||||||
@@ -1003,7 +1030,7 @@ const InvulRow = ({ hasInvul }) => {
|
|||||||
backgroundColor: "var(--primary-color)",
|
backgroundColor: "var(--primary-color)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
INVULNERABLE SAVE{isSpecialInvul ? "*" : ""}
|
INSV{isSpecialInvul ? "*" : ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 454 KiB After Width: | Height: | Size: 928 KiB |
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user