Art frames: add wood-grain Oak with mitered corners + frame the full-screen view

- New "Oak" frame: real wood-grain along the rails plus 45° mitered corner joints
  (CSS conic-free miter via per-corner diagonal seam gradients sized to the rail width),
  so it reads as four lengths of moulding meeting at the corners.
- The lightbox now wears the selected frame too — the same moulding + cream mat around
  the full-resolution image, which makes the piece pop on a dark full screen. Image is
  capped to leave room for the frame; "No frame" still maximizes size.
- Frame picker wraps gracefully now that there are five options.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-21 16:42:31 -04:00
parent 27788ba2a8
commit cd8175be81
+36 -9
View File
@@ -5,6 +5,7 @@
// Virtual frames the viewer can switch between — remembered locally, no account needed.
const FRAMES = [
{ id: 'walnut', label: 'Walnut' },
{ id: 'oak', label: 'Oak' },
{ id: 'gold', label: 'Gold' },
{ id: 'silver', label: 'Silver' },
{ id: 'none', label: 'No frame' },
@@ -116,8 +117,10 @@
</div>
{#if zoom && art}
<button class="lightbox" onclick={() => (zoom = false)} aria-label="Close">
<img src={art.image_url_large || art.image_url} alt={art.title} />
<button class="lightbox" onclick={() => (zoom = false)} aria-label="Close artwork">
<span class="frame frame--{frame} lb-frame">
<span class="mat"><img src={art.image_url_large || art.image_url} alt={art.title} /></span>
</span>
<span class="lb-cap">{art.title}{#if art.artist}<span class="sep">·</span>{art.artist}{/if}</span>
</button>
{/if}
@@ -178,7 +181,7 @@
border-radius: 5px;
box-shadow: 0 24px 58px rgba(20, 30, 45, 0.20), 0 3px 10px rgba(20, 30, 45, 0.10);
}
.frame--walnut, .frame--gold, .frame--silver { padding: clamp(11px, 2.3vw, 22px); border-radius: 4px; }
.frame--walnut, .frame--oak, .frame--gold, .frame--silver { padding: clamp(11px, 2.3vw, 22px); border-radius: 4px; }
.frame--walnut {
background: linear-gradient(135deg, #5c3d26, #87592f 38%, #4d3220 68%, #76512f);
box-shadow: 0 24px 58px rgba(20, 30, 45, 0.24),
@@ -201,10 +204,33 @@
inset 0 -3px 9px rgba(60, 70, 85, 0.42);
}
/* Oak: visible wood grain along each rail + mitered (45°) corner joints, like a real
frame cut from four lengths of moulding. --t = rail width, so the corner seams run
exactly from the outer corner to the mat. */
.frame--oak {
--t: clamp(11px, 2.3vw, 22px);
background:
repeating-linear-gradient(95deg, rgba(74, 46, 18, 0.20) 0 1.5px, rgba(150, 104, 52, 0) 1.5px 6px),
repeating-linear-gradient(95deg, rgba(255, 221, 170, 0.12) 0 1px, transparent 1px 4px),
linear-gradient(135deg, #a9763f, #c89a5b 40%, #8c5d2f 70%, #b07f46);
box-shadow: 0 24px 58px rgba(20, 30, 45, 0.24),
inset 0 0 0 1px rgba(255, 255, 255, 0.10),
inset 0 2px 3px rgba(255, 226, 185, 0.32),
inset 0 -3px 9px rgba(0, 0, 0, 0.45);
}
.frame--oak::after {
content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
background:
linear-gradient(45deg, transparent calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 1px), transparent calc(50% + 1px)) top left / var(--t) var(--t) no-repeat,
linear-gradient(135deg, transparent calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 1px), transparent calc(50% + 1px)) top right / var(--t) var(--t) no-repeat,
linear-gradient(135deg, transparent calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 1px), transparent calc(50% + 1px)) bottom left / var(--t) var(--t) no-repeat,
linear-gradient(45deg, transparent calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% - 0.9px), rgba(48, 28, 9, 0.6) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 0.1px), rgba(255, 228, 186, 0.42) calc(50% + 1px), transparent calc(50% + 1px)) bottom right / var(--t) var(--t) no-repeat;
}
.mat {
display: block; position: relative; background: #fbf8f1; border-radius: 1px;
}
.frame--walnut .mat, .frame--gold .mat, .frame--silver .mat {
.frame--walnut .mat, .frame--oak .mat, .frame--gold .mat, .frame--silver .mat {
padding: clamp(10px, 2.4vw, 22px);
box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.14), inset 0 0 0 1px rgba(0, 0, 0, 0.05);
}
@@ -239,7 +265,7 @@
.more { display: inline-block; margin-left: 8px; color: var(--accent); font-weight: 600; text-decoration: none; }
.more:hover { color: var(--accent-deep); }
.frames { display: flex; align-items: center; justify-content: center; gap: 9px; margin-top: 22px; }
.frames { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 9px; margin-top: 22px; }
.frames-label { font-size: 0.74rem; font-weight: 600; color: var(--muted); margin-right: 4px; text-transform: uppercase; letter-spacing: 0.06em; }
.swatch {
width: 26px; height: 26px; border-radius: 50%; border: 2px solid transparent;
@@ -247,6 +273,7 @@
}
.swatch.on { box-shadow: 0 0 0 2px var(--canvas), 0 0 0 4px var(--accent); }
.swatch--walnut { background: linear-gradient(135deg, #5c3d26, #87592f); }
.swatch--oak { background: linear-gradient(135deg, #a9763f, #c89a5b); }
.swatch--gold { background: linear-gradient(135deg, #b88c3d, #ecd293); }
.swatch--silver { background: linear-gradient(135deg, #a9b0ba, #edf0f3); }
.swatch--none { background: var(--surface); border: 2px solid var(--line); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.03); }
@@ -264,10 +291,10 @@
background: rgba(18, 22, 28, 0.94); display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 14px; padding: 3vmin;
}
.lightbox img {
max-width: 97vw; max-height: 92vh; width: auto; height: auto;
object-fit: contain; border-radius: 4px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
/* The full-screen view wears the same frame — it makes the piece pop. The frame +
mat eat a little room, so the image is capped to leave space for the moulding. */
.lb-frame { cursor: zoom-out; max-width: 92vw; max-height: 90vh; box-sizing: border-box; }
.lb-frame img { max-width: 84vw; max-height: 78vh; width: auto; height: auto; object-fit: contain; }
.lb-cap { color: #e9e2d6; font-size: 0.9rem; }
.lb-cap .sep { color: #9aa0a6; }
</style>