|
10 | 10 | <script src="https://cdn.tailwindcss.com"></script> |
11 | 11 | </head> |
12 | 12 | <body class="bg-slate-950 text-slate-100 min-h-screen"> |
13 | | - <div class="max-w-6xl mx-auto px-4 py-10 space-y-8"> |
14 | | - <header class="space-y-4"> |
15 | | - <h1 class="text-4xl font-black tracking-tight">Snap2Map</h1> |
16 | | - <p class="text-slate-300 max-w-3xl"> |
17 | | - Photograph any trailboard or printed map, drop a few reference pairs, and watch your live GPS position glide across the photo. Works offline, with OpenStreetMap context when you are connected. |
18 | | - </p> |
19 | | - </header> |
20 | | - |
21 | | - <section class="bg-slate-900/70 border border-slate-700 rounded-xl p-6 space-y-4"> |
22 | | - <h2 class="text-xl font-semibold text-slate-100">1. Import map photo</h2> |
23 | | - <p class="text-sm text-slate-300">Use a sharp, well-lit image. Snap2Map keeps only an optimized copy on device.</p> |
24 | | - <label class="block w-full"> |
25 | | - <span class="sr-only">Upload map image</span> |
26 | | - <input id="mapImageInput" type="file" accept="image/*" capture="environment" class="block w-full text-sm text-slate-200 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-600 file:text-white hover:file:bg-blue-500"> |
27 | | - </label> |
28 | | - </section> |
29 | | - |
30 | | - <section class="bg-slate-900/70 border border-slate-700 rounded-xl p-6 space-y-4"> |
31 | | - <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between"> |
32 | | - <div> |
33 | | - <h2 class="text-xl font-semibold text-slate-100">2. Create reference pairs</h2> |
34 | | - <p id="pairStatus" class="text-sm text-slate-300">Tap “Start pair” and select a pixel on the photo followed by its real-world location on the map.</p> |
35 | | - </div> |
36 | | - <div class="flex flex-wrap gap-2"> |
37 | | - <button id="addPairButton" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-semibold hover:bg-blue-500 transition">Start pair</button> |
38 | | - <button id="usePositionButton" class="px-4 py-2 rounded-lg bg-emerald-600 text-white text-sm font-semibold hover:bg-emerald-500 transition">Use my position</button> |
39 | | - <button id="confirmPairButton" class="px-4 py-2 rounded-lg bg-violet-600 text-white text-sm font-semibold opacity-80 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-violet-500 transition" disabled>Confirm pair</button> |
40 | | - <button id="cancelPairButton" class="px-4 py-2 rounded-lg bg-slate-700 text-white text-sm font-semibold opacity-80 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-slate-600 transition" disabled>Cancel</button> |
41 | | - </div> |
| 13 | + <div class="min-h-screen flex flex-col lg:items-stretch"> |
| 14 | + <section class="w-full border-b border-slate-800 bg-slate-950/70 backdrop-blur-sm"> |
| 15 | + <div class="px-4 py-6 sm:px-6 lg:px-8"> |
| 16 | + <header class="space-y-3"> |
| 17 | + <h1 class="text-4xl font-black tracking-tight">Snap2Map</h1> |
| 18 | + <p class="text-slate-300 text-sm leading-relaxed lg:text-base"> |
| 19 | + Photograph any trailboard or printed map, drop a few reference pairs, and watch your live GPS position glide across the photo. Works offline, with OpenStreetMap context when you are connected. |
| 20 | + </p> |
| 21 | + </header> |
42 | 22 | </div> |
| 23 | + </section> |
43 | 24 |
|
44 | | - <div class="bg-slate-950/60 border border-slate-700 rounded-lg"> |
45 | | - <div class="flex"> |
46 | | - <button id="photoTabButton" class="flex-1 px-4 py-2 text-sm font-semibold bg-blue-600 text-white rounded-tl-lg">Photo</button> |
47 | | - <button id="osmTabButton" class="flex-1 px-4 py-2 text-sm font-semibold bg-white/10 text-blue-300 rounded-tr-lg">OpenStreetMap</button> |
48 | | - </div> |
49 | | - <div class="p-3"> |
50 | | - <div id="photoView" class="rounded-lg overflow-hidden border border-slate-800"> |
51 | | - <div id="photoMap" class="h-96"></div> |
| 25 | + <main class="flex-1 flex flex-col bg-slate-950 min-h-0"> |
| 26 | + <section class="flex-1 flex flex-col px-4 py-6 sm:px-6 lg:px-10"> |
| 27 | + <div class="flex-1 flex flex-col bg-slate-900/70 border border-slate-700 rounded-2xl shadow-xl overflow-hidden"> |
| 28 | + <div class="border-b border-slate-800 p-4 sm:p-6 space-y-4"> |
| 29 | + <div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between"> |
| 30 | + <div class="space-y-2"> |
| 31 | + <div class="flex items-center gap-2 text-xs uppercase tracking-wide text-slate-400"> |
| 32 | + <span class="inline-flex items-center gap-1 rounded-full bg-blue-600/20 px-3 py-1 font-semibold text-blue-200">Step 2</span> |
| 33 | + <span class="font-semibold text-slate-200">Create reference pairs</span> |
| 34 | + </div> |
| 35 | + <p id="pairStatus" class="text-sm text-slate-300 max-w-2xl"> |
| 36 | + Tap “Start pair” and select a pixel on the photo followed by its real-world location on the map. |
| 37 | + </p> |
| 38 | + </div> |
| 39 | + <div class="flex flex-wrap gap-2"> |
| 40 | + <label id="replacePhotoButton" for="mapImageInput" class="hidden cursor-pointer px-4 py-2 rounded-lg bg-slate-700 text-white text-sm font-semibold hover:bg-slate-600 transition">Replace photo</label> |
| 41 | + <button id="addPairButton" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-semibold hover:bg-blue-500 transition">Start pair</button> |
| 42 | + <button id="usePositionButton" class="px-4 py-2 rounded-lg bg-emerald-600 text-white text-sm font-semibold hover:bg-emerald-500 transition">Use my position</button> |
| 43 | + <button id="confirmPairButton" class="px-4 py-2 rounded-lg bg-violet-600 text-white text-sm font-semibold opacity-80 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-violet-500 transition" disabled>Confirm pair</button> |
| 44 | + <button id="cancelPairButton" class="px-4 py-2 rounded-lg bg-slate-700 text-white text-sm font-semibold opacity-80 disabled:opacity-40 disabled:cursor-not-allowed hover:bg-slate-600 transition" disabled>Cancel</button> |
| 45 | + </div> |
| 46 | + </div> |
52 | 47 | </div> |
53 | | - <div id="osmView" class="hidden rounded-lg overflow-hidden border border-slate-800"> |
54 | | - <div id="osmMap" class="h-96"></div> |
| 48 | + <div class="flex-1 flex flex-col"> |
| 49 | + <div class="flex border-b border-slate-800"> |
| 50 | + <button id="photoTabButton" class="flex-1 px-4 py-2 text-sm font-semibold bg-blue-600 text-white">Photo</button> |
| 51 | + <button id="osmTabButton" class="flex-1 px-4 py-2 text-sm font-semibold bg-white/10 text-blue-300">OpenStreetMap</button> |
| 52 | + </div> |
| 53 | + <div class="flex-1 p-3 sm:p-4 lg:p-6 flex flex-col"> |
| 54 | + <div id="photoView" class="relative flex-1 rounded-xl overflow-hidden border border-slate-800 bg-slate-950/60"> |
| 55 | + <div id="photoPlaceholder" class="absolute inset-0 flex items-center justify-center p-6 sm:p-10"> |
| 56 | + <div class="w-full max-w-xl space-y-5 rounded-2xl border border-slate-800 bg-slate-900/80 p-6 shadow-lg backdrop-blur"> |
| 57 | + <div class="space-y-2"> |
| 58 | + <span class="inline-flex items-center gap-2 rounded-full bg-blue-600/10 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-blue-200">Step 1</span> |
| 59 | + <h2 class="text-2xl font-semibold text-slate-100">Import map photo</h2> |
| 60 | + </div> |
| 61 | + <p class="text-sm text-slate-300">Choose a sharp, well-lit image of your trailboard or printed map. Snap2Map keeps only an optimized copy on your device.</p> |
| 62 | + <label class="block w-full"> |
| 63 | + <span class="sr-only">Upload map image</span> |
| 64 | + <input id="mapImageInput" type="file" accept="image/*" class="block w-full text-sm text-slate-200 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-600 file:text-white hover:file:bg-blue-500"> |
| 65 | + </label> |
| 66 | + <p class="text-xs text-slate-400">Tip: Mobile browsers let you take a new photo or pick one from your gallery when you tap the button above.</p> |
| 67 | + </div> |
| 68 | + </div> |
| 69 | + <div id="photoMap" class="h-full w-full min-h-[55vh] sm:min-h-[60vh] lg:min-h-[65vh]"></div> |
| 70 | + </div> |
| 71 | + <div id="osmView" class="hidden flex-1 rounded-xl overflow-hidden border border-slate-800 min-h-[55vh] sm:min-h-[60vh] lg:min-h-[65vh]"> |
| 72 | + <div id="osmMap" class="h-full w-full min-h-[55vh] sm:min-h-[60vh] lg:min-h-[65vh]"></div> |
| 73 | + </div> |
| 74 | + </div> |
| 75 | + </div> |
| 76 | + <div class="border-t border-slate-800 bg-slate-900/80 p-4 sm:p-5 space-y-3"> |
| 77 | + <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"> |
| 78 | + <div class="flex items-center gap-2 text-sm"> |
| 79 | + <span id="calibrationBadge" class="px-2 py-1 rounded text-xs font-semibold bg-gray-200 text-gray-700">No calibration</span> |
| 80 | + <span id="calibrationStatus" class="text-slate-200">Add at least two reference pairs to calibrate the photo.</span> |
| 81 | + </div> |
| 82 | + <div class="text-sm text-slate-300" id="residualSummary"></div> |
| 83 | + </div> |
| 84 | + <div class="text-sm text-blue-200" id="accuracyDetails"></div> |
| 85 | + <div class="text-sm text-slate-200" id="gpsStatus">Import a map photo to get started.</div> |
55 | 86 | </div> |
56 | 87 | </div> |
57 | | - </div> |
58 | | - </section> |
| 88 | + </section> |
59 | 89 |
|
60 | | - <section class="bg-slate-900/70 border border-slate-700 rounded-xl p-6 space-y-4"> |
61 | | - <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3"> |
62 | | - <div class="flex items-center gap-2 text-sm"> |
63 | | - <span id="calibrationBadge" class="px-2 py-1 rounded text-xs font-semibold bg-gray-200 text-gray-700">No calibration</span> |
64 | | - <span id="calibrationStatus" class="text-slate-200">Add at least two reference pairs to calibrate the photo.</span> |
| 90 | + <section class="px-4 pb-10 sm:px-6 lg:px-10"> |
| 91 | + <div class="bg-slate-900/60 border border-slate-700 rounded-2xl shadow-lg overflow-hidden"> |
| 92 | + <div class="flex items-center justify-between px-4 py-4 sm:px-6 sm:py-5"> |
| 93 | + <h2 class="text-lg font-semibold text-slate-100">Reference pairs</h2> |
| 94 | + <span class="text-xs uppercase tracking-wide text-slate-500 hidden sm:block">Manage or remove points below</span> |
| 95 | + </div> |
| 96 | + <div class="border-t border-slate-800 max-h-80 overflow-y-auto"> |
| 97 | + <table class="min-w-full text-left" id="pairTable"> |
| 98 | + <thead class="text-xs uppercase tracking-wider text-slate-400 border-b border-slate-800 bg-slate-900/70"> |
| 99 | + <tr> |
| 100 | + <th class="px-4 py-3">Pixel (x, y)</th> |
| 101 | + <th class="px-4 py-3">World (lat, lon)</th> |
| 102 | + <th class="px-4 py-3">Residual</th> |
| 103 | + <th class="px-4 py-3 text-right">Actions</th> |
| 104 | + </tr> |
| 105 | + </thead> |
| 106 | + <tbody id="pairTableBody" class="divide-y divide-slate-800/70"></tbody> |
| 107 | + </table> |
| 108 | + </div> |
65 | 109 | </div> |
66 | | - <div class="text-sm text-slate-300" id="residualSummary"></div> |
67 | | - </div> |
68 | | - <div id="accuracyDetails" class="text-sm text-blue-200"></div> |
69 | | - <div class="text-sm" id="gpsStatus">Import a map photo to get started.</div> |
70 | | - </section> |
71 | | - |
72 | | - <section class="bg-slate-900/70 border border-slate-700 rounded-xl p-6"> |
73 | | - <h2 class="text-xl font-semibold text-slate-100 mb-4">Reference pairs</h2> |
74 | | - <div class="overflow-x-auto"> |
75 | | - <table class="min-w-full text-left" id="pairTable"> |
76 | | - <thead class="text-xs uppercase tracking-wider text-slate-400 border-b border-slate-700"> |
77 | | - <tr> |
78 | | - <th class="px-3 py-2">Pixel (x, y)</th> |
79 | | - <th class="px-3 py-2">World (lat, lon)</th> |
80 | | - <th class="px-3 py-2">Residual</th> |
81 | | - <th class="px-3 py-2 text-right">Actions</th> |
82 | | - </tr> |
83 | | - </thead> |
84 | | - <tbody id="pairTableBody" class="divide-y divide-slate-800"></tbody> |
85 | | - </table> |
86 | | - </div> |
87 | | - </section> |
| 110 | + </section> |
| 111 | + </main> |
88 | 112 | </div> |
89 | 113 |
|
90 | 114 | <div id="toastContainer" class="fixed bottom-6 left-1/2 -translate-x-1/2 md:left-auto md:right-8 md:translate-x-0 z-50 space-y-2 w-[calc(100%-2rem)] max-w-sm pointer-events-none"></div> |
|
0 commit comments