paper-dynasty-card-creation/docs/refractor-tier-mockup.html
Cal Corum f329d74ed8 docs: refractor tier mockup — diamond indicator, effects, and visual spec
Interactive mockup for refractor card art with:
- 4-quadrant diamond tier indicator (baseball base-path fill order)
- Metallic sheen + pulse glow effect (approved combo)
- Tier colors: T1 orange, T2 red, T3 purple, T4 blue-flame
- T3 gold shimmer sweep, T4 prismatic rainbow + dual glow + bar shimmer
- Cherry-pick reference: docs/refractor-visual-spec.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 19:33:31 -05:00

2240 lines
81 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Refractor Tier Visual Design — Paper Dynasty</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700&family=Source+Sans+3:wght@400;600;700&display=swap" rel="stylesheet">
<style>
/* ═══════════════════════════════════════════════════════
GLOBAL RESET & THEME
═══════════════════════════════════════════════════════ */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Open Sans', system-ui, sans-serif;
background: #0e0e12;
color: #ccc;
min-height: 100vh;
overflow-x: hidden;
}
/* ═══════════════════════════════════════════════════════
PAGE HEADER
═══════════════════════════════════════════════════════ */
.page-header {
text-align: center;
padding: 28px 24px 18px;
border-bottom: 1px solid #1e1e2a;
}
.page-header h1 {
font-size: 20px;
font-weight: 700;
color: #fff;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 5px;
}
.page-header .subtitle {
font-size: 12px;
color: #555;
letter-spacing: 0.4px;
}
/* ═══════════════════════════════════════════════════════
ANIMATION TOGGLE BAR
═══════════════════════════════════════════════════════ */
.anim-toggle-bar {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
padding: 14px 24px 6px;
}
.anim-toggle-btn {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.6px;
text-transform: uppercase;
padding: 8px 22px;
border-radius: 4px;
border: 2px solid #333;
background: #1a1a26;
color: #555;
cursor: pointer;
transition: all 0.2s;
}
.anim-toggle-btn.active {
border-color: #c9a94e;
background: #1c180a;
color: #c9a94e;
box-shadow: 0 0 14px rgba(201,169,78,0.28);
}
.anim-toggle-btn:hover:not(.active) {
border-color: #555;
color: #888;
}
.anim-toggle-hint {
font-size: 10px;
color: #3a3a4a;
letter-spacing: 0.3px;
}
/* ═══════════════════════════════════════════════════════
ANIMATION INFO PANEL
═══════════════════════════════════════════════════════ */
.anim-info-panel {
margin: 10px auto 0;
max-width: 760px;
background: #0f0f18;
border: 1px solid #1e1e2e;
border-left: 3px solid #c9a94e;
border-radius: 4px;
padding: 10px 16px;
font-size: 10px;
color: #666;
line-height: 1.65;
}
.anim-info-panel strong {
color: #999;
display: block;
margin-bottom: 5px;
font-size: 10px;
letter-spacing: 0.8px;
text-transform: uppercase;
}
.anim-info-panel ul {
padding-left: 16px;
}
.anim-info-panel li {
margin-bottom: 3px;
}
.anim-info-panel .tier-badge {
display: inline-block;
font-weight: 700;
margin-right: 2px;
}
.anim-info-panel .tier-badge.t3 { color: #c9a94e; }
.anim-info-panel .tier-badge.t4 { color: #7fdfdf; }
/* ═══════════════════════════════════════════════════════
COMPARISON SECTION — single row, all 5 tiers
═══════════════════════════════════════════════════════ */
.comparison-section {
padding: 20px 20px 12px;
}
.comparison-section .section-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #444;
margin-bottom: 14px;
text-align: center;
}
.comparison-row {
display: flex;
justify-content: center;
align-items: flex-end;
gap: 12px;
flex-wrap: nowrap;
margin-bottom: 18px;
}
.comparison-slot {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: transform 0.18s;
}
.comparison-slot:hover {
transform: translateY(-4px);
}
.comparison-slot.active .tier-label {
color: #fff;
}
.comparison-slot.active .card-thumb-wrap {
outline: 2px solid #6c8aff;
box-shadow: 0 4px 18px rgba(108,138,255,0.25);
}
.tier-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #777;
margin-bottom: 7px;
transition: color 0.2s;
white-space: nowrap;
}
.tier-added {
font-size: 9px;
color: #3a3a4a;
margin-top: 5px;
text-align: center;
max-width: 220px;
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.comparison-slot:hover .tier-added { color: #555; }
.comparison-slot.active .tier-added { color: #5a6a8a; }
/* Standard thumbnail: scale ~0.19 (1200x600 -> 228x114) */
.card-thumb-wrap {
overflow: hidden;
border-radius: 3px;
background: #111118;
transition: box-shadow 0.2s, outline 0.2s;
}
.standard-thumb .card-thumb-wrap {
width: 228px;
height: 114px;
}
.standard-thumb .card-scale-container {
transform: scale(0.19);
transform-origin: top left;
width: 1200px;
height: 600px;
}
/* T4 featured thumbnail: scale ~0.26 (1200x600 -> 312x156) */
.featured-thumb .card-thumb-wrap {
width: 312px;
height: 156px;
}
.featured-thumb .card-scale-container {
transform: scale(0.26);
transform-origin: top left;
width: 1200px;
height: 600px;
}
.featured-thumb .tier-label {
color: #c9a94e;
}
/* ═══════════════════════════════════════════════════════
FULL-SIZE DETAIL + CONTROLS LAYOUT
═══════════════════════════════════════════════════════ */
.detail-section {
display: flex;
gap: 0;
padding: 0 20px 48px;
justify-content: center;
align-items: flex-start;
}
.detail-card-area {
flex-shrink: 0;
}
.detail-card-area .detail-heading {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #444;
margin-bottom: 10px;
}
.detail-card-area .detail-heading span {
color: #8a9acc;
font-weight: 700;
}
.detail-card-wrap {
border: 1px solid #2a2a3a;
border-radius: 3px;
overflow: hidden;
background: #111118;
width: 1200px;
height: 600px;
}
/* ═══════════════════════════════════════════════════════
CONTROLS PANEL
═══════════════════════════════════════════════════════ */
#controls {
width: 310px;
min-width: 310px;
background: #13131a;
border: 1px solid #222230;
border-radius: 4px;
margin-left: 18px;
padding: 14px;
overflow-y: auto;
max-height: 620px;
flex-shrink: 0;
}
#controls h2 {
font-size: 13px;
color: #ddd;
margin-bottom: 3px;
font-weight: 700;
}
#controls .panel-subtitle {
font-size: 10px;
color: #444;
margin-bottom: 14px;
}
.preset-row {
display: flex;
gap: 4px;
margin-bottom: 14px;
flex-wrap: wrap;
}
.preset-btn {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 5px 9px;
border: 1px solid #2a2a3a;
border-radius: 3px;
background: #1a1a26;
color: #888;
cursor: pointer;
transition: all 0.12s;
white-space: nowrap;
}
.preset-btn:hover {
background: #242434;
color: #bbb;
}
.preset-btn.active {
background: #20304a;
border-color: #4a6aaa;
color: #aac0f0;
}
.ctrl-group {
margin-bottom: 12px;
}
.ctrl-group h3 {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 1px;
color: #555;
margin-bottom: 5px;
padding-bottom: 3px;
border-bottom: 1px solid #1e1e28;
}
.ctrl-row {
display: flex;
align-items: center;
gap: 7px;
padding: 2px 0;
font-size: 11px;
color: #999;
}
.ctrl-row label {
flex: 1;
font-size: 10px;
color: #888;
}
.ctrl-row input[type="color"] {
width: 26px;
height: 18px;
border: 1px solid #3a3a4a;
border-radius: 2px;
background: none;
cursor: pointer;
padding: 0;
}
.ctrl-row input[type="range"] {
width: 80px;
accent-color: #5a7adf;
}
.ctrl-row select {
background: #1a1a26;
color: #bbb;
border: 1px solid #2a2a3a;
border-radius: 2px;
padding: 2px 4px;
font-size: 10px;
}
.ctrl-row .val {
font-size: 9px;
color: #4a4a5a;
width: 26px;
text-align: right;
font-variant-numeric: tabular-nums;
}
.export-area { margin-top: 12px; }
.export-btn {
width: 100%;
padding: 7px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
background: #1e2e4a;
color: #7a9adf;
border: 1px solid #3a4a6a;
border-radius: 3px;
cursor: pointer;
transition: background 0.12s;
}
.export-btn:hover { background: #283858; }
#cssOutput {
margin-top: 7px;
width: 100%;
min-height: 100px;
background: #0c0c12;
color: #7aaa8a;
border: 1px solid #1e1e2a;
border-radius: 3px;
padding: 7px;
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
font-size: 9px;
line-height: 1.5;
resize: vertical;
display: none;
}
#cssOutput.visible { display: block; }
/* ═══════════════════════════════════════════════════════
CARD BASE STYLES (faithful to production card HTML)
═══════════════════════════════════════════════════════ */
.pd-card {
width: 1200px;
height: 600px;
background: #fff;
color: #000;
font-family: 'Open Sans', sans-serif;
font-weight: 400;
position: relative;
overflow: hidden;
}
/* HEADER */
.pd-card .card-header {
display: flex;
flex-direction: row;
height: 65px;
align-items: center;
position: relative;
background: #fff;
}
.pd-card .header-left {
width: 477px;
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
flex-shrink: 0;
}
.pd-card .hand-indicator {
width: 29px;
font-size: 30px;
font-weight: 700;
text-align: center;
margin-left: 6px;
}
.pd-card .header-vline {
height: 100%;
flex-shrink: 0;
}
.pd-card .player-info {
padding-left: 6px;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
flex: 1;
overflow: hidden;
}
.pd-card .player-name {
font-variant: small-caps;
font-size: 27px;
font-weight: 700;
line-height: 1.15;
white-space: nowrap;
}
.pd-card .player-position {
padding-left: 18px;
font-size: 18px;
margin-top: -1px;
white-space: nowrap;
}
.pd-card .header-middle {
width: 246px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.pd-card .rarity-badge {
font-size: 18px;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: #333;
padding: 4px 18px;
border: 2px solid #C9A94E;
border-radius: 3px;
background: linear-gradient(135deg, #FFF8E7, #F5E6C8);
white-space: nowrap;
}
.pd-card .header-right {
flex: 1;
height: 100%;
position: relative;
overflow: hidden;
}
.pd-card .stat-stealing {
position: absolute;
left: 4px;
top: 5px;
font-size: 22px;
white-space: nowrap;
}
.pd-card .stat-running {
position: absolute;
right: 10px;
top: 5px;
font-size: 22px;
white-space: nowrap;
}
.pd-card .stat-bunting {
position: absolute;
left: 4px;
top: 33px;
font-size: 22px;
white-space: nowrap;
}
.pd-card .stat-hitrun {
position: absolute;
right: 10px;
top: 33px;
font-size: 22px;
white-space: nowrap;
}
.pd-card .stat-cardset {
position: absolute;
right: 10px;
bottom: 4px;
font-size: 13px;
color: #666;
white-space: nowrap;
}
/* COLUMN HEADERS */
.pd-card .result-header {
display: flex;
flex-direction: row;
height: 30px;
}
.pd-card .col-header-left-group {
display: flex;
width: 600px;
flex-shrink: 0;
}
.pd-card .col-header-right-group {
display: flex;
width: 600px;
}
.pd-card .col-header {
width: 200px;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
font-weight: 700;
color: #fff;
flex-shrink: 0;
}
/* col-header border-right: controlled inline via style attribute for tier theming */
.pd-card .col-header.blue-grad {
background-image: linear-gradient(to right, rgba(0,156,224,1), rgba(0,156,224,0.5), rgba(0,156,224,1));
}
.pd-card .col-header.red-grad {
background-image: linear-gradient(to right, rgba(211,49,21,1), rgba(211,49,21,0.5), rgba(211,49,21,1));
}
/* RESULT AREA */
.pd-card .result-area {
display: flex;
flex-direction: row;
height: 505px;
font-family: 'Source Sans 3', sans-serif;
font-size: 26px;
line-height: 1.3;
}
.pd-card .result-left-group {
display: flex;
width: 600px;
background-color: #ACE6FF;
flex-shrink: 0;
}
.pd-card .result-right-group {
display: flex;
width: 600px;
background-color: #EAA49C;
}
.pd-card .result-col-wrap {
width: 200px;
position: relative;
flex-shrink: 0;
}
.pd-card .r-2d6 {
position: absolute;
left: 1px;
width: 35px;
text-align: right;
font-weight: 700;
}
.pd-card .r-text {
position: absolute;
left: 36px;
right: 65px;
padding-left: 4px;
text-align: left;
}
.pd-card .r-d20 {
position: absolute;
right: 0;
width: 65px;
text-align: right;
padding-right: 4px;
font-weight: 700;
}
.pd-card .r-row {
position: absolute;
width: 100%;
left: 0;
}
/* CORNER ACCENTS (T4) */
.pd-card .corner-accent {
position: absolute;
z-index: 6;
pointer-events: none;
}
.pd-card .corner-accent.tl {
top: 0; left: 0;
border-top: 3px solid;
border-left: 3px solid;
}
.pd-card .corner-accent.tr {
top: 0; right: 0;
border-top: 3px solid;
border-right: 3px solid;
}
.pd-card .corner-accent.bl {
bottom: 0; left: 0;
border-bottom: 3px solid;
border-left: 3px solid;
}
.pd-card .corner-accent.br {
bottom: 0; right: 0;
border-bottom: 3px solid;
border-right: 3px solid;
}
/* ═══════════════════════════════════════════════════════
T3 GOLD REFRACTOR ANIMATION — "Gold Shimmer Sweep"
One subtle animation: a narrow gold light sweeps left-to-right
across the card header, simulating a refractor catching light.
Only the header is animated — body and bars remain static.
Duration: 2.5s loop. Low opacity (0.35) for elegance.
═══════════════════════════════════════════════════════ */
/* Header must clip the shimmer pseudo-element as it exits the edges */
.pd-card.anim-on.tier-t3 .card-header,
.pd-card.anim-on.tier-t4 .card-header {
overflow: hidden;
}
/* T3: single gold shimmer stripe, translateX driven animation */
@keyframes t3-shimmer {
0% { transform: translateX(-130%); }
100% { transform: translateX(230%); }
}
.pd-card.anim-on.tier-t3 .card-header::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* Narrow diagonal gold stripe — subtle gleam, not a flashlight */
background: linear-gradient(
105deg,
transparent 38%,
rgba(255,240,140,0.18) 44%,
rgba(255,220, 80,0.38) 50%,
rgba(255,200, 60,0.30) 53%,
rgba(255,240,140,0.14) 58%,
transparent 64%
);
pointer-events: none;
z-index: 5;
animation: t3-shimmer 2.5s ease-in-out infinite;
will-change: transform;
}
/* ═══════════════════════════════════════════════════════
T4 SUPERFRACTOR ANIMATION — "Prismatic Shimmer + Glow Pulse"
Four layered effects that stack to create an unmistakably
premium look. T4 should feel qualitatively different from T3:
where T3 has a single gentle effect, T4 has a rich layered system.
Layer 1 — Prismatic header sweep (card-header ::after)
Layer 2+3 — Gold/Teal dual glow pulse (pd-card ::before)
Layer 4 — Column bar shimmer (col-header ::before)
═══════════════════════════════════════════════════════ */
/* ── Layer 1: Prismatic rainbow — always visible, wraparound ──
Two identical rainbow bands in a 200%-wide element. As one band
exits the right edge, the next is already entering from the left.
Translating by exactly 50% (one band width) creates a seamless loop. */
@keyframes t4-prismatic-sweep {
0% { transform: translateX(0%); }
100% { transform: translateX(-50%); }
}
.pd-card.anim-on.tier-t4 .card-header::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 100%;
/* Two rainbow bands side by side — each occupies 50% of the element.
When we translate -50%, band B takes band A's position = seamless loop. */
background: linear-gradient(
135deg,
transparent 2%,
rgba(255,100,100,0.28) 8%,
rgba(255,200, 50,0.32) 14%,
rgba(100,255,150,0.30) 20%,
rgba( 50,190,255,0.32) 26%,
rgba(140, 80,255,0.28) 32%,
rgba(255,100,180,0.24) 38%,
transparent 44%,
transparent 52%,
rgba(255,100,100,0.28) 58%,
rgba(255,200, 50,0.32) 64%,
rgba(100,255,150,0.30) 70%,
rgba( 50,190,255,0.32) 76%,
rgba(140, 80,255,0.28) 82%,
rgba(255,100,180,0.24) 88%,
transparent 94%
);
pointer-events: none;
z-index: 1;
animation: t4-prismatic-sweep 6s linear infinite;
will-change: transform;
}
/* Lift header children above the rainbow overlay */
.pd-card.anim-on.tier-t4 .card-header > * {
position: relative;
z-index: 2;
}
/* ── Layer 2+3: Gold/Teal dual glow pulse ──
A single keyframe encodes both gold (bright→dim) and teal (dim→bright)
in perfect opposition. When gold fades, teal brightens — a shifting
dual-tone breathing effect that suggests the card is alive.
Applied via ::before on the card itself (inset box-shadow overlay).
This is independent of the inline box-shadow on the .pd-card element. */
@keyframes t4-dual-pulse {
0% {
box-shadow:
inset 0 0 45px 12px rgba(201,169, 78,0.40),
inset 0 0 80px 5px rgba( 45,212,191,0.08);
}
50% {
box-shadow:
inset 0 0 45px 12px rgba(201,169, 78,0.08),
inset 0 0 80px 5px rgba( 45,212,191,0.38);
}
100% {
box-shadow:
inset 0 0 45px 12px rgba(201,169, 78,0.40),
inset 0 0 80px 5px rgba( 45,212,191,0.08);
}
}
/* .pd-card already has position:relative; overflow:hidden — ::before slots in cleanly */
.pd-card.anim-on.tier-t4::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
z-index: 4;
animation: t4-dual-pulse 2s ease-in-out infinite;
will-change: box-shadow;
}
/* ── Layer 4: Column bar shimmer ──
A fast traveling white highlight passes across each column bar.
Staggered animation-delay per bar creates a ripple effect across
all 6 columns. Duration 1.6s — noticeably faster than the header sweep. */
@keyframes t4-bar-shimmer {
0% { transform: translateX(-120%); opacity: 1; }
80% { transform: translateX(200%); opacity: 1; }
100% { transform: translateX(200%); opacity: 0; }
}
/* Enable positioning on col-headers so ::before can be absolute */
.pd-card.anim-on.tier-t4 .col-header {
position: relative;
overflow: hidden;
}
.pd-card.anim-on.tier-t4 .col-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 45%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255,255,255,0.28),
transparent
);
pointer-events: none;
animation: t4-bar-shimmer 1.6s ease-in-out infinite;
will-change: transform;
}
/* Stagger each of the 6 columns so highlights ripple left-to-right */
.pd-card.anim-on.tier-t4 .col-header-left-group .col-header:nth-child(1)::before { animation-delay: 0.00s; }
.pd-card.anim-on.tier-t4 .col-header-left-group .col-header:nth-child(2)::before { animation-delay: -0.55s; }
.pd-card.anim-on.tier-t4 .col-header-left-group .col-header:nth-child(3)::before { animation-delay: -1.10s; }
.pd-card.anim-on.tier-t4 .col-header-right-group .col-header:nth-child(1)::before { animation-delay: -0.25s; }
.pd-card.anim-on.tier-t4 .col-header-right-group .col-header:nth-child(2)::before { animation-delay: -0.80s; }
.pd-card.anim-on.tier-t4 .col-header-right-group .col-header:nth-child(3)::before { animation-delay: -1.35s; }
/* ═══════════════════════════════════════════════════════
T4b ALTERNATE — Full-card rainbow overlay
Same as T4 but the prismatic sweep covers the entire
card height, not just the header.
═══════════════════════════════════════════════════════ */
.pd-card.anim-on.tier-t4b .card-header {
overflow: visible;
}
/* Full-card rainbow: positioned on .pd-card itself via ::after */
.pd-card.anim-on.tier-t4b::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 100%;
background: linear-gradient(
135deg,
transparent 2%,
rgba(255,100,100,0.18) 8%,
rgba(255,200, 50,0.22) 14%,
rgba(100,255,150,0.20) 20%,
rgba( 50,190,255,0.22) 26%,
rgba(140, 80,255,0.18) 32%,
rgba(255,100,180,0.16) 38%,
transparent 44%,
transparent 52%,
rgba(255,100,100,0.18) 58%,
rgba(255,200, 50,0.22) 64%,
rgba(100,255,150,0.20) 70%,
rgba( 50,190,255,0.22) 76%,
rgba(140, 80,255,0.18) 82%,
rgba(255,100,180,0.16) 88%,
transparent 94%
);
pointer-events: none;
z-index: 6;
animation: t4-prismatic-sweep 6s linear infinite;
will-change: transform;
}
/* T4b also gets the dual glow pulse (reuse T4's keyframes) */
.pd-card.anim-on.tier-t4b > .dual-pulse-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
z-index: 4;
animation: t4-dual-pulse 2.8s ease-in-out infinite;
}
/* T4b column bar shimmer (reuse T4's bar effect) */
.pd-card.anim-on.tier-t4b .col-header {
position: relative;
overflow: hidden;
}
.pd-card.anim-on.tier-t4b .col-header::before {
content: '';
position: absolute;
top: 0; left: -60%; width: 40%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
pointer-events: none;
animation: t4-bar-shimmer 1.6s ease-in-out infinite;
}
.pd-card.anim-on.tier-t4b .col-header-left-group .col-header:nth-child(1)::before { animation-delay: 0.00s; }
.pd-card.anim-on.tier-t4b .col-header-left-group .col-header:nth-child(2)::before { animation-delay: -0.55s; }
.pd-card.anim-on.tier-t4b .col-header-left-group .col-header:nth-child(3)::before { animation-delay: -1.10s; }
.pd-card.anim-on.tier-t4b .col-header-right-group .col-header:nth-child(1)::before { animation-delay: -0.25s; }
.pd-card.anim-on.tier-t4b .col-header-right-group .col-header:nth-child(2)::before { animation-delay: -0.80s; }
.pd-card.anim-on.tier-t4b .col-header-right-group .col-header:nth-child(3)::before { animation-delay: -1.35s; }
/* ═══════════════════════════════════════════════════════
TIER DIAMOND INDICATOR — 4-quadrant fill at x=600, y=95
A square rotated 45deg, divided into a 2x2 grid.
Fill order: 1st(right) → 2nd(top) → 3rd(left) → home(bottom)
T0=none, T1=1st, T2=1st+2nd, T3=1st+2nd+3rd, T4=all 4
═══════════════════════════════════════════════════════ */
.tier-diamond {
position: absolute;
left: 600px;
top: 78.5px;
transform: translate(-50%, -50%) rotate(45deg);
display: grid;
grid-template: 1fr 1fr / 1fr 1fr;
gap: 2px;
z-index: 20;
pointer-events: none;
/* Dark gap color gives the cross visible definition */
background: rgba(0,0,0,0.75);
border-radius: 2px;
box-shadow:
0 0 0 1.5px rgba(0,0,0,0.7),
0 2px 5px rgba(0,0,0,0.5);
}
.diamond-quad {
background: rgba(0,0,0,0.3);
}
.diamond-quad.filled {
box-shadow:
inset 0 1px 2px rgba(255,255,255,0.45),
inset 0 -1px 2px rgba(0,0,0,0.35),
inset 1px 0 2px rgba(255,255,255,0.15);
}
/* Diamond glow pulse animation — applied to whole container */
@keyframes diamond-glow-pulse {
0% { box-shadow:
0 0 0 1.5px rgba(0,0,0,0.7),
0 2px 5px rgba(0,0,0,0.5),
0 0 5px 1px var(--diamond-glow-color, rgba(201,169,78,0.6));
}
50% { box-shadow:
0 0 0 1.5px rgba(0,0,0,0.5),
0 2px 4px rgba(0,0,0,0.3),
0 0 8px 3px var(--diamond-glow-color, rgba(201,169,78,0.8)),
0 0 14px 5px var(--diamond-glow-color, rgba(201,169,78,0.25));
}
100% { box-shadow:
0 0 0 1.5px rgba(0,0,0,0.7),
0 2px 5px rgba(0,0,0,0.5),
0 0 5px 1px var(--diamond-glow-color, rgba(201,169,78,0.6));
}
}
.tier-diamond.diamond-glow {
animation: diamond-glow-pulse 2s ease-in-out infinite;
}
/* Metallic sheen — boosted inset highlights on filled quads */
.diamond-quad.metallic.filled {
box-shadow:
inset 0 1px 3px rgba(255,255,255,0.7),
inset 0 -1px 2px rgba(0,0,0,0.5),
inset 1px 0 3px rgba(255,255,255,0.3),
inset -1px 0 2px rgba(0,0,0,0.2);
}
</style>
</head>
<body>
<!-- PAGE HEADER -->
<div class="page-header">
<h1>Refractor Tier Visual Design</h1>
<div class="subtitle">Paper Dynasty &mdash; T0 through T4 &mdash; Internal border progression, tier gem markers, sacred blue/salmon body</div>
</div>
<!-- ANIMATION TOGGLE -->
<div class="anim-toggle-bar">
<button id="animToggleBtn" class="anim-toggle-btn active">Preview Animation: ON</button>
<span class="anim-toggle-hint">Applies to T3 and T4 only &mdash; T0, T1, T2 always static</span>
</div>
<!-- COMPARISON ROW: all 5 tiers -->
<div class="comparison-section">
<div class="section-label">T0 Standard &rarr; T4 Superfractor &mdash; Click any card for full-size view</div>
<div class="comparison-row" id="compRow"></div>
<!-- Animation info panel — visible when preview is ON -->
<div id="animInfoPanel" class="anim-info-panel">
<strong>APNG Animation Info</strong>
<ul>
<li><span class="tier-badge t3">T3</span> and <span class="tier-badge t4">T4</span> cards render as APNG (Animated PNG) &mdash; auto-plays in Discord embeds and all browsers</li>
<li>Animation is achieved frame-by-frame: <span class="tier-badge t3">T3</span> uses 8 frames (~4.5s render time), <span class="tier-badge t4">T4</span> uses 12 frames (~6s render time)</li>
<li>T0&ndash;T2 remain static PNG &mdash; no animation overhead</li>
</ul>
</div>
</div>
<!-- FULL-SIZE DETAIL + CONTROLS -->
<div class="detail-section">
<div class="detail-card-area">
<div class="detail-heading">Full-size &mdash; <span id="detailTierName">T0 Standard</span></div>
<div class="detail-card-wrap" id="detailCard"></div>
</div>
<div id="controls">
<h2>Tier Controls</h2>
<div class="panel-subtitle">Adjust the selected tier</div>
<div class="preset-row" id="presetRow"></div>
<!-- Internal Borders -->
<div class="ctrl-group">
<h3>Internal Borders</h3>
<div class="ctrl-row">
<label>Color preset</label>
<select id="ctrlBorderPreset">
<option value="black">Black (standard)</option>
<option value="silver">Silver / chrome</option>
<option value="blue-silver">Blue-silver / prismatic</option>
<option value="gold">Gold</option>
<option value="gold-bold">Gold bold (T4)</option>
</select>
</div>
<div class="ctrl-row">
<label>Header bottom width</label>
<input type="range" id="ctrlHeaderBorderW" min="2" max="8" step="1" value="3">
<span class="val" id="valHeaderBorderW">3px</span>
</div>
<div class="ctrl-row">
<label>Section divider width</label>
<input type="range" id="ctrlDividerW" min="3" max="7" step="1" value="3">
<span class="val" id="valDividerW">3px</span>
</div>
</div>
<!-- Inset Glow -->
<div class="ctrl-group">
<h3>Inset Glow (depth)</h3>
<div class="ctrl-row">
<label>Color</label>
<input type="color" id="ctrlGlowColor" value="#000000">
</div>
<div class="ctrl-row">
<label>Blur</label>
<input type="range" id="ctrlGlowBlur" min="0" max="40" step="1" value="0">
<span class="val" id="valGlowBlur">0px</span>
</div>
<div class="ctrl-row">
<label>Spread</label>
<input type="range" id="ctrlGlowSpread" min="0" max="15" step="1" value="0">
<span class="val" id="valGlowSpread">0px</span>
</div>
<div class="ctrl-row">
<label>Opacity</label>
<input type="range" id="ctrlGlowOpacity" min="0" max="100" step="5" value="0">
<span class="val" id="valGlowOpacity">0%</span>
</div>
<div class="ctrl-row">
<label>Dual glow (T4)</label>
<select id="ctrlDualGlow">
<option value="off">Off</option>
<option value="on">On (gold + teal)</option>
</select>
</div>
</div>
<!-- Header -->
<div class="ctrl-group">
<h3>Header Background</h3>
<div class="ctrl-row">
<label>Type</label>
<select id="ctrlHeaderType">
<option value="solid">Solid white</option>
<option value="silver">Silver chrome</option>
<option value="iridescent">Iridescent / prismatic</option>
<option value="gold">Gold wash</option>
<option value="gold-teal">Gold + teal (T4)</option>
</select>
</div>
</div>
<!-- Column headers -->
<div class="ctrl-group">
<h3>Column Header Gradients</h3>
<div class="ctrl-row">
<label>Left side</label>
<select id="ctrlColLeft">
<option value="blue">Blue (standard)</option>
<option value="blue-purple">Blue-purple (refractor)</option>
<option value="gold">Gold</option>
</select>
</div>
<div class="ctrl-row">
<label>Right side</label>
<select id="ctrlColRight">
<option value="red">Red (standard)</option>
<option value="red-magenta">Red-magenta (refractor)</option>
<option value="gold">Gold</option>
</select>
</div>
</div>
<!-- Corner accents -->
<div class="ctrl-group">
<h3>Corner Accents (T4)</h3>
<div class="ctrl-row">
<label>Enabled</label>
<select id="ctrlCornerEnabled">
<option value="off">Off</option>
<option value="on">On</option>
</select>
</div>
<div class="ctrl-row">
<label>Color</label>
<input type="color" id="ctrlCornerColor" value="#C9A94E">
</div>
<div class="ctrl-row">
<label>Size</label>
<input type="range" id="ctrlCornerSize" min="15" max="60" step="5" value="30">
<span class="val" id="valCornerSize">30px</span>
</div>
<div class="ctrl-row">
<label>Thickness</label>
<input type="range" id="ctrlCornerThick" min="1" max="6" step="1" value="3">
<span class="val" id="valCornerThick">3px</span>
</div>
</div>
<!-- Tier Diamond -->
<div class="ctrl-group">
<h3>Tier Diamond</h3>
<div class="ctrl-row">
<label>Quadrants filled</label>
<select id="ctrlDiamondFill">
<option value="0">0 (none)</option>
<option value="1">1 (right)</option>
<option value="2">2 (right + top)</option>
<option value="3">3 (right + top + left)</option>
<option value="4">4 (full diamond)</option>
</select>
</div>
<div class="ctrl-row">
<label>Body color</label>
<input type="color" id="ctrlDiamondColor" value="#a82020">
</div>
<div class="ctrl-row">
<label>Highlight color</label>
<input type="color" id="ctrlDiamondHighlight" value="#e85050">
</div>
<div class="ctrl-row">
<label>Size</label>
<input type="range" id="ctrlDiamondSize" min="16" max="40" step="1" value="26">
<span class="val" id="valDiamondSize">26px</span>
</div>
<div class="ctrl-row">
<label>Glow</label>
<select id="ctrlDiamondGlow">
<option value="off">Off</option>
<option value="on">On (animated pulse)</option>
</select>
</div>
<div class="ctrl-row">
<label>Glow color</label>
<input type="color" id="ctrlDiamondGlowColor" value="#c9a94e">
</div>
<div class="ctrl-row">
<label>Effect</label>
<select id="ctrlDiamondEffect">
<option value="none">None</option>
<option value="bloom">Bloom glow</option>
<option value="metallic">Metallic sheen</option>
<option value="border">Border accent</option>
<option value="shadow">Shadow depth</option>
<option value="escalation">Tiered escalation</option>
</select>
</div>
</div>
<div class="export-area">
<button class="export-btn" id="exportBtn">Export CSS for Production</button>
<textarea id="cssOutput" readonly></textarea>
</div>
</div>
</div>
<script>
/* ─────────────────────────────────────────────────────────
CARD DATA — Mike Trout, batter
───────────────────────────────────────────────────────── */
const CARD = {
hand: 'R',
name: 'Mike Trout',
position: 'CF / RF / DH',
rarity: 'ALL-STAR',
stealing: 'Stl: 5-6/4-5 (A-B)',
running: 'Run: A',
bunting: 'Bnt: D',
hitAndRun: 'H&R: B',
cardset: '2005 Live',
columns: [
// Col 1 vs L
[
{ d6: '2', text: 'HOMERUN', d20: '1-8' },
{ d6: '3', text: 'DOUBLE', d20: '1-12' },
{ d6: '4', text: 'SINGLE', d20: '1-16' },
{ d6: '5', text: 'SINGLE', d20: '1-14' },
{ d6: '6', text: 'DOUBLE', d20: '1-10' },
{ d6: '7', text: 'HOMERUN', d20: '1-6' },
{ d6: '8', text: 'TRIPLE', d20: '1-4' },
{ d6: '9', text: 'SINGLE', d20: '1-12' },
{ d6: '10', text: 'WALK', d20: '1-10' },
{ d6: '11', text: 'DOUBLE', d20: '1-8' },
{ d6: '12', text: 'HOMERUN', d20: '1-4' },
],
// Col 2 vs L
[
{ d6: '2', text: 'SINGLE(bp)', d20: '1-4' },
{ d6: '3', text: 'SINGLE', d20: '1-14' },
{ d6: '4', text: 'WALK', d20: '1-11' },
{ d6: '5', text: 'WALK', d20: '12-20'},
{ d6: '6', text: 'SINGLE', d20: '1-18' },
{ d6: '7', text: 'HBP', d20: '1-3' },
{ d6: '8', text: 'STRIKEOUT', d20: '1-16' },
{ d6: '9', text: 'FLYOUT(a)', d20: '1-8' },
{ d6: '10', text: 'GROUNDOUT(a)', d20: '1-10' },
{ d6: '11', text: 'STRIKEOUT', d20: '1-14' },
{ d6: '12', text: 'FLYOUT(bq)', d20: '1-12' },
],
// Col 3 vs L
[
{ d6: '2', text: 'STRIKEOUT', d20: '1-20' },
{ d6: '3', text: 'FLYOUT(a)', d20: '1-9' },
{ d6: '4', text: 'GROUNDOUT(a)', d20: '1-8' },
{ d6: '5', text: 'GROUNDOUT(b)', d20: '1-12' },
{ d6: '6', text: 'FLYOUT(bq)', d20: '1-10' },
{ d6: '7', text: 'GROUNDOUT(c)', d20: '1-14' },
{ d6: '8', text: 'FLYOUT(lf)', d20: '1-8' },
{ d6: '9', text: 'FLYOUT(rf)', d20: '1-8' },
{ d6: '10', text: 'LINEOUT', d20: '1-6' },
{ d6: '11', text: 'POPOUT', d20: '1-20' },
{ d6: '12', text: 'STRIKEOUT', d20: '1-20' },
],
// Col 4 vs R
[
{ d6: '2', text: 'HOMERUN', d20: '1-5' },
{ d6: '3', text: 'SINGLE', d20: '1-14' },
{ d6: '4', text: 'DOUBLE', d20: '1-10' },
{ d6: '5', text: 'SINGLE', d20: '1-16' },
{ d6: '6', text: 'WALK', d20: '1-8' },
{ d6: '7', text: 'HOMERUN', d20: '1-4' },
{ d6: '8', text: 'SINGLE', d20: '1-12' },
{ d6: '9', text: 'DOUBLE', d20: '1-6' },
{ d6: '10', text: 'WALK', d20: '1-8' },
{ d6: '11', text: 'SINGLE', d20: '1-10' },
{ d6: '12', text: 'HOMERUN', d20: '1-3' },
],
// Col 5 vs R
[
{ d6: '2', text: 'SINGLE(bp)', d20: '1-6' },
{ d6: '3', text: 'SINGLE', d20: '1-12' },
{ d6: '4', text: 'WALK', d20: '1-8' },
{ d6: '5', text: 'HBP', d20: '1-4' },
{ d6: '6', text: 'SINGLE', d20: '1-14' },
{ d6: '7', text: 'STRIKEOUT', d20: '1-14' },
{ d6: '8', text: 'GROUNDOUT(a)', d20: '1-12' },
{ d6: '9', text: 'FLYOUT(a)', d20: '1-10' },
{ d6: '10', text: 'STRIKEOUT', d20: '1-16' },
{ d6: '11', text: 'GROUNDOUT(b)', d20: '1-14' },
{ d6: '12', text: 'FLYOUT(bq)', d20: '1-10' },
],
// Col 6 vs R
[
{ d6: '2', text: 'STRIKEOUT', d20: '1-20' },
{ d6: '3', text: 'FLYOUT(a)', d20: '1-10' },
{ d6: '4', text: 'GROUNDOUT(a)', d20: '1-10' },
{ d6: '5', text: 'GROUNDOUT(b)', d20: '1-14' },
{ d6: '6', text: 'FLYOUT(cf)', d20: '1-12' },
{ d6: '7', text: 'LINEOUT', d20: '1-8' },
{ d6: '8', text: 'POPOUT', d20: '1-10' },
{ d6: '9', text: 'FLYOUT(rf)', d20: '1-10' },
{ d6: '10', text: 'GROUNDOUT(c)', d20: '1-12' },
{ d6: '11', text: 'STRIKEOUT', d20: '1-18' },
{ d6: '12', text: 'STRIKEOUT', d20: '1-20' },
],
],
};
/* ─────────────────────────────────────────────────────────
COLUMN GRADIENT PRESETS
───────────────────────────────────────────────────────── */
const COL_GRADIENTS = {
'blue': 'linear-gradient(to right, rgba(0,156,224,1), rgba(0,156,224,0.5), rgba(0,156,224,1))',
'blue-purple': 'linear-gradient(to right, rgba(60,110,200,1), rgba(100,55,185,0.55), rgba(60,110,200,1))',
'gold': 'linear-gradient(to right, rgba(195,160,40,1), rgba(220,185,60,0.55), rgba(195,160,40,1))',
'red': 'linear-gradient(to right, rgba(211,49,21,1), rgba(211,49,21,0.5), rgba(211,49,21,1))',
'red-magenta': 'linear-gradient(to right, rgba(190,35,80,1), rgba(165,25,100,0.55), rgba(190,35,80,1))',
};
/* ─────────────────────────────────────────────────────────
HEADER BACKGROUND PRESETS
───────────────────────────────────────────────────────── */
const HEADER_BGS = {
'solid': '#ffffff',
'silver': 'linear-gradient(135deg, rgba(185,195,210,0.25) 0%, rgba(210,218,228,0.35) 50%, rgba(185,195,210,0.25) 100%), #ffffff',
'iridescent': 'linear-gradient(135deg, rgba(100,155,230,0.28) 0%, rgba(155,90,220,0.18) 25%, rgba(90,200,210,0.24) 50%, rgba(185,80,170,0.16) 75%, rgba(100,155,230,0.28) 100%), #ffffff',
'gold': 'linear-gradient(135deg, rgba(195,155,35,0.26) 0%, rgba(235,200,70,0.2) 50%, rgba(195,155,35,0.26) 100%), #ffffff',
'gold-teal': 'linear-gradient(135deg, rgba(195,155,35,0.28) 0%, rgba(235,200,70,0.2) 35%, rgba(38,198,175,0.22) 65%, rgba(195,155,35,0.26) 100%), #ffffff',
};
/* ─────────────────────────────────────────────────────────
INTERNAL BORDER PRESETS
color: the actual CSS color value used on the border properties
───────────────────────────────────────────────────────── */
const BORDER_COLORS = {
'black': 'black',
'silver': '#8e9baf',
'blue-silver': '#7a9cc4',
'gold': '#c9a94e',
'gold-bold': '#c9a94e',
};
/* ─────────────────────────────────────────────────────────
5-TIER PRESETS (T0 through T4)
borderPreset: key into BORDER_COLORS
headerBorderW: header bottom border width (px)
dividerW: result-header bottom + left/right dividers + sub-col dividers
───────────────────────────────────────────────────────── */
const TIER_PRESETS = {
t0: {
name: 'T0 Standard',
label: 'T0',
sublabel: 'Standard',
addedDesc: 'Base card — black borders, standard columns',
borderPreset: 'black',
headerBorderW: 3,
dividerW: 3,
glowColor: '#000000',
glowBlur: 0,
glowSpread: 0,
glowOpacity: 0,
dualGlow: 'off',
headerType: 'solid',
colLeft: 'blue',
colRight: 'red',
cornerEnabled: 'off',
cornerColor: '#C9A94E',
cornerSize: 30,
cornerThick: 3,
diamondFill: 0,
diamondColor: '#000000',
diamondHighlight: '#000000',
diamondSize: 19,
diamondGlow: 'off',
diamondGlowColor: '#000000',
diamondEffect: 'none',
},
t1: {
name: 'T1 Base Chrome',
label: 'T1',
sublabel: 'Base Chrome',
addedDesc: 'Added: silver header, silver borders, diamond 1 quadrant (right)',
borderPreset: 'silver',
headerBorderW: 4,
dividerW: 3,
glowColor: '#000000',
glowBlur: 0,
glowSpread: 0,
glowOpacity: 0,
dualGlow: 'off',
headerType: 'silver',
colLeft: 'blue',
colRight: 'red',
cornerEnabled: 'off',
cornerColor: '#8e9baf',
cornerSize: 30,
cornerThick: 3,
diamondFill: 1,
diamondColor: '#d46a1a',
diamondHighlight: '#f0a050',
diamondSize: 19,
diamondGlow: 'off',
diamondGlowColor: '#d46a1a',
diamondEffect: 'none',
},
t2: {
name: 'T2 Refractor',
label: 'T2',
sublabel: 'Refractor',
addedDesc: 'Added: prismatic header, shifted bars, inset glow, diamond 2 quadrants (right + top)',
borderPreset: 'blue-silver',
headerBorderW: 4,
dividerW: 4,
glowColor: '#5a8fcf',
glowBlur: 14,
glowSpread: 3,
glowOpacity: 22,
dualGlow: 'off',
headerType: 'iridescent',
colLeft: 'blue-purple',
colRight: 'red-magenta',
cornerEnabled: 'off',
cornerColor: '#7a9cc4',
cornerSize: 30,
cornerThick: 3,
diamondFill: 2,
diamondColor: '#b82020',
diamondHighlight: '#e85050',
diamondSize: 19,
diamondGlow: 'off',
diamondGlowColor: '#b82020',
diamondEffect: 'none',
},
t3: {
name: 'T3 Gold Refractor',
label: 'T3',
sublabel: 'Gold Refractor',
addedDesc: 'Added: gold header, gold borders, gold bars, gold glow, diamond 3 quadrants (right + top + left)',
borderPreset: 'gold',
headerBorderW: 4,
dividerW: 4,
glowColor: '#c8a530',
glowBlur: 16,
glowSpread: 4,
glowOpacity: 22,
dualGlow: 'off',
headerType: 'gold',
colLeft: 'gold',
colRight: 'gold',
cornerEnabled: 'off',
cornerColor: '#c9a94e',
cornerSize: 30,
cornerThick: 3,
diamondFill: 3,
diamondColor: '#7b2d8e',
diamondHighlight: '#b860d0',
diamondSize: 19,
diamondGlow: 'off',
diamondGlowColor: '#7b2d8e',
diamondEffect: 'none',
},
t4: {
name: 'T4 Superfractor',
label: 'T4',
sublabel: 'Superfractor',
addedDesc: 'Added: bold gold borders, dual glow, teal accent, corners, full diamond (all 4 quadrants, glowing)',
borderPreset: 'gold-bold',
headerBorderW: 6,
dividerW: 5,
glowColor: '#2dd4bf',
glowBlur: 22,
glowSpread: 6,
glowOpacity: 28,
dualGlow: 'on',
headerType: 'solid',
colLeft: 'gold',
colRight: 'gold',
cornerEnabled: 'on',
cornerColor: '#c9a94e',
cornerSize: 35,
cornerThick: 3,
diamondFill: 4,
diamondColor: '#1a6af0',
diamondHighlight: '#60b0ff',
diamondSize: 19,
diamondGlow: 'on',
diamondGlowColor: '#1a6af0',
diamondEffect: 'none',
},
t4b: {
name: 'T4b Superfractor (full-card rainbow)',
label: 'T4b',
sublabel: 'SF Alt',
addedDesc: 'Alt: prismatic rainbow covers the entire card, full diamond (all 4 quadrants, glowing)',
borderPreset: 'gold-bold',
headerBorderW: 6,
dividerW: 5,
glowColor: '#2dd4bf',
glowBlur: 22,
glowSpread: 6,
glowOpacity: 28,
dualGlow: 'on',
headerType: 'gold-teal',
colLeft: 'gold',
colRight: 'gold',
cornerEnabled: 'on',
cornerColor: '#c9a94e',
cornerSize: 35,
cornerThick: 3,
diamondFill: 4,
diamondColor: '#1a6af0',
diamondHighlight: '#60b0ff',
diamondSize: 19,
diamondGlow: 'on',
diamondGlowColor: '#1a6af0',
diamondEffect: 'none',
},
};
/* ─────────────────────────────────────────────────────────
STATE
───────────────────────────────────────────────────────── */
let activeTier = 't0';
let animPreviewOn = true; // animation toggle — applies to T3 and T4 only
const tierState = {};
for (const [k, v] of Object.entries(TIER_PRESETS)) {
tierState[k] = { ...v };
}
/* ─────────────────────────────────────────────────────────
RENDER A SINGLE CARD
All border styling injected inline — no outer card border.
Internal borders (header-bottom, section dividers, sub-col dividers)
change color and width per tier.
T3/T4 receive additional CSS classes for animation when preview is ON:
.tier-t3 / .tier-t4 — always present for T3/T4
.anim-on — added when animPreviewOn is true
───────────────────────────────────────────────────────── */
function renderCardHTML(tierKey) {
const t = tierState[tierKey];
// Resolve border color
const borderColor = BORDER_COLORS[t.borderPreset] || 'black';
const hw = t.headerBorderW; // header bottom border width
const dw = t.dividerW; // section divider width
const thin = Math.max(dw - 1, 2); // sub-col thin dividers (slightly thinner)
// Inset glow
const glowAlpha = t.glowOpacity / 100;
let shadowCSS = 'none';
if (t.glowBlur > 0 && glowAlpha > 0) {
const gc = hexToRGBA(t.glowColor, glowAlpha);
if (t.dualGlow === 'on') {
const gc2 = hexToRGBA('#c8a530', 0.15);
shadowCSS = `inset 0 0 ${t.glowBlur}px ${t.glowSpread}px ${gc}, inset 0 0 ${t.glowBlur * 1.8}px ${Math.round(t.glowSpread * 1.5)}px ${gc2}`;
} else {
shadowCSS = `inset 0 0 ${t.glowBlur}px ${t.glowSpread}px ${gc}`;
}
}
// Header background
const headerBG = HEADER_BGS[t.headerType] || '#ffffff';
// Column bar gradients
const blueGrad = COL_GRADIENTS[t.colLeft] || COL_GRADIENTS['blue'];
const redGrad = COL_GRADIENTS[t.colRight] || COL_GRADIENTS['red'];
// Corner accents
let cornerHTML = '';
if (t.cornerEnabled === 'on') {
const cs = t.cornerSize + 'px';
const cc = t.cornerColor;
const cw = t.cornerThick + 'px';
cornerHTML = `
<div class="corner-accent tl" style="width:${cs};height:${cs};border-color:${cc};border-top-width:${cw};border-left-width:${cw};"></div>
<div class="corner-accent tr" style="width:${cs};height:${cs};border-color:${cc};border-top-width:${cw};border-right-width:${cw};"></div>
<div class="corner-accent bl" style="width:${cs};height:${cs};border-color:${cc};border-bottom-width:${cw};border-left-width:${cw};"></div>
<div class="corner-accent br" style="width:${cs};height:${cs};border-color:${cc};border-bottom-width:${cw};border-right-width:${cw};"></div>`;
}
// Tier diamond indicator — 4-quadrant fill (baseball base path)
// Grid reading order: top-left, top-right, bottom-left, bottom-right
// After rotate(45deg): top-left=LEFT(3rd), top-right=BOTTOM(home), bottom-left=TOP(2nd), bottom-right=RIGHT(1st)
// Fill order: 1st(right) → 2nd(top) → 3rd(left) → home(bottom)
let diamondHTML = '';
if (t.diamondFill > 0) {
const ds = t.diamondSize;
const effect = t.diamondEffect || 'none';
// Standard gradient for filled quads; metallic uses a different one
let diamondBG = `linear-gradient(135deg, ${t.diamondHighlight} 0%, ${t.diamondColor} 50%, ${darkenHex(t.diamondColor, 0.75)} 100%)`;
if (effect === 'metallic') {
diamondBG = `linear-gradient(135deg, rgba(255,255,255,0.9) 0%, ${t.diamondHighlight} 20%, ${t.diamondColor} 50%, ${darkenHex(t.diamondColor, 0.6)} 80%, ${t.diamondHighlight} 100%)`;
}
// Metallic effect automatically includes glow pulse
const wantsGlow = t.diamondGlow === 'on' || effect === 'metallic';
const glowClass = wantsGlow ? ' diamond-glow' : '';
const glowVar = wantsGlow ? ` --diamond-glow-color: ${t.diamondGlowColor || t.diamondColor};` : '';
// Build container inline style — effects stack on top of the base box-shadow
let containerStyle = `width:${ds}px;height:${ds}px;${glowVar}`;
if (effect === 'bloom') {
containerStyle += `box-shadow: 0 0 0 1.5px rgba(0,0,0,0.7), 0 0 8px 3px ${hexToRGBA(t.diamondColor, 0.5)}, 0 0 16px 6px ${hexToRGBA(t.diamondColor, 0.25)};`;
} else if (effect === 'border') {
containerStyle += `border: 1.5px solid ${t.diamondHighlight}; box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 0 4px 1px ${hexToRGBA(t.diamondHighlight, 0.3)};`;
} else if (effect === 'shadow') {
containerStyle += `box-shadow: 0 0 0 1.5px rgba(0,0,0,0.7), 0 3px 8px 2px ${hexToRGBA(t.diamondColor, 0.45)}, 0 1px 3px rgba(0,0,0,0.6);`;
} else if (effect === 'escalation') {
containerStyle += `border: 1.5px solid ${t.diamondHighlight};`;
const intensity = t.diamondFill / 4; // 0.25 → 1.0
const bloomSize = Math.round(4 + intensity * 12);
const bloomSpread = Math.round(1 + intensity * 5);
containerStyle += `box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 0 ${bloomSize}px ${bloomSpread}px ${hexToRGBA(t.diamondColor, 0.2 + intensity * 0.3)};`;
}
// metallic has no container-level effect — it only changes gradient + quad shadows
// Extra CSS class for metallic quads
const metallicClass = effect === 'metallic' ? ' metallic' : '';
// Empirically verified grid-to-visual mapping after rotate(45deg):
// div 1 (grid top-left) → LEFT = 3rd base
// div 2 (grid top-right) → BOTTOM = home plate
// div 3 (grid bottom-left) → TOP = 2nd base
// Verified mapping: div1→TOP, div2→RIGHT, div3→LEFT, div4→BOTTOM
const firstFilled = t.diamondFill >= 1; // 1st base / right = div 2
const secFilled = t.diamondFill >= 2; // 2nd base / top = div 1
const thirdFilled = t.diamondFill >= 3; // 3rd base / left = div 3
const homeFilled = t.diamondFill >= 4; // home plate / bottom = div 4
diamondHTML =
`<div class="tier-diamond${glowClass}" style="${containerStyle}">` +
`<div class="diamond-quad${secFilled ? ' filled' + metallicClass : ''}" style="${secFilled ? 'background:' + diamondBG : ''}"></div>` +
`<div class="diamond-quad${firstFilled ? ' filled' + metallicClass : ''}" style="${firstFilled ? 'background:' + diamondBG : ''}"></div>` +
`<div class="diamond-quad${thirdFilled ? ' filled' + metallicClass : ''}" style="${thirdFilled ? 'background:' + diamondBG : ''}"></div>` +
`<div class="diamond-quad${homeFilled ? ' filled' + metallicClass : ''}" style="${homeFilled ? 'background:' + diamondBG : ''}"></div>` +
`</div>`;
}
// Result rows
function renderCol(colData) {
const rowH = 505 / colData.length;
return colData.map((row, i) => {
const top = Math.round(i * rowH);
return `<div class="r-row" style="top:${top}px;height:${Math.ceil(rowH)}px;">` +
`<span class="r-2d6">${row.d6}</span>` +
`<span class="r-text">${row.text}</span>` +
`<span class="r-d20">${row.d20}</span>` +
`</div>`;
}).join('');
}
// Header vertical line: same color as border
const headerVlineStyle = `border-left: 3px solid ${borderColor};`;
// Column header sub-divider style
const thinDivStyle = `border-right: ${thin}px solid ${borderColor};`;
// Half divider (between left 600 and right 600) in both header and body groups
const halfDivStyle = `border-right: ${dw + 2}px solid ${borderColor};`;
// Animation classes: added to .pd-card for T3 and T4 tiers.
// All CSS animation rules use .pd-card.anim-on.tier-t3 / .tier-t4 selectors,
// so animations only activate when both classes are present.
let animClasses = '';
if (tierKey === 't3' || tierKey === 't4' || tierKey === 't4b') {
animClasses = ` tier-${tierKey}`;
if (animPreviewOn) {
animClasses += ' anim-on';
}
}
return `<div class="pd-card${animClasses}" style="box-shadow:${shadowCSS};">
${cornerHTML}
${diamondHTML}
<div class="card-header" style="background:${headerBG}; border-bottom:${hw}px solid ${borderColor};">
<div class="header-left">
<div class="hand-indicator">${CARD.hand}</div>
<div class="header-vline" style="${headerVlineStyle}"></div>
<div class="player-info">
<div class="player-name">${CARD.name}</div>
<div class="player-position">${CARD.position}</div>
</div>
</div>
<div class="header-middle">
<div class="rarity-badge">${CARD.rarity}</div>
</div>
<div class="header-right">
<div class="stat-stealing">${CARD.stealing}</div>
<div class="stat-running">${CARD.running}</div>
<div class="stat-bunting">${CARD.bunting}</div>
<div class="stat-hitrun">${CARD.hitAndRun}</div>
<div class="stat-cardset">${CARD.cardset}</div>
</div>
</div>
<div class="result-header" style="border-bottom:${dw}px solid ${borderColor};">
<div class="col-header-left-group" style="${halfDivStyle}">
<div class="col-header" style="background-image:${blueGrad}; ${thinDivStyle}">1</div>
<div class="col-header" style="background-image:${blueGrad}; ${thinDivStyle}">2</div>
<div class="col-header" style="background-image:${blueGrad};">3</div>
</div>
<div class="col-header-right-group">
<div class="col-header" style="background-image:${redGrad}; ${thinDivStyle}">1</div>
<div class="col-header" style="background-image:${redGrad}; ${thinDivStyle}">2</div>
<div class="col-header" style="background-image:${redGrad};">3</div>
</div>
</div>
<div class="result-area">
<div class="result-left-group" style="background-color:#ACE6FF; ${halfDivStyle}">
<div class="result-col-wrap" style="${thinDivStyle}">${renderCol(CARD.columns[0])}</div>
<div class="result-col-wrap" style="${thinDivStyle}">${renderCol(CARD.columns[1])}</div>
<div class="result-col-wrap">${renderCol(CARD.columns[2])}</div>
</div>
<div class="result-right-group" style="background-color:#EAA49C;">
<div class="result-col-wrap" style="${thinDivStyle}">${renderCol(CARD.columns[3])}</div>
<div class="result-col-wrap" style="${thinDivStyle}">${renderCol(CARD.columns[4])}</div>
<div class="result-col-wrap">${renderCol(CARD.columns[5])}</div>
</div>
</div>
</div>`;
}
/* ─────────────────────────────────────────────────────────
COLOR UTILS
───────────────────────────────────────────────────────── */
function hexToRGBA(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r},${g},${b},${alpha.toFixed(2)})`;
}
function darkenHex(hex, factor) {
const r = Math.round(parseInt(hex.slice(1, 3), 16) * factor);
const g = Math.round(parseInt(hex.slice(3, 5), 16) * factor);
const b = Math.round(parseInt(hex.slice(5, 7), 16) * factor);
return `rgb(${r},${g},${b})`;
}
/* ─────────────────────────────────────────────────────────
BUILD COMPARISON ROW — all 5 tiers in one row
T4 gets a larger thumbnail (featured)
───────────────────────────────────────────────────────── */
function buildComparisonRow() {
const tierKeys = ['t0', 't1', 't2', 't3', 't4', 't4b'];
function buildSlot(tierKey) {
const t = tierState[tierKey];
const isFeatured = tierKey === 't4' || tierKey === 't4b';
const slot = document.createElement('div');
slot.className = 'comparison-slot ' + (isFeatured ? 'featured-thumb' : 'standard-thumb') + (tierKey === activeTier ? ' active' : '');
slot.dataset.tier = tierKey;
slot.innerHTML = `
<div class="tier-label">${t.label}${t.sublabel}</div>
<div class="card-thumb-wrap">
<div class="card-scale-container">${renderCardHTML(tierKey)}</div>
</div>
<div class="tier-added">${t.addedDesc}</div>`;
slot.addEventListener('click', () => selectTier(tierKey));
return slot;
}
const row = document.getElementById('compRow');
row.innerHTML = '';
tierKeys.forEach(k => row.appendChild(buildSlot(k)));
}
/* ─────────────────────────────────────────────────────────
DETAIL CARD
───────────────────────────────────────────────────────── */
function renderDetailCard() {
document.getElementById('detailCard').innerHTML = renderCardHTML(activeTier);
document.getElementById('detailTierName').textContent = tierState[activeTier].name;
}
/* ─────────────────────────────────────────────────────────
PRESET BUTTONS
───────────────────────────────────────────────────────── */
function buildPresetButtons() {
const row = document.getElementById('presetRow');
row.innerHTML = '';
const tiers = ['t0', 't1', 't2', 't3', 't4', 't4b'];
tiers.forEach(tk => {
const btn = document.createElement('button');
btn.className = 'preset-btn' + (tk === activeTier ? ' active' : '');
btn.textContent = TIER_PRESETS[tk].label;
btn.addEventListener('click', () => selectTier(tk));
row.appendChild(btn);
});
const resetBtn = document.createElement('button');
resetBtn.className = 'preset-btn';
resetBtn.textContent = 'Reset';
resetBtn.style.marginLeft = 'auto';
resetBtn.addEventListener('click', () => {
tierState[activeTier] = { ...TIER_PRESETS[activeTier] };
loadControlsFromState();
refreshAll();
});
row.appendChild(resetBtn);
}
/* ─────────────────────────────────────────────────────────
SELECT TIER
───────────────────────────────────────────────────────── */
function selectTier(tierKey) {
activeTier = tierKey;
loadControlsFromState();
refreshAll();
}
/* ─────────────────────────────────────────────────────────
LOAD CONTROLS FROM STATE
───────────────────────────────────────────────────────── */
function loadControlsFromState() {
const t = tierState[activeTier];
document.getElementById('ctrlBorderPreset').value = t.borderPreset;
document.getElementById('ctrlHeaderBorderW').value = t.headerBorderW;
document.getElementById('ctrlDividerW').value = t.dividerW;
document.getElementById('ctrlGlowColor').value = t.glowColor;
document.getElementById('ctrlGlowBlur').value = t.glowBlur;
document.getElementById('ctrlGlowSpread').value = t.glowSpread;
document.getElementById('ctrlGlowOpacity').value = t.glowOpacity;
document.getElementById('ctrlDualGlow').value = t.dualGlow;
document.getElementById('ctrlHeaderType').value = t.headerType;
document.getElementById('ctrlColLeft').value = t.colLeft;
document.getElementById('ctrlColRight').value = t.colRight;
document.getElementById('ctrlCornerEnabled').value = t.cornerEnabled;
document.getElementById('ctrlCornerColor').value = t.cornerColor;
document.getElementById('ctrlCornerSize').value = t.cornerSize;
document.getElementById('ctrlCornerThick').value = t.cornerThick;
document.getElementById('ctrlDiamondFill').value = t.diamondFill;
document.getElementById('ctrlDiamondColor').value = t.diamondColor;
document.getElementById('ctrlDiamondHighlight').value = t.diamondHighlight;
document.getElementById('ctrlDiamondSize').value = t.diamondSize;
document.getElementById('ctrlDiamondGlow').value = t.diamondGlow;
document.getElementById('ctrlDiamondGlowColor').value = t.diamondGlowColor;
document.getElementById('ctrlDiamondEffect').value = t.diamondEffect || 'none';
updateValueLabels();
}
/* ─────────────────────────────────────────────────────────
SAVE CONTROLS TO STATE
───────────────────────────────────────────────────────── */
function saveControlsToState() {
const t = tierState[activeTier];
t.borderPreset = document.getElementById('ctrlBorderPreset').value;
t.headerBorderW = parseInt(document.getElementById('ctrlHeaderBorderW').value);
t.dividerW = parseInt(document.getElementById('ctrlDividerW').value);
t.glowColor = document.getElementById('ctrlGlowColor').value;
t.glowBlur = parseInt(document.getElementById('ctrlGlowBlur').value);
t.glowSpread = parseInt(document.getElementById('ctrlGlowSpread').value);
t.glowOpacity = parseInt(document.getElementById('ctrlGlowOpacity').value);
t.dualGlow = document.getElementById('ctrlDualGlow').value;
t.headerType = document.getElementById('ctrlHeaderType').value;
t.colLeft = document.getElementById('ctrlColLeft').value;
t.colRight = document.getElementById('ctrlColRight').value;
t.cornerEnabled = document.getElementById('ctrlCornerEnabled').value;
t.cornerColor = document.getElementById('ctrlCornerColor').value;
t.cornerSize = parseInt(document.getElementById('ctrlCornerSize').value);
t.cornerThick = parseInt(document.getElementById('ctrlCornerThick').value);
t.diamondFill = parseInt(document.getElementById('ctrlDiamondFill').value);
t.diamondColor = document.getElementById('ctrlDiamondColor').value;
t.diamondHighlight = document.getElementById('ctrlDiamondHighlight').value;
t.diamondSize = parseInt(document.getElementById('ctrlDiamondSize').value);
t.diamondGlow = document.getElementById('ctrlDiamondGlow').value;
t.diamondGlowColor = document.getElementById('ctrlDiamondGlowColor').value;
t.diamondEffect = document.getElementById('ctrlDiamondEffect').value;
}
/* ─────────────────────────────────────────────────────────
VALUE LABELS
───────────────────────────────────────────────────────── */
function updateValueLabels() {
document.getElementById('valHeaderBorderW').textContent = document.getElementById('ctrlHeaderBorderW').value + 'px';
document.getElementById('valDividerW').textContent = document.getElementById('ctrlDividerW').value + 'px';
document.getElementById('valGlowBlur').textContent = document.getElementById('ctrlGlowBlur').value + 'px';
document.getElementById('valGlowSpread').textContent = document.getElementById('ctrlGlowSpread').value + 'px';
document.getElementById('valGlowOpacity').textContent = document.getElementById('ctrlGlowOpacity').value + '%';
document.getElementById('valCornerSize').textContent = document.getElementById('ctrlCornerSize').value + 'px';
document.getElementById('valCornerThick').textContent = document.getElementById('ctrlCornerThick').value + 'px';
document.getElementById('valDiamondSize').textContent = document.getElementById('ctrlDiamondSize').value + 'px';
}
/* ─────────────────────────────────────────────────────────
REFRESH ALL
───────────────────────────────────────────────────────── */
function refreshAll() {
buildComparisonRow();
renderDetailCard();
buildPresetButtons();
updateValueLabels();
document.getElementById('cssOutput').classList.remove('visible');
}
/* ─────────────────────────────────────────────────────────
CSS EXPORT
Produces the CSS overrides needed to implement this tier
in the production card HTML template.
For T3 and T4, outputs TWO sections:
Section 1 — Static CSS: frame 0 appearance / fallback for non-animated
Section 2 — Animation CSS: --anim-progress approach for Playwright APNG
frame-by-frame capture. All animated values derive from this
single CSS custom property (set by JS in the capture loop).
───────────────────────────────────────────────────────── */
function exportCSS() {
const t = tierState[activeTier];
const cls = `.refractor-${activeTier}`;
const borderColor = BORDER_COLORS[t.borderPreset] || 'black';
const hw = t.headerBorderW;
const dw = t.dividerW;
const thin = Math.max(dw - 1, 2);
const headerBG = HEADER_BGS[t.headerType] || '#ffffff';
const blueGrad = COL_GRADIENTS[t.colLeft];
const redGrad = COL_GRADIENTS[t.colRight];
const glowAlpha = t.glowOpacity / 100;
let shadowRule = '/* no glow */';
if (t.glowBlur > 0 && glowAlpha > 0) {
const gc = hexToRGBA(t.glowColor, glowAlpha);
if (t.dualGlow === 'on') {
const gc2 = hexToRGBA('#c8a530', 0.15);
shadowRule = `box-shadow: inset 0 0 ${t.glowBlur}px ${t.glowSpread}px ${gc}, inset 0 0 ${Math.round(t.glowBlur * 1.8)}px ${Math.round(t.glowSpread * 1.5)}px ${gc2};`;
} else {
shadowRule = `box-shadow: inset 0 0 ${t.glowBlur}px ${t.glowSpread}px ${gc};`;
}
}
// ── Section 1: Static CSS (identical for all tiers) ───────
const staticCSS = `/* ══════════════════════════════════════
Paper Dynasty — ${t.name}
${t.addedDesc}
══════════════════════════════════════ */
/* Card inset glow (no outer border — dimensions unchanged) */
${cls} #fullCard {
${shadowRule}
}
/* Header background + bottom border */
${cls} #header {
background: ${headerBG};
border-bottom: ${hw}px solid ${borderColor};
}
/* Header vertical divider */
${cls} #header .header-vline {
border-left-color: ${borderColor};
}
/* Result header bottom border */
${cls} #resultHeader {
border-bottom: ${dw}px solid ${borderColor};
}
/* Left/right half divider */
${cls} .col-header-left-group,
${cls} .result-left-group {
border-right: ${dw + 2}px solid ${borderColor};
}
/* Sub-column dividers */
${cls} .col-header.border-r-thin,
${cls} .result-col-wrap.border-r-thin {
border-right: ${thin}px solid ${borderColor};
}
/* Left column headers */
${cls} .blue-gradient {
background-image: ${blueGrad};
}
/* Right column headers */
${cls} .red-gradient {
background-image: ${redGrad};
}` + (t.diamondFill > 0 ? `
/* Tier diamond indicator — 4-quadrant fill at x=600, y=95 */
${cls} .tier-diamond {
position: absolute;
left: 600px;
top: 78.5px;
width: ${t.diamondSize}px;
height: ${t.diamondSize}px;
transform: translate(-50%, -50%) rotate(45deg);
display: grid;
grid-template: 1fr 1fr / 1fr 1fr;
gap: 2px;
z-index: 20;
pointer-events: none;
background: rgba(0,0,0,0.75);
border-radius: 2px;
box-shadow:
0 0 0 1.5px rgba(0,0,0,0.7),
0 2px 5px rgba(0,0,0,0.5);
}
${cls} .diamond-quad {
background: rgba(0,0,0,0.3);
}
${cls} .diamond-quad.filled {
background: ${(t.diamondEffect === 'metallic')
? `linear-gradient(135deg, rgba(255,255,255,0.9) 0%, ${t.diamondHighlight} 20%, ${t.diamondColor} 50%, ${darkenHex(t.diamondColor, 0.6)} 80%, ${t.diamondHighlight} 100%)`
: `linear-gradient(135deg, ${t.diamondHighlight} 0%, ${t.diamondColor} 50%, ${darkenHex(t.diamondColor, 0.75)} 100%)`};
box-shadow:
${(t.diamondEffect === 'metallic')
? `inset 0 1px 3px rgba(255,255,255,0.7),
inset 0 -1px 2px rgba(0,0,0,0.5),
inset 1px 0 3px rgba(255,255,255,0.3),
inset -1px 0 2px rgba(0,0,0,0.2)`
: `inset 0 1px 2px rgba(255,255,255,0.45),
inset 0 -1px 2px rgba(0,0,0,0.35),
inset 1px 0 2px rgba(255,255,255,0.15)`};
}` + (() => {
const eff = t.diamondEffect || 'none';
let effectCSS = '';
if (eff === 'bloom') {
effectCSS = `
${cls} .tier-diamond {
box-shadow: 0 0 0 1.5px rgba(0,0,0,0.7), 0 0 8px 3px ${hexToRGBA(t.diamondColor, 0.5)}, 0 0 16px 6px ${hexToRGBA(t.diamondColor, 0.25)};
}`;
} else if (eff === 'border') {
effectCSS = `
${cls} .tier-diamond {
border: 1.5px solid ${t.diamondHighlight};
box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 0 4px 1px ${hexToRGBA(t.diamondHighlight, 0.3)};
}`;
} else if (eff === 'shadow') {
effectCSS = `
${cls} .tier-diamond {
box-shadow: 0 0 0 1.5px rgba(0,0,0,0.7), 0 3px 8px 2px ${hexToRGBA(t.diamondColor, 0.45)}, 0 1px 3px rgba(0,0,0,0.6);
}`;
} else if (eff === 'escalation') {
const intensity = t.diamondFill / 4;
const bloomSize = Math.round(4 + intensity * 12);
const bloomSpread = Math.round(1 + intensity * 5);
effectCSS = `
${cls} .tier-diamond {
border: 1.5px solid ${t.diamondHighlight};
box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 0 ${bloomSize}px ${bloomSpread}px ${hexToRGBA(t.diamondColor, 0.2 + intensity * 0.3)};
}`;
}
return effectCSS;
})() + (t.diamondGlow === 'on' ? `
${cls} .tier-diamond.diamond-glow {
--diamond-glow-color: ${t.diamondGlowColor};
animation: diamond-glow-pulse 2s ease-in-out infinite;
}` : '') : '');
// ── Section 2: Animation CSS for T3 / T4 ────────────────
// Uses --anim-progress (0.0 to 1.0) set by Playwright for each frame.
// All calc() expressions derive visual position/opacity from this value.
let animCSS = '';
if (activeTier === 't3') {
animCSS = `
/* ══════════════════════════════════════
T3 ANIMATION CSS — --anim-progress
Playwright APNG capture: 8 frames
Set --anim-progress = frame / 8 for each frame (0.0 → 0.875)
══════════════════════════════════════ */
/* Header must clip the shimmer as it enters and exits */
${cls} #header {
overflow: hidden;
position: relative;
}
/* Gold shimmer stripe: position driven entirely by --anim-progress.
At progress=0.0 the stripe is off the left edge.
At progress=1.0 it has fully exited the right edge. */
${cls} #header::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(
105deg,
transparent 0%,
transparent calc(var(--anim-progress) * 120% - 22%),
rgba(255,240,140,0.18) calc(var(--anim-progress) * 120% - 14%),
rgba(255,220, 80,0.38) calc(var(--anim-progress) * 120%),
rgba(255,200, 60,0.30) calc(var(--anim-progress) * 120% + 3%),
rgba(255,240,140,0.14) calc(var(--anim-progress) * 120% + 10%),
transparent calc(var(--anim-progress) * 120% + 22%),
transparent 100%
);
pointer-events: none;
z-index: 5;
}
/* Playwright capture loop:
for (let frame = 0; frame < 8; frame++) {
const progress = frame / 8;
await page.evaluate(p => {
document.getElementById('header')
.style.setProperty('--anim-progress', p);
}, progress);
await page.screenshot({ path: \`frame_t3_\${frame}.png\` });
} */`;
}
if (activeTier === 't4' || activeTier === 't4b') {
animCSS = `
/* ══════════════════════════════════════
T4 ANIMATION CSS — --anim-progress
Playwright APNG capture: 12 frames
Set --anim-progress = frame / 12 for each frame (0.0 → 0.917)
══════════════════════════════════════ */
/* ── Layer 1: Prismatic header sweep ── */
${cls} #header {
overflow: hidden;
position: relative;
}
${cls} #header::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(
105deg,
transparent 0%,
transparent calc(var(--anim-progress) * 120% - 30%),
rgba(255,100,100,0.28) calc(var(--anim-progress) * 120% - 20%),
rgba(255,200, 50,0.32) calc(var(--anim-progress) * 120% - 12%),
rgba(100,255,150,0.30) calc(var(--anim-progress) * 120%),
rgba( 50,190,255,0.32) calc(var(--anim-progress) * 120% + 8%),
rgba(140, 80,255,0.28) calc(var(--anim-progress) * 120% + 16%),
rgba(255,100,180,0.24) calc(var(--anim-progress) * 120% + 24%),
transparent calc(var(--anim-progress) * 120% + 34%),
transparent 100%
);
pointer-events: none;
z-index: 5;
}
/* ── Layer 2+3: Gold/Teal dual glow pulse ──
Gold fades from 0.40 → 0.08 over the first half, recovers in second half.
Teal rises from 0.08 → 0.38 over first half, fades in second half.
Simple linear approximation using --anim-progress directly. */
${cls} #fullCard {
${shadowRule}
}
${cls} #fullCard::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
z-index: 4;
box-shadow:
inset 0 0 45px 12px rgba(201,169, 78,
calc(0.40 - var(--anim-progress) * 0.64 + var(--anim-progress) * var(--anim-progress) * 0.64)),
inset 0 0 80px 5px rgba( 45,212,191,
calc(0.08 + var(--anim-progress) * 0.60 - var(--anim-progress) * var(--anim-progress) * 0.60));
}
/* ── Layer 4: Column bar shimmer ──
Each column uses --bar-phase to offset its position in the cycle.
Set --bar-phase on each col-header before capture (or bake into template). */
${cls} .col-header {
position: relative;
overflow: hidden;
}
${cls} .col-header::before {
content: '';
position: absolute;
top: 0; left: 0; width: 45%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.28), transparent);
pointer-events: none;
transform: translateX(calc(((var(--anim-progress) + var(--bar-phase, 0)) * 320%) - 120%));
}
/* Apply per-column phase offsets in the HTML template:
Col-left 1: style="--bar-phase:0.000"
Col-left 2: style="--bar-phase:0.167"
Col-left 3: style="--bar-phase:0.333"
Col-right 1: style="--bar-phase:0.083"
Col-right 2: style="--bar-phase:0.250"
Col-right 3: style="--bar-phase:0.417" */
/* Playwright capture loop:
for (let frame = 0; frame < 12; frame++) {
const progress = frame / 12;
await page.evaluate(p => {
document.getElementById('fullCard')
.style.setProperty('--anim-progress', p);
document.getElementById('header')
.style.setProperty('--anim-progress', p);
// --bar-phase is baked into each col-header element
}, progress);
await page.screenshot({ path: \`frame_t4_\${frame}.png\` });
} */`;
}
// For T3/T4, wrap in two clearly labeled sections
const fullOutput = (activeTier === 't3' || activeTier === 't4' || activeTier === 't4b')
? `/* ═════════════════════════════════════════════════
SECTION 1 — STATIC CSS (frame 0 / fallback)
Use for non-animated contexts or as visual baseline.
═════════════════════════════════════════════════ */
${staticCSS}
/* ═════════════════════════════════════════════════
SECTION 2 — ANIMATION CSS (--anim-progress)
For Playwright APNG frame-by-frame capture.
See capture loop comments below.
═════════════════════════════════════════════════ */
${animCSS}`
: staticCSS;
const output = document.getElementById('cssOutput');
output.value = fullOutput;
output.classList.add('visible');
}
/* ─────────────────────────────────────────────────────────
EVENT LISTENERS
───────────────────────────────────────────────────────── */
function setupListeners() {
// Tier control inputs — save state, update preview
document.querySelectorAll('#controls input, #controls select').forEach(el => {
const evts = (el.type === 'range' || el.type === 'color') ? ['input'] : ['change'];
evts.forEach(evt => {
el.addEventListener(evt, () => {
saveControlsToState();
updateValueLabels();
renderDetailCard();
// Update only the active thumbnail in the comparison row
const slot = document.querySelector(`.comparison-slot[data-tier="${activeTier}"]`);
if (slot) {
const sc = slot.querySelector('.card-scale-container');
if (sc) sc.innerHTML = renderCardHTML(activeTier);
}
});
});
});
// CSS export button
document.getElementById('exportBtn').addEventListener('click', exportCSS);
// Animation toggle — flips animPreviewOn, updates button state and info panel,
// then re-renders all cards so animation classes are added or removed
document.getElementById('animToggleBtn').addEventListener('click', () => {
animPreviewOn = !animPreviewOn;
const btn = document.getElementById('animToggleBtn');
btn.textContent = animPreviewOn ? 'Preview Animation: ON' : 'Preview Animation: OFF';
btn.classList.toggle('active', animPreviewOn);
document.getElementById('animInfoPanel').style.display = animPreviewOn ? '' : 'none';
refreshAll();
});
}
/* ─────────────────────────────────────────────────────────
INIT
───────────────────────────────────────────────────────── */
document.addEventListener('DOMContentLoaded', () => {
buildComparisonRow();
renderDetailCard();
buildPresetButtons();
loadControlsFromState();
setupListeners();
// Ensure info panel visibility matches initial animPreviewOn state (true = visible)
document.getElementById('animInfoPanel').style.display = animPreviewOn ? '' : 'none';
});
</script>
</body>
</html>