General shifts

This commit is contained in:
2026-09-12 08:33:44 -07:00
parent 56f010a71e
commit 64c03a28a4
17 changed files with 176 additions and 27 deletions

View File

@@ -0,0 +1,14 @@
# Card-back concept comparison
Four independent concepts generated with the built-in image generation tool. The existing card-back.png and harness assets are unchanged.
| Concept | Direction | Review note |
| --- | --- | --- |
| 01 Cathedral Rose | Teal rose window, cream/sage panes, small central cross | Closest to existing stained-glass identity; still relatively ornamental. |
| 02 Woven Burgundy | Burgundy, cream medallion, vine geometry | Warm devotional book-cover direction; densest of the set. |
| 03 Cream Seal | Cream field, teal seal, quiet rose geometry | Calmest and clearest digital silhouette; strongest minimalist direction. |
| 04 Sacred Arches | Dark slate/teal architectural glass | Strong dark alternative; geometric framing and fewer floral motifs. |
Review all four in review/comparison.png. Originals are in source/, with exact generation prompts in source/prompts.json. No lettering was requested; any eventual wordmark should be typeset separately with the selected product font.
These are concept assets, not approved production backs. Requested two-way rotational symmetry is approximate in generated images and has not been established as exact. The fourth concept includes a shaded perimeter that should be flattened for the runtime texture. Before promotion, refine geometry/symmetry, verify thumbnail recognition, prepare delivery sizes, preserve opaque rectangular textures, and evaluate the back in the card harness. Use one approved reverse across the rarity ladder so it does not reveal card identity or rarity.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@@ -0,0 +1,22 @@
[
{
"key": "01-cathedral-rose",
"prompt": "Use case: stylized-concept. Asset type: full-face reverse artwork for a digital Christian collectible card game, Sanctification. Create ONE standalone portrait card-back design, aspect ratio 5:7, artwork flush to all four edges of an opaque rectangular canvas. Flat straight-on artwork, no mockup, no surroundings, no perspective, no cast shadow, no rounded transparent corners. Sacred, beautiful, stylized; stained-glass influence primary, tapestry and illuminated manuscript ornament secondary. Restrained high-quality devotional visual identity that remains readable at small digital binder size. No lettering, letters, numbers, wordmark, rarity, badges, watermark, glow, neon, photorealism, wear, torn paper or imperfections. This is a shared reverse across every rarity. Prefer balanced 180-degree rotational symmetry; use an equal-armed Christian cross if a cross is present, no directional symbols. Gold if used is a restrained matte antique-gold accent, never shiny casino filigree. Concept A \u2014 Cathedral Rose. A deep midnight teal field with generous quiet space. At the center, one beautifully constructed circular cathedral rose window, medium scale rather than occupying the entire card, made of large cream and muted sage panes, with a very small equal-armed cross structurally integrated into the middle. Dark lead segmentation and a few fine matte antique-gold lines. A subtle single inset architectural border with gently arched corner geometry. Strong circular silhouette, elegant restrained ornament, glass-like color depth without sparkling surface effects. No red ring, no heavy bright gold frame, no giant cross. Visually sophisticated and contemplative.",
"source": "/home/dkzver/.codex/generated_images/01a09262-4692-7342-ac44-be6af0e68cbf/exec-fbf523b0-2007-4ac3-945f-26376e382837.png"
},
{
"key": "02-woven-burgundy",
"prompt": "Use case: stylized-concept. Asset type: full-face reverse artwork for a digital Christian collectible card game, Sanctification. Create ONE standalone portrait card-back design, aspect ratio 5:7, artwork flush to all four edges of an opaque rectangular canvas. Flat straight-on artwork, no mockup, no surroundings, no perspective, no cast shadow, no rounded transparent corners. Sacred, beautiful, stylized; stained-glass influence primary, tapestry and illuminated manuscript ornament secondary. Restrained high-quality devotional visual identity that remains readable at small digital binder size. No lettering, letters, numbers, wordmark, rarity, badges, watermark, glow, neon, photorealism, wear, torn paper or imperfections. This is a shared reverse across every rarity. Prefer balanced 180-degree rotational symmetry; use an equal-armed Christian cross if a cross is present, no directional symbols. Gold if used is a restrained matte antique-gold accent, never shiny casino filigree. Concept B \u2014 Woven Devotion. Muted deep burgundy and aubergine with a cream central cruciform lozenge medallion. The outer field is a restrained tapestry-inspired geometric weave translated into stained-glass pane shapes: interwoven vines and small diamond forms at low contrast, not literal fabric photography. A modest cream equal-armed cross appears within the central medallion, drawn with dark lead lines, flanked symmetrically by simple olive-leaf geometry. Fine antique-gold accent stitches only. Flat perimeter is dark burgundy, no thick gold outer frame. Rich and warm but calm, sacred book-cover character, restrained ornament hierarchy.",
"source": "/home/dkzver/.codex/generated_images/01a09262-4692-7342-ac44-be6af0e68cbf/exec-5d8dae8c-8d06-4857-a83a-50acea28d1fc.png"
},
{
"key": "03-cream-seal",
"prompt": "Use case: stylized-concept. Asset type: full-face reverse artwork for a digital Christian collectible card game, Sanctification. Create ONE standalone portrait card-back design, aspect ratio 5:7, artwork flush to all four edges of an opaque rectangular canvas. Flat straight-on artwork, no mockup, no surroundings, no perspective, no cast shadow, no rounded transparent corners. Sacred, beautiful, stylized; stained-glass influence primary, tapestry and illuminated manuscript ornament secondary. Restrained high-quality devotional visual identity that remains readable at small digital binder size. No lettering, letters, numbers, wordmark, rarity, badges, watermark, glow, neon, photorealism, wear, torn paper or imperfections. This is a shared reverse across every rarity. Prefer balanced 180-degree rotational symmetry; use an equal-armed Christian cross if a cross is present, no directional symbols. Gold if used is a restrained matte antique-gold accent, never shiny casino filigree. Concept C \u2014 Quiet Cream Seal. Majority cream field, warm cream approximately #f4ead4, deep teal approximately #263f50 linework. Minimal spacious devotional design with one small-to-medium central dark teal circular seal containing a cream equal-armed Christian cross. Surround the seal with a subtle geometric rose-window halo drawn in fine dark teal lead lines, plenty of blank cream space. Thin double inset border, beautifully balanced tiny diamond motifs at the midpoint of each edge, and only a hint of muted sage within the central window. No gold except at most a tiny central accent; no text panels, decorative lettering, distressed parchment, embossed photography or busy background. Digital-first, striking, calm and timeless.",
"source": "/home/dkzver/.codex/generated_images/01a09262-4692-7342-ac44-be6af0e68cbf/exec-9af4cbbb-d6cc-4a24-8014-2f807f3af858.png"
},
{
"key": "04-sacred-arches",
"prompt": "Use case: stylized-concept. Asset type: full-face reverse artwork for a digital Christian collectible card game, Sanctification. Create ONE standalone portrait card-back design, aspect ratio 5:7, artwork flush to all four edges of an opaque rectangular canvas. Flat straight-on artwork, no mockup, no surroundings, no perspective, no cast shadow, no rounded transparent corners. Sacred, beautiful, stylized; stained-glass influence primary, tapestry and illuminated manuscript ornament secondary. Restrained high-quality devotional visual identity that remains readable at small digital binder size. No lettering, letters, numbers, wordmark, rarity, badges, watermark, glow, neon, photorealism, wear, torn paper or imperfections. This is a shared reverse across every rarity. Prefer balanced 180-degree rotational symmetry; use an equal-armed Christian cross if a cross is present, no directional symbols. Gold if used is a restrained matte antique-gold accent, never shiny casino filigree. Concept D \u2014 Sacred Arches. An architectural all-over pattern of interlocking cathedral-window arches in desaturated midnight blue, teal, and dark slate, built from stylized leaded glass geometry. The pattern is shallow and low contrast, with a strong central cream diamond-window opening holding a simple dark teal equal-armed Christian cross. Central opening occupies about one third of card width. A few muted sage panes and very spare antique-gold joints add depth. Two-way rotational symmetry through mirrored arch geometry top and bottom. A thin dark perimeter, without a bright gold border. More architectural than floral, modern sacred visual identity with generous pane shapes and quiet supporting detail.",
"source": "/home/dkzver/.codex/generated_images/01a09262-4692-7342-ac44-be6af0e68cbf/exec-8a041d78-bdaf-4cf4-86ad-37448154237c.png"
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -112,8 +112,8 @@ Changing a light type, color, position, intensity, environment contribution, or
| Control | Default | Available range |
|---|---:|---:|
| Finish | Holographic | Printed ink / Foil / Holographic |
| Material | Paper | Paper / Linen / Metal / Wood |
| Finish | Holographic | Printed ink / Foil / Holographic / Gold leaf / Frosted glass |
| Material | Paper | Paper / Linen / Metal / Wood / Leather |
| Finish strength | 0.60 | 0-1 |
| Roughness | 0.23 | 0.05-0.8 |
| Surface detail | 0.14 | 0-0.45 |
@@ -123,6 +123,16 @@ Changing a light type, color, position, intensity, environment contribution, or
Finish and material are independent concepts, but combinations should exist only when they have an intentional visual interpretation.
Leather, Gold leaf, and Frosted glass are experimental
September 11 additions awaiting visual review. They do not change the existing
default selection or approved recipes. Leather applies generic substrate
relief and edge treatments with grain suppressed over mask-protected ink; the two
new coatings follow each card's finish mask. Vellum and Pearlescent were removed
after visual review; Leather's initial aggressive grain was replaced with shallow,
softly filtered grain pending further approval.
Frosted glass is an opaque etched coating, not a transparent card material. See the
[experimental treatment descriptions](README.md#experimental-materials-and-finishes).
## Substrate Recipes
### Paper

View File

@@ -27,6 +27,32 @@ Transform gizmos and a general timeline are intentionally deferred. The separate
## Tear-open foil pack prototype
### Experimental materials and finishes
The toolbar, Lab controls, saved experiments, and random pack pool share the same
available choices. The following treatments are visual trials, not approved defaults:
- **Leather material:** shallow, softly rounded grain, restrained warm ink absorption,
and a brown edge. Mask-protected ink stays smooth; artwork and lettering stay anchored.
The original dense, deeply recessed grain was rejected for poor readability.
The current trial uses moderate soft-grain relief (0.0032 scene units at default
detail), between the original harsh treatment and the overly faint first revision.
- **Gold leaf finish:** warm metallic gilding with fine irregular folds and mottling,
rather than ordinary foil's mostly color-preserving sheen.
Its gold range and highlights are compressed, with more original ink retained,
to reduce contrast without removing the folded texture.
- **Frosted glass finish:** cool satin haze and fine etched microrelief, without
transparency or blurring the underlying artwork.
Vellum and Pearlescent were removed after visual review because they were not
sufficiently distinct. Older experiments using either report an unsupported format.
All finishes follow the supplied finish mask, including text protection. Leather also
uses the mask to suppress grain over protected ink. Other material textures cover the
substrate independently of that mask. Finish strength controls
the new coatings; Surface detail controls the material relief. No card-specific
regions, artwork generation, or additional texture files are required.
Choose **Pack** in the Mode selector (expand **Controls** first on mobile).
Use **Pack style** in the same toolbar to compare three wrapper-only design studies:

View File

@@ -1,8 +1,10 @@
import * as THREE from 'three'
import { cardSceneDimensions } from './cardGeometry'
export type FinishName = 'Printed ink' | 'Foil' | 'Holographic'
export type SubstrateName = 'Paper' | 'Linen' | 'Plastic' | 'Metal' | 'Wood'
export const finishNames = ['Printed ink', 'Foil', 'Holographic', 'Gold leaf', 'Frosted glass'] as const
export const substrateNames = ['Paper', 'Linen', 'Metal', 'Wood', 'Leather'] as const
export type FinishName = typeof finishNames[number]
export type SubstrateName = typeof substrateNames[number] | 'Plastic'
export interface MaterialControls {
finish: FinishName
@@ -31,6 +33,7 @@ export function applyEdgeMaterialControls(
Plastic: { color: '#d4cab0', wornColor: '#c7ccd2', roughness: 0.24, metalness: 0, clearcoat: 0.72 },
Metal: { color: '#a8adb7', wornColor: '#d0d5dc', roughness: 0.2, metalness: 0.86, clearcoat: 0.18 },
Wood: { color: '#6b3f21', wornColor: '#a87847', roughness: 0.56, metalness: 0, clearcoat: 0.08 },
Leather: { color: '#503023', wornColor: '#976c49', roughness: 0.72, metalness: 0, clearcoat: 0.04 },
}
const look = edgeLooks[controls.substrate]
material.color.set(look.color)
@@ -134,6 +137,13 @@ const fragmentShader = `
return sin(phase) * visibility;
}
float leatherGrain(vec2 uv) {
vec2 p = uv * vec2(42.0, 58.8);
float visibility = 1.0 - smoothstep(0.2, 0.65, max(fwidth(p.x), fwidth(p.y)));
float pebbles = smoothstep(0.15, 0.85, fiberNoise(p));
return mix(0.5, pebbles, visibility);
}
// Height and analytic UV slopes keep thread ridges lit even at oblique angles.
vec3 linenRelief(vec2 uv, vec4 visibility) {
vec2 frequency = vec2(52.0, 73.0) * 6.2831853;
@@ -205,6 +215,7 @@ const fragmentShader = `
if (substrateMode == 2) wearColor = vec3(0.78, 0.80, 0.82);
if (substrateMode == 3) wearColor = vec3(0.72, 0.75, 0.80);
if (substrateMode == 4) wearColor = vec3(0.62, 0.39, 0.20);
if (substrateMode == 6) wearColor = vec3(0.59, 0.40, 0.25);
float abrasion = clamp(
edgeWear * 0.55 + scratchWear * 0.30 + scuffWear * 0.22,
0.0,
@@ -280,6 +291,8 @@ const fragmentShader = `
float woodSurfaceGrain = 0.5;
float metalEtchDepth = 0.0;
float metalEtchExposure = 1.0;
float leatherPebble = 0.5;
float leatherCoverage = smoothstep(0.08, 0.65, mask);
if (substrateMode == 0) {
vec2 fiberUv = artworkUv * vec2(230.0, 322.0);
float visible = 1.0 - smoothstep(0.4, 1.2, max(fwidth(fiberUv.x), fwidth(fiberUv.y)));
@@ -328,8 +341,33 @@ const fragmentShader = `
surfaceHeight = woodSurfaceGrain * relief;
surfaceShade = 0.88 + woodSurfaceGrain * 0.12;
}
if (substrateMode == 6) {
vec3 view = normalize(cameraPosition - vWorldPosition);
vec2 alongSurface = vec2(dot(view, normalize(vWorldTangent)),
dot(view, normalize(vWorldBitangent)));
float relief = 0.0032 * min(detail, 1.5);
float grain = leatherGrain(artworkUv);
vec2 parallax = alongSurface / max(abs(dot(view, normalize(vWorldNormal))), 0.35)
* (grain - 0.5) * relief / cardFaceSize;
leatherPebble = leatherGrain(artworkUv + parallax);
// Treat protected ink as a smooth printed layer, not recessed leather grain.
surfaceHeight = (leatherPebble - 0.5) * relief * leatherCoverage;
surfaceShade = 1.0 - (1.0 - leatherPebble) * 0.10 * min(detail, 1.5) * leatherCoverage;
}
// Only coating microrelief follows the finish mask; protected text stays flat.
if (finishMode == 3) {
float leafFold = filteredWave(artworkUv.x * 270.0
+ fiberNoise(artworkUv * vec2(28.0, 39.2)) * 16.0);
surfaceHeight += leafFold * 0.0012 * mask * finishStrength;
}
if (finishMode == 5) {
vec2 frostUv = artworkUv * vec2(190.0, 266.0);
float visible = 1.0 - smoothstep(0.4, 1.2, max(fwidth(frostUv.x), fwidth(frostUv.y)));
surfaceHeight += (fiberNoise(frostUv) - 0.5) * visible * 0.0025 * mask * finishStrength;
}
surfaceHeight -= scratchWear * 0.0018 + scuffWear * 0.0007;
float substrateNormalScale = substrateMode == 3 ? 0.06 : 0.12;
if (substrateMode == 6) substrateNormalScale = 0.0;
vec3 normal = normalize(
vWorldNormal +
vWorldTangent * normalSample.x * normalStrength * substrateNormalScale +
@@ -369,6 +407,7 @@ const fragmentShader = `
if (substrateMode == 2) substrateRoughness = min(roughness, 0.25);
if (substrateMode == 3) substrateRoughness = min(roughness, 0.20);
if (substrateMode == 4) substrateRoughness = max(roughness, 0.48);
if (substrateMode == 6) substrateRoughness = max(roughness, 0.62);
float nDotV = max(dot(normal, viewDirection), 0.0);
float dielectricFresnel = 0.04 + 0.96 * pow(1.0 - nDotV, 5.0);
@@ -420,6 +459,10 @@ const fragmentShader = `
vec3 woodTint = mix(vec3(0.72, 0.40, 0.18), vec3(1.0, 0.86, 0.65), woodSurfaceGrain);
printedInk *= mix(vec3(1.0), woodTint, 0.25);
}
if (substrateMode == 6) {
printedInk *= mix(vec3(1.0), vec3(0.98, 0.94, 0.88), leatherCoverage);
printedInk += lightColor * substrateSpecular * leatherPebble * 0.12 * leatherCoverage;
}
if (finishMode == 0) {
vec3 wornInk = applyWearColor(printedInk, edgeWear, scratchWear, scuffWear);
gl_FragColor = vec4(wornInk, artworkSample.a);
@@ -434,6 +477,32 @@ const fragmentShader = `
float specularPower = mix(96.0, 10.0, substrateRoughness);
float directSpecular = pow(max(dot(normal, halfVector), 0.0), specularPower);
float environmentBrightness = dot(environment, vec3(0.2126, 0.7152, 0.0722));
if (finishMode >= 3) {
vec3 treatment = printedInk;
if (finishMode == 3) {
float leafMottle = fiberNoise(artworkUv * vec2(38.0, 53.2));
vec3 gold = mix(vec3(0.76, 0.51, 0.19), vec3(0.92, 0.71, 0.34), leafMottle);
float inkLuma = dot(artworkSample.rgb, vec3(0.2126, 0.7152, 0.0722));
vec3 gilding = gold * (0.42 + direct * 0.30 + inkLuma * 0.28);
gilding += gold * environment * environmentIntensity * (0.30 + fresnel * 0.35);
gilding += lightColor * vec3(1.0, 0.85, 0.46)
* min(directSpecular * lightIntensity * attenuation * 0.40, 0.28);
treatment = mix(printedInk, gilding, 0.48);
}
if (finishMode == 5) {
float satin = pow(max(dot(normal, halfVector), 0.0), 8.0)
* diffuse * lightIntensity * attenuation;
vec3 coolFrost = vec3(0.79, 0.90, 0.95);
treatment = mix(printedInk, coolFrost * (0.38 + direct * 0.32), 0.32);
treatment += coolFrost * (satin * 0.22 + fresnel * environmentIntensity * 0.32);
}
vec3 coated = mix(printedInk, treatment, clamp(mask * finishStrength, 0.0, 1.0));
coated = applyWearColor(coated, edgeWear, scratchWear, scuffWear);
gl_FragColor = vec4(coated, artworkSample.a);
#include <tonemapping_fragment>
#include <colorspace_fragment>
return;
}
if (substrateMode == 3 && finishMode == 1) {
float plateSpecular = pow(max(dot(normal, halfVector), 0.0), 150.0);
float plateEdge = smoothstep(0.003, 0.055, fwidth(mask));
@@ -555,6 +624,8 @@ export function applyMaterialControls(
'Printed ink': 0,
Foil: 1,
Holographic: 2,
'Gold leaf': 3,
'Frosted glass': 5,
}
const substrateModes: Record<SubstrateName, number> = {
Paper: 0,
@@ -562,6 +633,7 @@ export function applyMaterialControls(
Plastic: 2,
Metal: 3,
Wood: 4,
Leather: 6,
}
material.uniforms.finishMode.value = finishModes[controls.finish]
material.uniforms.substrateMode.value = substrateModes[controls.substrate]

View File

@@ -7,6 +7,8 @@ import {
applyMaterialControls,
createCardMaterial,
createStudioCubeTexture,
finishNames,
substrateNames,
type FinishName,
type MaterialControls,
type SubstrateName,
@@ -101,17 +103,12 @@ document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
</label>
<label>Finish
<select id="finish">
<option>Holographic</option>
<option>Foil</option>
<option>Printed ink</option>
${finishNames.map(name => `<option${name === 'Holographic' ? ' selected' : ''}>${name}</option>`).join('')}
</select>
</label>
<label>Material
<select id="substrate">
<option>Paper</option>
<option>Linen</option>
<option>Metal</option>
<option>Wood</option>
${substrateNames.map(name => `<option>${name}</option>`).join('')}
</select>
</label>
<label>Pose
@@ -426,13 +423,13 @@ let applyingLightingPreset = false
const gui = new GUI({ title: 'Material proof' })
const surfaceFolder = gui.addFolder('Surface')
surfaceFolder.add(controls, 'finish', ['Printed ink', 'Foil', 'Holographic']).name('Finish').onChange((value: FinishName) => {
surfaceFolder.add(controls, 'finish', [...finishNames]).name('Finish').onChange((value: FinishName) => {
pauseScriptedMotion()
finishSelect.value = value
updateMaterial()
updateStatus()
})
surfaceFolder.add(controls, 'substrate', ['Paper', 'Linen', 'Metal', 'Wood']).name('Material').onChange((value: SubstrateName) => {
surfaceFolder.add(controls, 'substrate', [...substrateNames]).name('Material').onChange((value: SubstrateName) => {
pauseScriptedMotion()
substrateSelect.value = value
updateMaterial()
@@ -832,16 +829,9 @@ function isExperimentSnapshot(value: unknown): value is ExperimentSnapshot {
if (typeof value !== 'object' || value === null) return false
const snapshot = value as Record<string, unknown>
const fixtureValid = typeof snapshot.fixture === 'string' && snapshot.fixture.length > 0
const finishValid =
snapshot.finish === 'Printed ink' ||
snapshot.finish === 'Foil' ||
snapshot.finish === 'Holographic'
const substrateValid =
snapshot.substrate === 'Paper' ||
snapshot.substrate === 'Linen' ||
snapshot.substrate === 'Plastic' ||
snapshot.substrate === 'Metal' ||
snapshot.substrate === 'Wood'
const finishValid = finishNames.some(name => name === snapshot.finish)
const substrateValid = snapshot.substrate === 'Plastic' ||
substrateNames.some(name => name === snapshot.substrate)
const lightingPresetValid =
snapshot.lightingPreset === 'Studio' ||
snapshot.lightingPreset === 'Warm gallery' ||

View File

@@ -6,6 +6,8 @@ import {
applyEdgeMaterialControls,
applyMaterialControls,
createCardMaterial,
finishNames,
substrateNames,
type FinishName,
type SubstrateName,
} from './cardMaterial'
@@ -20,8 +22,8 @@ export interface PackCardSpec {
}
export const packSize = 10
export const packFinishes: readonly FinishName[] = ['Printed ink', 'Foil', 'Holographic']
export const packSubstrates: readonly SubstrateName[] = ['Paper', 'Linen', 'Metal', 'Wood']
export const packFinishes = finishNames
export const packSubstrates = substrateNames
function randomChoice<T>(values: readonly T[], random: () => number) {
const roll = random()