diff --git a/frontend/src/routes/art/+page.svelte b/frontend/src/routes/art/+page.svelte index de141be..3407264 100644 --- a/frontend/src/routes/art/+page.svelte +++ b/frontend/src/routes/art/+page.svelte @@ -6,6 +6,7 @@ const FRAMES = [ { id: 'walnut', label: 'Walnut' }, { id: 'oak', label: 'Oak' }, + { id: 'mahogany', label: 'Mahogany' }, { id: 'gold', label: 'Gold' }, { id: 'silver', label: 'Silver' }, { id: 'none', label: 'No frame' }, @@ -210,13 +211,22 @@ border-radius: 4px; padding: var(--rail); box-shadow: 0 24px 58px rgba(20, 30, 45, 0.20), 0 3px 10px rgba(20, 30, 45, 0.10); } - .frame--walnut { - background: linear-gradient(135deg, #5c3d26, #87592f 38%, #4d3220 68%, #76512f); - 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, 190, 0.30), - inset 0 -3px 9px rgba(0, 0, 0, 0.48); + /* The three woods share one real (CC0, Poly Haven) fine-grain texture, recolored per + species with a CSS filter, with a soft bevel baked into the texture layer. Metals + stay gradient. The texture sits on ::before (behind the mat); miters on ::after. */ + .frame--walnut, .frame--oak, .frame--mahogany { + background: linear-gradient(135deg, #5a3a24, #34210f); /* fallback if texture misses */ } + .frame--walnut::before, .frame--oak::before, .frame--mahogany::before { + content: ""; position: absolute; inset: 0; z-index: 0; border-radius: inherit; pointer-events: none; + background: + linear-gradient(158deg, rgba(255, 250, 240, 0.14), rgba(0, 0, 0, 0.24) 80%), + url("/textures/wood-grain.jpg") center / cover; + } + .frame--walnut::before { filter: brightness(1.08) saturate(1.05); } + .frame--oak::before { filter: brightness(1.72) contrast(0.92) saturate(1.12) sepia(0.22) hue-rotate(-6deg); } + .frame--mahogany::before { filter: brightness(0.98) contrast(1.06) saturate(1.55) sepia(0.4) hue-rotate(-16deg); } + .frame--gold { background: linear-gradient(135deg, #b88c3d, #ecd293 42%, #a9772f 60%, #dcbd71); box-shadow: 0 24px 58px rgba(20, 30, 45, 0.20), @@ -232,31 +242,19 @@ inset 0 -3px 9px rgba(60, 70, 85, 0.42); } - /* Oak: real wood grain from an feTurbulence noise field (organic, no drawn lines), - topped with a soft light/shade for moulding roundness, plus mitered (45°) corner - joints. --t = rail width, so the corner seams run from the outer corner to the mat. */ - .frame--oak { - --t: var(--rail); + /* Mitered (45°) corner joints for the woods — each seam runs from the outer corner to + the mat, sized to the rail width so it tracks the thickness slider. */ + .frame--walnut::after, .frame--oak::after, .frame--mahogany::after { + content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none; border-radius: inherit; background: - linear-gradient(158deg, rgba(255, 236, 205, 0.20), rgba(0, 0, 0, 0.14) 74%), - url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='600'%20height='400'%3E%3Cfilter%20id='w'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.011%200.06'%20numOctaves='5'%20seed='17'/%3E%3CfeColorMatrix%20type='matrix'%20values='0%200%200%200.7%200.36%200%200%200%200.46%200.23%200%200%200%200.26%200.09%200%200%200%200%201'/%3E%3C/filter%3E%3Crect%20width='600'%20height='400'%20filter='url(%23w)'/%3E%3C/svg%3E") center / 100% 100% no-repeat, - linear-gradient(135deg, #a9763f, #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; + linear-gradient(45deg, transparent calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 1px), transparent calc(50% + 1px)) top left / var(--rail) var(--rail) no-repeat, + linear-gradient(135deg, transparent calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 1px), transparent calc(50% + 1px)) top right / var(--rail) var(--rail) no-repeat, + linear-gradient(135deg, transparent calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 1px), transparent calc(50% + 1px)) bottom left / var(--rail) var(--rail) no-repeat, + linear-gradient(45deg, transparent calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% - 0.9px), rgba(28, 16, 6, 0.7) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 0.1px), rgba(255, 230, 190, 0.34) calc(50% + 1px), transparent calc(50% + 1px)) bottom right / var(--rail) var(--rail) no-repeat; } .mat { - display: block; position: relative; background: #fbf8f1; border-radius: 1px; + display: block; position: relative; z-index: 1; background: #fbf8f1; border-radius: 1px; padding: var(--mat); box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.14), inset 0 0 0 1px rgba(0, 0, 0, 0.05); } @@ -309,6 +307,7 @@ .swatch.on { outline-color: var(--accent); } .swatch--walnut { background: linear-gradient(150deg, #6b4528, #8a5d31 55%, #4f3420); } .swatch--oak { background: linear-gradient(150deg, #b07f46, #cb9c5b 55%, #8c5d2f); } + .swatch--mahogany { background: linear-gradient(150deg, #5a241a, #7e3826 55%, #3a160e); } .swatch--gold { background: linear-gradient(150deg, #c79a45, #ecd293 55%, #a9772f); } .swatch--silver { background: linear-gradient(150deg, #aab1bb, #edf0f3 55%, #98a0ab); } .swatch--none { background: linear-gradient(150deg, #ffffff, #ece6da); } @@ -327,17 +326,18 @@ .lightbox { position: fixed; inset: 0; z-index: 50; border: none; cursor: zoom-out; background: rgba(18, 22, 28, 0.94); display: flex; flex-direction: column; - align-items: center; justify-content: center; gap: 14px; padding: 3vmin; + align-items: center; justify-content: center; gap: 12px; padding: 2.5vmin; } - /* The full-screen view wears the same frame — it makes the piece pop. Bigger rail + mat - here so the white border keeps its proportion against the much larger image. */ + /* The full-screen view wears the same frame. Rail + mat are only a touch larger than the + page (keeping the ~1:1 wood:white proportion), and the image is capped so the whole + framed piece — including the bottom rail — always fits on screen. */ .lb-frame { - cursor: zoom-out; max-width: 94vw; max-height: 92vh; - --rail: calc(clamp(16px, 1.8vw, 36px) * var(--frame-scale, 1)); - --mat: calc(clamp(22px, 3.2vw, 60px) * var(--frame-scale, 1)); + cursor: zoom-out; max-width: 92vw; + --rail: calc(clamp(14px, 1.4vw, 26px) * var(--frame-scale, 1)); + --mat: calc(clamp(16px, 1.6vw, 30px) * var(--frame-scale, 1)); } .lb-frame.frame--none { --rail: 0px; --mat: 0px; } /* bare art fills the screen */ - .lb-frame img { max-width: 86vw; max-height: 74vh; width: auto; height: auto; object-fit: contain; } + .lb-frame img { max-width: 86vw; max-height: 66vh; width: auto; height: auto; object-fit: contain; } .lb-cap { color: #e9e2d6; font-size: 0.9rem; } .lb-cap .sep { color: #9aa0a6; } diff --git a/frontend/static/textures/CREDITS.txt b/frontend/static/textures/CREDITS.txt new file mode 100644 index 0000000..f7d1764 --- /dev/null +++ b/frontend/static/textures/CREDITS.txt @@ -0,0 +1,2 @@ +wood-grain.jpg — "Fine Grained Wood" by Rob Tuytel, Poly Haven (https://polyhaven.com/a/fine_grained_wood) +License: CC0 (Public Domain). Free to use, no attribution required. Recolored in CSS for frame variants. diff --git a/frontend/static/textures/wood-grain.jpg b/frontend/static/textures/wood-grain.jpg new file mode 100644 index 0000000..1571e4b Binary files /dev/null and b/frontend/static/textures/wood-grain.jpg differ