Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 261a14c575 | |||
| e7372b0ccb | |||
| ec5178142e |
@@ -53,3 +53,5 @@ Thumbs.db
|
|||||||
# Node.js / esbuild
|
# Node.js / esbuild
|
||||||
node_modules/
|
node_modules/
|
||||||
media_server/static/dist/
|
media_server/static/dist/
|
||||||
|
# Added by code-review-graph
|
||||||
|
.code-review-graph/
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"code-review-graph": {
|
||||||
|
"command": "uvx",
|
||||||
|
"args": [
|
||||||
|
"code-review-graph",
|
||||||
|
"serve"
|
||||||
|
],
|
||||||
|
"type": "stdio"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -196,3 +196,42 @@ pytest --tb=short -q
|
|||||||
|
|
||||||
- **ALWAYS ask for user approval before committing and pushing changes.**
|
- **ALWAYS ask for user approval before committing and pushing changes.**
|
||||||
- When pushing, always push to all remotes: `git push origin master && git push github master`
|
- When pushing, always push to all remotes: `git push origin master && git push github master`
|
||||||
|
|
||||||
|
<!-- code-review-graph MCP tools -->
|
||||||
|
## MCP Tools: code-review-graph
|
||||||
|
|
||||||
|
**IMPORTANT: This project has a knowledge graph. ALWAYS use the
|
||||||
|
code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore
|
||||||
|
the codebase.** The graph is faster, cheaper (fewer tokens), and gives
|
||||||
|
you structural context (callers, dependents, test coverage) that file
|
||||||
|
scanning cannot.
|
||||||
|
|
||||||
|
### When to use graph tools FIRST
|
||||||
|
|
||||||
|
- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep
|
||||||
|
- **Understanding impact**: `get_impact_radius` instead of manually tracing imports
|
||||||
|
- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files
|
||||||
|
- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for
|
||||||
|
- **Architecture questions**: `get_architecture_overview` + `list_communities`
|
||||||
|
|
||||||
|
Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need.
|
||||||
|
|
||||||
|
### Key Tools
|
||||||
|
|
||||||
|
| Tool | Use when |
|
||||||
|
|------|----------|
|
||||||
|
| `detect_changes` | Reviewing code changes — gives risk-scored analysis |
|
||||||
|
| `get_review_context` | Need source snippets for review — token-efficient |
|
||||||
|
| `get_impact_radius` | Understanding blast radius of a change |
|
||||||
|
| `get_affected_flows` | Finding which execution paths are impacted |
|
||||||
|
| `query_graph` | Tracing callers, callees, imports, tests, dependencies |
|
||||||
|
| `semantic_search_nodes` | Finding functions/classes by name or keyword |
|
||||||
|
| `get_architecture_overview` | Understanding high-level codebase structure |
|
||||||
|
| `refactor_tool` | Planning renames, finding dead code |
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
1. The graph auto-updates on file changes (via hooks).
|
||||||
|
2. Use `detect_changes` for code review.
|
||||||
|
3. Use `get_affected_flows` to understand impact.
|
||||||
|
4. Use `query_graph` pattern="tests_for" to check coverage.
|
||||||
|
|||||||
+11
-18
@@ -1,24 +1,19 @@
|
|||||||
## v0.2.1 (2026-04-25)
|
## v0.2.2 (2026-05-01)
|
||||||
|
|
||||||
A small polish release on top of the Studio Reference redesign — accent picker fix,
|
### UI / Player
|
||||||
visualizer performance work, and a couple of layout refinements for tablet and
|
|
||||||
small-desktop widths.
|
|
||||||
|
|
||||||
### Bug Fixes
|
- Replace sticky footer with a dedicated **About** dialog opened from a new header button — frees up bottom space and removes the always-visible colophon strip ([ec51781](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ec51781))
|
||||||
- **Accent picker** now wired to the editorial copper palette + visual polish ([f4be2bd](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/f4be2bd))
|
- Reclaim dead space on the player view: drop ~64 px of bottom container padding now that the footer is gone ([ec51781](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ec51781))
|
||||||
|
- Loosen the vinyl stage aspect ratio (`1:1` → `1:0.85`) and switch the tonearm from `height: 36%` to `aspect-ratio: 1` so the disc no longer leaves a tall empty band below the sleeve ([ec51781](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ec51781))
|
||||||
### Performance
|
- Add `about.*` and `dialog.close` i18n keys for **EN** and **RU** ([ec51781](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ec51781))
|
||||||
- **Visualizer** — significant CPU cuts on spectrum rendering and track switches ([51ec150](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/51ec150))
|
|
||||||
|
|
||||||
### UI Improvements
|
|
||||||
- Meaningful caps for tablet / small-desktop range + tighter footer ([25a492d](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/25a492d))
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Development / Internal
|
### Development / Internal
|
||||||
|
|
||||||
#### CI/Build
|
#### Chores
|
||||||
- Skip test workflow on release commits ([08c3c80](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/08c3c80))
|
|
||||||
|
- Wire up the **code-review-graph** MCP server: add `.mcp.json` (uvx, stdio), document the graph tools in `CLAUDE.md` so structural exploration prefers graph queries over Grep/Read, and ignore the `.code-review-graph/` index directory ([e7372b0](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/e7372b0))
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,9 +22,7 @@ small-desktop widths.
|
|||||||
|
|
||||||
| Hash | Message | Author |
|
| Hash | Message | Author |
|
||||||
|------|---------|--------|
|
|------|---------|--------|
|
||||||
| [25a492d](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/25a492d) | ui(player): meaningful caps for tablet/small-desktop range + tighter footer | alexei.dolgolyov |
|
| [ec51781](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/ec51781) | ui(player): replace footer with About dialog + reclaim dead space | alexei.dolgolyov |
|
||||||
| [f4be2bd](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/f4be2bd) | fix(player): wire accent picker to editorial copper palette + visual polish | alexei.dolgolyov |
|
| [e7372b0](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/e7372b0) | chore: wire up code-review-graph MCP server | alexei.dolgolyov |
|
||||||
| [51ec150](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/51ec150) | perf(visualizer): cut spectrum + track-switch CPU significantly | alexei.dolgolyov |
|
|
||||||
| [08c3c80](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/08c3c80) | ci: skip test workflow on release commits | alexei.dolgolyov |
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -2768,36 +2768,6 @@ button.primary svg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
footer {
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
transition: padding-bottom 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mini-player-visible footer {
|
|
||||||
padding-bottom: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
color: var(--accent);
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a:hover {
|
|
||||||
color: var(--accent-hover);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer .separator {
|
|
||||||
margin: 0 0.5rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
Media Browser Styles
|
Media Browser Styles
|
||||||
======================================== */
|
======================================== */
|
||||||
@@ -3926,14 +3896,6 @@ html {
|
|||||||
padding-right: max(1rem, env(safe-area-inset-right));
|
padding-right: max(1rem, env(safe-area-inset-right));
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
|
||||||
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mini-player-visible footer {
|
|
||||||
padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-banner {
|
.connection-banner {
|
||||||
padding-top: max(10px, env(safe-area-inset-top));
|
padding-top: max(10px, env(safe-area-inset-top));
|
||||||
}
|
}
|
||||||
@@ -3991,13 +3953,17 @@ body.mini-player-visible footer {
|
|||||||
════════════════════════════════════════════════════════════════ */
|
════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
/* ─── Container & header ────────────────────────────────────── */
|
/* ─── Container & header ────────────────────────────────────── */
|
||||||
|
/* The footer was removed in favour of an About dialog, so the page
|
||||||
|
bottom is now whatever the active tab content ends with. A 64px
|
||||||
|
bottom pad left a visible dead band under the player view; 24px
|
||||||
|
keeps a breath of breathing room without painting an empty page. */
|
||||||
.container {
|
.container {
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
padding: 56px 48px 64px;
|
padding: 56px 48px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.container { padding: 48px 18px 56px; }
|
.container { padding: 48px 18px 24px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Folio marks (page corners, all tabs) ────────────────── */
|
/* ─── Folio marks (page corners, all tabs) ────────────────── */
|
||||||
@@ -4450,9 +4416,15 @@ header .brand-sub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Vinyl stage ──────────────────────────────────────────── */
|
/* ─── Vinyl stage ──────────────────────────────────────────── */
|
||||||
|
/* Aspect-ratio is intentionally wider than tall: the sleeve+disc
|
||||||
|
composition only fills the top ~82% of a square; a strict 1:1 stage
|
||||||
|
left an ~18% empty band below the disc and forced the grid row
|
||||||
|
taller than the masthead column, painting a large dead gap at the
|
||||||
|
bottom of the page. 1:0.85 trims that band while keeping the disc
|
||||||
|
(bottom anchor at top:19.4% + 63% = 82.4% of height) safely inside. */
|
||||||
.album-art-container.vinyl-stage {
|
.album-art-container.vinyl-stage {
|
||||||
position: relative;
|
position: relative;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1 / 0.85;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -4589,7 +4561,7 @@ header .brand-sub {
|
|||||||
top: 26%;
|
top: 26%;
|
||||||
right: -6%;
|
right: -6%;
|
||||||
width: 36%;
|
width: 36%;
|
||||||
height: 36%;
|
aspect-ratio: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transform-origin: 88% 12%;
|
transform-origin: 88% 12%;
|
||||||
transform: rotate(-22deg);
|
transform: rotate(-22deg);
|
||||||
@@ -6506,38 +6478,60 @@ dialog::backdrop {
|
|||||||
.toast.info { border-color: var(--rule-strong); box-shadow: 0 14px 40px rgba(0,0,0,0.5); }
|
.toast.info { border-color: var(--rule-strong); box-shadow: 0 14px 40px rgba(0,0,0,0.5); }
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════
|
||||||
FOOTER (colophon)
|
ABOUT DIALOG (colophon)
|
||||||
═══════════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════════ */
|
||||||
footer {
|
.about-dialog {
|
||||||
margin-top: 36px;
|
max-width: 520px;
|
||||||
padding: 20px 0 0;
|
}
|
||||||
border-top: 1px solid var(--rule-strong);
|
.about-credit {
|
||||||
background: transparent;
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
margin: 0 0 22px;
|
||||||
|
font-variation-settings: 'opsz' 30;
|
||||||
|
}
|
||||||
|
.about-credit strong {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--ink);
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
font-variation-settings: 'opsz' 60;
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.about-links {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-top: 1px solid var(--rule);
|
||||||
|
}
|
||||||
|
.about-links li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 14px 0;
|
||||||
|
border-bottom: 1px solid var(--rule);
|
||||||
|
}
|
||||||
|
.about-links-label {
|
||||||
font-family: var(--mono);
|
font-family: var(--mono);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
letter-spacing: 0.16em;
|
letter-spacing: 0.16em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--ink-faint);
|
color: var(--ink-faint);
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
footer a {
|
.about-links a {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 12px;
|
||||||
color: var(--copper);
|
color: var(--copper);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
transition: border-color 200ms var(--ease);
|
transition: border-color 200ms var(--ease);
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
footer a:hover { border-bottom-color: var(--copper); }
|
.about-links a:hover { border-bottom-color: var(--copper); }
|
||||||
footer strong {
|
|
||||||
font-family: var(--serif);
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--ink-soft);
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
text-transform: none;
|
|
||||||
font-variation-settings: 'opsz' 30;
|
|
||||||
}
|
|
||||||
footer .separator { color: var(--ink-ghost); margin: 0 8px; }
|
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════
|
||||||
DISPLAY container (monitors tab)
|
DISPLAY container (monitors tab)
|
||||||
@@ -6597,7 +6591,6 @@ footer .separator { color: var(--ink-ghost); margin: 0 8px; }
|
|||||||
.auth-modal h2 { font-size: 28px; }
|
.auth-modal h2 { font-size: 28px; }
|
||||||
.settings-section { padding: 20px; }
|
.settings-section { padding: 20px; }
|
||||||
.settings-section summary { font-size: 22px; }
|
.settings-section summary { font-size: 22px; }
|
||||||
footer { font-size: 9px; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ════════════════════════════════════════════════════════════════
|
/* ════════════════════════════════════════════════════════════════
|
||||||
@@ -8707,14 +8700,6 @@ select option {
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Footer: ultra-compact ───────────────────────────── */
|
|
||||||
footer {
|
|
||||||
font-size: 10px !important;
|
|
||||||
padding: 12px 12px !important;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
}
|
|
||||||
footer .separator { margin: 0 4px !important; }
|
|
||||||
|
|
||||||
/* Auth modal: full-bleed feel on phones */
|
/* Auth modal: full-bleed feel on phones */
|
||||||
.auth-modal {
|
.auth-modal {
|
||||||
width: 92% !important;
|
width: 92% !important;
|
||||||
|
|||||||
@@ -91,6 +91,9 @@
|
|||||||
<a class="header-btn" href="/docs" target="_blank" title="API Documentation" aria-label="API Documentation">
|
<a class="header-btn" href="/docs" target="_blank" title="API Documentation" aria-label="API Documentation">
|
||||||
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 2l5 5h-5V4zM6 20V4h5v7h7v9H6zm2-4h8v2H8v-2zm0-3h8v2H8v-2z"/></svg>
|
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 2l5 5h-5V4zM6 20V4h5v7h7v9H6zm2-4h8v2H8v-2zm0-3h8v2H8v-2z"/></svg>
|
||||||
</a>
|
</a>
|
||||||
|
<button class="header-btn" onclick="showAboutDialog()" data-i18n-title="about.button_title" title="About" aria-label="About">
|
||||||
|
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M11 7h2v2h-2V7zm0 4h2v6h-2v-6zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
|
||||||
|
</button>
|
||||||
<div class="accent-picker">
|
<div class="accent-picker">
|
||||||
<button class="header-btn" onclick="toggleAccentPicker()" title="Accent color" aria-label="Accent color">
|
<button class="header-btn" onclick="toggleAccentPicker()" title="Accent color" aria-label="Accent color">
|
||||||
<span class="accent-dot" id="accentDot"></span>
|
<span class="accent-dot" id="accentDot"></span>
|
||||||
@@ -788,16 +791,31 @@
|
|||||||
<!-- Toast Notifications -->
|
<!-- Toast Notifications -->
|
||||||
<div class="toast-container" id="toast-container"></div>
|
<div class="toast-container" id="toast-container"></div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- About Dialog -->
|
||||||
<footer>
|
<dialog id="aboutDialog" class="about-dialog">
|
||||||
<div>
|
<div class="dialog-header">
|
||||||
<span data-i18n="footer.created_by">Created by</span> <strong>Alexei Dolgolyov</strong>
|
<h3 data-i18n="about.title">About</h3>
|
||||||
<span class="separator">•</span>
|
|
||||||
<a href="mailto:dolgolyov.alexei@gmail.com">dolgolyov.alexei@gmail.com</a>
|
|
||||||
<span class="separator">•</span>
|
|
||||||
<a href="https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server" target="_blank" rel="noopener noreferrer" data-i18n="footer.source_code">Source Code</a>
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
<div class="dialog-body">
|
||||||
|
<p class="about-credit">
|
||||||
|
<span data-i18n="about.created_by">Created by</span>
|
||||||
|
<strong>Alexei Dolgolyov</strong>
|
||||||
|
</p>
|
||||||
|
<ul class="about-links">
|
||||||
|
<li>
|
||||||
|
<span class="about-links-label" data-i18n="about.email">Email</span>
|
||||||
|
<a href="mailto:dolgolyov.alexei@gmail.com">dolgolyov.alexei@gmail.com</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="about-links-label" data-i18n="about.repository">Repository</span>
|
||||||
|
<a href="https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server" target="_blank" rel="noopener noreferrer" data-i18n="about.source_code">Source Code</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button type="button" class="btn-secondary" onclick="closeAboutDialog()" data-i18n="dialog.close">Close</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<script src="/static/dist/app.bundle.js"></script>
|
<script src="/static/dist/app.bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
VOLUME_THROTTLE_MS, VOLUME_RELEASE_DELAY_MS,
|
VOLUME_THROTTLE_MS, VOLUME_RELEASE_DELAY_MS,
|
||||||
changeLocale, t,
|
changeLocale, t,
|
||||||
setAuthRequired,
|
setAuthRequired,
|
||||||
|
showAboutDialog, closeAboutDialog,
|
||||||
} from './core.js';
|
} from './core.js';
|
||||||
|
|
||||||
// Layer 1: Player (tabs, theme, accent, vinyl, visualizer, UI)
|
// Layer 1: Player (tabs, theme, accent, vinyl, visualizer, UI)
|
||||||
@@ -129,6 +130,8 @@ Object.assign(window, {
|
|||||||
toggleDisplayPower,
|
toggleDisplayPower,
|
||||||
// Audio device
|
// Audio device
|
||||||
onAudioDeviceChanged,
|
onAudioDeviceChanged,
|
||||||
|
// About
|
||||||
|
showAboutDialog, closeAboutDialog,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -399,6 +402,16 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// About dialog backdrop click to close
|
||||||
|
const aboutDialog = document.getElementById('aboutDialog');
|
||||||
|
if (aboutDialog) {
|
||||||
|
aboutDialog.addEventListener('click', (e) => {
|
||||||
|
if (e.target === aboutDialog) {
|
||||||
|
closeAboutDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Delegated click handlers for link table actions (XSS-safe)
|
// Delegated click handlers for link table actions (XSS-safe)
|
||||||
document.getElementById('linksTableBody').addEventListener('click', (e) => {
|
document.getElementById('linksTableBody').addEventListener('click', (e) => {
|
||||||
const btn = e.target.closest('[data-action]');
|
const btn = e.target.closest('[data-action]');
|
||||||
|
|||||||
@@ -397,6 +397,16 @@ export function closeDialog(dialog) {
|
|||||||
}, { once: true });
|
}, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showAboutDialog() {
|
||||||
|
const dialog = document.getElementById('aboutDialog');
|
||||||
|
if (dialog) dialog.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeAboutDialog() {
|
||||||
|
const dialog = document.getElementById('aboutDialog');
|
||||||
|
if (dialog) closeDialog(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
export function showConfirm(message) {
|
export function showConfirm(message) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const dialog = document.getElementById('confirmDialog');
|
const dialog = document.getElementById('confirmDialog');
|
||||||
|
|||||||
@@ -259,8 +259,13 @@
|
|||||||
"links.msg.load_failed": "Failed to load link details",
|
"links.msg.load_failed": "Failed to load link details",
|
||||||
"links.confirm.delete": "Are you sure you want to delete the link \"{name}\"?",
|
"links.confirm.delete": "Are you sure you want to delete the link \"{name}\"?",
|
||||||
"links.confirm.unsaved": "You have unsaved changes. Are you sure you want to discard them?",
|
"links.confirm.unsaved": "You have unsaved changes. Are you sure you want to discard them?",
|
||||||
"footer.created_by": "Created by",
|
"about.button_title": "About",
|
||||||
"footer.source_code": "Source Code",
|
"about.title": "About",
|
||||||
|
"about.created_by": "Created by",
|
||||||
|
"about.email": "Email",
|
||||||
|
"about.repository": "Repository",
|
||||||
|
"about.source_code": "Source Code",
|
||||||
|
"dialog.close": "Close",
|
||||||
"update.available": "Update available: v{version}",
|
"update.available": "Update available: v{version}",
|
||||||
"update.view_release": "View Release"
|
"update.view_release": "View Release"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,8 +259,13 @@
|
|||||||
"links.msg.load_failed": "Не удалось загрузить данные ссылки",
|
"links.msg.load_failed": "Не удалось загрузить данные ссылки",
|
||||||
"links.confirm.delete": "Вы уверены, что хотите удалить ссылку \"{name}\"?",
|
"links.confirm.delete": "Вы уверены, что хотите удалить ссылку \"{name}\"?",
|
||||||
"links.confirm.unsaved": "У вас есть несохраненные изменения. Вы уверены, что хотите отменить их?",
|
"links.confirm.unsaved": "У вас есть несохраненные изменения. Вы уверены, что хотите отменить их?",
|
||||||
"footer.created_by": "Создано",
|
"about.button_title": "О программе",
|
||||||
"footer.source_code": "Исходный код",
|
"about.title": "О программе",
|
||||||
|
"about.created_by": "Создано",
|
||||||
|
"about.email": "Эл. почта",
|
||||||
|
"about.repository": "Репозиторий",
|
||||||
|
"about.source_code": "Исходный код",
|
||||||
|
"dialog.close": "Закрыть",
|
||||||
"update.available": "Доступно обновление: v{version}",
|
"update.available": "Доступно обновление: v{version}",
|
||||||
"update.view_release": "Перейти к релизу"
|
"update.view_release": "Перейти к релизу"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,911 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vinyl Variants · Studio Reference</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/static/icons/icon.svg">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* ───────── Local fonts (re-using main app's woff2 files) ───── */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fraunces';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/static/fonts/Fraunces-italic-latin.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fraunces';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/static/fonts/Fraunces-latin.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Geist';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/static/fonts/Geist-latin.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/static/fonts/GeistMono-latin.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───────── Tokens (Studio Reference, dark) ───── */
|
||||||
|
:root {
|
||||||
|
--bg-deep: #0E0D0B;
|
||||||
|
--bg-paper: #18150F;
|
||||||
|
--bg-card: #211E18;
|
||||||
|
--bg-card-2: #26211A;
|
||||||
|
--bg-rule: #2E2820;
|
||||||
|
--ink: #F2EBDC;
|
||||||
|
--ink-soft: #D6CDB9;
|
||||||
|
--ink-mute: #9C937F;
|
||||||
|
--ink-faint: #5C5447;
|
||||||
|
--ink-ghost: #3A3528;
|
||||||
|
--copper: #E08038;
|
||||||
|
--copper-hi: #F4A064;
|
||||||
|
--copper-lo: #B0561F;
|
||||||
|
--copper-glow: rgba(224, 128, 56, 0.35);
|
||||||
|
--rule: rgba(242, 235, 220, 0.08);
|
||||||
|
--rule-strong: rgba(242, 235, 220, 0.18);
|
||||||
|
--serif: 'Fraunces', Georgia, serif;
|
||||||
|
--sans: 'Geist', system-ui, sans-serif;
|
||||||
|
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||||
|
--ease: cubic-bezier(.2, .7, .2, 1);
|
||||||
|
--ease-out: cubic-bezier(.16, 1, .3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
html { background: var(--bg-deep); }
|
||||||
|
body {
|
||||||
|
font-family: var(--sans);
|
||||||
|
background: var(--bg-deep);
|
||||||
|
color: var(--ink);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 56px 36px 80px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Film grain */
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
opacity: 0.05;
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.95 0 0 0 0 0.92 0 0 0 0 0.86 0 0 0 0.7 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───────── Page header (editorial) ───── */
|
||||||
|
header.page-head {
|
||||||
|
max-width: 1320px;
|
||||||
|
margin: 0 auto 48px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.kicker {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.32em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--copper);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
.kicker::before, .kicker::after {
|
||||||
|
content: "";
|
||||||
|
height: 1px;
|
||||||
|
width: 40px;
|
||||||
|
background: var(--copper);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: clamp(36px, 5vw, 56px);
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-variation-settings: 'opsz' 144;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-mute);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.return-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 24px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid var(--ink-faint);
|
||||||
|
padding-bottom: 2px;
|
||||||
|
transition: all 200ms var(--ease);
|
||||||
|
}
|
||||||
|
.return-link:hover { color: var(--copper); border-color: var(--copper); }
|
||||||
|
|
||||||
|
/* ───────── Variant grid ───── */
|
||||||
|
.grid {
|
||||||
|
max-width: 1320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||||
|
gap: 56px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.variant {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
width: 100%;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at center, var(--bg-card-2) 0%, var(--bg-deep) 80%);
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: visible;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px solid var(--rule);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.label-num {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: var(--copper);
|
||||||
|
}
|
||||||
|
.label-name {
|
||||||
|
font-family: var(--serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-variation-settings: 'opsz' 60;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.label-tag {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
padding: 3px 8px;
|
||||||
|
border: 1px solid var(--rule-strong);
|
||||||
|
}
|
||||||
|
.tag-css { color: var(--jade, #7AB294); border-color: rgba(122, 178, 148, 0.3); }
|
||||||
|
.tag-needs-js { color: var(--copper); border-color: var(--copper-lo); }
|
||||||
|
|
||||||
|
p.descr {
|
||||||
|
font-family: var(--sans);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
}
|
||||||
|
p.descr strong {
|
||||||
|
color: var(--ink);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───────── Shared vinyl base ───── */
|
||||||
|
.vinyl {
|
||||||
|
position: relative;
|
||||||
|
width: 86%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 50%,
|
||||||
|
#0a0907 0%, #0a0907 18%,
|
||||||
|
#1a1611 18.3%, #0a0907 18.6%,
|
||||||
|
#14110c 22%, #0a0907 22.3%,
|
||||||
|
#14110c 26%, #0a0907 26.3%,
|
||||||
|
#14110c 30%, #0a0907 30.3%,
|
||||||
|
#14110c 34%, #0a0907 34.3%,
|
||||||
|
#14110c 38%, #0a0907 38.3%,
|
||||||
|
#14110c 42%, #0a0907 42.3%,
|
||||||
|
#14110c 46%, #0a0907 46.3%,
|
||||||
|
#1c1812 47%, #0a0907 100%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 60px rgba(0, 0, 0, 0.7),
|
||||||
|
0 30px 80px rgba(0, 0, 0, 0.6),
|
||||||
|
0 6px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
animation: spin 14s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.vinyl::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 12%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
conic-gradient(from 0deg,
|
||||||
|
rgba(255,255,255,0.04) 0deg,
|
||||||
|
transparent 30deg,
|
||||||
|
rgba(255,255,255,0.06) 90deg,
|
||||||
|
transparent 150deg,
|
||||||
|
rgba(255,255,255,0.03) 210deg,
|
||||||
|
transparent 270deg,
|
||||||
|
rgba(255,255,255,0.05) 330deg,
|
||||||
|
transparent 360deg);
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vinyl-label {
|
||||||
|
position: absolute;
|
||||||
|
inset: 28%;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 24px rgba(0, 0, 0, 0.4),
|
||||||
|
0 0 0 4px var(--bg-deep),
|
||||||
|
0 0 0 5px var(--copper-lo);
|
||||||
|
background: var(--bg-card);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.vinyl-label::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 8%; height: 8%;
|
||||||
|
top: 46%; left: 46%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.1);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.vinyl-label img,
|
||||||
|
.vinyl-label svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Album art (shared SVG used by every variant) */
|
||||||
|
.album-art {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tonearm (decorative, on every stage so they read as "now playing") */
|
||||||
|
.tonearm {
|
||||||
|
position: absolute;
|
||||||
|
top: -4%;
|
||||||
|
right: -2%;
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
transform-origin: 88% 12%;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
z-index: 5;
|
||||||
|
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ════════════════════════════════════════════════════════════════
|
||||||
|
ORIGINAL — current shipping look (control)
|
||||||
|
════════════════════════════════════════════════════════════════ */
|
||||||
|
.v0 .stage { /* nothing extra */ }
|
||||||
|
|
||||||
|
/* ════════════════════════════════════════════════════════════════
|
||||||
|
VARIANT 1 — Sleeve frame
|
||||||
|
Vinyl peeks out of a square cardstock sleeve.
|
||||||
|
════════════════════════════════════════════════════════════════ */
|
||||||
|
.v1 .stage {
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at center, #1a1611 0%, var(--bg-deep) 80%);
|
||||||
|
}
|
||||||
|
.v1 .sleeve-stage {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.v1 .sleeve {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 6%;
|
||||||
|
width: 70%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background: var(--bg-card-2);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(0,0,0,0.4),
|
||||||
|
inset 4px 4px 24px rgba(0,0,0,0.35),
|
||||||
|
-2px 8px 24px rgba(0,0,0,0.5),
|
||||||
|
-4px 16px 40px rgba(0,0,0,0.35);
|
||||||
|
z-index: 3;
|
||||||
|
/* Casually-placed tilt — like a sleeve set down on a console */
|
||||||
|
transform: rotate(-3.2deg);
|
||||||
|
transform-origin: 60% 60%;
|
||||||
|
/* worn-edge cardstock effect */
|
||||||
|
filter: contrast(1.05) brightness(0.97);
|
||||||
|
}
|
||||||
|
.v1 .sleeve::before {
|
||||||
|
/* Cardstock paper grain */
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.08 0 0 0 0 0.06 0 0 0 0.7 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.v1 .sleeve::after {
|
||||||
|
/* Ring-wear: faint circle from the LP rubbing the cardstock */
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 6%;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(0,0,0,0.25);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 12px rgba(0,0,0,0.18),
|
||||||
|
inset 0 0 0 1px rgba(255,255,255,0.04);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.v1 .sleeve-art {
|
||||||
|
position: absolute;
|
||||||
|
inset: 6%;
|
||||||
|
z-index: 1;
|
||||||
|
filter: contrast(0.88) saturate(0.6) brightness(0.88);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.v1 .sleeve-art svg { width: 100%; height: 100%; }
|
||||||
|
/* Worn corner notch */
|
||||||
|
.v1 .sleeve-corner {
|
||||||
|
position: absolute;
|
||||||
|
width: 14%;
|
||||||
|
height: 14%;
|
||||||
|
bottom: -1px;
|
||||||
|
right: -1px;
|
||||||
|
background: var(--bg-deep);
|
||||||
|
clip-path: polygon(100% 0, 100% 100%, 0 100%);
|
||||||
|
opacity: 0.7;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.v1 .vinyl-wrap {
|
||||||
|
position: absolute;
|
||||||
|
right: -2%;
|
||||||
|
top: 16%;
|
||||||
|
width: 70%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.v1 .vinyl-wrap .vinyl {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.v1 .vinyl-label {
|
||||||
|
/* Smaller label since the disc here is showing; album art lives on sleeve */
|
||||||
|
inset: 32%;
|
||||||
|
background: #2E2820;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 18px rgba(0,0,0,0.4),
|
||||||
|
0 0 0 3px var(--bg-deep),
|
||||||
|
0 0 0 4px var(--copper-lo);
|
||||||
|
}
|
||||||
|
.v1 .vinyl-label::before {
|
||||||
|
/* Plain-color label with faux pressing imprint */
|
||||||
|
content: "REF · 24";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
color: var(--copper);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.v1 .tonearm {
|
||||||
|
right: -8%;
|
||||||
|
top: 8%;
|
||||||
|
width: 44%;
|
||||||
|
height: 44%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ════════════════════════════════════════════════════════════════
|
||||||
|
VARIANT 2 — Sheen + paper grain + dead-wax + off-center
|
||||||
|
The high-impact variant.
|
||||||
|
════════════════════════════════════════════════════════════════ */
|
||||||
|
.v2 .vinyl-label {
|
||||||
|
/* Slightly off-center spindle for "pressed off-axis" feel */
|
||||||
|
inset: 27% 27% 29% 29%;
|
||||||
|
}
|
||||||
|
.v2 .vinyl-label::after {
|
||||||
|
/* Spindle hole offset 1.5% from true center */
|
||||||
|
top: 47%;
|
||||||
|
left: 47.5%;
|
||||||
|
}
|
||||||
|
/* Paper grain on the label, multiplied so it sits inside the print */
|
||||||
|
.v2 .vinyl-label .label-grain {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.6' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.05 0 0 0 0 0.04 0 0 0 0 0.03 0 0 0 0.55 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
/* Dead-wax: micro-text engraved between the label and the run-out groove */
|
||||||
|
.v2 .dead-wax {
|
||||||
|
position: absolute;
|
||||||
|
inset: 21%;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
/* Animation OFF the disc — engraving is part of the press, so it does spin with the vinyl */
|
||||||
|
animation: spin 14s linear infinite;
|
||||||
|
}
|
||||||
|
.v2 .dead-wax svg { width: 100%; height: 100%; }
|
||||||
|
/* Reflection sweep — fixed in viewer space, not rotating with the disc */
|
||||||
|
.v2 .sheen {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
conic-gradient(from 110deg,
|
||||||
|
transparent 0deg,
|
||||||
|
rgba(255, 245, 220, 0) 30deg,
|
||||||
|
rgba(255, 245, 220, 0.07) 60deg,
|
||||||
|
rgba(255, 245, 220, 0.14) 80deg,
|
||||||
|
rgba(255, 245, 220, 0.07) 100deg,
|
||||||
|
transparent 140deg,
|
||||||
|
transparent 280deg,
|
||||||
|
rgba(255, 245, 220, 0.04) 305deg,
|
||||||
|
rgba(255, 245, 220, 0.08) 320deg,
|
||||||
|
rgba(255, 245, 220, 0.04) 335deg,
|
||||||
|
transparent 360deg);
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ════════════════════════════════════════════════════════════════
|
||||||
|
VARIANT 3 — Tone-graded album art (duotone)
|
||||||
|
════════════════════════════════════════════════════════════════ */
|
||||||
|
.v3 .vinyl-label .album-art {
|
||||||
|
filter:
|
||||||
|
saturate(0.35)
|
||||||
|
sepia(0.45)
|
||||||
|
hue-rotate(345deg)
|
||||||
|
brightness(0.85)
|
||||||
|
contrast(1.18);
|
||||||
|
}
|
||||||
|
.v3 .vinyl-label::before {
|
||||||
|
/* Subtle copper duotone overlay tints the highlights */
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg,
|
||||||
|
rgba(224, 128, 56, 0.18) 0%,
|
||||||
|
rgba(31, 78, 61, 0.10) 50%,
|
||||||
|
rgba(0,0,0,0.18) 100%);
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.v3 .vinyl-label::after {
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.v3 .vinyl-label .vignette {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at 50% 45%,
|
||||||
|
transparent 35%,
|
||||||
|
rgba(0,0,0,0.45) 100%);
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ════════════════════════════════════════════════════════════════
|
||||||
|
VARIANT 4 — Sleeve-to-disc reveal animation
|
||||||
|
(Hover the card to see the disc slide out)
|
||||||
|
════════════════════════════════════════════════════════════════ */
|
||||||
|
.v4 .sleeve-stage {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.v4 .sleeve {
|
||||||
|
position: absolute;
|
||||||
|
left: 14%;
|
||||||
|
top: 12%;
|
||||||
|
width: 72%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background: var(--bg-card-2);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(0,0,0,0.4),
|
||||||
|
-2px 6px 18px rgba(0,0,0,0.5);
|
||||||
|
z-index: 4;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.v4 .sleeve::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.08 0 0 0 0 0.06 0 0 0 0.5 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.v4 .sleeve-art {
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
filter: contrast(0.92) saturate(0.7) brightness(0.92);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.v4 .vinyl-slot {
|
||||||
|
position: absolute;
|
||||||
|
left: 14%;
|
||||||
|
top: 12%;
|
||||||
|
width: 72%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
z-index: 3;
|
||||||
|
transition: transform 1.2s var(--ease-out);
|
||||||
|
}
|
||||||
|
.v4 .vinyl-slot .vinyl {
|
||||||
|
width: 100%;
|
||||||
|
animation-play-state: paused;
|
||||||
|
transition: animation-play-state 0.4s;
|
||||||
|
}
|
||||||
|
.v4 .stage:hover .vinyl-slot {
|
||||||
|
transform: translateX(46%);
|
||||||
|
}
|
||||||
|
.v4 .stage:hover .vinyl-slot .vinyl {
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
.v4 .hover-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 12px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
|
letter-spacing: 0.24em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.v4 .stage:hover .hover-hint { opacity: 0.4; }
|
||||||
|
|
||||||
|
/* Note row at top of every variant */
|
||||||
|
.note {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 14px;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 9px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───────── Mobile ───── */
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
body { padding: 36px 16px 60px; }
|
||||||
|
.grid { gap: 36px 20px; grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="page-head">
|
||||||
|
<div class="kicker">Studio Reference · Album Art Variants</div>
|
||||||
|
<h1>Vinyl Cover Treatments</h1>
|
||||||
|
<p class="subtitle">Five renderings of the same disc · Hover variant 04 for the sleeve reveal</p>
|
||||||
|
<a class="return-link" href="/">← Return to player</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
|
||||||
|
<!-- ═════════ ORIGINAL ═════════ -->
|
||||||
|
<article class="variant v0">
|
||||||
|
<div class="stage">
|
||||||
|
<span class="note">As shipping</span>
|
||||||
|
<div class="vinyl">
|
||||||
|
<div class="vinyl-label">
|
||||||
|
<svg viewBox="0 0 400 400" class="album-art" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgA" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#1F4E3D"/>
|
||||||
|
<stop offset="55%" stop-color="#143E2F"/>
|
||||||
|
<stop offset="100%" stop-color="#0a2519"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="vigA" cx="50%" cy="50%" r="70%">
|
||||||
|
<stop offset="55%" stop-color="rgba(0,0,0,0)"/>
|
||||||
|
<stop offset="100%" stop-color="rgba(0,0,0,0.55)"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="400" height="400" fill="url(#bgA)"/>
|
||||||
|
<g stroke="#e08038" stroke-width="1" fill="none" opacity="0.55">
|
||||||
|
<circle cx="200" cy="200" r="60"/>
|
||||||
|
<circle cx="200" cy="200" r="100"/>
|
||||||
|
<circle cx="200" cy="200" r="140"/>
|
||||||
|
<circle cx="200" cy="200" r="180"/>
|
||||||
|
</g>
|
||||||
|
<text x="200" y="195" text-anchor="middle" font-family="serif" font-style="italic" fill="#f2ebdc" font-size="34">Reference</text>
|
||||||
|
<text x="200" y="225" text-anchor="middle" font-family="monospace" fill="#e08038" font-size="11" letter-spacing="4">VOL · I</text>
|
||||||
|
<text x="200" y="368" text-anchor="middle" font-family="monospace" fill="#9c937f" font-size="9" letter-spacing="6" opacity="0.6">STUDIO PRESSING</text>
|
||||||
|
<rect width="400" height="400" fill="url(#vigA)"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<svg class="tonearm" viewBox="0 0 200 200" aria-hidden="true">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="armGrad0" x1="0" x2="1">
|
||||||
|
<stop offset="0" stop-color="#3a3528"/>
|
||||||
|
<stop offset="0.5" stop-color="#9C937F"/>
|
||||||
|
<stop offset="1" stop-color="#5C5447"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<circle cx="176" cy="24" r="14" fill="#1a1611" stroke="#3A3528" stroke-width="1"/>
|
||||||
|
<circle cx="176" cy="24" r="6" fill="#3A3528"/>
|
||||||
|
<circle cx="176" cy="24" r="2" fill="#E08038"/>
|
||||||
|
<line x1="176" y1="24" x2="64" y2="136" stroke="url(#armGrad0)" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
<rect x="180" y="14" width="14" height="20" fill="#26211A" stroke="#3A3528"/>
|
||||||
|
<rect x="56" y="128" width="22" height="18" rx="2" fill="#26211A" stroke="#3A3528" transform="rotate(-45 67 137)"/>
|
||||||
|
<circle cx="62" cy="138" r="3" fill="#E08038" opacity="0.8"/>
|
||||||
|
<circle cx="62" cy="138" r="6" fill="none" stroke="#E08038" stroke-width="0.5" opacity="0.4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label-row">
|
||||||
|
<span class="label-num">00</span>
|
||||||
|
<span class="label-name">Original</span>
|
||||||
|
<span class="label-tag tag-css">control</span>
|
||||||
|
</div>
|
||||||
|
<p class="descr">Current shipping vinyl: pressed grooves, copper-bordered label rim, full album art on the label. Reference baseline for everything below.</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- ═════════ VARIANT 1 — SLEEVE FRAME ═════════ -->
|
||||||
|
<article class="variant v1">
|
||||||
|
<div class="stage">
|
||||||
|
<span class="note">CSS only</span>
|
||||||
|
<div class="sleeve-stage">
|
||||||
|
<div class="sleeve">
|
||||||
|
<div class="sleeve-art">
|
||||||
|
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgB" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#1F4E3D"/>
|
||||||
|
<stop offset="55%" stop-color="#143E2F"/>
|
||||||
|
<stop offset="100%" stop-color="#0a2519"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="400" height="400" fill="url(#bgB)"/>
|
||||||
|
<g stroke="#e08038" stroke-width="1" fill="none" opacity="0.55">
|
||||||
|
<circle cx="200" cy="200" r="60"/>
|
||||||
|
<circle cx="200" cy="200" r="100"/>
|
||||||
|
<circle cx="200" cy="200" r="140"/>
|
||||||
|
<circle cx="200" cy="200" r="180"/>
|
||||||
|
</g>
|
||||||
|
<text x="200" y="195" text-anchor="middle" font-family="serif" font-style="italic" fill="#f2ebdc" font-size="34">Reference</text>
|
||||||
|
<text x="200" y="225" text-anchor="middle" font-family="monospace" fill="#e08038" font-size="11" letter-spacing="4">VOL · I</text>
|
||||||
|
<text x="200" y="368" text-anchor="middle" font-family="monospace" fill="#9c937f" font-size="9" letter-spacing="6" opacity="0.6">STUDIO PRESSING</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="sleeve-corner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="vinyl-wrap">
|
||||||
|
<div class="vinyl">
|
||||||
|
<div class="vinyl-label"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<svg class="tonearm" viewBox="0 0 200 200" aria-hidden="true">
|
||||||
|
<use href="#armGrad0"/>
|
||||||
|
<circle cx="176" cy="24" r="14" fill="#1a1611" stroke="#3A3528" stroke-width="1"/>
|
||||||
|
<circle cx="176" cy="24" r="6" fill="#3A3528"/>
|
||||||
|
<circle cx="176" cy="24" r="2" fill="#E08038"/>
|
||||||
|
<line x1="176" y1="24" x2="80" y2="120" stroke="#9C937F" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
<rect x="180" y="14" width="14" height="20" fill="#26211A" stroke="#3A3528"/>
|
||||||
|
<rect x="72" y="112" width="22" height="18" rx="2" fill="#26211A" stroke="#3A3528" transform="rotate(-45 83 121)"/>
|
||||||
|
<circle cx="78" cy="122" r="3" fill="#E08038" opacity="0.8"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label-row">
|
||||||
|
<span class="label-num">01</span>
|
||||||
|
<span class="label-name">Sleeve Frame</span>
|
||||||
|
<span class="label-tag tag-css">CSS only</span>
|
||||||
|
</div>
|
||||||
|
<p class="descr">Vinyl peeks out of a square cardstock <strong>sleeve</strong> — paper grain, ring-wear circle, worn-corner notch. The album art lives on the sleeve; the disc gets a plain typographic label. Reads instantly as "record on a turntable", not "spinning disc."</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- ═════════ VARIANT 2 — SHEEN + GRAIN + DEAD-WAX ═════════ -->
|
||||||
|
<article class="variant v2">
|
||||||
|
<div class="stage">
|
||||||
|
<span class="note">CSS only · highest ROI</span>
|
||||||
|
<div class="vinyl">
|
||||||
|
<div class="dead-wax">
|
||||||
|
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<path id="dwPath" d="M 50,50 m -36,0 a 36,36 0 1,1 72,0 a 36,36 0 1,1 -72,0"/>
|
||||||
|
</defs>
|
||||||
|
<text font-family="monospace" font-size="2.4" fill="#3a3528" letter-spacing="0.45" opacity="0.85">
|
||||||
|
<textPath href="#dwPath">· STUDIO REFERENCE PRESSING · A-SIDE · MASTER LACQUER 24-S · DOLG.AD MASTERED · ½ SPEED</textPath>
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="vinyl-label">
|
||||||
|
<svg viewBox="0 0 400 400" class="album-art" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgC" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#1F4E3D"/>
|
||||||
|
<stop offset="55%" stop-color="#143E2F"/>
|
||||||
|
<stop offset="100%" stop-color="#0a2519"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="400" height="400" fill="url(#bgC)"/>
|
||||||
|
<g stroke="#e08038" stroke-width="1" fill="none" opacity="0.55">
|
||||||
|
<circle cx="200" cy="200" r="60"/>
|
||||||
|
<circle cx="200" cy="200" r="100"/>
|
||||||
|
<circle cx="200" cy="200" r="140"/>
|
||||||
|
<circle cx="200" cy="200" r="180"/>
|
||||||
|
</g>
|
||||||
|
<text x="200" y="195" text-anchor="middle" font-family="serif" font-style="italic" fill="#f2ebdc" font-size="34">Reference</text>
|
||||||
|
<text x="200" y="225" text-anchor="middle" font-family="monospace" fill="#e08038" font-size="11" letter-spacing="4">VOL · I</text>
|
||||||
|
<text x="200" y="368" text-anchor="middle" font-family="monospace" fill="#9c937f" font-size="9" letter-spacing="6" opacity="0.6">STUDIO PRESSING</text>
|
||||||
|
</svg>
|
||||||
|
<div class="label-grain"></div>
|
||||||
|
</div>
|
||||||
|
<div class="sheen"></div>
|
||||||
|
</div>
|
||||||
|
<svg class="tonearm" viewBox="0 0 200 200" aria-hidden="true">
|
||||||
|
<circle cx="176" cy="24" r="14" fill="#1a1611" stroke="#3A3528" stroke-width="1"/>
|
||||||
|
<circle cx="176" cy="24" r="6" fill="#3A3528"/>
|
||||||
|
<circle cx="176" cy="24" r="2" fill="#E08038"/>
|
||||||
|
<line x1="176" y1="24" x2="64" y2="136" stroke="#9C937F" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
<rect x="180" y="14" width="14" height="20" fill="#26211A" stroke="#3A3528"/>
|
||||||
|
<rect x="56" y="128" width="22" height="18" rx="2" fill="#26211A" stroke="#3A3528" transform="rotate(-45 67 137)"/>
|
||||||
|
<circle cx="62" cy="138" r="3" fill="#E08038" opacity="0.8"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label-row">
|
||||||
|
<span class="label-num">02</span>
|
||||||
|
<span class="label-name">Sheen, Grain & Dead-Wax</span>
|
||||||
|
<span class="label-tag tag-css">CSS only</span>
|
||||||
|
</div>
|
||||||
|
<p class="descr">Three layers added to the existing vinyl: a <strong>fixed reflection sweep</strong> (doesn't rotate with the disc — the studio-light look), <strong>paper grain</strong> on the label so the print sits in cardstock, and a <strong>dead-wax engraving</strong> of the master‑lacquer code spinning with the disc. Off-center spindle by 1.5%. Highest visual ROI for the smallest amount of new code.</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- ═════════ VARIANT 3 — TONE-GRADED ═════════ -->
|
||||||
|
<article class="variant v3">
|
||||||
|
<div class="stage">
|
||||||
|
<span class="note">CSS only</span>
|
||||||
|
<div class="vinyl">
|
||||||
|
<div class="vinyl-label">
|
||||||
|
<svg viewBox="0 0 400 400" class="album-art" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgD" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#1F4E3D"/>
|
||||||
|
<stop offset="55%" stop-color="#143E2F"/>
|
||||||
|
<stop offset="100%" stop-color="#0a2519"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="400" height="400" fill="url(#bgD)"/>
|
||||||
|
<g stroke="#e08038" stroke-width="1" fill="none" opacity="0.55">
|
||||||
|
<circle cx="200" cy="200" r="60"/>
|
||||||
|
<circle cx="200" cy="200" r="100"/>
|
||||||
|
<circle cx="200" cy="200" r="140"/>
|
||||||
|
<circle cx="200" cy="200" r="180"/>
|
||||||
|
</g>
|
||||||
|
<text x="200" y="195" text-anchor="middle" font-family="serif" font-style="italic" fill="#f2ebdc" font-size="34">Reference</text>
|
||||||
|
<text x="200" y="225" text-anchor="middle" font-family="monospace" fill="#e08038" font-size="11" letter-spacing="4">VOL · I</text>
|
||||||
|
<text x="200" y="368" text-anchor="middle" font-family="monospace" fill="#9c937f" font-size="9" letter-spacing="6" opacity="0.6">STUDIO PRESSING</text>
|
||||||
|
</svg>
|
||||||
|
<div class="vignette"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<svg class="tonearm" viewBox="0 0 200 200" aria-hidden="true">
|
||||||
|
<circle cx="176" cy="24" r="14" fill="#1a1611" stroke="#3A3528" stroke-width="1"/>
|
||||||
|
<circle cx="176" cy="24" r="6" fill="#3A3528"/>
|
||||||
|
<circle cx="176" cy="24" r="2" fill="#E08038"/>
|
||||||
|
<line x1="176" y1="24" x2="64" y2="136" stroke="#9C937F" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
<rect x="180" y="14" width="14" height="20" fill="#26211A" stroke="#3A3528"/>
|
||||||
|
<rect x="56" y="128" width="22" height="18" rx="2" fill="#26211A" stroke="#3A3528" transform="rotate(-45 67 137)"/>
|
||||||
|
<circle cx="62" cy="138" r="3" fill="#E08038" opacity="0.8"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label-row">
|
||||||
|
<span class="label-num">03</span>
|
||||||
|
<span class="label-name">Tone-Graded Cover</span>
|
||||||
|
<span class="label-tag tag-css">CSS only</span>
|
||||||
|
</div>
|
||||||
|
<p class="descr">Same disc, but the album art on the label is <strong>color-graded</strong> — duotone copper/emerald, deeper saturation drop, vignette around the label rim. Effect: every album cover ends up looking like it came from the same pressing plant, matching the Studio Reference chrome.</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- ═════════ VARIANT 4 — SLEEVE-TO-DISC REVEAL ═════════ -->
|
||||||
|
<article class="variant v4">
|
||||||
|
<div class="stage">
|
||||||
|
<span class="note">CSS hover · JS in production</span>
|
||||||
|
<div class="sleeve-stage">
|
||||||
|
<div class="sleeve">
|
||||||
|
<div class="sleeve-art">
|
||||||
|
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgE" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#1F4E3D"/>
|
||||||
|
<stop offset="55%" stop-color="#143E2F"/>
|
||||||
|
<stop offset="100%" stop-color="#0a2519"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="400" height="400" fill="url(#bgE)"/>
|
||||||
|
<g stroke="#e08038" stroke-width="1" fill="none" opacity="0.55">
|
||||||
|
<circle cx="200" cy="200" r="60"/>
|
||||||
|
<circle cx="200" cy="200" r="100"/>
|
||||||
|
<circle cx="200" cy="200" r="140"/>
|
||||||
|
<circle cx="200" cy="200" r="180"/>
|
||||||
|
</g>
|
||||||
|
<text x="200" y="195" text-anchor="middle" font-family="serif" font-style="italic" fill="#f2ebdc" font-size="34">Reference</text>
|
||||||
|
<text x="200" y="225" text-anchor="middle" font-family="monospace" fill="#e08038" font-size="11" letter-spacing="4">VOL · I</text>
|
||||||
|
<text x="200" y="368" text-anchor="middle" font-family="monospace" fill="#9c937f" font-size="9" letter-spacing="6" opacity="0.6">STUDIO PRESSING</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="vinyl-slot">
|
||||||
|
<div class="vinyl">
|
||||||
|
<div class="vinyl-label"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="hover-hint">Hover to play</span>
|
||||||
|
</div>
|
||||||
|
<svg class="tonearm" viewBox="0 0 200 200" aria-hidden="true">
|
||||||
|
<circle cx="176" cy="24" r="14" fill="#1a1611" stroke="#3A3528" stroke-width="1"/>
|
||||||
|
<circle cx="176" cy="24" r="6" fill="#3A3528"/>
|
||||||
|
<circle cx="176" cy="24" r="2" fill="#E08038"/>
|
||||||
|
<line x1="176" y1="24" x2="64" y2="136" stroke="#9C937F" stroke-width="3.5" stroke-linecap="round"/>
|
||||||
|
<rect x="180" y="14" width="14" height="20" fill="#26211A" stroke="#3A3528"/>
|
||||||
|
<rect x="56" y="128" width="22" height="18" rx="2" fill="#26211A" stroke="#3A3528" transform="rotate(-45 67 137)"/>
|
||||||
|
<circle cx="62" cy="138" r="3" fill="#E08038" opacity="0.8"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label-row">
|
||||||
|
<span class="label-num">04</span>
|
||||||
|
<span class="label-name">Sleeve-to-Disc Reveal</span>
|
||||||
|
<span class="label-tag tag-needs-js">needs JS</span>
|
||||||
|
</div>
|
||||||
|
<p class="descr"><strong>Hover this card</strong> — the disc slides out of the sleeve and starts spinning. In production, this would be wired to the play/pause state: paused = tucked-in sleeve view, playing = disc revealed and spinning. Most evocative, also the most code (animation choreography + state coupling).</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "media-server-frontend",
|
"name": "media-server-frontend",
|
||||||
"version": "0.2.0",
|
"version": "0.2.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "media-server-frontend",
|
"name": "media-server-frontend",
|
||||||
"version": "0.2.0",
|
"version": "0.2.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.27.4"
|
"esbuild": "^0.27.4"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "media-server-frontend",
|
"name": "media-server-frontend",
|
||||||
"version": "0.2.0",
|
"version": "0.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Frontend build tooling for media server WebUI",
|
"description": "Frontend build tooling for media server WebUI",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "media-server"
|
name = "media-server"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
description = "REST API server for controlling system-wide media playback"
|
description = "REST API server for controlling system-wide media playback"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|||||||
Reference in New Issue
Block a user