opencd: murdock prototype — opencd.css + three templates (jewel-case, leaflet, back-tray)

Prototype build incorporating Amy's gate review findings:
- Custom --cd-surface-* tokens (oklch), not Open Props
- Custom --cd-grid-* tokens via gray-3/gray-2
- --cd-scale: 1 = default 2x display scale, all dims calc()
- --cd-font-label using font-neo-grotesque for spine legibility

Files:
- opencd.css (622 lines) — full monolith with variables, reset, components,
  grid overlays, advisory badge, print styles, responsive containers
- templates/jewel-case.html — front jewel case with spine + disc + advisory
- templates/leaflet.html — 4-page booklet with page-turn navigation
- templates/back-tray.html — back tray with tracklist, dual spines, grid

GPG: ABE295FFEB4571F8 — H.M. Murdock <murdock@a-team.dev>
This commit is contained in:
H.M. Murdock 2026-05-25 22:47:24 +02:00
parent f3d4fc1626
commit e3dbfa2fd1
Signed by: murdock
GPG key ID: ABE295FFEB4571F8
4 changed files with 1090 additions and 0 deletions

623
opencd.css Normal file
View file

@ -0,0 +1,623 @@
/* ==========================================================================
OpenCD Jewel Case CSS Framework
Prototype: single-file system
Author: H.M. Murdock <murdock@a-team.dev>
Base: Open Props v2 via unpkg
========================================================================== */
@import "https://unpkg.com/open-props";
/* ==========================================================================
1. Custom Properties CD Dimension & Design Tokens
========================================================================== */
:root {
/* Scale
--cd-scale: 1 default 2× display scale (280px = 142mm at 2×)
Set to 0.5 for 1× (physical mm px at 96dpi), 2 for 4×, etc.
*/
--cd-scale: 1;
/* ── Physical CD Dimensions (at 2× scale) ── */
--cd-jewel-width: calc(280px * var(--cd-scale));
--cd-jewel-height: calc(245px * var(--cd-scale));
--cd-jewel-open-width: calc(560px * var(--cd-scale));
--cd-disc-diameter: calc(240px * var(--cd-scale));
--cd-disc-hole: calc(30px * var(--cd-scale)); /* Ø15mm × 2 */
--cd-leaflet-size: calc(240px * var(--cd-scale));
--cd-spine-width: calc(14px * var(--cd-scale));
--cd-tray-width: calc(260px * var(--cd-scale));
/* ── Derived aspect ratios ── */
--cd-aspect-jewel: 280 / 245;
--cd-aspect-leaflet: 1 / 1;
--cd-aspect-disc: 1 / 1;
/* ── Surface Colors (ASW-style, NOT from Open Props — Amy finding #1) ── */
--cd-surface-1: oklch(30% .015 265); /* tray paper base */
--cd-surface-2: oklch(35% .015 265); /* tray raised panel */
--cd-surface-3: oklch(40% .015 265); /* leaflet inner area */
--cd-surface-4: oklch(15% .01 265); /* spine background — darkest */
/* ── Typography ── */
--cd-font-label: var(--font-neo-grotesque); /* Inter/Roboto stack — Amy finding #4 */
--cd-font-body: var(--font-serif);
--cd-font-mono: var(--font-mono-code, var(--font-mono));
--cd-font-size-body: var(--font-size-fluid-1);
--cd-font-size-title: var(--font-size-fluid-2);
--cd-font-size-display: var(--font-size-fluid-3);
/* ── Spacing insets ── */
--cd-space-inset: var(--size-fluid-1);
--cd-space-stack: var(--size-fluid-2);
--cd-space-gutter: var(--size-fluid-3);
/* ── Semantic Text Colors ── */
--cd-text-primary: var(--gray-9);
--cd-text-secondary: var(--gray-7);
--cd-text-muted: var(--gray-5);
--cd-text-on-spine: var(--gray-1);
/* ── Surface / Tray Mappings ── */
--cd-tray-bg: var(--cd-surface-1);
--cd-tray-bg-raised: var(--cd-surface-2);
--cd-tray-bg-sunken: var(--cd-surface-3);
--cd-spine-bg: var(--cd-surface-4);
/* ── Grid Colors (custom — NOT Open Props tokens, Amy finding #2) ── */
--cd-grid-color: var(--gray-3);
--cd-grid-color-alt: var(--gray-2);
--cd-grid-fine: 4px;
--cd-grid-coarse: 8px;
/* ── Advisory Badge ── */
--cd-advisory-red: var(--red-6);
--cd-advisory-red-dark: var(--red-7);
--cd-advisory-bg: var(--red-0);
/* ── Jewel Case Decorations ── */
--cd-jewel-radius: var(--radius-2); /* 5px */
--cd-jewel-shadow: var(--shadow-2);
--cd-jewel-shadow-raised: var(--shadow-3);
--cd-leaflet-radius: var(--radius-3); /* 1rem */
/* ── Spine Typography ── */
--cd-spine-font-xs: var(--font-size-0);
--cd-spine-font-sm: var(--font-size-1);
--cd-spine-font-md: var(--font-size-2);
--cd-spine-font-lg: var(--font-size-3);
/* ── Motion & Print ── */
--cd-transition-duration: 200ms;
--cd-ease-flip: cubic-bezier(0.34, 1.56, 0.64, 1);
--cd-print-jewel-width: 142mm;
--cd-print-jewel-height: 125mm;
}
/* ==========================================================================
2. Reset
========================================================================== */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ==========================================================================
3. Jewel Case Container
========================================================================== */
.cd-jewel-case {
display: grid;
grid-template-columns: var(--cd-spine-width) 1fr;
grid-template-rows: auto 1fr;
width: var(--cd-jewel-width);
min-height: var(--cd-jewel-height);
background: var(--gray-0);
border-radius: var(--cd-jewel-radius);
box-shadow: var(--cd-jewel-shadow);
overflow: hidden;
position: relative;
transition: width var(--cd-transition-duration) var(--ease-3);
font-family: var(--cd-font-body);
color: var(--cd-text-primary);
container-type: inline-size;
}
.cd-jewel-case--open {
width: var(--cd-jewel-open-width);
}
.cd-jewel-case[data-jewel-state="open"] {
width: var(--cd-jewel-open-width);
}
/* ── Internal layout wrapper ── */
.cd-jewel-case > .cd-jewel-inner {
grid-column: 2;
grid-row: 1 / -1;
display: flex;
flex-direction: column;
padding: var(--cd-space-inset);
gap: var(--cd-space-stack);
min-height: 0;
}
/* ==========================================================================
4. Spine
========================================================================== */
.cd-spine {
grid-column: 1;
grid-row: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: var(--cd-spine-bg);
color: var(--cd-text-on-spine);
font-family: var(--cd-font-label);
padding: var(--size-2) var(--size-1);
gap: var(--size-2);
writing-mode: vertical-rl;
text-orientation: mixed;
user-select: none;
}
.cd-spine .spine-label {
font-size: var(--cd-spine-font-sm);
font-weight: 600;
letter-spacing: var(--font-letterspacing-3, 0.1em);
text-transform: uppercase;
}
.cd-spine .spine-track {
font-size: var(--cd-spine-font-xs);
letter-spacing: var(--font-letterspacing-1, 0.05em);
opacity: 0.8;
}
/* ── Side spine variant ── */
.cd-spine--side {
writing-mode: horizontal-tb;
flex-direction: row;
}
/* ==========================================================================
5. Leaflet Content & Pages
========================================================================== */
.leaflet-content {
display: flex;
flex-direction: column;
gap: var(--cd-space-stack);
max-width: var(--cd-leaflet-size);
position: relative;
background:
repeating-linear-gradient(
0deg,
transparent 0 calc(var(--cd-grid-coarse) * 3 - 1px),
var(--cd-grid-color) calc(var(--cd-grid-coarse) * 3 - 1px) calc(var(--cd-grid-coarse) * 3)
),
var(--cd-tray-bg);
border-radius: var(--cd-leaflet-radius);
padding: var(--cd-space-inset);
}
.leaflet-page {
aspect-ratio: var(--cd-aspect-leaflet);
background: var(--gray-0);
border-radius: calc(var(--cd-leaflet-radius) / 2);
padding: var(--cd-space-inset);
display: flex;
flex-direction: column;
gap: var(--size-2);
box-shadow: 0 1px 3px rgba(0,0,0,.08);
transition: transform var(--cd-transition-duration) var(--cd-ease-flip);
position: relative;
}
.leaflet-page[data-leaflet-active="true"] {
box-shadow: var(--cd-jewel-shadow);
}
.leaflet-page[data-leaflet-direction="rtl"] {
direction: rtl;
}
.leaflet-page h1,
.leaflet-page h2 {
font-family: var(--cd-font-label);
font-size: var(--cd-font-size-title);
color: var(--cd-text-primary);
line-height: 1.2;
}
.leaflet-page p {
font-family: var(--cd-font-body);
font-size: var(--cd-font-size-body);
color: var(--cd-text-secondary);
line-height: 1.6;
}
.leaflet-page .leaflet-meta {
font-family: var(--cd-font-mono);
font-size: var(--cd-spine-font-xs);
color: var(--cd-text-muted);
margin-top: auto;
}
/* ==========================================================================
6. Disc Art
========================================================================== */
.disc-art {
width: var(--cd-disc-diameter);
aspect-ratio: var(--cd-aspect-disc);
border-radius: 50%;
background:
radial-gradient(
circle at 30% 30%,
var(--gray-4) 0%,
var(--gray-2) 40%,
var(--gray-6) 70%,
var(--gray-2) 100%
);
display: flex;
align-items: center;
justify-content: center;
position: relative;
box-shadow: inset 0 0 8px rgba(0,0,0,.12);
}
.disc-art::before {
content: "";
position: absolute;
width: var(--cd-disc-hole);
aspect-ratio: 1;
border-radius: 50%;
background: var(--gray-0);
box-shadow: inset 0 0 3px rgba(0,0,0,.15);
}
.disc-hole {
position: absolute;
width: calc(var(--cd-disc-hole) * 0.6);
aspect-ratio: 1;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(var(--cd-disc-hole) * 0.5);
color: var(--gray-4);
z-index: 1;
pointer-events: none;
}
/* ── Disc art variants ── */
.disc-art--sheen {
background:
radial-gradient(
circle at 25% 25%,
var(--gray-4) 0%,
var(--gray-2) 35%,
var(--gray-5) 50%,
transparent 65%
),
radial-gradient(
circle at 75% 75%,
var(--gray-7) 0%,
var(--gray-3) 40%,
transparent 70%
),
repeating-radial-gradient(
circle at 50%,
transparent 0 calc(var(--cd-disc-hole) * 0.5),
var(--gray-3) calc(var(--cd-disc-hole) * 0.5) calc(var(--cd-disc-hole) * 0.5 + 1px)
);
}
/* ==========================================================================
7. Human Advisory Badge
========================================================================== */
.human-advisory {
display: flex;
flex-direction: column;
align-items: center;
background: #000;
color: #fff;
border: 2px solid #000;
font-family: Impact, "Arial Black", var(--cd-font-label), sans-serif;
text-transform: uppercase;
text-align: center;
padding: 0;
width: fit-content;
max-width: 100%;
position: absolute;
bottom: var(--cd-space-inset);
right: var(--cd-space-inset);
}
.human-advisory .advisory-row {
width: 100%;
padding: var(--size-1) var(--size-3);
font-size: var(--cd-spine-font-sm);
letter-spacing: var(--font-letterspacing-3, 0.1em);
line-height: 1.2;
white-space: nowrap;
}
.human-advisory .advisory-row--black {
background: #000;
color: #fff;
}
.human-advisory .advisory-row--white {
background: #fff;
color: #000;
}
.human-advisory .advisory-row--red {
background: var(--cd-advisory-red);
color: #fff;
}
/* ── Human Advisory variants ── */
.human-advisory--red .advisory-row--black {
background: var(--cd-advisory-red-dark);
color: #fff;
}
.human-advisory--red .advisory-row--white {
background: var(--cd-advisory-red);
color: #fff;
}
/* ==========================================================================
8. Back Tray
========================================================================== */
.back-tray {
display: grid;
grid-template-columns: var(--cd-spine-width) 1fr var(--cd-spine-width);
background: var(--cd-tray-bg);
border-top: 1px solid var(--cd-grid-color);
padding: 0;
position: relative;
}
.back-tray .tray-credits {
grid-column: 2;
padding: var(--cd-space-inset);
display: flex;
flex-direction: column;
gap: var(--cd-space-stack);
background: var(--cd-tray-bg-raised);
border-radius: 0 0 var(--cd-leaflet-radius) var(--cd-leaflet-radius);
}
.tray-credits h2 {
font-family: var(--cd-font-label);
font-size: var(--cd-font-size-title);
color: var(--cd-text-primary);
text-transform: uppercase;
letter-spacing: var(--font-letterspacing-2, 0.075em);
}
.tray-credits ol {
list-style: none;
counter-reset: track;
display: flex;
flex-direction: column;
gap: var(--size-1);
}
.tray-credits ol li {
counter-increment: track;
font-family: var(--cd-font-label);
font-size: var(--cd-font-size-body);
color: var(--cd-text-secondary);
padding: var(--size-1) 0;
border-bottom: 1px solid var(--cd-grid-color-alt);
display: flex;
gap: var(--size-2);
}
.tray-credits ol li::before {
content: counter(track, decimal-leading-zero) ".";
font-family: var(--cd-font-mono);
color: var(--cd-text-muted);
min-width: 2.5ch;
}
.tray-credits .credits-label {
font-family: var(--cd-font-mono);
font-size: var(--cd-spine-font-xs);
color: var(--cd-text-muted);
text-transform: uppercase;
letter-spacing: var(--font-letterspacing-1, 0.05em);
}
.tray-credits p {
font-family: var(--cd-font-body);
font-size: var(--cd-font-size-body);
color: var(--cd-text-secondary);
line-height: 1.5;
}
/* ── Back tray spine labels ── */
.back-tray .cd-spine {
grid-row: 1;
background: var(--cd-spine-bg);
}
/* ==========================================================================
9. Grid Overlay Utility
========================================================================== */
.cd-grid {
background-image:
repeating-linear-gradient(
0deg,
transparent 0 calc(var(--cd-grid-coarse) - 1px),
var(--cd-grid-color) calc(var(--cd-grid-coarse) - 1px) var(--cd-grid-coarse)
);
}
.cd-grid--fine {
background-image:
repeating-linear-gradient(
0deg,
transparent 0 calc(var(--cd-grid-fine) - 1px),
var(--cd-grid-color) calc(var(--cd-grid-fine) - 1px) var(--cd-grid-fine)
);
}
.cd-grid--coarse {
background-image:
repeating-linear-gradient(
0deg,
transparent 0 calc(var(--cd-grid-coarse) - 1px),
var(--cd-grid-color) calc(var(--cd-grid-coarse) - 1px) var(--cd-grid-coarse)
);
}
.cd-grid--crosshatch {
background-image:
repeating-linear-gradient(
0deg,
transparent 0 calc(var(--cd-grid-fine) - 1px),
var(--cd-grid-color-alt) calc(var(--cd-grid-fine) - 1px) var(--cd-grid-fine)
),
repeating-linear-gradient(
90deg,
transparent 0 calc(var(--cd-grid-coarse) - 1px),
var(--cd-grid-color) calc(var(--cd-grid-coarse) - 1px) var(--cd-grid-coarse)
);
}
/* ==========================================================================
10. Open / Closed State Transitions
========================================================================== */
.cd-jewel-case .back-tray {
display: none;
}
.cd-jewel-case--open .back-tray,
.cd-jewel-case[data-jewel-state="open"] .back-tray {
display: grid;
}
/* ==========================================================================
11. Responsive Breakpoints (Open Props container queries)
========================================================================== */
@container (max-width: 350px) {
.cd-jewel-case {
width: 100% !important;
grid-template-columns: 1fr;
}
.cd-spine {
writing-mode: horizontal-tb;
flex-direction: row;
padding: var(--size-1) var(--size-2);
grid-column: 1;
grid-row: 1;
}
.cd-jewel-inner {
grid-column: 1;
grid-row: 2;
}
.disc-art {
width: 100% !important;
max-width: var(--cd-disc-diameter);
margin: 0 auto;
}
.leaflet-content {
max-width: 100%;
}
.human-advisory {
position: static;
margin-top: var(--cd-space-stack);
align-self: center;
}
}
@container (min-width: 351px) and (max-width: 549px) {
.cd-jewel-case {
width: 100% !important;
max-width: var(--cd-jewel-width);
}
.leaflet-content {
gap: var(--size-2);
}
.human-advisory .advisory-row {
font-size: var(--cd-spine-font-xs);
padding: var(--size-1) var(--size-2);
}
}
/* ==========================================================================
12. Print Styles
========================================================================== */
@media print {
.cd-jewel-case {
width: var(--cd-print-jewel-width);
height: var(--cd-print-jewel-height);
box-shadow: none;
border: 1px solid #ccc;
}
.cd-jewel-case--open {
width: calc(var(--cd-print-jewel-width) * 2);
}
.back-tray {
display: grid !important;
}
.human-advisory {
position: absolute;
}
.disc-art {
box-shadow: none;
}
.cd-jewel-case,
.leaflet-page,
.back-tray {
border-radius: 0;
}
}
/* ==========================================================================
13. Grain Texture Utility
========================================================================== */
.cd-grain {
background-image: repeating-radial-gradient(
circle at 50% 50%,
transparent 0 1px,
rgba(0,0,0,.02) 1px 2px,
transparent 2px 4px
),
repeating-radial-gradient(
circle at 100% 100%,
transparent 0 3px,
rgba(0,0,0,.015) 3px 4px,
transparent 4px 8px
);
background-size: 120px 120px, 200px 200px;
}