Files
pinkudex/actress-rating-mockup.html
T
2026-05-26 22:46:00 +02:00

600 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Pinkudex — Actress rating banner mockup</title>
<style>
:root {
--bg-0: #0b0d10;
--bg-1: #14171c;
--bg-2: #1c2027;
--bg-3: #252a33;
--fg: #e6e8ec;
--fg-dim: #a4abb6;
--fg-muted: #6b7380;
--cyan: #22d3ee;
--mint: #34d399;
--amber: #fbbf24;
--coral: #f87171;
--violet: #a78bfa;
--pink: #f472b6;
--glass: rgba(255,255,255,0.04);
--glass-border: rgba(255,255,255,0.10);
--glass-border-strong: rgba(255,255,255,0.18);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg-0);
color: var(--fg);
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI";
min-height: 100vh;
}
.page { max-width: 1320px; margin: 0 auto; padding: 28px; }
h1 { font-weight: 500; font-size: 22px; margin: 0 0 6px; letter-spacing: -0.01em; }
h2 { font-weight: 600; font-size: 11px; margin: 28px 0 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--cyan); }
.sub { color: var(--fg-muted); font-size: 13px; margin-bottom: 20px; }
/* Control panel */
.controls {
display: flex; gap: 18px; align-items: center; flex-wrap: wrap;
background: var(--bg-1); border: 1px solid var(--glass-border);
border-radius: 12px; padding: 14px 18px; margin-bottom: 20px;
}
.ctrl-group { display: flex; align-items: center; gap: 10px; }
.ctrl-group label {
font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--fg-muted); font-weight: 600;
}
.seg {
display: flex; gap: 0; padding: 3px;
background: var(--bg-0); border: 1px solid var(--glass-border);
border-radius: 7px;
}
.seg button {
background: transparent; border: 0; color: var(--fg-dim);
font-family: ui-monospace, monospace; font-size: 11px;
padding: 5px 11px; border-radius: 5px; cursor: pointer;
transition: all 120ms; min-width: 56px; text-align: center;
}
.seg button:hover { color: var(--fg); }
.seg button.active {
background: rgba(34,211,238,0.18);
color: var(--cyan);
}
.swatch-row { display: flex; gap: 6px; }
.swatch-row button {
width: 28px; height: 28px; border-radius: 6px; border: 2px solid transparent;
cursor: pointer; padding: 0; transition: border-color 120ms, transform 120ms;
}
.swatch-row button:hover { transform: scale(1.06); }
.swatch-row button.active { border-color: white; }
/* Grid */
.grid {
display: grid;
gap: 16px;
}
.grid.portrait { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.grid.landscape { grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); }
/* Actress card */
.card {
position: relative;
background: var(--bg-1);
border: 1px solid var(--glass-border);
border-radius: 12px; overflow: hidden;
cursor: pointer;
transition: transform 180ms, border-color 180ms;
}
.card:hover {
transform: translateY(-2px);
border-color: var(--glass-border-strong);
}
.card-portrait {
position: relative; width: 100%;
background: linear-gradient(135deg, #2a2540, #15101e);
overflow: hidden;
}
.grid.portrait .card-portrait { aspect-ratio: 1 / 1.618; }
.grid.landscape .card-portrait { aspect-ratio: 1.618 / 1; }
.card-portrait .face {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
color: rgba(255,255,255,0.18);
font-family: ui-monospace, monospace; font-size: 14px;
}
.card-portrait .face::before {
content: "";
width: 60%; height: 60%; border-radius: 50%;
background: radial-gradient(circle at 35% 30%, rgba(255,255,255,0.18), transparent 70%);
position: absolute;
}
.card-portrait .face::after {
content: attr(data-letter);
font-size: 64px; font-weight: 200; color: rgba(255,255,255,0.15);
z-index: 1;
}
/* Category badges (already in the codebase, matching the modified ActressCard) */
.cat-badges {
position: absolute; top: 8px; left: 8px; z-index: 10;
display: flex; flex-wrap: wrap; gap: 4px; max-width: 60%;
}
.cat-badge {
display: inline-flex; align-items: center; gap: 4px;
font-family: ui-monospace, monospace; font-size: 9px;
font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
background: rgba(0,0,0,0.8); backdrop-filter: blur(4px);
padding: 3px 8px; border-radius: 999px;
text-shadow: 0 1px 2px rgba(0,0,0,0.9);
}
/* === THE RATING RIBBON ===
Standard pattern:
- .ribbon = a square clipping wrap pinned to a corner of the card,
overflow:hidden so anything outside the corner triangle gets cut.
- .band = a wide strip rotated 45° and offset so its visible
portion fills the diagonal exactly. Width must exceed the wrap's
diagonal (≈ wrap × √2) so the strip reaches edge-to-edge.
*/
.ribbon {
position: absolute;
width: 96px; height: 96px;
overflow: hidden;
pointer-events: none;
z-index: 11;
}
.ribbon .band {
position: absolute;
display: block;
width: 160px; /* > 96 × √2 (~136), with margin for end clip */
line-height: 22px;
padding: 3px 0;
text-align: center;
font-family: ui-monospace, monospace;
font-size: 10px; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.08em;
color: white;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
box-shadow: 0 2px 6px rgba(0,0,0,0.35);
}
/* Top-right diagonal:
pin top-right of band to (top:22px, right:-38px), rotate +45°.
Numbers chosen so the strip's cut ends sit just inside the wrap. */
.ribbon.top-right { top: 0; right: 0; }
.ribbon.top-right .band {
top: 22px;
right: -38px;
transform: rotate(45deg);
}
/* Top-left mirror */
.ribbon.top-left { top: 0; left: 0; }
.ribbon.top-left .band {
top: 22px;
left: -38px;
transform: rotate(-45deg);
}
/* Variants */
.ribbon.coral .band { background: var(--coral); }
.ribbon.amber .band { background: var(--amber); color: #2a1a00; text-shadow: none; }
.ribbon.gradient .band {
background: linear-gradient(90deg, var(--cyan), var(--violet));
}
.ribbon.cyan .band { background: var(--cyan); color: #002028; text-shadow: none; }
.ribbon.pink .band { background: var(--pink); }
/* Style: stars vs numeric */
.ribbon.numeric .band::before { content: "RATING: " attr(data-r); }
.ribbon.numeric.compact .band::before { content: "★ " attr(data-r); letter-spacing: 0.04em; }
.ribbon.stars .band::before {
content: attr(data-stars);
letter-spacing: 0.18em;
}
/* Card meta strip */
.card-meta {
background: var(--bg-2);
padding: 10px 12px;
display: flex; flex-direction: column; gap: 4px;
}
.card-name {
display: flex; align-items: center; gap: 6px;
font-weight: 500;
}
.card-name .gender {
color: var(--pink);
font-size: 14px;
}
.card-age {
font-size: 11px; color: var(--fg-muted);
}
.card-count {
margin-top: 6px;
background: var(--bg-3);
border-radius: 6px;
padding: 6px 10px;
font-family: ui-monospace, monospace; font-size: 11px;
color: var(--fg-dim);
display: flex; align-items: center; gap: 8px;
}
.card-count .play { color: var(--cyan); font-size: 12px; }
/* Editor panel */
.editor {
margin-top: 28px;
background: var(--bg-1);
border: 1px solid var(--glass-border);
border-radius: 14px; padding: 18px 20px;
}
.editor h3 {
margin: 0 0 12px;
font-size: 13px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.08em;
color: var(--cyan);
}
.editor .row {
display: grid; grid-template-columns: 130px 1fr; gap: 14px;
align-items: center;
padding: 8px 0;
border-bottom: 1px dashed var(--glass-border);
}
.editor .row:last-child { border-bottom: 0; }
.editor .lbl {
font-size: 12px; color: var(--fg-muted);
text-transform: uppercase; letter-spacing: 0.06em;
font-weight: 600;
}
.stars {
display: flex; gap: 2px;
}
.stars button {
background: transparent; border: 0;
color: var(--fg-muted); cursor: pointer;
font-size: 22px; line-height: 1;
padding: 4px;
transition: color 120ms, transform 120ms;
}
.stars button:hover { transform: scale(1.15); }
.stars button.on { color: var(--amber); }
.stars button.clear {
margin-left: 8px;
font-size: 11px; font-family: ui-monospace, monospace;
color: var(--fg-muted);
text-transform: uppercase; letter-spacing: 0.06em;
border: 1px solid var(--glass-border-strong);
border-radius: 5px; padding: 4px 8px;
}
.stars button.clear:hover { color: var(--coral); border-color: var(--coral); }
/* Detail hero (showing what the bigger ribbon looks like on /actress/[slug]) */
.detail {
display: grid; grid-template-columns: 320px 1fr; gap: 20px;
background: var(--bg-1); border: 1px solid var(--glass-border);
border-radius: 14px; overflow: hidden;
margin-top: 20px;
}
.detail .hero {
position: relative;
aspect-ratio: 1 / 1.618;
background: linear-gradient(135deg, #3a2a52, #1c1430);
}
.detail .hero .face {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
}
.detail .hero .face::after {
content: attr(data-letter);
font-size: 96px; font-weight: 200; color: rgba(255,255,255,0.18);
}
.detail .hero .ribbon {
width: 140px; height: 140px;
}
.detail .hero .ribbon .band {
width: 220px;
line-height: 28px;
padding: 4px 0;
font-size: 12px;
}
.detail .hero .ribbon.top-right .band {
top: 32px;
right: -52px;
transform: rotate(45deg);
}
.detail .hero .ribbon.top-left .band {
top: 32px;
left: -52px;
transform: rotate(-45deg);
}
.detail .info {
padding: 24px;
}
.detail .info h3 {
margin: 0 0 4px; font-size: 28px; font-weight: 500;
display: flex; align-items: center; gap: 12px;
}
.detail .info h3 .female { color: var(--pink); font-size: 22px; }
.detail .info .meta-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 14px; margin-top: 22px;
border-top: 1px solid var(--glass-border); padding-top: 18px;
}
.detail .info .meta-grid .it {
display: flex; flex-direction: column;
}
.detail .info .meta-grid .it .k {
font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--fg-muted); font-family: ui-monospace, monospace;
}
.detail .info .meta-grid .it .v {
font-size: 14px; color: var(--fg);
}
/* Comparison row showing top-left vs top-right with categories */
.comparison {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
}
.comparison .label {
font-family: ui-monospace, monospace; font-size: 10px;
text-transform: uppercase; letter-spacing: 0.08em;
color: var(--fg-muted);
margin-bottom: 8px;
}
.comparison .label.bad { color: var(--coral); }
.comparison .label.good { color: var(--mint); }
/* Demo data positioning */
.legend {
background: var(--bg-1);
border: 1px solid var(--glass-border);
border-radius: 10px;
padding: 12px 16px;
margin-bottom: 16px;
color: var(--fg-dim);
font-size: 12px;
line-height: 1.7;
}
.legend strong { color: var(--fg); }
.legend code {
background: var(--bg-0); padding: 1px 5px; border-radius: 4px;
font-size: 11px; color: var(--cyan);
}
</style>
</head>
<body>
<div class="page">
<h1>Actress rating banner — visual mockup</h1>
<div class="sub">
Click the stars in the editor to apply a rating to all cards. Use the controls to swap color / position / style.
</div>
<div class="controls">
<div class="ctrl-group">
<label>View</label>
<div class="seg" id="seg-view">
<button class="active" data-view="portrait">Portrait</button>
<button data-view="landscape">Landscape</button>
</div>
</div>
<div class="ctrl-group">
<label>Style</label>
<div class="seg" id="seg-style">
<button class="active" data-style="numeric">RATING: N</button>
<button data-style="compact">★ N</button>
<button data-style="stars">★★★★★</button>
</div>
</div>
<div class="ctrl-group">
<label>Position</label>
<div class="seg" id="seg-pos">
<button class="active" data-pos="top-right">Top-right</button>
<button data-pos="top-left">Top-left</button>
</div>
</div>
<div class="ctrl-group">
<label>Color</label>
<div class="swatch-row" id="swatch-row">
<button class="active" data-color="coral" style="background: #f87171;" title="coral"></button>
<button data-color="amber" style="background: #fbbf24;" title="amber"></button>
<button data-color="gradient" style="background: linear-gradient(90deg, #22d3ee, #a78bfa);" title="cyan→violet"></button>
<button data-color="cyan" style="background: #22d3ee;" title="cyan"></button>
<button data-color="pink" style="background: #f472b6;" title="pink"></button>
</div>
</div>
</div>
<h2>Actress grid · current view</h2>
<div class="grid portrait" id="grid"></div>
<div class="editor">
<h3>Meta editor — set ratings</h3>
<div id="editor-rows"></div>
</div>
<h2>Detail page hero</h2>
<div class="detail">
<div class="hero">
<div class="ribbon top-right coral numeric" id="hero-ribbon">
<div class="band" data-r="5" data-stars="★★★★★"></div>
</div>
<div class="face" data-letter="V"></div>
</div>
<div class="info">
<h3><span class="female"></span> Vina Sky</h3>
<div style="color: var(--fg-muted); font-family: ui-monospace, monospace; font-size: 13px;">
Age 27 · 20 covers · 5 categories
</div>
<div class="meta-grid">
<div class="it"><span class="k">Ethnicity</span><span class="v">Asian</span></div>
<div class="it"><span class="k">Country</span><span class="v">USA</span></div>
<div class="it"><span class="k">Eye color</span><span class="v">Brown</span></div>
<div class="it"><span class="k">Hair color</span><span class="v">Black</span></div>
<div class="it"><span class="k">First seen</span><span class="v">2018</span></div>
<div class="it"><span class="k">Active</span><span class="v">Yes</span></div>
</div>
</div>
</div>
<h2>Position trade-off · with category badges</h2>
<div class="legend">
<strong>The clash.</strong> The actress card already renders category chips at <code>top-2 left-2 z-10</code> (see ActressCard.tsx:182).
Top-left ribbon overlaps them. Top-right is conflict-free.
</div>
<div class="comparison">
<div>
<div class="label bad">✗ Top-left collides with category chips</div>
<div class="card" style="max-width: 240px;">
<div class="card-portrait">
<div class="cat-badges">
<span class="cat-badge" style="color: #fbbf24; border: 1px solid #fbbf24aa;">★ Top</span>
<span class="cat-badge" style="color: #22d3ee; border: 1px solid #22d3eeaa;">◆ VIP</span>
</div>
<div class="ribbon top-left coral numeric">
<div class="band" data-r="5" data-stars="★★★★★"></div>
</div>
<div class="face" data-letter="X"></div>
</div>
<div class="card-meta">
<div class="card-name"><span class="gender"></span>Xoey Li</div>
<div class="card-count"><span class="play"></span>4</div>
</div>
</div>
</div>
<div>
<div class="label good">✓ Top-right keeps both visible</div>
<div class="card" style="max-width: 240px;">
<div class="card-portrait">
<div class="cat-badges">
<span class="cat-badge" style="color: #fbbf24; border: 1px solid #fbbf24aa;">★ Top</span>
<span class="cat-badge" style="color: #22d3ee; border: 1px solid #22d3eeaa;">◆ VIP</span>
</div>
<div class="ribbon top-right coral numeric">
<div class="band" data-r="5" data-stars="★★★★★"></div>
</div>
<div class="face" data-letter="X"></div>
</div>
<div class="card-meta">
<div class="card-name"><span class="gender"></span>Xoey Li</div>
<div class="card-count"><span class="play"></span>4</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Synthetic data
const ACTRESSES = [
{ name: "Xoey Li", age: 28, count: 4, letter: "X", rating: 4 },
{ name: "Willa", age: 34, count: 1, letter: "W", rating: 0 },
{ name: "Vina Sky", age: 27, count: 20, letter: "V", rating: 5 },
{ name: "Aria Kai", age: 25, count: 12, letter: "A", rating: 3 },
{ name: "Mira Saito", age: 31, count: 7, letter: "M", rating: 0 },
{ name: "Yui Nanase", age: 24, count: 33, letter: "Y", rating: 5 },
{ name: "Elena Voss", age: 29, count: 8, letter: "E", rating: 2 },
{ name: "Luna Park", age: 26, count: 15, letter: "L", rating: 4 },
];
let state = {
view: "portrait",
style: "numeric",
pos: "top-right",
color: "coral",
};
function ribbonHTML(r) {
if (r <= 0) return "";
const stars = "★".repeat(r) + "☆".repeat(5 - r);
return `
<div class="ribbon ${state.pos} ${state.color} ${state.style}">
<div class="band" data-r="${r}" data-stars="${stars}"></div>
</div>
`;
}
function cardHTML(a) {
return `
<div class="card" data-name="${a.name}">
<div class="card-portrait">
${ribbonHTML(a.rating)}
<div class="face" data-letter="${a.letter}"></div>
</div>
<div class="card-meta">
<div class="card-name">
<span class="gender">♀</span>
<span>${a.name}</span>
</div>
<div class="card-age">${a.age} years old</div>
<div class="card-count"><span class="play">▶</span>${a.count}</div>
</div>
</div>
`;
}
function render() {
const grid = document.getElementById("grid");
grid.className = "grid " + state.view;
grid.innerHTML = ACTRESSES.map(cardHTML).join("");
// Detail hero ribbon
const hero = document.getElementById("hero-ribbon");
const heroR = ACTRESSES.find(a => a.name === "Vina Sky").rating;
if (heroR > 0) {
const stars = "★".repeat(heroR) + "☆".repeat(5 - heroR);
hero.className = `ribbon ${state.pos} ${state.color} ${state.style}`;
hero.innerHTML = `<div class="band" data-r="${heroR}" data-stars="${stars}"></div>`;
hero.style.display = "";
} else {
hero.style.display = "none";
}
// Editor rows
document.getElementById("editor-rows").innerHTML = ACTRESSES.map((a) => `
<div class="row">
<div class="lbl">${a.name}</div>
<div class="stars" data-actress="${a.name}">
${[1,2,3,4,5].map(i => `<button class="${i <= a.rating ? "on" : ""}" data-v="${i}">${i <= a.rating ? "★" : "☆"}</button>`).join("")}
<button class="clear" data-v="0">Clear</button>
</div>
</div>
`).join("");
// wire stars
document.querySelectorAll(".stars").forEach(el => {
el.addEventListener("click", (e) => {
const t = e.target.closest("button");
if (!t) return;
const v = Number(t.dataset.v);
const name = el.dataset.actress;
const a = ACTRESSES.find(x => x.name === name);
if (a) a.rating = v;
render();
});
});
}
// Wire controls
document.querySelectorAll("#seg-view button").forEach(b => b.addEventListener("click", () => {
document.querySelectorAll("#seg-view button").forEach(x => x.classList.remove("active"));
b.classList.add("active"); state.view = b.dataset.view; render();
}));
document.querySelectorAll("#seg-style button").forEach(b => b.addEventListener("click", () => {
document.querySelectorAll("#seg-style button").forEach(x => x.classList.remove("active"));
b.classList.add("active"); state.style = b.dataset.style; render();
}));
document.querySelectorAll("#seg-pos button").forEach(b => b.addEventListener("click", () => {
document.querySelectorAll("#seg-pos button").forEach(x => x.classList.remove("active"));
b.classList.add("active"); state.pos = b.dataset.pos; render();
}));
document.querySelectorAll("#swatch-row button").forEach(b => b.addEventListener("click", () => {
document.querySelectorAll("#swatch-row button").forEach(x => x.classList.remove("active"));
b.classList.add("active"); state.color = b.dataset.color; render();
}));
render();
</script>
</body>
</html>