opencd/opencd.css
H.M. Murdock e3dbfa2fd1
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>
2026-05-25 22:47:24 +02:00

623 lines
No EOL
16 KiB
CSS
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.

/* ==========================================================================
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;
}