PagesStructures['id1789764293867'] = {"structures":[{"type":"page","id":"id1789764293867","elementProperties":{"tagName":"DIV","behaviours":null,"isLocked":true,"name":"Mobile Grid app V2","baseName":"Precision Art Grid (Demo)","templateID":null,"templateName":null,"order":0,"customStates":null,"isElementWasEditedInMode":true,"showHoverEffects":true,"skinInformation":{"categoryId":245},"isSupportsMobile":false,"isVisibleInMode":true,"isFixed":false,"topZIndex":0,"elementOpacity":1,"staticMargin":{"horizontalMargin":0,"verticalMargin":0},"sizeAndPosition":{"width":0,"height":1068,"top":0,"left":0},"lastResizingSize":{"width":0,"height":1068,"top":0,"left":0},"isVisibleInLastSection":true,"isVisibleInLastDataItem":true,"attributes":null,"metaProps":null,"metaItemType":null,"metaTitle":null,"styleBindingId":null,"styleBindingFields":null,"shadow":{"top":0,"left":0,"spread":0,"blur":0,"color":"black"},"glow":{"spread":0,"blur":0,"color":"black"},"isParallaxBackground":false,"parallaxBackgroundSpeed":0.5,"isParallaxElement":{"isActive":false,"speed":1,"delay":0},"effectsOptions":{"isParallax":false,"oneTimeAnimation":false,"duration":1200,"startDistance":200,"startVisibility":0.8,"distance":300,"measureDistanceType":3,"delay":0,"distanceDelay":0,"runInLoop":false,"flyDirection":2,"direction":"horizontal","movementDistanceHorizontally":200,"movementDistanceVertically":0,"autoStartDistanceSettings":true,"autoDistanceSettings":true},"effects":null,"binding":null,"verticalAlign":"center","horizontalAlign":"center","horizontalContentAlign":"center","basicUserCanEdit":true,"overflow":false,"isGrowable":{"bottom":false,"right":false},"rowsItems":null,"childrenAnchor":"center","backgroundColor":{"mainColor":0,"variation":0,"opacity":1,"brightness":0},"imageUrl":"","imageLayout":"fitWhenBigger","imagePosition":{"horizontal":"center","vertical":"center"},"origImage":"","imageOpacity":1,"backgroundOverlay":"transparent","gridLines":[],"fixedBackground":false,"statesGroups":null},"metaData":{"position":{"left":-625.8333740234375,"top":0},"size":{"width":1252,"height":2342}},"childElements":[{"type":"SectionElement","id":"id1789764293868","elementProperties":{"tagName":"SECTION","behaviours":null,"isLocked":false,"name":"Section 2","baseName":null,"templateID":null,"templateName":null,"order":3,"customStates":null,"isElementWasEditedInMode":false,"showHoverEffects":true,"skinInformation":{},"isSupportsMobile":false,"isVisibleInMode":true,"isFixed":false,"topZIndex":0,"elementOpacity":1,"staticMargin":{"horizontalMargin":0,"verticalMargin":0},"sizeAndPosition":{"height":600,"left":0,"width":0,"top":0},"lastResizingSize":{"height":600,"left":0,"width":0,"top":0},"isVisibleInLastSection":true,"isVisibleInLastDataItem":true,"attributes":null,"metaProps":null,"metaItemType":null,"metaTitle":null,"styleBindingId":null,"styleBindingFields":null,"shadow":{"top":0,"left":0,"spread":0,"blur":0,"color":"black"},"glow":{"spread":0,"blur":0,"color":"black"},"isParallaxBackground":false,"parallaxBackgroundSpeed":0.5,"isParallaxElement":{"isActive":false,"speed":1,"delay":0},"effectsOptions":{"isParallax":false,"oneTimeAnimation":false,"duration":1200,"startDistance":200,"startVisibility":0.8,"distance":300,"measureDistanceType":3,"delay":0,"distanceDelay":0,"runInLoop":false,"flyDirection":2,"direction":"horizontal","movementDistanceHorizontally":200,"movementDistanceVertically":0,"autoStartDistanceSettings":true,"autoDistanceSettings":true},"effects":null,"binding":null,"innerGlow":{"spread":0,"blur":0,"color":"black"},"innerShadow":{"top":0,"left":0,"spread":0,"blur":0,"color":"black"},"padding":{"top":0,"left":0,"bottom":0,"right":0},"basicUserCanEdit":true,"backgroundColor":"transparent","imageUrl":"","imageLayout":"stretchedToFill","imagePosition":{"vertical":"center","horizontal":"center"},"origImage":"","imageOpacity":1,"backgroundOverlay":"transparent","verticalLayout":"normal","horizontalLayout":"normal","fillLayout":{"fillVertical":false,"fillHorizontal":false},"margin":{"top":0,"left":0,"bottom":0,"right":0},"overflow":false,"isGrowable":{"bottom":false,"right":false},"rowsItems":null,"childrenAnchor":"center","resizingGripPosiotion":"s","sectionName":"Section A","pageContentType":1,"borderWidth":{"top":0,"left":0,"bottom":0,"right":0},"roundness":{"topLeft":0,"bottomLeft":0,"topRight":0,"bottomRight":0},"borderColor":{"top":"Black","left":"Black","bottom":"Black","right":"Black"},"borderStyle":{"top":"solid","left":"solid","bottom":"solid","right":"solid"}},"metaData":{"position":{"left":-625.8333740234375,"top":100},"size":{"width":1252,"height":600}}},{"type":"SectionElement","id":"id1789764322875","elementProperties":{"tagName":"SECTION","behaviours":null,"isLocked":false,"name":"Section 5","baseName":null,"templateID":null,"templateName":null,"order":4,"customStates":null,"isElementWasEditedInMode":false,"showHoverEffects":true,"skinInformation":{"categoryId":199},"isSupportsMobile":false,"isVisibleInMode":true,"isFixed":false,"topZIndex":0,"elementOpacity":1,"staticMargin":{"horizontalMargin":0,"verticalMargin":0},"sizeAndPosition":{"height":1366,"left":0,"width":1248,"top":0},"lastResizingSize":{"width":1248,"height":1366},"isVisibleInLastSection":true,"isVisibleInLastDataItem":true,"attributes":null,"metaProps":null,"metaItemType":null,"metaTitle":null,"styleBindingId":null,"styleBindingFields":null,"shadow":{"top":0,"left":0,"spread":0,"blur":0,"color":"black"},"glow":{"spread":0,"blur":0,"color":"black"},"isParallaxBackground":false,"parallaxBackgroundSpeed":0.5,"isParallaxElement":{"isActive":false,"speed":1,"delay":0},"effectsOptions":{"isParallax":false,"oneTimeAnimation":false,"duration":1200,"startDistance":200,"startVisibility":0.8,"distance":300,"measureDistanceType":3,"delay":0,"distanceDelay":0,"runInLoop":false,"flyDirection":2,"direction":"horizontal","movementDistanceHorizontally":200,"movementDistanceVertically":0,"autoStartDistanceSettings":true,"autoDistanceSettings":true},"effects":null,"binding":null,"innerGlow":{"spread":0,"blur":0,"color":"black"},"innerShadow":{"top":0,"left":0,"spread":0,"blur":0,"color":"black"},"padding":{"top":0,"left":0,"bottom":0,"right":0},"basicUserCanEdit":true,"backgroundColor":"transparent","imageUrl":"","imageLayout":"stretchedToFill","imagePosition":{"vertical":"center","horizontal":"center"},"origImage":"","imageOpacity":1,"backgroundOverlay":"transparent","verticalLayout":"normal","horizontalLayout":"normal","fillLayout":{"fillVertical":false,"fillHorizontal":false},"margin":{"top":0,"left":0,"bottom":0,"right":0},"overflow":false,"isGrowable":{"bottom":false,"right":false},"rowsItems":null,"childrenAnchor":"center","resizingGripPosiotion":"s","sectionName":"Section 1","pageContentType":1,"borderWidth":{"top":0,"left":0,"bottom":0,"right":0},"roundness":{"topLeft":0,"bottomLeft":0,"topRight":0,"bottomRight":0},"borderColor":{"top":"Black","left":"Black","bottom":"Black","right":"Black"},"borderStyle":{"top":"solid","left":"solid","bottom":"solid","right":"solid"}},"metaData":{"position":{"left":-625.8333740234375,"top":700},"size":{"width":1252,"height":1366}},"childElements":[{"type":"ExternalAppembed_html_app1","id":"id1789764335683","elementProperties":{"tagName":"DIV","behaviours":null,"isLocked":false,"name":"Embed Html 2","baseName":null,"templateID":"templateid1789764335684","templateName":null,"order":58,"customStates":null,"isElementWasEditedInMode":false,"showHoverEffects":true,"skinInformation":{},"isSupportsMobile":false,"isVisibleInMode":true,"isFixed":false,"topZIndex":0,"elementOpacity":1,"staticMargin":{"horizontalMargin":0,"verticalMargin":0},"sizeAndPosition":{"width":880,"height":840,"left":-436,"top":-150},"lastResizingSize":{"width":880,"height":840},"isVisibleInLastSection":true,"isVisibleInLastDataItem":true,"attributes":null,"metaProps":null,"metaItemType":null,"metaTitle":null,"styleBindingId":null,"styleBindingFields":null,"shadow":{"top":0,"left":0,"spread":0,"blur":0,"color":"black"},"glow":{"spread":0,"blur":0,"color":"black"},"isParallaxBackground":false,"parallaxBackgroundSpeed":0.5,"isParallaxElement":{"isActive":false,"speed":1,"delay":0},"effectsOptions":{"isParallax":false,"oneTimeAnimation":false,"duration":1200,"startDistance":200,"startVisibility":0.8,"distance":300,"measureDistanceType":3,"delay":0,"distanceDelay":0,"runInLoop":false,"flyDirection":2,"direction":"horizontal","movementDistanceHorizontally":200,"movementDistanceVertically":0,"autoStartDistanceSettings":true,"autoDistanceSettings":true},"effects":null,"binding":null,"innerGlow":{"spread":0,"blur":0,"color":"black"},"innerShadow":{"top":0,"left":0,"spread":0,"blur":0,"color":"black"},"basicUserCanEdit":true,"backgroundColor":"transparent","padding":{"top":0,"left":0,"bottom":0,"right":0},"imageUrl":"","imageLayout":"stretchedToFill","imagePosition":{"vertical":"center","horizontal":"center"},"origImage":"","imageOpacity":1,"backgroundOverlay":"transparent","verticalLayout":"normal","horizontalLayout":"normal","fillLayout":{"fillHorizontal":false,"fillVertical":false},"margin":{"top":0,"left":0,"bottom":0,"right":0},"isVisible":true,"borderWidth":{"top":0,"left":0,"bottom":0,"right":0},"roundness":{"topLeft":0,"bottomLeft":0,"topRight":0,"bottomRight":0},"borderColor":{"top":"Black","left":"Black","bottom":"Black","right":"Black"},"borderStyle":{"top":"solid","left":"solid","bottom":"solid","right":"solid"},"rotationAngle":0,"display":"fixed","cellItem":null,"cellHorizontalAlignment":"none","verticalAlign":"center","horizontalAlign":"center","horizontalContentAlign":"center","embed_html_app1html":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n<title>Ray Naso Grid Precision PRO</title>\n<style>\n:root {\n  --accent: #d4af37;\n  --accent-hover: #e8c547;\n  --bg: #0c0c0c;\n  --panel: #161616;\n  --panel-2: #1e1e1e;\n  --border: #2a2a2a;\n  --text: #e0e0e0;\n  --text-dim: #888;\n  --cyan: #00d4ff;\n  --red: #ff4757;\n  --green: #2ed573;\n  --safe-bottom: env(safe-area-inset-bottom, 0px);\n}\n\n* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }\n\nhtml, body {\n  height: 100%;\n  overflow: hidden;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n  background: var(--bg);\n  color: var(--text);\n}\n\nbody {\n  display: flex;\n  flex-direction: column;\n}\n\n/* ========== TOP BAR ========== */\n.topbar {\n  height: 48px;\n  background: var(--panel);\n  border-bottom: 1px solid var(--border);\n  display: flex;\n  align-items: center;\n  padding: 0 10px;\n  gap: 8px;\n  flex-shrink: 0;\n  z-index: 200;\n}\n.topbar .brand {\n  font-size: 13px;\n  font-weight: 700;\n  color: var(--accent);\n  white-space: nowrap;\n}\n.topbar .brand span { color: #aaa; font-weight: 500; font-size: 11px; }\n.topbar .spacer { flex: 1; }\n\n.icon-btn {\n  width: 36px; height: 36px;\n  border: 1px solid #333;\n  background: #222;\n  color: #ddd;\n  border-radius: 8px;\n  font-size: 16px;\n  display: flex; align-items: center; justify-content: center;\n  cursor: pointer;\n  flex-shrink: 0;\n}\n.icon-btn:active { background: #333; }\n.icon-btn.active { background: var(--accent); color: #000; border-color: var(--accent); }\n\n/* ========== MAIN LAYOUT ========== */\n.main {\n  flex: 1;\n  display: flex;\n  min-height: 0;\n  position: relative;\n}\n\n/* Desktop sidebar */\n.sidebar {\n  width: 280px;\n  background: var(--panel);\n  border-right: 1px solid var(--border);\n  display: flex;\n  flex-direction: column;\n  flex-shrink: 0;\n  overflow: hidden;\n  transition: transform 0.25s ease;\n  z-index: 150;\n}\n.sidebar-inner {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  overflow-y: auto;\n  padding-bottom: 20px;\n}\n\n.tabs {\n  display: flex;\n  padding: 8px 10px 0;\n  gap: 4px;\n  border-bottom: 1px solid var(--border);\n}\n.tab {\n  flex: 1;\n  padding: 10px 6px;\n  background: transparent;\n  border: none;\n  color: var(--text-dim);\n  font-size: 12px;\n  font-weight: 600;\n  text-transform: uppercase;\n  letter-spacing: 0.3px;\n  cursor: pointer;\n  border-radius: 6px 6px 0 0;\n}\n.tab.active {\n  background: var(--panel-2);\n  color: var(--accent);\n}\n\n.panel {\n  display: none;\n  flex-direction: column;\n  gap: 14px;\n  padding: 14px;\n  background: var(--panel-2);\n  flex: 1;\n}\n.panel.active { display: flex; }\n\n.section-title {\n  font-size: 10px;\n  font-weight: 700;\n  text-transform: uppercase;\n  letter-spacing: 0.5px;\n  color: var(--accent);\n  margin-bottom: 2px;\n}\n\nlabel {\n  font-size: 10px;\n  color: var(--text-dim);\n  font-weight: 600;\n  text-transform: uppercase;\n  display: block;\n  margin-bottom: 4px;\n}\n\ninput[type=\"number\"],\ninput[type=\"text\"],\nselect {\n  width: 100%;\n  background: #111;\n  border: 1px solid #333;\n  color: #fff;\n  padding: 10px 10px;\n  border-radius: 8px;\n  font-size: 14px;\n  outline: none;\n}\ninput:focus, select:focus { border-color: var(--accent); }\n\ninput[type=\"range\"] {\n  width: 100%;\n  accent-color: var(--accent);\n  height: 6px;\n}\n\ninput[type=\"color\"] {\n  width: 100%;\n  height: 36px;\n  border: 1px solid #333;\n  border-radius: 8px;\n  background: #111;\n  padding: 3px;\n}\n\n.row { display: flex; gap: 8px; }\n.row > * { flex: 1; }\n\n.btn {\n  background: #252525;\n  border: 1px solid #3a3a3a;\n  color: #ddd;\n  padding: 11px 12px;\n  border-radius: 8px;\n  font-size: 12px;\n  font-weight: 600;\n  cursor: pointer;\n  text-transform: uppercase;\n  letter-spacing: 0.3px;\n  transition: 0.15s;\n  text-align: center;\n}\n.btn:active { transform: scale(0.97); }\n.btn-primary {\n  background: var(--accent);\n  color: #111;\n  border-color: var(--accent);\n}\n.btn-cyan { background: var(--cyan); color: #000; border-color: var(--cyan); }\n.btn-danger { background: transparent; border-color: var(--red); color: var(--red); }\n.btn-sm { padding: 8px 10px; font-size: 11px; }\n.btn.active, .btn.toggle-on {\n  background: var(--accent);\n  color: #000;\n  border-color: var(--accent);\n}\n.btn.toggle-on { background: var(--red); color: #fff; border-color: var(--red); }\n\n.stats {\n  background: #0a0a0a;\n  border: 1px solid #2a2a2a;\n  border-radius: 8px;\n  padding: 10px;\n  font-family: ui-monospace, monospace;\n  font-size: 12px;\n  color: var(--accent);\n  text-align: center;\n}\n\n/* Plot list */\n.plot-list {\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n  max-height: 280px;\n  overflow-y: auto;\n}\n.plot-item {\n  background: #111;\n  border-left: 3px solid var(--cyan);\n  padding: 10px 12px;\n  border-radius: 0 8px 8px 0;\n  font-size: 12px;\n}\n.plot-item .id {\n  font-weight: 700;\n  color: var(--cyan);\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 4px;\n}\n.plot-item .meta { color: #aaa; font-size: 11px; line-height: 1.4; }\n.plot-item .del {\n  background: none; border: none; color: var(--red);\n  font-size: 18px; cursor: pointer; line-height: 1;\n}\n\n/* ========== WORKSPACE ========== */\n.workspace {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n  position: relative;\n  background: #0a0a0a;\n}\n\n#viewport {\n  flex: 1;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  overflow: hidden;\n  position: relative;\n  touch-action: none;\n}\n\n.canvas-wrap {\n  position: relative;\n  box-shadow: 0 0 60px rgba(0,0,0,0.6);\n  /* Allow the canvas to be as large as calculated – no artificial max */\n}\n#mainCanvas, #plotCanvas {\n  display: block;\n}\n#plotCanvas {\n  position: absolute;\n  top: 0; left: 0;\n  pointer-events: auto;\n}\n\n/* On mobile let the viewport scroll if the canvas is larger than the screen */\n@media (max-width: 900px) {\n  #viewport {\n    overflow: auto;\n    -webkit-overflow-scrolling: touch;\n    align-items: flex-start;\n    justify-content: center;\n    padding: 4px;\n  }\n  .canvas-wrap {\n    margin: auto;\n  }\n}\n\n/* Empty state */\n.empty-state {\n  position: absolute;\n  inset: 0;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  padding: 24px;\n  z-index: 10;\n  background: #0a0a0a;\n}\n.empty-state.hidden { display: none; }\n.empty-state h2 {\n  color: var(--accent);\n  font-size: 20px;\n  margin-bottom: 8px;\n}\n.empty-state p {\n  color: var(--text-dim);\n  font-size: 14px;\n  max-width: 320px;\n  line-height: 1.5;\n  margin-bottom: 24px;\n}\n.steps {\n  display: flex;\n  gap: 12px;\n  margin-bottom: 28px;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n.step {\n  background: var(--panel-2);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  padding: 14px;\n  width: 130px;\n  text-align: left;\n}\n.step-num {\n  width: 22px; height: 22px;\n  background: var(--accent);\n  color: #000;\n  border-radius: 50%;\n  display: flex; align-items: center; justify-content: center;\n  font-weight: 800; font-size: 11px;\n  margin-bottom: 8px;\n}\n.step strong { display: block; font-size: 12px; margin-bottom: 3px; }\n.step span { font-size: 10px; color: var(--text-dim); line-height: 1.3; }\n\n/* ========== MOBILE BOTTOM SHEET ========== */\n.sheet-overlay {\n  display: none;\n  position: fixed;\n  inset: 0;\n  background: rgba(0,0,0,0.55);\n  z-index: 300;\n}\n.sheet-overlay.open { display: block; }\n\n.bottom-sheet {\n  display: none;\n  position: fixed;\n  left: 0; right: 0; bottom: 0;\n  background: var(--panel);\n  border-radius: 16px 16px 0 0;\n  z-index: 310;\n  max-height: 78vh;\n  flex-direction: column;\n  transform: translateY(100%);\n  transition: transform 0.28s ease;\n  padding-bottom: var(--safe-bottom);\n}\n.bottom-sheet.open {\n  transform: translateY(0);\n}\n.sheet-handle {\n  width: 40px; height: 4px;\n  background: #444;\n  border-radius: 2px;\n  margin: 10px auto 6px;\n}\n.sheet-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 4px 16px 10px;\n  border-bottom: 1px solid var(--border);\n}\n.sheet-header h3 {\n  font-size: 15px;\n  color: var(--accent);\n}\n.sheet-body {\n  overflow-y: auto;\n  padding: 14px 16px 24px;\n  flex: 1;\n}\n\n/* Mobile floating action bar */\n.mobile-fab {\n  display: none;\n  position: fixed;\n  bottom: calc(16px + var(--safe-bottom));\n  left: 50%;\n  transform: translateX(-50%);\n  background: var(--panel);\n  border: 1px solid var(--border);\n  border-radius: 14px;\n  padding: 8px 10px;\n  gap: 6px;\n  z-index: 250;\n  box-shadow: 0 8px 30px rgba(0,0,0,0.5);\n}\n.mobile-fab .fab-btn {\n  width: 42px; height: 42px;\n  border-radius: 10px;\n  border: 1px solid #333;\n  background: #222;\n  color: #ddd;\n  font-size: 16px;\n  display: flex; align-items: center; justify-content: center;\n  cursor: pointer;\n}\n.mobile-fab .fab-btn.active {\n  background: var(--accent);\n  color: #000;\n  border-color: var(--accent);\n}\n.mobile-fab .fab-btn.plot-active {\n  background: var(--cyan);\n  color: #000;\n  border-color: var(--cyan);\n}\n\n/* Artist rail - desktop only */\n.artist-rail {\n  width: 110px;\n  background: var(--panel);\n  border-left: 1px solid var(--border);\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  padding: 12px 6px;\n  gap: 10px;\n  overflow-y: auto;\n  flex-shrink: 0;\n}\n.artist-rail h4 {\n  font-size: 9px;\n  text-transform: uppercase;\n  letter-spacing: 0.4px;\n  color: var(--accent);\n}\n.snap-add {\n  width: 90px; height: 44px;\n  border: 1px dashed #444;\n  border-radius: 8px;\n  background: transparent;\n  color: #888;\n  font-size: 10px;\n  font-weight: 600;\n  cursor: pointer;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 2px;\n}\n.snap-card {\n  width: 90px;\n  position: relative;\n}\n.snap-card img {\n  width: 100%;\n  height: 60px;\n  object-fit: contain;\n  background: #000;\n  border: 1px solid #333;\n  border-radius: 4px;\n  cursor: pointer;\n}\n.snap-card .label {\n  font-size: 9px;\n  color: #777;\n  text-align: center;\n  margin-top: 2px;\n}\n.snap-card .x {\n  position: absolute;\n  top: -5px; right: -5px;\n  width: 18px; height: 18px;\n  background: var(--red);\n  color: #fff;\n  border: none;\n  border-radius: 50%;\n  font-size: 12px;\n  cursor: pointer;\n}\n\n/* Modals */\n.modal-overlay {\n  position: fixed;\n  inset: 0;\n  background: rgba(0,0,0,0.8);\n  z-index: 400;\n  display: none;\n  align-items: center;\n  justify-content: center;\n  padding: 16px;\n}\n.modal-overlay.open { display: flex; }\n.modal {\n  background: var(--panel);\n  border: 1px solid var(--border);\n  border-radius: 12px;\n  padding: 20px;\n  max-width: 560px;\n  width: 100%;\n  max-height: 85vh;\n  overflow: auto;\n}\n.modal h3 {\n  color: var(--accent);\n  margin-bottom: 12px;\n  font-size: 16px;\n}\n.modal-actions {\n  display: flex;\n  gap: 10px;\n  justify-content: flex-end;\n  margin-top: 16px;\n}\n\n/* Toast */\n.toast {\n  position: fixed;\n  bottom: calc(80px + var(--safe-bottom));\n  left: 50%;\n  transform: translateX(-50%) translateY(30px);\n  background: #1a1a1a;\n  border: 1px solid var(--accent);\n  color: #fff;\n  padding: 10px 18px;\n  border-radius: 10px;\n  font-size: 13px;\n  z-index: 500;\n  opacity: 0;\n  transition: 0.25s;\n  pointer-events: none;\n  max-width: 90%;\n  text-align: center;\n}\n.toast.show {\n  transform: translateX(-50%) translateY(0);\n  opacity: 1;\n}\n\n/* ========== RESPONSIVE ========== */\n@media (max-width: 900px) {\n  .sidebar {\n    position: fixed;\n    left: 0; top: 0; bottom: 0;\n    transform: translateX(-100%);\n    box-shadow: 8px 0 30px rgba(0,0,0,0.4);\n    width: 300px;\n  }\n  .sidebar.open { transform: translateX(0); }\n  .artist-rail { display: none; }\n  .mobile-fab { display: flex; }\n  .bottom-sheet { display: flex; }\n\n  /* Larger, more readable controls on phones/tablets */\n  .section-title { font-size: 13px !important; margin-bottom: 8px; }\n  label { font-size: 13px !important; margin-bottom: 6px; }\n  input[type=\"number\"],\n  input[type=\"text\"],\n  select {\n    padding: 14px 12px !important;\n    font-size: 16px !important; /* stops iOS auto-zoom */\n    border-radius: 10px;\n  }\n  input[type=\"range\"] {\n    height: 32px !important;\n    margin: 8px 0;\n  }\n  input[type=\"color\"] { height: 48px !important; }\n  .btn {\n    padding: 14px !important;\n    font-size: 14px !important;\n    border-radius: 10px;\n    min-height: 48px;\n  }\n  .btn-sm {\n    padding: 12px !important;\n    font-size: 13px !important;\n    min-height: 46px;\n  }\n  .stats { font-size: 14px !important; padding: 12px !important; }\n  .plot-item { padding: 14px !important; font-size: 14px !important; }\n  .plot-item .meta { font-size: 13px !important; }\n  .tab { padding: 14px 8px !important; font-size: 13px !important; }\n  .panel { padding: 16px !important; gap: 18px !important; }\n  .sheet-body { padding: 18px 18px 36px !important; overscroll-behavior: contain; }\n  .sheet-header h3 { font-size: 17px !important; }\n  .mobile-fab .fab-btn {\n    width: 50px !important;\n    height: 50px !important;\n    font-size: 20px !important;\n    border-radius: 12px;\n  }\n  .topbar { height: 54px; }\n  .icon-btn { width: 44px !important; height: 44px !important; font-size: 18px !important; }\n  .bottom-sheet { max-height: 72vh; }\n  input[type=\"range\"] { touch-action: none; }\n}\n\n@media (min-width: 901px) {\n  .mobile-fab { display: none !important; }\n  .sheet-overlay, .bottom-sheet { display: none !important; }\n}\n\n@media (max-width: 500px) {\n  .steps { gap: 8px; }\n  .step { width: 110px; padding: 12px; }\n  .empty-state h2 { font-size: 18px; }\n}\n</style>\n</head>\n<body>\n\n<!-- TOP BAR -->\n<div class=\"topbar\">\n  <button class=\"icon-btn\" id=\"menuBtn\" onclick=\"toggleSidebar()\" title=\"Menu\">☰</button>\n  <div class=\"brand\">RAY NASO <span>Grid Precision</span></div>\n  <div class=\"spacer\"></div>\n  <button class=\"icon-btn\" id=\"plotToggleBtn\" onclick=\"togglePlotMode()\" title=\"Plot Mode\">📍</button>\n  <button class=\"icon-btn\" onclick=\"openMobileSheet('controls')\" title=\"Controls\">⚙️</button>\n</div>\n\n<div class=\"main\">\n  <!-- SIDEBAR (desktop + slide-in on mobile) -->\n  <aside class=\"sidebar\" id=\"sidebar\">\n    <div class=\"sidebar-inner\">\n      <div class=\"tabs\">\n        <button class=\"tab active\" data-tab=\"controls\" onclick=\"switchTab('controls')\">Controls</button>\n        <button class=\"tab\" data-tab=\"plot\" onclick=\"switchTab('plot')\">Plot <span id=\"plotCount\">(0)</span></button>\n      </div>\n\n      <div class=\"panel active\" id=\"panel-controls\">\n        <div>\n          <div class=\"section-title\">1. Reference Image</div>\n          <input type=\"file\" id=\"fileInput\" accept=\"image/*\" style=\"font-size:13px; padding:8px;\">\n        </div>\n\n        <div>\n          <div class=\"section-title\">2. Canvas Size</div>\n          <div class=\"row\">\n            <div>\n              <label>Unit</label>\n              <select id=\"unit\" onchange=\"redraw()\">\n                <option value=\"in\">Inches</option>\n                <option value=\"cm\">Centimeters</option>\n              </select>\n            </div>\n          </div>\n          <div class=\"row\" style=\"margin-top:8px;\">\n            <div>\n              <label>Width</label>\n              <input type=\"number\" id=\"canvW\" value=\"20\" min=\"1\" step=\"0.25\" oninput=\"redraw()\">\n            </div>\n            <div>\n              <label>Height</label>\n              <input type=\"number\" id=\"canvH\" value=\"16\" min=\"1\" step=\"0.25\" oninput=\"redraw()\">\n            </div>\n          </div>\n        </div>\n\n        <div>\n          <div class=\"section-title\">3. Grid Setup</div>\n          <div class=\"row\">\n            <button class=\"btn btn-sm active\" id=\"modeCols\" onclick=\"setGridMode('cols')\">Columns</button>\n            <button class=\"btn btn-sm\" id=\"modeSize\" onclick=\"setGridMode('size')\">Square Size</button>\n          </div>\n          <div id=\"colsGroup\" style=\"margin-top:8px;\">\n            <label>Columns</label>\n            <input type=\"number\" id=\"cols\" value=\"5\" min=\"1\" max=\"40\" oninput=\"redraw()\">\n          </div>\n          <div id=\"sizeGroup\" style=\"display:none; margin-top:8px;\">\n            <label>Square Size</label>\n            <input type=\"number\" id=\"sqSize\" value=\"2\" min=\"0.25\" step=\"0.25\" oninput=\"redraw()\">\n          </div>\n          <div class=\"stats\" id=\"gridStats\" style=\"margin-top:8px;\">GRID: 5 × 4</div>\n        </div>\n\n        <div>\n          <div class=\"section-title\">4. Appearance</div>\n          <label>Line Thickness</label>\n          <input type=\"range\" id=\"thickness\" min=\"1\" max=\"6\" value=\"1\" oninput=\"redraw()\">\n          <label style=\"margin-top:8px;\">Grid Color</label>\n          <input type=\"color\" id=\"gridColor\" value=\"#ff0000\" oninput=\"redraw()\">\n          <div class=\"row\" style=\"margin-top:10px;\">\n            <button class=\"btn btn-sm\" id=\"btnCenterX\" onclick=\"toggleCenterX()\">Centering X</button>\n            <button class=\"btn btn-sm\" id=\"btnThirds\" onclick=\"toggleThirds()\">3rds</button>\n          </div>\n        </div>\n\n        <div>\n          <div class=\"section-title\">5. View & Filters</div>\n          <label>Zoom</label>\n          <input type=\"range\" id=\"zoom\" min=\"0.2\" max=\"5\" step=\"0.05\" value=\"1\" oninput=\"redraw()\">\n          <div class=\"row\" style=\"margin-top:8px;\">\n            <button class=\"btn btn-sm\" onclick=\"fitImage()\">Fit</button>\n            <button class=\"btn btn-sm\" id=\"btnLock\" onclick=\"toggleLock()\">Lock</button>\n            <button class=\"btn btn-sm\" onclick=\"resetView()\">Reset</button>\n          </div>\n          <div class=\"row\" style=\"margin-top:8px;\">\n            <button class=\"btn btn-sm\" id=\"btnBW\" onclick=\"toggleBW()\">B&W</button>\n            <button class=\"btn btn-sm\" onclick=\"toggleFlip('h')\">Flip H</button>\n            <button class=\"btn btn-sm\" onclick=\"toggleFlip('v')\">Flip V</button>\n          </div>\n          <label style=\"margin-top:10px;\">Squint (Blur)</label>\n          <input type=\"range\" id=\"blur\" min=\"0\" max=\"12\" value=\"0\" oninput=\"redraw()\">\n        </div>\n\n        <div>\n          <div class=\"section-title\">Project</div>\n          <div class=\"row\">\n            <button class=\"btn btn-sm\" onclick=\"saveProject(false)\">Save Settings</button>\n            <button class=\"btn btn-sm\" onclick=\"saveProject(true)\">Save Full</button>\n          </div>\n          <button class=\"btn btn-sm\" style=\"margin-top:8px; width:100%;\" onclick=\"document.getElementById('loadProject').click()\">Load Project</button>\n          <input type=\"file\" id=\"loadProject\" accept=\".json\" style=\"display:none;\" onchange=\"loadProject(event)\">\n        </div>\n\n        <button class=\"btn btn-primary\" style=\"margin-top:6px;\" onclick=\"downloadImage()\">Export Gridded Image</button>\n        <button class=\"btn\" style=\"margin-top:8px;\" onclick=\"exportPureGrid()\">Export Pure Grid</button>\n      </div>\n\n      <div class=\"panel\" id=\"panel-plot\">\n        <div>\n          <div class=\"section-title\">Plot Points</div>\n          <p style=\"font-size:12px; color:var(--text-dim); line-height:1.45; margin-bottom:10px;\">\n            Turn on <strong>Plot Mode</strong> (📍 button) then tap the image to drop precise points.\n          </p>\n          <label style=\"display:flex; align-items:center; gap:8px; margin-bottom:6px;\">\n            <input type=\"checkbox\" id=\"forcePlot\" onchange=\"syncPlotMode()\"> Force Plot Mode\n          </label>\n          <label style=\"display:flex; align-items:center; gap:8px; margin-bottom:6px;\">\n            <input type=\"checkbox\" id=\"connectDots\" checked onchange=\"redrawPlots()\"> Connect Dots\n          </label>\n          <label style=\"display:flex; align-items:center; gap:8px;\">\n            <input type=\"checkbox\" id=\"showLabels\" checked onchange=\"redrawPlots()\"> Show Labels\n          </label>\n          <div class=\"row\" style=\"margin-top:12px;\">\n            <button class=\"btn btn-sm\" onclick=\"undoPlot()\">Undo</button>\n            <button class=\"btn btn-sm btn-danger\" onclick=\"clearPlots()\">Clear</button>\n          </div>\n          <button class=\"btn btn-cyan btn-sm\" style=\"margin-top:10px; width:100%;\" onclick=\"printPlotList()\">Print Plot List</button>\n        </div>\n        <div>\n          <div class=\"section-title\">Ledger</div>\n          <div class=\"plot-list\" id=\"plotList\">\n            <div style=\"color:#666; font-size:13px; padding:16px 0; text-align:center;\">No points yet</div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </aside>\n\n  <!-- WORKSPACE -->\n  <div class=\"workspace\">\n    <div id=\"viewport\">\n      <div class=\"empty-state\" id=\"emptyState\">\n        <h2>Ray Naso Grid Precision</h2>\n        <p>Transfer any reference to canvas with precise proportions. Built for traditional painters.</p>\n        <div class=\"steps\">\n          <div class=\"step\">\n            <div class=\"step-num\">1</div>\n            <strong>Upload Image</strong>\n            <span>Choose your reference</span>\n          </div>\n          <div class=\"step\">\n            <div class=\"step-num\">2</div>\n            <strong>Set Canvas</strong>\n            <span>Match real dimensions</span>\n          </div>\n          <div class=\"step\">\n            <div class=\"step-num\">3</div>\n            <strong>Adjust Grid</strong>\n            <span>Columns or square size</span>\n          </div>\n        </div>\n        <button class=\"btn btn-primary\" style=\"padding:14px 28px; font-size:14px;\" onclick=\"document.getElementById('fileInput').click()\">\n          Upload Reference Image\n        </button>\n      </div>\n\n      <div class=\"canvas-wrap\" id=\"canvasWrap\" style=\"display:none;\">\n        <canvas id=\"mainCanvas\"></canvas>\n        <canvas id=\"plotCanvas\"></canvas>\n      </div>\n    </div>\n  </div>\n\n  <!-- Artist Views (desktop) -->\n  <aside class=\"artist-rail\" id=\"artistRail\">\n    <h4>Artist Views</h4>\n    <button class=\"snap-add\" onclick=\"takeSnapshot()\">+ View</button>\n    <div id=\"snapList\"></div>\n  </aside>\n</div>\n\n<!-- Mobile FAB -->\n<div class=\"mobile-fab\" id=\"mobileFab\">\n  <button class=\"fab-btn\" onclick=\"openMobileSheet('controls')\" title=\"Controls\">⚙️</button>\n  <button class=\"fab-btn\" id=\"fabPlot\" onclick=\"togglePlotMode()\" title=\"Plot Mode\">📍</button>\n  <button class=\"fab-btn\" id=\"fabLedger\" onclick=\"openMobileSheet('plot')\" title=\"Plot List\">📋</button>\n  <button class=\"fab-btn\" onclick=\"fitImage()\" title=\"Fit\">⤢</button>\n  <button class=\"fab-btn\" onclick=\"downloadImage()\" title=\"Export\">⬇️</button>\n</div>\n\n<!-- Mobile Bottom Sheet -->\n<div class=\"sheet-overlay\" id=\"sheetOverlay\" onclick=\"closeMobileSheet()\"></div>\n<div class=\"bottom-sheet\" id=\"bottomSheet\">\n  <div class=\"sheet-handle\"></div>\n  <div class=\"sheet-header\">\n    <h3 id=\"sheetTitle\">Controls</h3>\n    <button class=\"icon-btn\" onclick=\"closeMobileSheet()\" style=\"width:32px;height:32px;font-size:18px;\">✕</button>\n  </div>\n  <div class=\"sheet-body\" id=\"sheetBody\">\n    <!-- content injected -->\n  </div>\n</div>\n\n<!-- Zoom Modal -->\n<div class=\"modal-overlay\" id=\"zoomModal\">\n  <div class=\"modal\">\n    <h3 id=\"zoomTitle\">Square Detail</h3>\n    <canvas id=\"zoomCanvas\" width=\"400\" height=\"400\" style=\"width:100%; background:#fff; border-radius:6px;\"></canvas>\n    <div style=\"display:flex; gap:12px; align-items:center; margin-top:12px; flex-wrap:wrap;\">\n      <label style=\"font-size:12px;\">Sub-grid</label>\n      <input type=\"number\" id=\"subDivs\" value=\"5\" min=\"2\" max=\"12\" style=\"width:60px;\" onchange=\"drawZoom()\">\n      <label style=\"font-size:12px; display:flex; align-items:center; gap:4px;\">\n        <input type=\"checkbox\" id=\"smoothZoom\" checked onchange=\"drawZoom()\"> Smooth\n      </label>\n    </div>\n    <div class=\"modal-actions\">\n      <button class=\"btn\" onclick=\"closeZoom()\">Close</button>\n    </div>\n  </div>\n</div>\n\n<div class=\"toast\" id=\"toast\"></div>\n\n<script>\n/* ============================================================\n   RAY NASO GRID PRECISION PRO – Responsive / Mobile Version\n   ============================================================ */\n\nconst mainCanvas = document.getElementById('mainCanvas');\nconst ctx = mainCanvas.getContext('2d');\nconst plotCanvas = document.getElementById('plotCanvas');\nconst pCtx = plotCanvas.getContext('2d');\nconst viewport = document.getElementById('viewport');\n\nlet img = new Image();\nlet hasImage = false;\nlet moveX = 0, moveY = 0;\nlet flipH = 1, flipV = 1;\nlet isLocked = false;\nlet isDragging = false;\nlet startX = 0, startY = 0;\nlet plotMode = false;\n\nlet filters = { bw: false, thirds: false, centerX: false };\nlet gridMode = 'cols';\nlet plots = [];\nlet snapshots = [];\nlet activeSquare = { col: 0, row: 0, letter: 'A', number: 1 };\n\n/* ---------- Helpers ---------- */\nfunction $(id) { return document.getElementById(id); }\nfunction isMobile() { return window.innerWidth <= 900; }\n\nfunction toast(msg) {\n  const t = $('toast');\n  t.textContent = msg;\n  t.classList.add('show');\n  setTimeout(() => t.classList.remove('show'), 2200);\n}\n\nfunction formatMeasurement(val, unit) {\n  if (unit === 'cm') return parseFloat(val).toFixed(2) + ' cm';\n  const num = parseFloat(val);\n  const whole = Math.floor(num);\n  let sixteenths = Math.round((num - whole) * 16);\n  if (sixteenths === 16) return (whole + 1) + '\"';\n  if (sixteenths === 0) return whole + '\"';\n  let n = sixteenths, d = 16;\n  if (n % 8 === 0) { n /= 8; d /= 8; }\n  else if (n % 4 === 0) { n /= 4; d /= 4; }\n  else if (n % 2 === 0) { n /= 2; d /= 2; }\n  return whole === 0 ? `${n}/${d}\"` : `${whole} ${n}/${d}\"`;\n}\n\nfunction getCanvasDims() {\n  return {\n    w: parseFloat($('canvW').value) || 1,\n    h: parseFloat($('canvH').value) || 1,\n    unit: $('unit').value\n  };\n}\n\nfunction getGridInfo() {\n  const { w, h } = getCanvasDims();\n  let cols, rows, step;\n  if (gridMode === 'cols') {\n    cols = Math.max(1, parseInt($('cols').value) || 1);\n    step = w / cols;\n    rows = Math.ceil(h / step);\n  } else {\n    step = parseFloat($('sqSize').value) || 1;\n    cols = Math.max(1, Math.round(w / step));\n    rows = Math.max(1, Math.round(h / step));\n    step = w / cols;\n  }\n  return { cols, rows, step };\n}\n\n/* ---------- UI ---------- */\nfunction switchTab(name) {\n  document.querySelectorAll('.tab').forEach(b => b.classList.toggle('active', b.dataset.tab === name));\n  document.querySelectorAll('.panel').forEach(p => p.classList.toggle('active', p.id === 'panel-' + name));\n}\n\nfunction setGridMode(mode) {\n  gridMode = mode;\n  $('modeCols').classList.toggle('active', mode === 'cols');\n  $('modeSize').classList.toggle('active', mode === 'size');\n  $('colsGroup').style.display = mode === 'cols' ? 'block' : 'none';\n  $('sizeGroup').style.display = mode === 'size' ? 'block' : 'none';\n  redraw();\n}\n\nfunction toggleSidebar() {\n  if (isMobile()) {\n    $('sidebar').classList.toggle('open');\n  } else {\n    $('sidebar').classList.toggle('open');\n  }\n}\n\nfunction togglePlotMode() {\n  plotMode = !plotMode;\n  $('forcePlot').checked = plotMode;\n  $('plotToggleBtn').classList.toggle('active', plotMode);\n  $('fabPlot').classList.toggle('plot-active', plotMode);\n  toast(plotMode ? 'Plot Mode ON – tap to place points' : 'Plot Mode OFF');\n}\n\nfunction syncPlotMode() {\n  plotMode = $('forcePlot').checked;\n  $('plotToggleBtn').classList.toggle('active', plotMode);\n  $('fabPlot').classList.toggle('plot-active', plotMode);\n}\n\n/* Mobile bottom sheet */\nfunction openMobileSheet(which) {\n  const sheet = $('bottomSheet');\n  const body = $('sheetBody');\n  const title = $('sheetTitle');\n\n  if (which === 'controls') {\n    title.textContent = 'Controls';\n    // Clone the controls panel content\n    const src = $('panel-controls');\n    body.innerHTML = '';\n    src.querySelectorAll(':scope > div, :scope > button').forEach(el => {\n      body.appendChild(el.cloneNode(true));\n    });\n    // Re-bind critical inputs (cloning loses live bindings for some)\n    rebindSheetControls(body);\n  } else if (which === 'plot') {\n    title.textContent = 'Plot Ledger';\n    renderPlotSheet(body);\n  }\n\n  $('sheetOverlay').classList.add('open');\n  sheet.classList.add('open');\n  $('sidebar').classList.remove('open');\n}\n\nfunction renderPlotSheet(container) {\n  const unit = $('unit').value;\n  let listHtml = '';\n  if (plots.length === 0) {\n    listHtml = '<div style=\"color:#666; font-size:14px; padding:20px 0; text-align:center;\">No points yet.<br>Turn on Plot Mode (📍) and tap the image.</div>';\n  } else {\n    listHtml = plots.map((p, i) => `\n      <div class=\"plot-item\">\n        <div class=\"id\">\n          <span>Point ${i + 1}</span>\n          <button class=\"del\" onclick=\"deletePlot(${i}); renderPlotSheet(document.getElementById('sheetBody'));\">×</button>\n        </div>\n        <div class=\"meta\">\n          Square: <strong>${p.gridStr}</strong> (${p.gX}%, ${p.gY}%)<br>\n          Position: ${formatMeasurement(p.inchX, unit)} × ${formatMeasurement(p.inchY, unit)}\n        </div>\n      </div>\n    `).join('');\n  }\n\n  container.innerHTML = `\n    <div style=\"margin-bottom:14px;\">\n      <p style=\"font-size:13px; color:var(--text-dim); line-height:1.4; margin-bottom:12px;\">\n        ${plots.length} point${plots.length === 1 ? '' : 's'} placed. Tap 📍 then tap the image to add more.\n      </p>\n      <div class=\"row\" style=\"margin-bottom:10px;\">\n        <button class=\"btn btn-sm\" onclick=\"undoPlot(); renderPlotSheet(document.getElementById('sheetBody'));\">Undo Last</button>\n        <button class=\"btn btn-sm btn-danger\" onclick=\"clearPlots(); renderPlotSheet(document.getElementById('sheetBody'));\">Clear All</button>\n      </div>\n      <button class=\"btn btn-cyan btn-sm\" style=\"width:100%; margin-bottom:14px;\" onclick=\"printPlotList()\">Print Plot List</button>\n    </div>\n    <div class=\"section-title\">Ledger</div>\n    <div class=\"plot-list\">${listHtml}</div>\n  `;\n}\n\nfunction closeMobileSheet() {\n  $('sheetOverlay').classList.remove('open');\n  $('bottomSheet').classList.remove('open');\n}\n\nfunction rebindSheetControls(container) {\n  // Simple approach: make the sheet controls call the same global functions\n  // Most already use inline onchange/onclick that reference global functions\n}\n\n/* ---------- Image ---------- */\n$('fileInput').addEventListener('change', e => {\n  const file = e.target.files[0];\n  if (!file) return;\n  const reader = new FileReader();\n  reader.onload = ev => {\n    img = new Image();\n    img.onload = () => {\n      hasImage = true;\n      $('emptyState').classList.add('hidden');\n      $('canvasWrap').style.display = 'block';\n      resetView();\n      fitImage();\n      toast('Image loaded');\n      closeMobileSheet();\n    };\n    img.src = ev.target.result;\n  };\n  reader.readAsDataURL(file);\n});\n\n/* ---------- Drawing ---------- */\nfunction redraw() {\n  if (!hasImage) return;\n\n  const { w, h, unit } = getCanvasDims();\n  const { cols, rows, step } = getGridInfo();\n  const zoom = parseFloat($('zoom').value);\n\n  const mobile = window.innerWidth <= 900;\n  // Almost no padding on mobile – maximize image size\n  const pad = mobile ? 8 : 40;\n\n  $('gridStats').textContent = `GRID: ${cols} × ${rows}  •  ≈ ${formatMeasurement(step, unit)}`;\n\n  // On mobile use nearly the full viewport width and height\n  const availW = viewport.clientWidth - (mobile ? 2 : 16);\n  const availH = viewport.clientHeight - (mobile ? 2 : 16);\n\n  // Fill as much as possible (prefer filling the smaller dimension fully)\n  let scale = Math.min(availW / w, availH / h);\n  if (mobile) {\n    // On phones, push even harder – use 100% of the limiting dimension\n    scale = Math.min(availW / w, availH / h);\n  } else {\n    scale *= 0.94;\n  }\n\n  const dispW = w * scale;\n  const dispH = h * scale;\n\n  mainCanvas.width = Math.round(dispW + pad * 2);\n  mainCanvas.height = Math.round(dispH + pad * 2);\n  plotCanvas.width = mainCanvas.width;\n  plotCanvas.height = mainCanvas.height;\n\n  // Force the canvas element to display at its full calculated size on mobile\n  if (mobile) {\n    mainCanvas.style.width = mainCanvas.width + 'px';\n    mainCanvas.style.height = mainCanvas.height + 'px';\n    plotCanvas.style.width = plotCanvas.width + 'px';\n    plotCanvas.style.height = plotCanvas.height + 'px';\n  } else {\n    mainCanvas.style.width = '';\n    mainCanvas.style.height = '';\n    plotCanvas.style.width = '';\n    plotCanvas.style.height = '';\n  }\n\n  ctx.fillStyle = '#111';\n  ctx.fillRect(0, 0, mainCanvas.width, mainCanvas.height);\n  ctx.fillStyle = '#fff';\n  ctx.fillRect(pad, pad, dispW, dispH);\n\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(pad, pad, dispW, dispH);\n  ctx.clip();\n\n  let filterStr = '';\n  if (filters.bw) filterStr += 'grayscale(100%) ';\n  const blurVal = parseFloat($('blur').value);\n  if (blurVal > 0) filterStr += `blur(${blurVal}px)`;\n  ctx.filter = filterStr || 'none';\n\n  const cx = pad + dispW / 2 + moveX;\n  const cy = pad + dispH / 2 + moveY;\n  ctx.translate(cx, cy);\n  ctx.scale(flipH, flipV);\n  ctx.translate(-cx, -cy);\n\n  const imgRatio = img.width / img.height;\n  const canvRatio = w / h;\n  let drawW, drawH;\n  if (imgRatio > canvRatio) {\n    drawW = dispW; drawH = dispW / imgRatio;\n  } else {\n    drawH = dispH; drawW = dispH * imgRatio;\n  }\n\n  ctx.drawImage(img,\n    cx - (drawW * zoom) / 2,\n    cy - (drawH * zoom) / 2,\n    drawW * zoom, drawH * zoom\n  );\n  ctx.restore();\n\n  // Grid\n  if (cols > 0) {\n    const sq = dispW / cols;\n    ctx.strokeStyle = $('gridColor').value;\n    ctx.lineWidth = parseInt($('thickness').value) || 1;\n\n    for (let i = 0; i <= cols; i++) {\n      const x = pad + i * sq;\n      ctx.beginPath(); ctx.moveTo(x, pad); ctx.lineTo(x, pad + dispH); ctx.stroke();\n    }\n    for (let j = 0; j <= rows; j++) {\n      const y = pad + j * sq;\n      if (y > pad + dispH + 0.5) break;\n      ctx.beginPath(); ctx.moveTo(pad, y); ctx.lineTo(pad + dispW, y); ctx.stroke();\n    }\n\n    ctx.fillStyle = '#fff';\n    ctx.font = 'bold 12px sans-serif';\n    ctx.textAlign = 'center';\n    ctx.shadowColor = '#000';\n    ctx.shadowBlur = 3;\n    for (let i = 0; i < cols; i++) {\n      ctx.fillText(String.fromCharCode(65 + i), pad + i * sq + sq / 2, pad - 8);\n    }\n    ctx.textAlign = 'right';\n    ctx.textBaseline = 'middle';\n    for (let j = 0; j < rows; j++) {\n      const y = pad + j * sq + sq / 2;\n      if (y > pad + dispH) break;\n      ctx.fillText(j + 1, pad - 8, y);\n    }\n    ctx.shadowBlur = 0;\n    ctx.textBaseline = 'alphabetic';\n\n    if (filters.centerX) {\n      ctx.save();\n      ctx.strokeStyle = '#ff3b30';\n      ctx.globalAlpha = 0.4;\n      ctx.lineWidth = 1;\n      for (let r = 0; r < rows; r++) {\n        for (let c = 0; c < cols; c++) {\n          const x1 = pad + c * sq, y1 = pad + r * sq;\n          const x2 = x1 + sq, y2 = Math.min(y1 + sq, pad + dispH);\n          ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();\n          ctx.beginPath(); ctx.moveTo(x2, y1); ctx.lineTo(x1, y2); ctx.stroke();\n        }\n      }\n      ctx.restore();\n    }\n  }\n\n  if (filters.thirds) {\n    ctx.save();\n    ctx.strokeStyle = '#ff4757';\n    ctx.lineWidth = 1.5;\n    ctx.setLineDash([6, 4]);\n    for (let t = 1; t <= 2; t++) {\n      ctx.beginPath();\n      ctx.moveTo(pad + (dispW / 3) * t, pad);\n      ctx.lineTo(pad + (dispW / 3) * t, pad + dispH);\n      ctx.stroke();\n      ctx.beginPath();\n      ctx.moveTo(pad, pad + (dispH / 3) * t);\n      ctx.lineTo(pad + dispW, pad + (dispH / 3) * t);\n      ctx.stroke();\n    }\n    ctx.restore();\n  }\n\n  redrawPlots();\n}\n\nfunction getPad() {\n  return window.innerWidth <= 900 ? 8 : 40;\n}\n\nfunction redrawPlots() {\n  pCtx.clearRect(0, 0, plotCanvas.width, plotCanvas.height);\n  if (!hasImage || plots.length === 0) return;\n\n  const pad = getPad();\n  const dispW = mainCanvas.width - pad * 2;\n  const dispH = mainCanvas.height - pad * 2;\n\n  if ($('connectDots').checked) {\n    pCtx.beginPath();\n    pCtx.strokeStyle = '#ff4757';\n    pCtx.lineWidth = 1.5;\n    pCtx.setLineDash([5, 4]);\n    plots.forEach((p, i) => {\n      const x = pad + p.pctX * dispW;\n      const y = pad + p.pctY * dispH;\n      if (i === 0) pCtx.moveTo(x, y); else pCtx.lineTo(x, y);\n    });\n    pCtx.stroke();\n    pCtx.setLineDash([]);\n  }\n\n  plots.forEach((p, i) => {\n    const x = pad + p.pctX * dispW;\n    const y = pad + p.pctY * dispH;\n    pCtx.beginPath();\n    pCtx.arc(x, y, 6, 0, Math.PI * 2);\n    pCtx.fillStyle = '#ff4757';\n    pCtx.fill();\n    pCtx.strokeStyle = '#fff';\n    pCtx.lineWidth = 1.5;\n    pCtx.stroke();\n\n    if ($('showLabels').checked) {\n      pCtx.fillStyle = '#fff';\n      pCtx.font = 'bold 11px monospace';\n      pCtx.shadowColor = '#000';\n      pCtx.shadowBlur = 3;\n      pCtx.fillText(`P${i + 1}`, x + 9, y - 7);\n      pCtx.shadowBlur = 0;\n    }\n  });\n}\n\n/* ---------- Plotting ---------- */\nfunction addPlot(clientX, clientY) {\n  if (!hasImage) return;\n  const rect = plotCanvas.getBoundingClientRect();\n  const scaleX = plotCanvas.width / rect.width;\n  const scaleY = plotCanvas.height / rect.height;\n  const x = (clientX - rect.left) * scaleX;\n  const y = (clientY - rect.top) * scaleY;\n\n  const pad = getPad();\n  const dispW = plotCanvas.width - pad * 2;\n  const dispH = plotCanvas.height - pad * 2;\n\n  if (x < pad || x > pad + dispW || y < pad || y > pad + dispH) return;\n\n  const pctX = (x - pad) / dispW;\n  const pctY = (y - pad) / dispH;\n  const { w, h, unit } = getCanvasDims();\n  const { cols } = getGridInfo();\n\n  let gridStr = '—', gX = 0, gY = 0;\n  if (cols > 0) {\n    const sq = dispW / cols;\n    const c = Math.floor((x - pad) / sq);\n    const r = Math.floor((y - pad) / sq);\n    gridStr = String.fromCharCode(65 + c) + (r + 1);\n    gX = Math.round(((x - pad - c * sq) / sq) * 100);\n    gY = Math.round(((y - pad - r * sq) / sq) * 100);\n  }\n\n  plots.push({\n    pctX, pctY, gridStr, gX, gY,\n    inchX: pctX * w, inchY: pctY * h\n  });\n  updatePlotUI();\n  redrawPlots();\n  toast(`Point ${plots.length} added`);\n}\n\nfunction updatePlotUI() {\n  $('plotCount').textContent = `(${plots.length})`;\n  const list = $('plotList');\n  if (plots.length === 0) {\n    list.innerHTML = '<div style=\"color:#666; font-size:13px; padding:16px 0; text-align:center;\">No points yet</div>';\n    return;\n  }\n  const unit = $('unit').value;\n  list.innerHTML = plots.map((p, i) => `\n    <div class=\"plot-item\">\n      <div class=\"id\">\n        <span>Point ${i + 1}</span>\n        <button class=\"del\" onclick=\"deletePlot(${i})\">×</button>\n      </div>\n      <div class=\"meta\">\n        Square: <strong>${p.gridStr}</strong> (${p.gX}%, ${p.gY}%)<br>\n        Position: ${formatMeasurement(p.inchX, unit)} × ${formatMeasurement(p.inchY, unit)}\n      </div>\n    </div>\n  `).join('');\n}\n\nfunction deletePlot(i) { plots.splice(i, 1); updatePlotUI(); redrawPlots(); }\nfunction undoPlot() { if (plots.length) { plots.pop(); updatePlotUI(); redrawPlots(); } }\nfunction clearPlots() { plots = []; updatePlotUI(); redrawPlots(); }\n\nfunction printPlotList() {\n  if (plots.length === 0) { toast('No points to print'); return; }\n  const unit = $('unit').value;\n  const { w, h } = getCanvasDims();\n  let html = `<html><head><title>Plot Ledger</title>\n    <style>body{font-family:system-ui;padding:24px} table{width:100%;border-collapse:collapse}\n    th,td{border:1px solid #ccc;padding:8px;text-align:left} th{background:#f0f0f0}</style></head><body>\n    <h1>Ray Naso – Plot Ledger</h1>\n    <p>Canvas: ${w} × ${h} ${unit} | Points: ${plots.length}</p>\n    <table><thead><tr><th>#</th><th>Square</th><th>Inside</th><th>Position</th></tr></thead><tbody>`;\n  plots.forEach((p, i) => {\n    html += `<tr><td>${i+1}</td><td><b>${p.gridStr}</b></td><td>X${p.gX}% Y${p.gY}%</td>\n      <td>${formatMeasurement(p.inchX,unit)} × ${formatMeasurement(p.inchY,unit)}</td></tr>`;\n  });\n  html += `</tbody></table></body></html>`;\n  const win = window.open('', '_blank');\n  win.document.write(html);\n  win.document.close();\n  setTimeout(() => win.print(), 250);\n}\n\n/* ---------- Pointer / Touch handling ---------- */\nlet lastTap = 0;\n\nplotCanvas.addEventListener('pointerdown', e => {\n  e.preventDefault();\n  plotCanvas.setPointerCapture(e.pointerId);\n\n  if (plotMode || $('forcePlot').checked) {\n    addPlot(e.clientX, e.clientY);\n    return;\n  }\n\n  if (isLocked) return;\n  isDragging = true;\n  startX = e.clientX - moveX;\n  startY = e.clientY - moveY;\n});\n\nplotCanvas.addEventListener('pointermove', e => {\n  if (!isDragging || isLocked) return;\n  moveX = e.clientX - startX;\n  moveY = e.clientY - startY;\n  redraw();\n});\n\nplotCanvas.addEventListener('pointerup', e => {\n  isDragging = false;\n  // Double-tap detection for zoom square\n  const now = Date.now();\n  if (now - lastTap < 300) {\n    handleDoubleTap(e.clientX, e.clientY);\n  }\n  lastTap = now;\n});\n\nplotCanvas.addEventListener('pointercancel', () => { isDragging = false; });\n\nfunction handleDoubleTap(clientX, clientY) {\n  if (!hasImage) return;\n  const rect = plotCanvas.getBoundingClientRect();\n  const scaleX = plotCanvas.width / rect.width;\n  const scaleY = plotCanvas.height / rect.height;\n  const x = (clientX - rect.left) * scaleX;\n  const y = (clientY - rect.top) * scaleY;\n  const pad = getPad();\n  const { cols } = getGridInfo();\n  const dispW = plotCanvas.width - pad * 2;\n  const sq = dispW / cols;\n\n  if (x < pad || x > pad + dispW) return;\n  const c = Math.floor((x - pad) / sq);\n  const r = Math.floor((y - pad) / sq);\n  activeSquare = { col: c, row: r, letter: String.fromCharCode(65 + c), number: r + 1 };\n  openZoom();\n}\n\n/* ---------- View controls ---------- */\nfunction fitImage() {\n  if (!hasImage || isLocked) return;\n  moveX = 0; moveY = 0;\n  // On mobile we keep zoom at 1 so the canvas itself fills the screen.\n  // On desktop we can slightly zoom the image to fill the canvas area.\n  const mobile = window.innerWidth <= 900;\n  if (mobile) {\n    $('zoom').value = 1;\n  } else {\n    const { w, h } = getCanvasDims();\n    const imgRatio = img.width / img.height;\n    const canvRatio = w / h;\n    const target = imgRatio > canvRatio ? imgRatio / canvRatio : canvRatio / imgRatio;\n    $('zoom').value = Math.min(5, Math.max(0.2, target));\n  }\n  redraw();\n}\n\nfunction resetView() {\n  if (isLocked) return;\n  moveX = 0; moveY = 0;\n  flipH = 1; flipV = 1;\n  $('zoom').value = 1;\n  redraw();\n}\n\nfunction toggleLock() {\n  isLocked = !isLocked;\n  $('btnLock').classList.toggle('active', isLocked);\n  toast(isLocked ? 'View locked' : 'View unlocked');\n}\n\nfunction toggleBW() {\n  filters.bw = !filters.bw;\n  $('btnBW').classList.toggle('toggle-on', filters.bw);\n  redraw();\n}\nfunction toggleThirds() {\n  filters.thirds = !filters.thirds;\n  $('btnThirds').classList.toggle('toggle-on', filters.thirds);\n  redraw();\n}\nfunction toggleCenterX() {\n  filters.centerX = !filters.centerX;\n  $('btnCenterX').classList.toggle('toggle-on', filters.centerX);\n  redraw();\n}\nfunction toggleFlip(dir) {\n  if (isLocked) return;\n  if (dir === 'h') flipH *= -1; else flipV *= -1;\n  redraw();\n}\n\n/* ---------- Snapshots ---------- */\nfunction takeSnapshot() {\n  if (!hasImage) return;\n  snapshots.push({\n    moveX, moveY,\n    zoom: parseFloat($('zoom').value),\n    flipH, flipV,\n    filters: { ...filters },\n    blur: $('blur').value,\n    thumb: mainCanvas.toDataURL('image/jpeg', 0.3)\n  });\n  renderSnaps();\n  toast('View saved');\n}\n\nfunction renderSnaps() {\n  const list = $('snapList');\n  if (!list) return;\n  list.innerHTML = '';\n  snapshots.forEach((s, i) => {\n    const div = document.createElement('div');\n    div.className = 'snap-card';\n    div.innerHTML = `\n      <img src=\"${s.thumb}\" onclick=\"toast('View ${i+1}')\">\n      <div class=\"label\">View ${i+1}</div>\n      <button class=\"x\" onclick=\"snapshots.splice(${i},1); renderSnaps();\">×</button>\n    `;\n    list.appendChild(div);\n  });\n}\n\n/* ---------- Zoom modal ---------- */\nfunction openZoom() {\n  $('zoomTitle').textContent = `Square ${activeSquare.letter}${activeSquare.number}`;\n  $('zoomModal').classList.add('open');\n  drawZoom();\n}\nfunction closeZoom() { $('zoomModal').classList.remove('open'); }\n\nfunction drawZoom() {\n  const zCanvas = $('zoomCanvas');\n  const zCtx = zCanvas.getContext('2d');\n  const pad = getPad();\n  const { cols } = getGridInfo();\n  const dispW = mainCanvas.width - pad * 2;\n  const sq = dispW / cols;\n  const srcX = pad + activeSquare.col * sq;\n  const srcY = pad + activeSquare.row * sq;\n\n  zCtx.fillStyle = '#fff';\n  zCtx.fillRect(0, 0, zCanvas.width, zCanvas.height);\n  zCtx.imageSmoothingEnabled = $('smoothZoom').checked;\n  zCtx.drawImage(mainCanvas, srcX, srcY, sq, sq, 0, 0, zCanvas.width, zCanvas.height);\n\n  const divs = parseInt($('subDivs').value) || 5;\n  const step = zCanvas.width / divs;\n  zCtx.strokeStyle = $('gridColor').value;\n  zCtx.lineWidth = 1;\n  for (let i = 0; i <= divs; i++) {\n    const p = i * step;\n    zCtx.beginPath(); zCtx.moveTo(p, 0); zCtx.lineTo(p, zCanvas.height); zCtx.stroke();\n    zCtx.beginPath(); zCtx.moveTo(0, p); zCtx.lineTo(zCanvas.width, p); zCtx.stroke();\n  }\n}\n\n/* ---------- Export & Project ---------- */\nfunction downloadImage() {\n  if (!hasImage) return;\n  const a = document.createElement('a');\n  a.download = 'Ray_Naso_Grid_Precision.jpg';\n  a.href = mainCanvas.toDataURL('image/jpeg', 0.95);\n  a.click();\n  toast('Image exported');\n}\n\nfunction exportPureGrid() {\n  if (!hasImage) return;\n  const { w, h, unit } = getCanvasDims();\n  const { cols, rows } = getGridInfo();\n  const pad = 40;\n  const scale = 40;\n  const c = document.createElement('canvas');\n  c.width = w * scale + pad * 2;\n  c.height = h * scale + pad * 2;\n  const g = c.getContext('2d');\n  g.fillStyle = '#fff';\n  g.fillRect(0, 0, c.width, c.height);\n  g.strokeStyle = '#000';\n  g.lineWidth = 1.5;\n  const sq = (w * scale) / cols;\n  for (let i = 0; i <= cols; i++) {\n    const x = pad + i * sq;\n    g.beginPath(); g.moveTo(x, pad); g.lineTo(x, pad + h * scale); g.stroke();\n  }\n  for (let j = 0; j <= rows; j++) {\n    const y = pad + j * sq;\n    if (y > pad + h * scale + 1) break;\n    g.beginPath(); g.moveTo(pad, y); g.lineTo(pad + w * scale, y); g.stroke();\n  }\n  g.fillStyle = '#000';\n  g.font = 'bold 14px sans-serif';\n  g.textAlign = 'center';\n  for (let i = 0; i < cols; i++) g.fillText(String.fromCharCode(65 + i), pad + i * sq + sq / 2, pad - 12);\n  g.textAlign = 'right';\n  g.textBaseline = 'middle';\n  for (let j = 0; j < rows; j++) g.fillText(j + 1, pad - 10, pad + j * sq + sq / 2);\n\n  const a = document.createElement('a');\n  a.download = 'Ray_Naso_Pure_Grid.png';\n  a.href = c.toDataURL('image/png');\n  a.click();\n  toast('Pure grid exported');\n}\n\nfunction saveProject(includeImage) {\n  if (!hasImage) { toast('Load an image first'); return; }\n  const data = {\n    version: 'PRO-2.1-mobile',\n    settings: {\n      unit: $('unit').value, width: $('canvW').value, height: $('canvH').value,\n      gridMode, cols: $('cols').value, sqSize: $('sqSize').value,\n      thickness: $('thickness').value, gridColor: $('gridColor').value,\n      zoom: $('zoom').value, moveX, moveY, flipH, flipV,\n      blur: $('blur').value, filters: { ...filters }\n    },\n    plots\n  };\n  if (includeImage) data.imageSrc = img.src;\n\n  const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });\n  const a = document.createElement('a');\n  a.href = URL.createObjectURL(blob);\n  a.download = `RayNaso_Project_${new Date().toISOString().slice(0,10)}.json`;\n  a.click();\n  URL.revokeObjectURL(a.href);\n  toast(includeImage ? 'Full project saved' : 'Settings saved');\n}\n\nfunction loadProject(e) {\n  const file = e.target.files[0];\n  if (!file) return;\n  const reader = new FileReader();\n  reader.onload = ev => {\n    try {\n      const data = JSON.parse(ev.target.result);\n      const s = data.settings;\n      $('unit').value = s.unit;\n      $('canvW').value = s.width;\n      $('canvH').value = s.height;\n      setGridMode(s.gridMode || 'cols');\n      $('cols').value = s.cols;\n      $('sqSize').value = s.sqSize || 2;\n      $('thickness').value = s.thickness;\n      $('gridColor').value = s.gridColor;\n      $('zoom').value = s.zoom;\n      $('blur').value = s.blur;\n      moveX = s.moveX; moveY = s.moveY;\n      flipH = s.flipH; flipV = s.flipV;\n      filters = s.filters || { bw: false, thirds: false, centerX: false };\n      plots = data.plots || [];\n      updatePlotUI();\n\n      if (data.imageSrc) {\n        img = new Image();\n        img.onload = () => {\n          hasImage = true;\n          $('emptyState').classList.add('hidden');\n          $('canvasWrap').style.display = 'block';\n          redraw();\n        };\n        img.src = data.imageSrc;\n      } else if (hasImage) redraw();\n      toast('Project loaded');\n    } catch (err) {\n      toast('Invalid project file');\n    }\n  };\n  reader.readAsText(file);\n}\n\n/* ---------- Init ---------- */\nwindow.addEventListener('resize', () => { if (hasImage) redraw(); });\n\n// Close sidebar when clicking outside on mobile\ndocument.addEventListener('click', e => {\n  if (isMobile() && $('sidebar').classList.contains('open')) {\n    if (!e.target.closest('.sidebar') && !e.target.closest('#menuBtn')) {\n      $('sidebar').classList.remove('open');\n    }\n  }\n});\n</script>\n</body>\n</html>\n\n","embed_html_app1headerHtml":""},"metaData":{"position":{"left":-436.0000305175781,"top":550},"size":{"width":880,"height":840}}}]}],"offset":{"left":231,"top":64}}],"connections":{"targetConnections":{},"sourceConnections":{}},"customStatesProperties":{},"pageId":"id1789764293867","version":17.004,"id":"id1789767590808","relativePoint":{"left":231,"top":64}}