(onChange)
| 30 | }; |
| 31 | |
| 32 | export function CameraControls(onChange) { |
| 33 | const container = document.createElement('div'); |
| 34 | // Added padding-bottom to ensure scrollbar doesn't overlap content if visible |
| 35 | // Changed justify-center to justify-start md:justify-center to allow left-aligned scrolling on mobile |
| 36 | container.className = 'w-full flex justify-start md:justify-center gap-3 md:gap-6 py-4 md:py-8 overflow-x-auto no-scrollbar snap-x px-4 md:px-0'; |
| 37 | |
| 38 | let state = { |
| 39 | camera: Object.keys(CAMERA_MAP)[0], |
| 40 | lens: Object.keys(LENS_MAP)[0], |
| 41 | focal: 35, |
| 42 | aperture: "f/1.4" |
| 43 | }; |
| 44 | |
| 45 | const updateState = (key, value) => { |
| 46 | state[key] = value; |
| 47 | if (onChange) onChange(state); |
| 48 | }; |
| 49 | |
| 50 | const createColumn = (title, items, key, initialValue) => { |
| 51 | const colWrapper = document.createElement('div'); |
| 52 | colWrapper.className = 'flex flex-col items-center relative w-[140px] md:w-[160px] shrink-0 snap-center group'; |
| 53 | |
| 54 | const viewport = document.createElement('div'); |
| 55 | // Responsive height: h-[50vh] on mobile, h-[320px] on desktop |
| 56 | viewport.className = 'relative overflow-hidden w-full h-[40vh] md:h-[320px] bg-[#1a1a1a]/80 rounded-[2rem] border border-white/5 shadow-2xl backdrop-blur-xl transition-transform duration-300 hover:scale-[1.02] hover:border-white/10'; |
| 57 | |
| 58 | const list = document.createElement('div'); |
| 59 | list.className = 'h-full overflow-y-auto no-scrollbar snap-y snap-mandatory relative z-10'; |
| 60 | |
| 61 | // Spacer to allow first item to be centered |
| 62 | const topSpacer = document.createElement('div'); |
| 63 | topSpacer.style.height = 'calc(50% - 50px)'; // Half viewport - half item height |
| 64 | list.appendChild(topSpacer); |
| 65 | |
| 66 | const topMask = document.createElement('div'); |
| 67 | topMask.className = 'absolute top-0 left-0 right-0 h-24 bg-gradient-to-b from-[#1a1a1a] via-[#1a1a1a]/80 to-transparent z-20 pointer-events-none rounded-t-[2rem]'; |
| 68 | viewport.appendChild(topMask); |
| 69 | |
| 70 | const bottomMask = document.createElement('div'); |
| 71 | bottomMask.className = 'absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-[#1a1a1a] via-[#1a1a1a]/80 to-transparent z-20 pointer-events-none rounded-b-[2rem]'; |
| 72 | viewport.appendChild(bottomMask); |
| 73 | |
| 74 | const glow = document.createElement('div'); |
| 75 | glow.className = 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-4/5 h-[80px] bg-primary/5 blur-xl rounded-full pointer-events-none z-0'; |
| 76 | viewport.appendChild(glow); |
| 77 | |
| 78 | // DRAG TO SCROLL LOGIC |
| 79 | let isDown = false; |
| 80 | let startY; |
| 81 | let scrollTop; |
| 82 | |
| 83 | list.addEventListener('mousedown', (e) => { |
| 84 | isDown = true; |
| 85 | list.classList.add('cursor-grabbing'); |
| 86 | list.classList.remove('cursor-pointer', 'snap-y'); // Disable snap while dragging |
| 87 | startY = e.pageY - list.offsetTop; |
| 88 | scrollTop = list.scrollTop; |
| 89 | e.preventDefault(); // Prevent text selection |
no test coverage detected