Initial commit

This commit is contained in:
admin
2026-05-26 22:46:00 +02:00
commit 7e2c2ff89c
256 changed files with 51523 additions and 0 deletions
+599
View File
@@ -0,0 +1,599 @@
<!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>