/* Map builder — app styles (from design/mockup-figma-v2.html) */
:root{
  --ink:#1e2328; --dim:#6b7480; --faint:#9aa3ad;
  --panel:#ffffff; --canvas:#eef0f2;
  --line:#e5e8eb; --line-2:#d7dbe0;
  --hover:#f4f6f8;
  --accent:#2d7ff9; --accent-soft:rgba(45,127,249,.10); --accent-line:rgba(45,127,249,.5);
  --r:7px;
  --font:-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --topH:48px; --railW:60px; --leftW:200px; --rightW:240px;
}
*{box-sizing:border-box;margin:0;padding:0}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
body{font-family:var(--font);color:var(--ink);height:100vh;overflow:hidden;background:var(--canvas);font-size:13px}
button{font:inherit}
.ico{display:block;width:15px;height:15px;stroke:currentColor;stroke-width:1.7;fill:none;stroke-linecap:round;stroke-linejoin:round}
.ico.sm{width:13px;height:13px}
.ico.md{width:18px;height:18px}

/* ---------- top nav ---------- */
.top{position:fixed;top:0;left:0;right:0;height:var(--topH);background:var(--panel);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:4px;padding:0 12px 0 0;z-index:40}
.logo{display:flex;align-items:center;gap:8px;font-weight:700;font-size:13.5px;letter-spacing:-.01em;width:calc(var(--railW) + var(--leftW));height:100%;padding:0 12px;flex:none;color:inherit;text-decoration:none}
.logo img{width:20px;height:20px;border-radius:50%}
.betatag{font-size:9.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--dim);background:var(--hover);border:1px solid var(--line-2);border-radius:999px;padding:1px 6px}
/* .loadedchip/.lm-* kept for the design/ mockups that still reference them —
   the live top bar replaced the chip with .doctitle below (2026-07-19,
   planning/feature-map-modes.md) */
.loadedchip{display:flex;align-items:center;gap:2px;max-width:220px;background:var(--accent-soft);border:1px solid var(--accent-line);border-radius:999px;padding:3px 4px 3px 10px;flex:none}
.loadedchip[hidden]{display:none}
.lm-name{font-size:12px;font-weight:600;color:var(--accent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lm-x{display:grid;place-items:center;width:18px;height:18px;border:none;background:none;border-radius:50%;color:var(--accent);cursor:pointer;padding:0}
.lm-x:hover{background:var(--accent-soft)}
.lm-x .ico{width:12px;height:12px}
/* Mode 2 — a saved map is open (js/mymaps.js setCurrent): branding shrinks
   to the bare icon, centered over the rail column below it (width:--railW +
   centering matches .railbtn's own, verified in the reference mockup
   design/mapname-option-b2-icon-title.html); wordmark + Beta tag drop with
   it and the map's name takes their place as an editable doc title. */
.logo.icononly{width:var(--railW);justify-content:center;padding:0}
.logo.icononly .logotext,.logo.icononly .betatag{display:none}
.logo.icononly img{width:22px;height:22px}
.doctitle{display:flex;align-items:center;height:32px;padding:0 10px 0 8px;border-radius:6px;cursor:text;max-width:320px;flex:none}
.doctitle[hidden]{display:none} /* display:flex would beat the hidden attr */
.doctitle:hover{background:var(--hover)}
.doctitle .dt-name{font-size:11px;font-weight:650;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* mid-edit: the name swaps out for a real input (js/mymaps.js) */
.doctitle.editing:hover{background:none}
.doctitle.editing .dt-name{display:none}
.dt-input{font-size:14px;font-weight:650;color:var(--ink);border:none;outline:none;background:#fff;box-shadow:0 0 0 2px var(--accent);border-radius:5px;padding:2px 6px;width:220px;font-family:inherit}
/* the title's actions menu (Rename / Duplicate / Delete) off a small chevron */
.dt-chev{width:24px;height:24px;border:none;background:none;border-radius:6px;color:var(--dim);cursor:pointer;display:grid;place-items:center;padding:0}
.dt-chev:hover{background:var(--hover);color:var(--ink)}
#doctitle-menu{top:32px}
.navfeedback{font-size:11.5px;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.navfeedback a{color:var(--dim);text-decoration:none}
.navfeedback a:hover{color:var(--accent);text-decoration:underline}
.vsep{width:1px;height:22px;background:var(--line);margin:0 6px}
.navicon{width:32px;height:32px;border:none;background:none;border-radius:6px;color:var(--ink);display:grid;place-items:center;cursor:pointer}
.navicon:hover{background:var(--hover)}
.navicon.on{background:var(--accent-soft);color:var(--accent)}
.navicon.locked{opacity:.45}
.navicon:disabled{color:var(--line-2);cursor:default}
.navicon:disabled:hover{background:none}
.spacer{flex:1}
/* .savebtn: unused live (Save is a plain .navicon now, next to Help) — kept
   only because several design/ mockups still link the real app.css and
   reference it (same convention as .loadedchip). */
.savebtn{height:32px;width:96px;border-radius:6px;background:var(--hover);border:1px solid var(--line-2);color:var(--ink);font-size:13px;font-weight:600;cursor:pointer}
.savebtn:hover{background:var(--line-2)}
/* Publish + Download share one width so they read as a paired action, not
   two differently-sized buttons — Publish is the lower-emphasis (outlined,
   no fill) of the pair, Download stays the one solid/primary action. */
.navbtn.publish{height:32px;width:96px;padding:0;border-radius:6px;background:none;border:1px solid var(--accent-line);color:var(--accent);font-size:13px;font-weight:600;cursor:pointer}
.navbtn.publish:hover{background:var(--accent-soft)}
/* Terrain has no publish option — grayed but still clickable (unlike a real
   disabled button) so the click still opens #pubtip explaining why. */
.navbtn.publish.disabled{opacity:.45}
.navbtn.publish.disabled:hover{background:none}
/* Anchored under the top bar itself (js/codeview.js positions it against
   .btn-publish/.top) rather than .locktip's usual .maptools-relative
   top:42px — same box styling, different anchor. */
#pubtip{top:100%;margin-top:6px}
.navbtn.export{height:32px;width:96px;padding:0;border-radius:6px;border:none;background:var(--accent);color:#fff;font-size:13px;font-weight:600;cursor:pointer}
.navbtn.export:hover{filter:brightness(1.06)}
.acct{width:32px;height:32px;border-radius:50%;border:none;padding:0;cursor:pointer;background:linear-gradient(135deg,#2d7ff9,#7db4ff);color:#fff;display:grid;place-items:center;font-size:11px;font-weight:640}
.acct.loggedout{width:auto;height:32px;padding:0 14px;border-radius:6px;background:none;border:1px solid var(--accent-line);color:var(--accent);font-size:12.5px;font-weight:600}
.acct.loggedout:hover{background:var(--accent-soft)}

.navdrop{position:relative}
.dropmenu{position:absolute;top:36px;left:0;background:#fff;border:1px solid var(--line);border-radius:8px;box-shadow:0 8px 24px rgba(28,44,60,.16);padding:4px;min-width:150px;z-index:60}
/* flex, not block: an icon+text item (data-icon span + label, e.g.
   #doctitle-menu/#mymaps-rowmenu/#overlay-add-menu) needs them on one row,
   not stacked — a plain text-only item (e.g. #menu-account/#menu-language)
   renders the same either way, so one rule covers every .dropmenu instance
   rather than each icon-bearing menu needing its own ID-scoped override. */
.dropmenu button{display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:7px 10px;border:none;background:none;border-radius:5px;cursor:pointer;font-size:12.5px}
.dropmenu button:hover{background:var(--hover)}
/* .dropmenu button's reset (0,1,1) otherwise beats .mini-btn (0,1,0) and
   silently strips its accent background/color — restore it here. */
.dropmenu button.mini-btn{background:var(--accent);color:#fff;text-align:center}
.dropmenu button.mini-btn:hover{background:var(--accent)}
.dropmenu button.on{color:var(--accent);font-weight:600}
/* Named "align-right" not "right" — a plain .right class collides with the
   #inspector panel's .right rule below (same specificity, later in source,
   so it silently won: position:fixed + bottom:0 + width:288px stretched any
   .dropmenu.right dropdown to the full viewport height). */
.dropmenu.align-right{left:auto;right:0}
.dm-section-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);padding:8px 6px 4px}

/* account menu */
.acctmenu{width:230px;max-height:75vh;overflow-y:auto;padding:10px}
.acct-form{display:flex;flex-direction:column;gap:8px}
.acct-form input{height:32px;border:1px solid var(--line-2);border-radius:6px;padding:0 10px;font:inherit;font-size:12.5px;outline:none;width:100%}
.acct-form input:focus{border-color:var(--accent-line)}
.acct-row{display:flex;gap:6px}
.acct-row .mini-btn{flex:1}
.mini-btn.ghost{background:none;color:var(--accent);border:1px solid var(--accent-line)}
.acct-google{display:flex;align-items:center;justify-content:center;gap:7px;height:32px;border:1px solid var(--line-2);border-radius:6px;background:none;font-size:12.5px;font-weight:600;cursor:pointer;width:100%}
.acct-google:hover{background:var(--hover)}
.acct-info{padding:2px 4px 8px;border-bottom:1px solid var(--line);margin-bottom:6px}
.acct-email{font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acct-tier{font-size:11.5px;color:var(--dim);margin-top:2px}
#acct-signout,#acct-portal,#acct-plans,#acct-exports{display:block;width:100%;text-align:left;padding:7px 6px;border:none;background:none;border-radius:5px;cursor:pointer;font-size:12.5px}
#acct-signout:hover,#acct-portal:hover,#acct-plans:hover,#acct-exports:hover{background:var(--hover)}
.acct-cta{width:100%;margin-bottom:4px}
.acct-hint{padding:0 2px 8px}
.acctmenu .acct-row{margin-bottom:6px}
.acct-legal{border-top:1px solid var(--line);margin-top:6px;padding:8px 4px 2px;font-size:11px;color:var(--faint)}
.acct-legal a{color:var(--dim);text-decoration:none}
.acct-legal a:hover{text-decoration:underline}

/* help/use-cases menu — same dropmenu base as the account menu. A 2-column
   card grid of the 8 SEO use-case pages (emoji instead of the Lucide icon
   set — cheaper to keep in sync as pages are added/renamed, and reads
   warmer for a "solutions" menu than the app's usual line-icon style),
   plus "How to use" pinned at the bottom in the same muted, border-top
   style .acct-legal already uses for Help/Terms/Privacy in the account
   menu (2026-07-23 design review — design/helpmenu-footer-index.html). */
.helpmenu{width:420px;max-height:80vh;overflow-y:auto;padding:10px}
.helpmenu .dm-section-label:first-child{padding-top:4px}
.helpmenu-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.helpmenu-grid a{display:flex;flex-direction:column;align-items:flex-start;gap:6px;padding:12px;border:1px solid var(--line);border-radius:8px;font-size:12px;line-height:1.3;color:inherit;text-decoration:none}
.helpmenu-grid a:hover{background:var(--hover);border-color:var(--line-2)}
.helpmenu-grid a .emoji{font-size:18px;line-height:1}
/* A separate full-bleed line, not a border on the row itself — negative
   margin cancels .helpmenu's own 10px padding so the line runs truly edge
   to edge, independent of .helpmenu-howto's padding below it (mixing the
   two, via a border-top + negative margin on the row itself, was what
   broke left-alignment with the grid last time). */
.helpmenu-divider{height:1px;background:var(--line);margin:10px -10px 0}
/* Sibling of .helpmenu-grid, not nested inside it, so it's already full
   width and naturally left/right-aligned with the grid above. Equal
   padding on every side, color --ink (matches the rest of the app's body
   text) instead of --faint, per Aaron's call. */
.helpmenu-howto{padding:12px 12px 12px 17px;border-radius:8px;font-size:11.5px;color:var(--ink);display:flex;align-items:center;gap:6px;text-decoration:none}
.helpmenu-howto:hover{background:var(--hover)}
.helpmenu-howto .ico{width:15px;height:15px}
.helpmenu-howto .emoji{font-size:13px;line-height:1}

/* ---------- icon rail ---------- */
.rail{position:fixed;top:var(--topH);left:0;bottom:0;width:var(--railW);background:var(--panel);border-right:1px solid var(--line);z-index:36;display:flex;flex-direction:column;padding:8px 0}
.railbtn{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;height:60px;border:none;background:none;color:var(--dim);cursor:pointer}
.railbtn:hover{background:var(--hover)}
.railbtn .lbl{font-size:10.5px;letter-spacing:.01em}
.railbtn.active{background:var(--accent-soft);color:var(--accent)}
.railbtn.active .lbl{font-weight:640}

/* ---------- left secondary panel ---------- */
.left{position:fixed;top:var(--topH);left:var(--railW);bottom:0;width:var(--leftW);background:var(--panel);border-right:1px solid var(--line);z-index:35;overflow:hidden}
/* Preset panel "expand" — widens over the canvas (already higher z-index)
   and switches the list to a grid so more cards are visible at once; the
   right inspector is already hidden while on the Preset tab, so nothing
   else needs to move out of the way. */
body.preset-expanded .left{width:calc(100vw - var(--railW))}
body.preset-expanded .presetlist{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));grid-auto-rows:min-content;gap:14px;align-content:start;align-items:start}
body.preset-expanded .presetcard{width:auto}
.panelbody{height:100%;display:flex;flex-direction:column}
.panelbody[hidden]{display:none}
.lhead{display:flex;align-items:center;justify-content:space-between;height:38px;padding:0 12px;border-bottom:1px solid var(--line);flex:none}
.lhead .ttl{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}

/* layers tree */
.tree{flex:1;overflow-y:auto;padding:6px}
.tree.disabled{opacity:.45;pointer-events:none}
.row{display:flex;align-items:center;gap:6px;height:30px;padding:0 8px;border-radius:6px;cursor:pointer;position:relative}
.row:hover{background:var(--hover)}
.row:hover .eye{color:var(--ink)}
.row.sel{background:var(--accent-soft)}
.row.sel .nm{font-weight:600}
.row .chev{width:14px;height:14px;display:grid;place-items:center;color:var(--faint);flex:none}
.row .chev.leaf{visibility:hidden}
.row .sw{width:14px;height:14px;border-radius:4px;flex:none;box-shadow:inset 0 0 0 1px rgba(20,30,45,.18)}
.row .sw.cat{background:none;box-shadow:none;display:grid;place-items:center;color:var(--dim)}
.row .nm{flex:1;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.row .eye{color:var(--dim);display:grid;place-items:center;width:20px;height:20px;border:none;background:none;cursor:pointer}
.row.off .nm{color:var(--faint)}
.row.off .eye{color:var(--faint)}
.indent{width:16px;flex:none}

/* basemap type (Map / Satellite / Terrain) */
.basemaptypes{display:flex;gap:6px;padding:8px;flex:none}
.basemapcard{flex:1;padding:8px 4px;border-radius:8px;cursor:pointer;border:1px solid var(--line);background:none;text-align:center;font-size:12px;font-weight:600;color:var(--dim)}
.basemapcard:hover{background:var(--hover)}
.basemapcard.sel{background:var(--accent-soft);border-color:var(--accent-line);color:var(--accent)}

/* presets — preview tiles; tap applies */
.presetimport{display:flex;flex-direction:column;gap:6px}
.presetimport textarea{border:1px solid var(--line-2);border-radius:6px;padding:7px 9px;font:inherit;font-size:11.5px;outline:none;resize:vertical;min-height:44px;font-family:ui-monospace,Consolas,monospace}
.presetimport textarea:focus{border-color:var(--accent-line)}
.presetlist{flex:1;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:10px}
.presetlist.disabled{opacity:.45;pointer-events:none}
/* Shared "this tab is disabled on Satellite/Terrain" banner (Preset + Style
   tabs) — sits at the top of the panel, above the (dimmed) list/tree, so it
   reads as an explanation rather than a caption underneath. */
.disablednote{margin:10px 12px;padding:10px 12px;background:var(--hover);border:1px solid var(--line-2);border-radius:8px;font-size:11.5px;color:var(--dim);line-height:1.45}
/* flex-shrink:0 is the fix here — .presetlist already scrolls
   (overflow-y:auto), but a flex column's items still shrink to fit by
   default even so, so without this the cards themselves got squeezed
   (image + name row clipped) instead of the list just scrolling past them */
.presetcard{position:relative;display:flex;flex-direction:column;flex-shrink:0;width:100%;padding:0;border-radius:9px;cursor:pointer;border:1.5px solid var(--line);background:var(--panel);text-align:left;overflow:hidden}
.presetcard:hover{border-color:var(--accent-line)}
.presetcard.sel{border-color:var(--accent)}
.presetcard .pp-map{width:100%;display:block;flex:none}
.presetcard .prow{display:flex;align-items:center;gap:6px;padding:6px 9px;border-top:1px solid var(--line);flex:none}
.presetcard .pname{font-size:12px;font-weight:600;line-height:1.3;flex:1;word-break:break-word}
.presetcard .check{color:var(--accent);opacity:0;display:grid;place-items:center}
.presetcard.sel .check{opacity:1}
/* 3-dot row menu — one shared #mymaps-rowmenu (below), not a per-card dropdown */
.presetcard .pmore{color:var(--faint);flex:none;display:grid;place-items:center;width:22px;height:22px;border-radius:5px}
.presetcard .pmore:hover{color:var(--ink);background:var(--hover)}
/* .dropmenu is normally position:absolute inside a .navdrop{position:relative}
   (see #doctitle-drop) — this one is shared across every scrolling row in
   .mymaplist, so it's positioned in fixed viewport coordinates instead
   (js/mymaps.js openRowMenu()), same reasoning as the CSV importer's .mp-menu. */
#mymaps-rowmenu{position:fixed}

/* admin preset publishing block */
.admin-preset{flex:none;border-top:1px solid var(--line);padding:10px;display:flex;flex-direction:column;gap:6px}
.admin-preset[hidden]{display:none} /* display:flex would beat the hidden attr */

/* import */
.importbody{padding:14px;display:flex;flex-direction:column;gap:14px;overflow-y:auto;flex:1}
.fieldlabel{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);margin-bottom:6px}
.pasterow{display:flex;gap:6px}
.pasterow input{flex:1;height:32px;border:1px solid var(--line-2);border-radius:6px;padding:0 10px;font:inherit;font-size:12.5px;outline:none;min-width:0}
.mini-btn{height:32px;padding:0 12px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-size:12.5px;font-weight:600;cursor:pointer}
.mini-btn:disabled{background:var(--line-2);cursor:default}
/* An <a class="mini-btn"> (e.g. #maplimit-upgrade) gets none of a <button>'s
   default centered-content rendering — plain inline text just sits at its
   line-height position instead of centering in the fixed height, and picks
   up the browser default underline. */
a.mini-btn{display:inline-flex;align-items:center;justify-content:center;text-decoration:none}
.hint{font-size:11.5px;color:var(--faint);line-height:1.45}
.ordiv{display:flex;align-items:center;gap:8px;color:var(--faint);font-size:11px}
.ordiv:before,.ordiv:after{content:"";flex:1;height:1px;background:var(--line)}
.dropzone{border:1.5px dashed var(--line-2);border-radius:8px;padding:22px 10px;display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--faint);font-size:12px;text-align:center;cursor:pointer}
.dropzone.dragover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}

/* my maps — same card look as presets (screenshot + name + actions) */
.mymaplist{flex:1;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:10px}

/* save/rename dialog input */
.dlg-input{width:100%;height:34px;border:1px solid var(--line-2);border-radius:6px;padding:0 10px;font:inherit;font-size:13px;outline:none}
.dlg-input:focus{border-color:var(--accent-line)}
.dlg-note{margin-top:10px;font-size:12px;line-height:1.5;color:var(--dim);background:var(--accent-soft);border:1px solid var(--accent-line);border-radius:6px;padding:8px 10px}
.dlg-note[hidden]{display:none}

/* delete-map confirm — first destructive confirm in the app; #d33 is the
   shared danger red reused wherever else something destructive needs one */
.mini-btn.danger{background:#d33}
.mini-btn.danger:hover{background:#b82c2c}
.confirmtext{font-size:13px;line-height:1.55}
.confirmtext .dlg-warn-name{font-weight:650}

/* ---------- canvas / map ---------- */
.canvas{position:fixed;top:var(--topH);left:calc(var(--railW) + var(--leftW));right:var(--rightW);bottom:0;background:var(--canvas);z-index:10}
body.inspector-hidden .canvas{right:0}
body.inspector-floating .canvas{right:0}
body.inspector-floating .right{box-shadow:-8px 0 24px rgba(15,25,40,.18)}

/* ---------- guided export mode ---------- */
/* chrome collapses: rail + left panel + inspector gone, nav shows only the
   logo and the active export step's controls */
body.export-mode .rail,
body.export-mode .left,
body.export-mode .right{display:none}
body.export-mode .canvas{left:0;right:0}
body.export-mode .top > *:not(.logo):not(.exportnav){display:none}
.exportnav{display:flex;align-items:center;gap:10px;flex:1;min-width:0;padding:0 4px}
.exportnav[hidden]{display:none}
.exd-instr{font-size:12.5px;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.exd-detail-label{font-size:12px;font-weight:600;white-space:nowrap;min-width:170px}
#exd-detail-slider{width:220px}
#export-box{position:absolute;z-index:25;border:2px solid var(--accent);box-shadow:0 0 0 100000px rgba(20,30,45,.35);pointer-events:none}
/* MapLibre defaults to a grab-hand cursor at rest; features are clickable
   here, so use a pointer at rest and keep the grabbing hand only while the
   mouse is actually held down for panning. */
#map .maplibregl-canvas-container.maplibregl-interactive{cursor:pointer}
#map .maplibregl-canvas-container.maplibregl-interactive:active{cursor:grabbing}
#map{position:absolute;inset:0}
#setup-note{position:absolute;top:12px;left:50%;transform:translateX(-50%);background:#fff8e6;border:1px solid #eadfb8;color:#6e5c1e;border-radius:8px;padding:8px 14px;font-size:12px;z-index:20;max-width:80%;box-shadow:0 4px 14px rgba(28,44,60,.10)}
#setup-note code{background:rgba(0,0,0,.06);padding:1px 4px;border-radius:3px}
#measure-badge{position:absolute;left:50%;transform:translateX(-50%);bottom:18px;background:#fff;border:1px solid var(--line);border-radius:999px;padding:8px 16px;font-size:12.5px;font-weight:600;box-shadow:0 6px 20px rgba(28,44,60,.14);z-index:20}

/* ---------- bottom-right corner: privacy/terms ---------- */
.cornerbar{position:absolute;right:16px;bottom:10px;display:flex;align-items:center;gap:7px;background:rgba(255,255,255,.92);border:1px solid var(--line);border-radius:999px;padding:3px 12px;font-size:11px;color:var(--dim);z-index:20;box-shadow:0 4px 14px rgba(28,44,60,.08)}
.cornerbar a{color:var(--dim);text-decoration:none}
.cornerbar a:hover{color:var(--ink);text-decoration:underline}
.cornerbar .sep{color:var(--faint)}

/* ---------- support banner (free tier only, js/ads.js) ---------- */
.supportbanner{position:absolute;left:50%;bottom:18px;transform:translateX(-50%);display:flex;align-items:center;gap:16px;background:#fff;border:1px solid var(--line);border-radius:16px;padding:14px 14px 14px 22px;box-shadow:0 10px 30px rgba(28,44,60,.16);z-index:20;font-size:12.5px;max-width:min(90vw,620px)}
.supportbanner[hidden]{display:none} /* display:flex would beat the hidden attr */
.supportbanner span{color:var(--ink)}
.supportbanner .mini-btn{flex:none;white-space:nowrap}
.supportbanner-close{width:22px;height:22px;border:none;background:var(--hover);border-radius:50%;cursor:pointer;color:var(--dim);display:grid;place-items:center;flex:none}
.supportbanner-close:hover{background:var(--line-2)}
/* Same fix as .overlaybar below, same cause: on the Overlay tab the inspector
   floats over the map instead of shrinking it (.canvas goes right:0,
   body.inspector-floating), so a flat left:50% centers on the whole
   (partly-covered) canvas box and reads as shifted right of the actually-
   visible area. The banner can show on any tab (free tier, not tab-gated),
   so it hits this whenever it's up while Overlay is active. */
body.inspector-floating .supportbanner{left:calc(50% - var(--rightW) / 2)}

/* ---------- overlay draw/place toolbar (js/overlay.js, js/ui.js switchPanel;
   design/overlaybar-option-a-fullbar.html) — desktop only, docked to the
   bottom of the canvas whenever the Overlay tab is active. Section has no map
   geometry to draw/place, so it stays the lhead's own header button instead
   of living here. ---------- */
.overlaybar{position:absolute;left:50%;bottom:16px;transform:translateX(-50%);display:flex;align-items:center;gap:1px;background:#fff;border:1px solid var(--line);border-radius:12px;padding:6px;box-shadow:0 8px 24px rgba(28,44,60,.18);z-index:26}
.overlaybar[hidden]{display:none} /* display:flex above would beat the hidden attr */
/* Overlay-tab props float over the map (.canvas goes right:0, .right sits on
   top of it — see body.inspector-floating below) rather than pushing the
   canvas over, so .canvas's own box is full-viewport-width even though
   --rightW of it is visually covered by the floating panel. Centering at a
   flat 50% of that box (as if the whole thing were free map) reads as
   shifted right of the actually-visible area by half of --rightW — pull it
   back by that much whenever the panel is actually floating on top.
   body.inspector-hidden (interactive embed off + nothing selected) has no
   panel covering anything, so the plain 50% above is already correct there. */
body.inspector-floating .overlaybar{left:calc(50% - var(--rightW) / 2)}
.overlaybar button{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;width:54px;height:50px;border:none;background:none;border-radius:8px;color:var(--dim);cursor:pointer;font-size:9.5px;font-family:inherit;line-height:1}
.overlaybar button:hover{background:var(--hover);color:var(--ink)}
.overlaybar button.on{background:var(--accent-soft);color:var(--accent)}
.overlaybar .obsep{width:1px;height:30px;background:var(--line);margin:0 3px;flex:none}
/* the toolbar and the support banner dock to the same bottom-center spot —
   give the banner room above it rather than letting them overlap */
.canvas:has(#overlaybar:not([hidden])) .supportbanner{bottom:90px}
/* #toast (js/ui.js) is also bottom-center, and every startDraw() call shows
   one ("Click to add points…", "Drag on the map to draw a…") — at its
   default position it lands directly on top of the toolbar, at a HIGHER
   z-index (100, a #body-level sibling of .canvas, so .overlaybar's own
   z-index:26 can never outrank it from inside .canvas's stacking context),
   silently eating the very click that would start the next shape. Raise it
   clear of the bar instead. */
body:has(#overlaybar:not([hidden])) .toast{bottom:96px}

/* ---------- floating map toolbar: Search / Map style / Measure / Zoom ---------- */
/* Search is a permanent expanded bar (not a toggle) — Map style/Zoom instead
   expand THIS pill in place (icons swap out for that tool's own row) rather
   than opening a second floating box below it. Only one of .maptools-icons /
   #menu-mapstyle / #menu-detail is ever visible at a time, alongside the
   always-visible search bar, so the pill's width just follows whatever's
   currently showing. */
.maptools{position:absolute;top:12px;left:12px;display:flex;align-items:center;gap:1px;background:#fff;border:1px solid var(--line);border-radius:9px;padding:3px;box-shadow:0 8px 24px rgba(28,44,60,.16);z-index:26}
.maptools .navicon{border-radius:6px}
.maptools .divider{width:1px;height:18px;background:var(--line-2);margin:0 3px}

/* the permanently-expanded search bar — Map style/Zoom are plain dropdowns
   (.navdrop/.dropmenu below), not part of this */
.maptools-panel{display:flex;align-items:center;gap:8px;height:32px;padding:0 4px 0 8px;color:var(--dim);position:relative}
.maptools-panel.locked{opacity:.45}
.maptools-panel #search-input{flex:1;min-width:200px;border:none;outline:none;font:inherit;font-size:13px;color:var(--ink)}
.search-clear{border:none;background:none;color:var(--faint);cursor:pointer;display:grid;place-items:center;width:22px;height:22px;flex:none;border-radius:5px}
.search-clear:hover{background:var(--hover);color:var(--ink)}

/* Level-of-detail dropdown (below the zoom icon) */
.detailpop{width:200px;padding:12px;display:flex;flex-direction:column;gap:8px}
/* .detailpop's own `display:flex` otherwise ties with (and as an author rule,
   beats) the browser's default [hidden]{display:none} — needs restating here */
.detailpop[hidden]{display:none}
.detailpop input[type=range]{width:100%}
.detail-label{font-size:12px;color:var(--dim);text-align:center}

/* anchored to #searchbox itself so it stays under just the search bar's own width */
.searchresults{position:absolute;top:38px;left:0;right:0;background:#fff;border:1px solid var(--line);border-radius:8px;box-shadow:0 8px 24px rgba(28,44,60,.16);overflow:hidden}
.searchresults button{display:block;width:100%;text-align:left;border:none;background:none;padding:9px 12px;cursor:pointer;font-size:12.5px}
.searchresults button:hover{background:var(--hover)}
.searchresults .sub{display:block;font-size:11px;color:var(--faint)}

/* Pro-lock popover (Search focus / Map style click, signed-in free tier) —
   anchored under whichever button triggered it (MBUI.showLockTip() sets
   `left` inline against .maptools, its positioned ancestor); replaces the
   account dropdown + bottom toast that used to fire instead. */
.locktip{position:absolute;top:42px;background:#fff;border:1px solid var(--line);border-radius:8px;box-shadow:0 8px 24px rgba(28,44,60,.16);padding:12px;width:210px;z-index:27}
.locktip[hidden]{display:none}
.locktip p{font-size:12.5px;line-height:1.45;color:var(--ink);margin-bottom:10px}
.locktip .mini-btn{width:100%;display:flex;align-items:center;justify-content:center;text-decoration:none}

/* ---------- right inspector ---------- */
.right{position:fixed;top:var(--topH);right:0;bottom:0;width:var(--rightW);background:var(--panel);border-left:1px solid var(--line);z-index:35;display:flex;flex-direction:column;overflow-y:auto}
.right[hidden]{display:none}
.insp-head{padding:14px 16px 12px;border-bottom:1px solid var(--line)}
.insp-head .crumb{font-size:11px;color:var(--faint);margin-bottom:3px}
.insp-head .crumb:empty{display:none;margin-bottom:0}
.insp-head .title{font-size:15px;font-weight:680;letter-spacing:-.01em}
/* editable overlay-item name at the top of the right panel (overlay
   inspector only — reuses .title's font-size/weight, incl. the mobile
   override below, via the shared class) */
.insp-head .title.title-edit{display:block;width:100%;font:inherit;border:1px solid transparent;border-radius:5px;padding:2px 4px;margin:-2px -4px;background:none;color:inherit;outline:none}
.insp-head .title.title-edit:hover{border-color:var(--line-2)}
.insp-head .title.title-edit:focus{border-color:var(--accent-line);background:#fff}
.section{border-bottom:1px solid var(--line);padding:14px 16px}
.sec-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.sec-top .st{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dim)}
.sec-top .toggle{width:22px;height:22px;border-radius:5px;border:none;background:none;color:var(--ink);cursor:pointer;display:grid;place-items:center}
.sec-top .toggle:hover{background:var(--hover)}
.section.off{opacity:.55}
.section.off .toggle{color:var(--faint)}
.prop{display:flex;align-items:center;gap:8px;margin-bottom:9px}
.prop:last-child{margin-bottom:0}
/* plain grouping rule between rows within a section (currently: Road's
   Geometry, above Stroke) — no label/interaction, just a visual break */
.prop-divider{height:1px;background:var(--line);margin-bottom:9px}
.prop .plabel{width:44px;font-size:12px;color:var(--dim)}
/* Fill/Stroke rows specifically: force the label to a true fixed width —
   plain `width` on a flex item is only a hint (a single unbreakable word
   like "Stroke" can still report a larger min-content size and win), which
   was throwing off the colorfield + propeye position after it, row to row */
.propcolor .plabel{flex:none;overflow:hidden;white-space:nowrap}
.colorfield{flex:1;min-width:0;display:flex;align-items:center;gap:6px;height:32px;border:1px solid var(--line-2);border-radius:6px;padding:0 6px;background:#fff;position:relative}
/* being itself a flex container, .colorfield's default min-width:auto pins
   it to its own content's min size, ignoring `flex:1` — that was pushing the
   whole row (and the propeye after it) past the section's right edge */
/* property hidden (eye toggled off) — stays in place (so the box + the
   .propeye after it never shift position) but reads as disabled */
.colorfield.off{opacity:.5;background:var(--hover)}
.colorfield.off .chip,.colorfield.off input{cursor:not-allowed}
/* same lighter --faint the disabled Width slider's "Width" title uses (see
   .prop-disabled .plabel below) — a hidden Fill/Stroke row's own title
   should read just as de-emphasized, not stay full-strength while only the
   swatch/eye next to it dim. */
.prop.off .plabel{color:var(--faint)}
/* native select arrow sits flush against the box's right edge regardless of
   padding in Chromium — appearance:none + a hand-drawn chevron is the only
   way to actually control its inset */
.prop select.dlg-input{flex:1;width:auto;height:32px;appearance:none;-webkit-appearance:none;padding-right:30px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7480' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;background-size:14px}
.colorfield .chip{width:16px;height:16px;border-radius:4px;box-shadow:inset 0 0 0 1px rgba(20,30,45,.16);flex:none;cursor:pointer;border:none;padding:0}
.colorfield .chip:disabled{cursor:not-allowed}
.colorfield .hex{font-size:11.5px;font-variant-numeric:tabular-nums;flex:1;border:none;outline:none;background:none;font-family:inherit;min-width:0;text-transform:uppercase}
/* propcolor.mixed: a parent row (e.g. Landscape) whose children currently
   disagree — the swatch still paints a plain neutral fallback (inline style
   from MBColorPicker.paintChip, so a CSS background here would just lose to
   it), so 'Mixed' in dimmed italic text is the only signal, same spot a real
   hex value would sit. */
.propcolor.mixed .hex{color:var(--faint);font-style:italic;text-transform:none}
.propeye{width:22px;height:22px;flex:none;border-radius:5px;border:none;background:none;color:var(--ink);cursor:pointer;display:grid;place-items:center}
.propeye:hover{background:var(--hover)}
.propeye.off{color:var(--faint)}
/* fixed-width trailing column, shared with .rv (the Opacity/Width readout)
   below — this is what actually makes .colorfield and the slider render
   the same width: both rows reserve the identical trailing-column width,
   so their flex:1 control gets the identical remaining space. The icon
   itself stays a normal 22px square button, right-aligned within the
   wider column rather than stretched to fill it. */
.propaccessory{width:36px;flex:none;display:flex;justify-content:flex-end;align-items:center}
.insp-empty{padding:20px 16px;color:var(--faint);font-size:12.5px;line-height:1.5}

/* ---------- shared color picker popover (js/colorpicker.js) ---------- */
.cpicker{position:fixed;z-index:200;width:232px;background:#fff;border:1px solid var(--line);border-radius:10px;box-shadow:0 12px 40px rgba(15,25,40,.25);padding:12px}
.cpicker[hidden]{display:none}
.cp-sl{position:relative;width:100%;height:140px;border-radius:6px;cursor:crosshair;margin-bottom:10px;user-select:none}
.cp-sl-dot{position:absolute;width:14px;height:14px;border-radius:50%;border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.4),0 1px 4px rgba(0,0,0,.3);transform:translate(-50%,-50%);pointer-events:none}
.cp-controls{display:flex;align-items:center;gap:8px;margin-bottom:10px}
.cp-eyedrop{width:28px;height:28px;flex:none;border:none;background:none;border-radius:6px;color:var(--dim);cursor:pointer;display:grid;place-items:center}
.cp-eyedrop:hover{background:var(--hover)}
.cp-eyedrop[hidden]{display:none}
.cp-sliders{flex:1;display:flex;flex-direction:column;gap:10px;min-width:0}
.cp-hue{position:relative;height:12px;border-radius:999px;cursor:pointer;user-select:none}
.cp-hue{background:linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)}
.cp-slider-dot{position:absolute;top:50%;width:14px;height:14px;border-radius:50%;background:#fff;border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.4),0 1px 4px rgba(0,0,0,.3);transform:translate(-50%,-50%);pointer-events:none}
.cp-fields{display:flex;gap:8px}
/* format picker (Hex/RGB/HSL) — same appearance:none + hand-drawn-chevron
   treatment as .prop select.dlg-input, just sized for the 232px popover */
.cp-format{flex:none;width:62px;height:30px;border:1px solid var(--line-2);border-radius:6px;padding:0 18px 0 8px;font:inherit;font-size:12px;color:var(--ink);background-color:#fff;appearance:none;-webkit-appearance:none;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7480' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 5px center;background-size:12px}
/* Hex is a single free-text field (unchanged from before); RGB/HSL instead
   render 3 short per-channel fields side by side, divided by hairlines, so
   switching format swaps what's inside the same box rather than changing
   its shape. */
.cp-valuefield{flex:1;display:flex;align-items:center;height:30px;border:1px solid var(--line-2);border-radius:6px;min-width:0;overflow:hidden}
.cp-valuefield input{flex:1;width:0;min-width:0;border:none;outline:none;font-size:12.5px;font-family:inherit;height:100%;background:none;text-align:center;padding:0 2px}
.cp-valuefield.cp-hexmode input{text-align:left;padding:0 8px;text-transform:uppercase}
.cp-valuefield:not(.cp-hexmode) input:not(:first-child){border-left:1px solid var(--line-2)}

/* ---------- export dialog ---------- */
.dialog-scrim{position:fixed;inset:0;background:rgba(20,28,38,.35);z-index:80;display:grid;place-items:center}
.dialog-scrim[hidden]{display:none}
/* The save dialog can be triggered from inside another open dialog (Publish
   tapped with no saved map, or with unsaved edits — js/mymaps.js's
   saveThenRun) — same z-index as every other .dialog-scrim would leave DOM
   order deciding the stack, and #savemap-scrim comes BEFORE #code-scrim in
   the markup, so it would render underneath instead of on top of it. */
#savemap-scrim{z-index:82}
/* Same bug, same fix, one more caller: the concurrent-save conflict dialog
   (js/mymaps.js askConflict()) fires from inside that same saveThenRun()
   path — Publish tapped, saveThenRun's save hits a real conflict — and
   #conflict-scrim also sits before #code-scrim in the markup. Without its
   own bump it rendered behind the still-open Publish dialog (Aaron's
   report, 2026-08-06): "This map changed" opening underneath, invisible
   until Publish was dismissed first. */
#conflict-scrim{z-index:82}
.dialog{width:430px;max-width:94vw;background:#fff;border-radius:12px;box-shadow:0 20px 60px rgba(15,25,40,.3);overflow:hidden}
.dlg-head{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line);font-weight:680;font-size:14px}
.dlg-close{width:28px;height:28px;border:none;background:none;display:grid;place-items:center;cursor:pointer;color:var(--dim);border-radius:6px}
.dlg-close:hover{background:var(--hover)}
.dlg-body{padding:16px}
.dlg-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 16px;border-top:1px solid var(--line)}
.dlg-foot-actions{display:flex;gap:8px}
.dialog.wide{width:640px}
.dialog.pubdialog{width:480px}

/* ---- CSV import dialog: three columns (js/importcsv.js, index.html) ----
   design/csvimport3col-option-b-stickypreview.html, Aaron's pick 2026-08-07
   ("reuse the existing UI as much as possible"). Set up (Type + Visual —
   the purely stylistic, whole-file choices) / Data (Location + On the map
   + In the popup — every "which column holds what" question, Location
   included, moved here the same day) / Preview (always two boxes, stacked:
   Marker-or-Photo above Popup). Type is the exact .ctrl-cards picker
   js/overlay.js's own merged Marker/Photo Appearance tab uses
   (typeCardsSection) — same component, not a lookalike. */
.dialog.csvdlg{width:980px}
/* align-items:stretch (the default) so Set up and Data always match the
   grid row's height, same reasoning .csvsplit's stretch used to give
   .csvdata/.csvlook — Preview opts back OUT of that via its own
   align-self below, since unlike those two it should size to its own
   short content, not be stretched to match them. Preview narrower than
   its original 280px (240px) now that its two boxes stack instead of
   sitting side by side — Data gets the freed 40px, matching it now also
   carrying Location. */
.csv3col{display:grid;grid-template-columns:220px 1fr 240px}
.colhead{padding:14px 16px 10px;font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dim)}
.col-setup{min-width:0;border-right:1px solid var(--line);max-height:min(58vh,520px);overflow-y:auto}
/* Same cap, same scroll, as .col-setup — without it, a short viewport
   shrinks whichever column has somewhere to put the overflow while the
   other keeps its own natural minimum height, so the taller one drives the
   grid row height and the shorter one's own lower rows scroll out of view
   even though the dialog is clearly tall enough. Matching caps mean
   neither side can out-grow the other. */
.col-data{min-width:0;border-right:1px solid var(--line);background:var(--hover);
  max-height:min(58vh,520px);overflow-y:auto}
/* Preview is a plain sibling column, not nested inside either scroller
   above — so it's already always in view regardless of how far Set up or
   Data have scrolled internally, with no position:sticky needed to
   achieve that. align-self:start keeps it sized to its own short content
   instead of being stretched (the row default) to match the taller,
   capped columns beside it. */
.col-preview{min-width:0;align-self:start}
/* #csvmap-note sits here, below .csv3col, not inside either scrolling
   column — dialog-level ("N rows skipped"), so it stays fully visible
   regardless of any column's own scroll position, instead of getting
   scrolled down to a sliver of its own top border peeking above the
   footer. */
.csvdlg > .dlg-note{margin:14px 16px 0}
.csvfile{color:var(--dim);font-weight:400}
.csvprev-btn{margin-left:10px;height:22px;padding:0 9px;border-radius:999px;border:1px solid var(--line-2);
  background:var(--panel);color:var(--dim);font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;vertical-align:middle}
.csvprev-btn:hover{color:var(--ink);border-color:var(--dim)}

/* ---- CSV file-preview modal (js/importcsv.js #csvpreview-dialog) ----
   A separate modal the same width as .csvdlg, with its height set inline by
   js/importcsv.js to match #csvmap-dialog exactly — so it fills flex:1 rather
   than assuming a fixed height the way a 210px inline insert could. No
   dlg-foot — the header close button is the only way out (Aaron's call). */
.dialog.csvprevdlg{display:flex;flex-direction:column}
.csvpreview{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--canvas)}
.csvpreview-scroll{flex:1;min-height:0;overflow:auto}
#csvmap-preview-table{border-collapse:collapse;font-size:11.5px;white-space:nowrap}
#csvmap-preview-table th,#csvmap-preview-table td{border-right:1px solid var(--line);border-bottom:1px solid var(--line);
  padding:5px 10px;text-align:left;max-width:220px;overflow:hidden;text-overflow:ellipsis}
/* the header row stays put while the body scrolls under it */
#csvmap-preview-table th{position:sticky;top:0;background:var(--panel);color:var(--dim);font-weight:600;z-index:1}
#csvmap-preview-table td{color:var(--ink);background:var(--panel)}
.csvpreview-foot{padding:6px 10px;font-size:11px;color:var(--faint);border-top:1px solid var(--line)}
.col-setup .section:last-of-type,.col-data .section:last-of-type{border-bottom:none}
.col-setup .prop .plabel{width:70px;flex:none}
.col-data .prop .plabel{width:80px;flex:none}
.col-setup .prop select.dlg-input,.col-data .prop select.dlg-input{height:32px}
/* Type's .ctrl-cards (js/overlay.js's own typeCardsSection reuses this exact
   markup) defaults to a 2-up grid sized for the export dialog's much wider
   .split-controls — .col-setup is 220px, so one column, same override
   #insp-body's own Type picker already needs at 240px. */
.col-setup .ctrl-cards{grid-template-columns:1fr}
/* .prop select is sized for single-line dropdowns — a multi-select needs its
   natural height back or its options overlap inside 32px (was a real bug in
   the first version of this dialog, caught by the design spike) */
.csv3col .prop select[multiple].dlg-input{height:auto;min-height:76px;padding:6px 8px;background-image:none}
/* Multi-column mappings (Address, Details). A native <select multiple> needs
   ctrl/cmd-click to pick more than one — undiscoverable, and impossible on
   touch — and it stands as tall as the column count, so those two rows dwarfed
   every single-select row beside them. This is a normal-looking one-line field
   that opens a checklist instead; the row above it stays a plain .prop. */
.multipick{flex:1;position:relative;min-width:0}
/* The native <select> stays in the DOM as the source of truth (currentMapping
   reads it, dev/smoke.js drives it) — it's just never shown. Not display:none:
   a select that isn't rendered can't be focused or driven the same way. */
.skinned-select{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;left:-9999px}
/* single-choice flavour: one option wins, so the menu has no Done — picking
   closes it, same as a native select */
.onepick .mp-opt .mp-box{border-radius:50%}
.mp-field{width:100%;height:32px;border:1px solid var(--line-2);border-radius:6px;padding:0 30px 0 10px;
  font:inherit;font-size:13px;text-align:left;background:var(--panel);color:var(--ink);cursor:pointer;outline:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7480' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 8px center;background-size:14px}
.mp-field:focus-visible,.multipick.open .mp-field{border-color:var(--accent-line)}
/* The selection can be several column names long — clip it rather than let the
   field grow and shove the layout around. */
.mp-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mp-text.empty{color:var(--faint)}
/* Fixed, not absolute: .col-setup/.col-data are overflow-y:auto scrollers, so
   an absolutely positioned menu gets clipped at the dialog edge — and
   Details is the second-to-last row, i.e. exactly where that bites.
   Coordinates come from the field's rect at open time (see place() in
   js/importcsv.js), which also lets it flip
   above when there's no room below. z-index clears .dialog-scrim's 80. */
.mp-menu{position:fixed;z-index:90;background:var(--panel);
  border:1px solid var(--line-2);border-radius:8px;box-shadow:0 8px 24px rgba(20,30,45,.14);overflow:hidden}
.mp-menu[hidden]{display:none}
.mp-list{max-height:190px;overflow-y:auto;padding:4px}
.mp-opt{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:none;background:none;
  border-radius:5px;font:inherit;font-size:12.5px;color:var(--ink);text-align:left;cursor:pointer}
.mp-opt:hover{background:var(--hover)}
.mp-box{width:15px;height:15px;flex:none;border:1px solid var(--line-2);border-radius:4px;background:var(--panel);
  display:grid;place-items:center}
.mp-box svg{width:11px;height:11px;display:none}
.mp-opt.on .mp-box{background:var(--accent);border-color:var(--accent)}
.mp-opt.on .mp-box svg{display:block;stroke:#fff}
.mp-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mp-foot{display:flex;justify-content:flex-end;padding:6px;border-top:1px solid var(--line)}
.mp-foot .mini-btn{height:26px;padding:0 14px;font-size:12px}
.reqdot{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--accent);vertical-align:middle;margin-left:5px}
.secnote{font-size:11.5px;color:var(--faint);line-height:1.45;margin:-4px 0 11px}
/* #csvmap-geonote is nested inside the Location .section, which already
   supplies 16px of horizontal padding, so a margin here needs zero of its
   own to match the Address row's width, and margin-top:0 would collapse
   away that row's own spacing — 9px top matches the standard gap between
   stacked .prop rows instead. */
#csvmap-geonote{margin:9px 0 0}

/* No flex:1/stretch mechanism here any more (2026-08-07) — .col-preview is a
   plain sibling column (align-items:start on .csv3col), not force-matched
   to .col-setup/.col-data's height the way .csvlook used to be, so there's
   no leftover space for it to absorb between Marker/Photo mode. min-height
   just keeps a consistent visual footprint either way. */
.previewpad{margin:0 16px 16px;min-height:168px;border-radius:8px;border:1px solid var(--line-2);position:relative;
  background:#eef2ee;
  background-image:linear-gradient(rgba(255,255,255,.6) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.6) 1px,transparent 1px),linear-gradient(120deg,#e7eee6 0 40%,#eaf0f4 40% 100%);
  background-size:22px 22px,22px 22px,100% 100%;display:grid;place-items:center;overflow:hidden;padding:16px 0}
/* Stacked (2026-08-07, Aaron's ask — was side by side), Marker-or-Photo
   above Popup, so each box gets the column's own full width instead of
   splitting it. */
.prevstage{display:flex;flex-direction:column;align-items:center;gap:16px;max-width:100%;padding:0 10px}
/* Marker/Photo and Popup each sit in their own column so the caption
   underneath (.prevlabel) lands under its own box rather than beside both. */
.prevcol{display:flex;flex-direction:column;align-items:center;gap:6px}
.prevlabel{font:700 10px/1.3 inherit;letter-spacing:.05em;text-transform:uppercase;color:var(--faint)}
/* the click-popup, at the same scale the marker previews at */
.popprev{width:132px;background:#fff;border-radius:8px;box-shadow:0 3px 14px rgba(15,25,40,.22);
  overflow:hidden;text-align:left;flex:none}
.popprev[hidden]{display:none}
.popprev img{display:block;width:100%;height:52px;object-fit:cover;background:var(--hover)}
.popprev .pp-body{padding:7px 9px}
.popprev .pp-title{font-size:11.5px;font-weight:700;color:var(--ink);line-height:1.25;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.popprev .pp-desc{font-size:10.5px;color:var(--dim);line-height:1.45;margin-top:3px;white-space:pre-line;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.popprev .pp-link{display:block;font-size:10.5px;color:var(--accent);font-weight:600;margin-top:5px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the panel's 5-column icon grid assumes a 240px panel; pack by size instead
   so the buttons stay square at .col-setup's 220px */
.col-setup .iconpick{grid-template-columns:repeat(auto-fill,minmax(34px,1fr))}
#csvmap-style[hidden]{display:none}
.photonote{margin:12px 0 0}

/* live marker preview — mirrors buildLabelIcon(): icon and text in ONE box
   with a downward tail. No label text means a plain round icon badge. */
.mkprev{position:relative;display:inline-flex;align-items:center;gap:6px;background:var(--accent);color:#fff;
  border-radius:999px;padding:6px 12px 6px 9px;box-shadow:0 0 0 2px #fff,0 2px 8px rgba(15,25,40,.25);
  font-size:12.5px;font-weight:600;line-height:1;max-width:232px}
.mkprev svg{width:15px;height:15px;fill:currentColor;flex:none}
.mkprev .mktext{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mkprev::after{content:"";position:absolute;left:50%;top:100%;transform:translateX(-50%);
  border:5px solid transparent;border-top-color:#fff;border-bottom:none}
/* No label: the real marker is the glyph alone (buildIcon) — no pill, no
   circular container, no callout tail. Sized up so it reads at preview scale,
   and outlined the way buildIcon outlines it. */
.mkprev.bare{background:none!important;box-shadow:none;padding:0;border-radius:0}
.mkprev.bare .mktext{display:none}
.mkprev.bare::after{display:none}
.mkprev.bare svg{width:44px;height:44px;fill:currentColor;stroke:#fff;stroke-width:1.4;
  paint-order:stroke fill;filter:drop-shadow(0 1px 2px rgba(15,25,40,.3))}
.mkprev.bare.sm svg{width:34px;height:34px}
.mkprev.bare.lg svg{width:56px;height:56px}
.mkprev.sm{font-size:11px;padding:4px 9px 4px 7px}
.mkprev.sm svg{width:12px;height:12px}
.mkprev.lg{font-size:14.5px;padding:9px 16px 9px 12px}
.mkprev.lg svg{width:20px;height:20px}
.photoprev{width:96px;height:72px;border-radius:6px;background:#fff;border:1px solid var(--line-2);
  display:grid;place-items:center;color:var(--faint);box-shadow:0 2px 8px rgba(15,25,40,.18)}
/* explicit [hidden] overrides — a display value in the rule above otherwise
   wins over the attribute and the element stays visible. Fourth time this
   exact trap has bitten in this codebase (.ovpz-urlrow, .overlaybar,
   .supportbanner); any new display:* rule here needs its own pair. */
.mkprev[hidden],.photoprev[hidden]{display:none}

.csvcount{font-size:12px;color:var(--dim)}
.csvcount b{color:var(--ink);font-variant-numeric:tabular-nums}
.csvprogress{flex:1;display:flex;align-items:center;gap:10px;min-width:0}
.csvprogress[hidden]{display:none}
.csvmap-progress-track{flex:1;height:6px;border-radius:3px;background:var(--line);overflow:hidden;min-width:60px}
.csvmap-progress-bar{height:100%;width:0;background:var(--accent);transition:width .2s}
#csvmap-progress-text{flex:none}

/* export dialog: order-summary split (2026-07-13) — fixed height so
   switching Format/Output/size never resizes the box; shorter panels
   (vector) just leave blank space below, taller ones scroll within
   .split-controls instead of growing. #export-go is unique to this
   dialog (no other dialog has one), so it's safe to fully own its sizing
   here rather than sharing a generic footer-button rule. */
.dialog.split{width:720px;max-width:96vw;height:min(700px,88vh);display:flex;flex-direction:column}
.split-body{display:flex;flex:1;min-height:0}
.split-controls{flex:1;padding:16px;overflow-y:auto;min-width:0}
.split-summary{width:250px;flex:none;border-left:1px solid var(--line);background:var(--hover);display:flex;flex-direction:column;padding:16px;overflow-y:auto}
#export-go{width:100%;height:38px;font-size:13px;white-space:nowrap;margin-top:auto}

/* gradient + crosshair lines are a loading placeholder — replaced by a real
   thumbnail of the drawn box once renderPreviewThumb() finishes (a tiny
   client-side MapLibre render, same technique as the free 2K export, so it
   costs nothing); .has-image drops the placeholder look once it lands */
.preview-swatch{border:1px solid var(--line-2);border-radius:8px;background:linear-gradient(135deg,#dfe7ee,#c9d6e2) center/cover;aspect-ratio:3/2;margin-bottom:14px;position:relative;overflow:hidden;cursor:crosshair}
.preview-swatch::after{content:"";position:absolute;inset:0;background:
  linear-gradient(90deg,transparent 31%,rgba(255,255,255,.5) 32%,transparent 33%),
  linear-gradient(0deg,transparent 61%,rgba(255,255,255,.5) 62%,transparent 63%)}
.preview-swatch.has-image::after{display:none}

/* draggable dot marking which spot the close-up preview below is centered
   on — pointer-events:none because the drag listeners live on
   .preview-swatch itself (dragging anywhere moves the dot, not just a grab
   handle), see MBExport's pointer handlers */
.preview-marker{position:absolute;left:50%;top:50%;width:14px;height:14px;margin:-8px 0 0 -8px;border-radius:50%;background:var(--accent);border:2px solid #fff;box-shadow:0 0 0 1px rgba(20,30,45,.3),0 1px 3px rgba(20,30,45,.35);pointer-events:none;z-index:1}
.preview-marker[hidden]{display:none}

/* speech-bubble hint pointing at the dot — replaces a standalone "Check a
   close-up spot" button (Aaron, 2026-07-24): the dot itself is already the
   whole interaction (drag to move, release to open), so the affordance
   belongs on the dot, not a separate control below it. Shares the dot's
   left/top (see positionCropMarker in export.js) and offsets upward. */
.marker-tip{position:absolute;left:50%;top:50%;transform:translate(-50%,calc(-100% - 12px));background:#1e2328;color:#fff;font-size:10.5px;font-weight:600;padding:4px 9px;border-radius:6px;white-space:nowrap;pointer-events:none;z-index:2}
.marker-tip::after{content:"";position:absolute;left:50%;top:100%;transform:translateX(-50%);border:5px solid transparent;border-top-color:#1e2328}
.marker-tip[hidden]{display:none}

/* collapsed by default (Aaron's call, 2026-07-24 — the earlier
   always-stacked version was too crowded): the panel is positioned OVER
   the recap/price/note/button below rather than pushing them down —
   .closeup-anchor has no in-flow content of its own (.crop-panel is
   position:absolute), so it contributes zero height to the rail, and
   .crop-panel's top:100% anchors right where the swatch's own margin
   leaves off regardless of the swatch's aspect-ratio-driven height */
.closeup-anchor{position:relative}

/* the floating panel itself: opaque card + shadow so it reads as
   elevated ABOVE the rail, not squeezed into it — same loading-
   placeholder technique as .preview-swatch, rendered at the export's
   actual pixel density (see renderCropPreview in export.js) */
.crop-panel[hidden]{display:none}
.crop-panel{position:absolute;top:0;left:0;right:0;background:#fff;border:1px solid var(--line-2);border-radius:10px;box-shadow:0 14px 34px rgba(20,30,45,.24);padding:12px;z-index:5}
.crop-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:8px}
.crop-close{width:22px;height:22px;flex:none;border:none;background:none;color:var(--faint);cursor:pointer;border-radius:5px;display:grid;place-items:center}
.crop-close:hover{background:var(--hover);color:var(--ink)}
.crop-label{font-size:11px;font-weight:650;color:var(--dim)}
.crop-hint{font-weight:400;color:var(--faint)}
.crop-swatch{border:1px solid var(--line-2);border-radius:8px;background:linear-gradient(135deg,#dfe7ee,#c9d6e2) center/cover;aspect-ratio:1/1;position:relative;overflow:hidden}
.crop-swatch::after{content:"";position:absolute;inset:0;background:
  linear-gradient(90deg,transparent 31%,rgba(255,255,255,.5) 32%,transparent 33%),
  linear-gradient(0deg,transparent 61%,rgba(255,255,255,.5) 62%,transparent 63%)}
.crop-swatch.has-image::after{display:none}
/* rendered at a fixed ground span (not a fixed pixel count), so the raw
   render is genuinely low-res at coarse tiers — image-rendering:pixelated
   keeps the browser from smoothing that into a deceptively soft blur */
.crop-swatch img{display:none;width:100%;height:100%;object-fit:fill;image-rendering:pixelated}
.crop-swatch.has-image img{display:block}
.crop-caption{font-size:10.5px;color:var(--faint);margin-top:4px}

.recap{font-size:12px;color:var(--dim);display:flex;flex-direction:column;gap:6px;margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid var(--line-2)}
.recap .row{display:flex;justify-content:space-between;gap:10px}
.recap .row[hidden]{display:none}
.recap .row b{color:var(--ink);font-weight:600;text-align:right}

.summary-price{font-size:22px;font-weight:750;color:var(--ink);margin-bottom:10px}
.summary-price .unit{font-size:12px;font-weight:600;color:var(--dim)}
.split-summary .exp-note{margin-top:0;flex:1}

/* Format/Output as descriptive cards, not pills (2026-07-13) — each option
   is its own card with an icon + one-line caption; selected gets an accent
   border + check, same visual language as .sizecard.sel */
.ctrl-cards{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.ctrl-card{border:1px solid var(--line-2);border-radius:8px;padding:9px 10px;cursor:pointer;background:none;text-align:left;display:flex;gap:9px;align-items:flex-start;position:relative;font:inherit}
.ctrl-card:hover{border-color:var(--accent-line)}
.ctrl-card.on{border-color:var(--accent);background:var(--accent-soft)}
.ctrl-card .ico-wrap{width:26px;height:26px;border-radius:6px;background:var(--line);display:grid;place-items:center;flex:none;color:var(--dim)}
.ctrl-card.on .ico-wrap{background:var(--accent);color:#fff}
.ctrl-card .lbl{display:block;font-size:12.5px;font-weight:650;color:var(--ink)}
.ctrl-card .desc{display:block;font-size:10.5px;color:var(--faint);margin-top:1px}
.ctrl-card .checkmark{position:absolute;top:8px;right:8px;color:var(--accent);display:none}
.ctrl-card.on .checkmark{display:block}
/* Overlay inspector's Marker/Photo Type picker reuses .ctrl-cards verbatim
   (design/markerphoto-option-c-cards.html) but #insp-body is 240px wide, not
   the export dialog's much wider .split-controls — stacked to one column. */
#insp-body .ctrl-cards{grid-template-columns:1fr}
.codetabs{display:flex;gap:4px;padding:12px 16px 0}
.codetab{flex:1;height:30px;padding:0 8px;border-radius:6px;border:none;background:none;color:var(--dim);font-size:12.5px;font-weight:600;cursor:pointer;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.codetab:hover{background:var(--hover)}
.codetab.sel{background:var(--accent-soft);color:var(--accent)}
/* Publish dialog body sizes to its content (no more fixed 52vh box full of
   empty space) — capped + scrollable only as a safety net for a long Stats
   list, not a baseline height every state has to fill. */
.code-body{padding:14px 16px 16px;max-height:70vh;overflow-y:auto}
/* min-height matched to the Stats tab's typical content (tiles + chart +
   two list sections, ~450px) so the dialog doesn't visibly resize switching
   Publish <-> Stats or between not-published/published — shorter states
   (empty-state card, the link card) center in the same box instead of
   leaving it pinned to the top with dead space below; content taller than
   this (a long Stats list) still just grows/scrolls via .code-body above. */
/* fixed height so the dialog doesn't resize between the Links and Analytics
   tabs; taller content (Analytics) scrolls inside instead of growing the box */
.code-hosted{display:flex;flex-direction:column;gap:12px;height:460px;overflow-y:auto}
.pub-empty{font-size:12px;color:var(--faint);padding:4px 2px}

/* Shared empty-state card — sign-in gate, no-map-yet, not-published-yet,
   basemap-blocked all use this one visual language; only the icon/copy/
   button change, so the tab never looks like four different half-built UIs. */
.pub-emptystate{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;padding:22px 14px}
.pub-emptystate .pub-icon{width:40px;height:40px;border-radius:50%;background:var(--accent-soft);color:var(--accent);display:grid;place-items:center;margin-bottom:4px}
.pub-emptystate .pub-icon .ico{width:20px;height:20px}
.pub-emptystate h3{font-size:13.5px;font-weight:650;color:var(--ink)}
.pub-emptystate p{font-size:12px;color:var(--dim);line-height:1.5;max-width:300px}
.pub-emptystate .mini-btn{margin-top:6px}

/* Published: a labeled live-status row (link + copy) and a labeled
   embed-code box (the actual <iframe> snippet, wrapped + copy — not a bare
   "Copy" button hiding what it copies, and grown to fill the dialog's
   height rather than sitting in the same single-line row as the link),
   then Unpublish (left) + Publish (right) sharing one row —
   .pub-publishrow also holds just Publish alone pre-publish, where flex:1
   on a single child is simply full width. */
/* Status row: whether the link (if any) actually matches what's on screen —
   green/amber/gray dot + one line, same spot regardless of which of the
   three states it is so the tab doesn't reshuffle around it. */
.pub-status{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--ink)}
.pub-status .dot{width:7px;height:7px;border-radius:50%;flex:none}
.pub-status-working .dot{background:#16a34a}
.pub-status-outdated .dot{background:#d97706}
.pub-status-unpublished .dot{background:var(--faint)}
.pub-section-label{font-size:11px;font-weight:650;color:var(--dim);text-transform:uppercase;letter-spacing:.04em;margin-bottom:-4px}
.pub-linkcard,.share-linkcard{display:flex;align-items:center;gap:9px;background:var(--canvas);border:1px solid var(--line-2);border-radius:8px;padding:10px 12px;flex:none}
.pub-linkcard a,.share-linkcard a{flex:1;min-width:0;font-size:12.5px;font-weight:650;color:var(--ink);text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pub-linkcard a:hover,.share-linkcard a:hover{color:var(--accent);text-decoration:underline}
.pub-linkcard .navicon,.share-linkcard .navicon{width:28px;height:28px;flex:none}
.pub-embedbox{flex:1;min-height:80px;display:flex;align-items:flex-start;gap:9px;background:var(--canvas);border:1px solid var(--line-2);border-radius:8px;padding:10px 12px}
.pub-embedbox code{flex:1;min-width:0;font-family:ui-monospace,Consolas,monospace;font-size:11px;line-height:1.6;color:var(--dim);white-space:pre-wrap;word-break:break-all}
.pub-embedbox .navicon{width:28px;height:28px;flex:none}
.pub-publishrow{display:flex;gap:8px;flex:none}
.pub-publishrow .mini-btn{flex:1}

/* account monthly view usage banner (Stats pane) */
.pub-usage{background:var(--hover);border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin:12px 0}
.pub-usage-top{display:flex;justify-content:space-between;align-items:baseline;font-size:12px;color:var(--dim)}
.pub-usage-top b{font-size:12.5px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums}
.pub-usage-bar{height:6px;border-radius:999px;background:var(--line-2);margin-top:7px;overflow:hidden}
.pub-usage-bar i{display:block;height:100%;background:var(--accent);border-radius:999px}
.pub-usage.near .pub-usage-bar i{background:#d97706}
.pub-usage.over .pub-usage-bar i{background:#d33}
.pub-usage-msg{font-size:11.5px;color:var(--dim);margin-top:8px}
.pub-usage-msg a{color:var(--accent);font-weight:600;text-decoration:none}
.pub-usage-msg a:hover{text-decoration:underline}

/* hosted-page stats pane (Stats button on a pubrow) */
.statshead{display:flex;align-items:center;gap:10px}
.statshead-name{font-size:13px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1}
.statshead-range{font-size:11px;color:var(--faint);flex:none}
.stattiles{display:flex;gap:8px}
.stattile{flex:1;background:#fff;border:1px solid var(--line-2);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}
.stattile b{font-size:20px;font-weight:700;letter-spacing:-.01em}
.stattile span{font-size:11px;color:var(--dim)}
.statsec{background:#fff;border:1px solid var(--line-2);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:6px}
.statsec-title{font-size:11px;font-weight:650;color:var(--dim);text-transform:uppercase;letter-spacing:.04em}
.statchartwrap{position:relative}
.statchartwrap svg{display:block;width:100%;height:auto}
.stattip{position:absolute;top:-4px;transform:translateX(-50%);background:var(--ink);color:#fff;font-size:11px;line-height:1.3;padding:4px 8px;border-radius:5px;white-space:nowrap;pointer-events:none;z-index:2}
.stattip[hidden]{display:none}
.statrow{display:flex;align-items:center;gap:10px}
.statrow-name{flex:0 0 160px;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.statrow.muted .statrow-name{color:var(--faint);font-style:italic}
.statrow-bar{flex:1;height:8px;display:flex}
.statrow-bar i{display:block;height:100%;background:var(--accent);border-radius:2px;min-width:2px}
.statrow-bar i[hidden]{display:none}
.statrow-n{flex:none;font-size:11.5px;color:var(--dim);min-width:26px;text-align:right;font-variant-numeric:tabular-nums}
/* single column (2026-07-13, was a 2-col grid) — a vertical list reads more
   like a comparison table and matches the print quality list, which reuses
   this same class so both "how big" choices look identical */
.sizegrid{display:grid;grid-template-columns:1fr;gap:8px}
/* the display:grid above would silently defeat the hidden attribute (author
   display beats the UA [hidden] rule) — print mode hides the tier grid */
.sizegrid[hidden]{display:none}
.sizecard{border:1px solid var(--line-2);border-radius:8px;padding:10px 12px;cursor:pointer;background:none;text-align:left;position:relative}
.sizecard:hover{border-color:var(--accent-line)}
.sizecard.sel{border-color:var(--accent);background:var(--accent-soft)}
.sizecard .sz{font-weight:650;font-size:13px}
.sizecard .sub{font-size:11px;color:var(--faint);margin-top:2px}
.sizecard .price{position:absolute;top:9px;right:10px;font-size:11px;font-weight:700;color:var(--accent)}
.sizecard.locked{opacity:.6;cursor:default}
.sizecard.locked .price{color:var(--faint)}
.sizecard.warned .sub{color:#b8860b}
.rec-badge{font-size:9.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#0a7d38;background:rgba(16,163,74,.12);border-radius:4px;padding:1px 5px;margin-left:4px;vertical-align:2px}
/* .seg/.detailrow kept for the design/ mockups that still reference them —
   the live export dialog no longer uses either (Format/Output are cards,
   print quality is a .sizecard list; see the split-dialog rules above) */
.seg{height:28px;padding:0 12px;border:1px solid var(--line-2);background:none;border-radius:999px;font-size:12px;color:var(--dim);cursor:pointer}
.seg.on{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
.coverage{margin-top:10px;font-size:11.5px;color:var(--faint)}
.exp-note{margin-top:10px;font-size:11.5px;color:var(--dim);line-height:1.5}
.exp-note b{color:var(--ink)}
.exp-note:empty{margin-top:0}

/* print panel inside the download dialog (scale + DPI quality) */
#print-panel .fieldlabel{margin-top:14px}
#print-panel .fieldlabel:first-child{margin-top:0}
.scalefield{display:flex;align-items:center;gap:6px}
.scalefield span{font-size:13px;color:var(--dim);flex:none}
.scalefield .dlg-input{width:130px}
/* physical print size shown once here (depends only on Scale) instead of
   repeated on every quality card (DPI just changes resolution/price) */
.printsize-line{font-size:12px;color:var(--dim);margin:10px 0 0}
.printsize-line b{color:var(--ink)}
.printsize-line:empty{display:none}
/* wrapper for the print result card (.sizecard inside, same as the tiers) —
   still used by design/download-option-c-print.html */
.printout{margin-top:12px}
.printout:empty{display:none}

/* step-1 ground-dimension inputs (export nav) */
.exd-dims{display:flex;align-items:center;gap:5px}
.exd-dims input{width:74px;height:28px;border:1px solid var(--line-2);border-radius:6px;padding:0 8px;font:inherit;font-size:12px;outline:none;background:none}
.exd-dims input:focus{border-color:var(--accent-line)}
.exd-dims select{height:28px;border:1px solid var(--line-2);border-radius:6px;font:inherit;font-size:12px;color:var(--dim);background:none;outline:none;padding:0 4px}
.exd-dims .dimx{font-size:12px;color:var(--faint)}

/* my exports dialog */
.exportlist{display:flex;flex-direction:column;gap:6px;max-height:60vh;overflow-y:auto}
.exportrow{display:flex;align-items:center;gap:10px;padding:9px 10px;border:1px solid var(--line-2);border-radius:8px}
.exportrow .ex-info{flex:1;min-width:0}
.exportrow .ex-dim{font-weight:650;font-size:12.5px}
.exportrow .ex-meta{display:block;font-size:11px;color:var(--faint);margin-top:2px}
.exportrow.s-expired,.exportrow.s-failed,.exportrow.s-stale{opacity:.7}
.ex-state{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;color:var(--dim)}
.ex-state.ex-bad{color:#b3261e}
.ex-dl{height:28px;padding:0 12px}
.acct-badge{font-size:9.5px;font-weight:700;color:var(--accent);vertical-align:1px}
.spin{width:12px;height:12px;border-radius:50%;border:2px solid var(--line-2);border-top-color:var(--accent);display:inline-block;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* toast */
.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);background:var(--ink);color:#fff;border-radius:8px;padding:10px 18px;font-size:12.5px;z-index:100;box-shadow:0 8px 24px rgba(0,0,0,.25)}

/* maplibre chrome adjustments */
.maplibregl-ctrl-bottom-left{z-index:15}
.maplibregl-ctrl-attrib{font-size:10.5px}

/* ===== overlay editor (2026-07-06) ===== */
.lhead .headbtn{width:24px;height:24px;border:none;background:none;border-radius:6px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--dim);padding:0}
.lhead .headbtn:hover{background:var(--hover);color:var(--accent)}
/* #btn-language shows the current language's code (e.g. "EN") once one is
   picked, in place of the generic languages icon. A resting background
   (unlike the other plain .headbtn icons) marks it as its own tappable
   control, since text alone next to a section title could read as a label. */
#btn-language{font:700 9.5px/1 var(--font);letter-spacing:-.02em;width:auto;min-width:24px;padding:0 6px;background:var(--hover)}
#btn-language:hover{background:var(--accent-soft);color:var(--accent)}
#overlay-add-menu{top:28px}
#overlay-add-menu button{display:flex;align-items:center;gap:8px}
.ovlist{flex:1;overflow-y:auto;padding:8px}
.ovrow{position:relative;display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:7px;cursor:pointer;font-size:12.5px}
.ovrow:hover{background:var(--hover)}
/* Selected rows: square by default so a contiguous run joins into one block;
   only the first and last of a run round off (see renderList's sel-top/sel-bot).
   A single selected row gets both, so it looks exactly as it always did. */
.ovrow.sel{background:var(--accent-soft);color:var(--accent);border-radius:0}
.ovrow.sel.sel-top{border-top-left-radius:7px;border-top-right-radius:7px}
.ovrow.sel.sel-bot{border-bottom-left-radius:7px;border-bottom-right-radius:7px}
.ovrow.drawing{color:var(--dim);font-style:italic;cursor:default}
.ovrow .ovic{color:var(--dim);display:flex;flex:none}
.ovrow.sel .ovic{color:var(--accent)}
.ovrow .ovname{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* always display:flex (not display:none until hover) — reserves the
   button's full height in the row at all times, so a row's height doesn't
   grow on hover (the button, icon+padding, is taller than the plain text
   line) and push every row below it down, which re-triggers hover on the
   shifted target and reads as rows jittering up/down as the cursor moves. */
.ovrow .ovbtn{border:none;background:none;cursor:pointer;color:var(--faint);padding:2px;visibility:hidden;display:flex;flex:none}
.ovrow:hover .ovbtn{visibility:visible}
.ovrow .ovbtn:hover{color:var(--accent)}
.ovrow input.ovedit{flex:1;font-size:12.5px;padding:3px 6px;border:1px solid var(--accent);border-radius:5px;min-width:0;font-family:inherit;outline:none}
.ovempty{padding:14px 12px;font-size:12px;color:var(--faint);line-height:1.5}
/* Section: a title in the list, not a drawn shape — no click-select, no map
   icon, bold/uppercase like a group header. No border on the row itself —
   .ovlist-divider (below) is a real standalone rule inserted before it
   instead, skipped when the section is the very first row in the list. */
.ovrow.ovrow-section{font-weight:700;text-transform:uppercase;font-size:11px;letter-spacing:.04em;color:var(--dim);cursor:grab}
.ovrow.ovrow-section .ovic{display:none}
.ovlist-divider{height:1px;background:var(--line);margin:8px 6px}
/* list drag-to-reorder (every row, sections included). Rather than
   highlighting the row being dragged over (reads as "drop into this row"),
   a thin bar between rows shows exactly where the dragged row will land —
   above the target when the cursor is over its top half, below when over
   its bottom half. */
.ovrow.dragging{opacity:.4}
.ovrow.drop-before::before,
.ovrow.drop-after::after{content:'';position:absolute;left:6px;right:6px;height:2px;background:var(--accent);border-radius:1px;pointer-events:none}
.ovrow.drop-before::before{top:-1px}
.ovrow.drop-after::after{bottom:-1px}
.rangefield{flex:1;height:32px;display:flex;align-items:center;gap:8px;min-width:0}
.rangefield input[type=range]{flex:1;min-width:0}
/* Overlay-tab's own rangeRow (Weight/Sides/Rotation/Size) still nests its
   readout inside .rangefield, so this descendant rule keeps styling it. */
.rangefield .rv{font-size:11.5px;color:var(--dim);font-variant-numeric:tabular-nums;width:38px;text-align:right;flex:none}
/* Layers-tab Opacity/Width rows (js/ui.js) instead render .rv as a sibling
   of .rangefield, not nested inside it — same fixed-width-trailing-column
   trick as .propaccessory above, so input[type=range] — the part that
   actually reads as "the slider" — renders the exact same width as
   .colorfield, and the two trailing columns (the eye icon, the %/px
   readout) land flush on the same right edge across every row. */
.rv{font-size:11.5px;color:var(--dim);font-variant-numeric:tabular-nums;width:36px;text-align:right;flex:none}

/* ---------- range sliders (shared: Layers/Overlay dimension + Opacity knobs,
   top-nav detail-zoom, export-flow detail-zoom) — a plain accent-colored
   native thumb sits on an accent-colored filled track and visually vanishes
   into it, so both are re-skinned: filled portion in --accent, thumb a white
   disc with an --accent ring (same look as the color picker's own hue/SL
   dots) so it reads as a distinct handle at any position. --pct (0-100%,
   set from JS on render + on input) draws the fill boundary. */
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:16px;margin:0;background:none;cursor:pointer}
input[type=range]::-webkit-slider-runnable-track{height:4px;border-radius:999px;background:linear-gradient(to right,var(--accent) 0 var(--pct,0%),var(--line-2) var(--pct,0%) 100%)}
input[type=range]::-moz-range-track{height:4px;border-radius:999px;background:var(--line-2)}
input[type=range]::-moz-range-progress{height:4px;border-radius:999px 0 0 999px;background:var(--accent)}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;box-sizing:border-box;margin-top:-6px;width:16px;height:16px;border-radius:50%;background:#fff;border:2px solid var(--accent);box-shadow:0 1px 3px rgba(15,25,40,.35)}
input[type=range]::-moz-range-thumb{box-sizing:border-box;width:16px;height:16px;border-radius:50%;background:#fff;border:2px solid var(--accent);box-shadow:0 1px 3px rgba(15,25,40,.35)}

/* Property panel's Opacity and Width sliders re-skin the shared range
   control in --faint instead of --accent — neither is a selection control
   (Opacity's a transparency knob, Width's a stroke-thickness knob), so
   neither should read as an active/interactive accent color. prop-muted is
   the same treatment for every Overlay-tab slider (js/overlay.js's
   rangeRow) — Weight/Opacity/Size/Rotation/Sides are all dimension knobs
   too, so they match the Layers tab instead of standing out in accent. */
.prop-opacity input[type=range]::-webkit-slider-runnable-track,.prop-width input[type=range]::-webkit-slider-runnable-track,.prop-muted input[type=range]::-webkit-slider-runnable-track{background:linear-gradient(to right,var(--faint) 0 var(--pct,0%),var(--line-2) var(--pct,0%) 100%)}
.prop-opacity input[type=range]::-moz-range-progress,.prop-width input[type=range]::-moz-range-progress,.prop-muted input[type=range]::-moz-range-progress{background:var(--faint)}
.prop-opacity input[type=range]::-webkit-slider-thumb,.prop-width input[type=range]::-webkit-slider-thumb,.prop-muted input[type=range]::-webkit-slider-thumb{border-color:var(--faint)}
.prop-opacity input[type=range]::-moz-range-thumb,.prop-width input[type=range]::-moz-range-thumb,.prop-muted input[type=range]::-moz-range-thumb{border-color:var(--faint)}

/* Buildings' Stroke width when Render isn't "Outline" — inert but tappable
   (see js/ui.js widthProp's disabledReason): the slider itself gets
   pointer-events:none so dragging/clicking it does nothing, while a
   transparent overlay sibling sits on top and catches the tap to explain
   why via toast. A real `disabled` attribute would swallow that tap
   silently instead. Visually it stays the same regular slider look (same
   track/thumb, no opacity wash) just recolored a step lighter than the
   already-light --faint regular slider — --line-2/--line instead — and its
   label/readout drop to --faint, so "disabled" reads as a lighter color
   rather than a translucent film over the same one. */
.prop-disabled .rangefield{position:relative}
.prop-disabled .rangefield input[type=range]{pointer-events:none}
.prop-disabled .plabel{color:var(--faint)}
.prop-disabled .rv{color:var(--faint)}
.prop-disabled input[type=range]::-webkit-slider-runnable-track{background:linear-gradient(to right,var(--line-2) 0 var(--pct,0%),var(--line) var(--pct,0%) 100%)}
.prop-disabled input[type=range]::-moz-range-track{background:var(--line)}
.prop-disabled input[type=range]::-moz-range-progress{background:var(--line-2)}
.prop-disabled input[type=range]::-webkit-slider-thumb{border-color:var(--line-2)}
.prop-disabled input[type=range]::-moz-range-thumb{border-color:var(--line-2)}
.width-disabled-overlay{position:absolute;inset:0;background:none;border:none;padding:0;margin:0;cursor:not-allowed}
.insp-hint{padding:12px 16px;font-size:11.5px;color:var(--faint);line-height:1.5}

/* marker icon picker (overlay inspector) */
.prop.iconprop{align-items:flex-start}
.prop.iconprop .plabel{padding-top:5px}
/* Wraps the search input + its dropdown grid. */
.iconpickwrap{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
.iconpickwrap .iconsearch{flex:none}
/* The grid used to sit inline here, always visible under the search box —
   325 icons made that a lot of permanent dead space (Aaron: "we dont need
   to show the icons on the screen"). Now a dropdown, shown on focusing the
   search input — .mp-menu (position:fixed, same reasoning as the Address/
   Details dropdowns above: .col-setup/.col-data's own overflow-y:auto would
   otherwise clip it) supplies the popover chrome, .iconpick keeps its own grid layout
   on top of that. Coordinates come from the input's rect at open time (see
   placeIconMenu() in js/importcsv.js). */
.iconpick{display:grid;grid-template-columns:repeat(5,1fr);gap:4px;max-height:190px;overflow-y:auto;align-content:start;padding:4px}
.iconpick .ipbtn{height:28px;border:1px solid var(--line-2);border-radius:6px;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--dim);padding:0}
/* [hidden] needs its own rule: the plain .ipbtn rule's display:flex (two
   classes, specificity 0,2,0) otherwise beats the UA stylesheet's
   [hidden]{display:none} (0,1,0) — the search filter was setting el.hidden
   correctly and the button just stayed visible anyway. Caught by actually
   screenshotting a live search, not by the DOM check alone (b.hidden read
   true the whole time). */
.iconpick .ipbtn[hidden]{display:none}
.iconpick .ipbtn:hover{border-color:var(--accent-line);color:var(--accent)}
.iconpick .ipbtn.on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}
.iconpick .ipbtn svg{width:16px;height:16px}
.iconpick .ipdot{width:12px;height:12px;border-radius:50%;background:currentColor;box-shadow:0 0 0 2px #fff,0 0 0 3px currentColor}
/* Numbered-pin tile: the pin-outline svg (fills currentColor same as any
   other icon tile) with a plain "1" placeholder overlaid near the pin's own
   head circle — a representative preview, not a live count (that only
   exists once a real item is placed in a section, js/overlay.js's
   sectionOrderOf). */
.iconpick .ipnum{position:relative;display:flex;width:16px;height:16px}
.iconpick .ipnum svg{width:16px;height:16px}
.iconpick .ipnum b{position:absolute;top:44%;left:50%;transform:translate(-50%,-50%);font:700 7px/1 system-ui,sans-serif;color:#fff;pointer-events:none}
/* Shown in place of the grid while js/overlay-icons.js is being fetched
   (2026-08-04, on-demand load) — same box in both places it's used: the
   inspector's freshly-built .prop.iconprop, and the CSV dialog's static
   .iconpick grid, so no flex/grid context to lean on either way. */
.iconpick-loading{padding:10px 0;font-size:12.5px;color:var(--dim)}

/* marker Title + Location fields (Marker section in overlay inspector) */
.proptext{flex:1;min-width:0;height:32px;border:1px solid var(--line-2);border-radius:6px;padding:0 9px;font:inherit;font-size:12.5px;outline:none}
.proptext:focus{border-color:var(--accent-line)}
.proplocation{align-items:flex-start}
.proplocation .plabel{padding-top:7px}
.locfield{flex:1;min-width:0;position:relative}
/* Overlay inspector's shared Location section (js/overlay.js
   locationSearchRow): .locfield sits directly in a titled .section now, not
   inside a .prop flex row next to a side label, so the input needs its own
   full-width rule instead of relying on the row's flex:1. */
.section > .locfield .proptext{width:100%}
.locresults{position:fixed;background:#fff;border:1px solid var(--line-2);border-radius:6px;max-height:180px;overflow:auto;z-index:20;box-shadow:0 4px 12px rgba(20,30,45,.12)}
.locresults .locsearching{padding:7px 9px;font-size:12px;color:var(--faint)}
.locresults button{display:block;width:100%;text-align:left;border:none;background:none;padding:7px 9px;font:inherit;font-size:12px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.locresults button:hover{background:var(--accent-soft)}
.locresults .sub{display:block;font-size:10.5px;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* text overlay item (Text section in overlay inspector) */
.ovtextarea{flex:1;min-width:0;height:56px;resize:vertical;border:1px solid var(--line-2);border-radius:6px;padding:6px 8px;font:inherit;font-size:12.5px;outline:none}
.ovtextarea:focus{border-color:var(--accent-line)}

.ovtogglegroup{flex:1;display:flex;gap:6px}
.ovtogglebtn{height:28px;flex:1;border:1px solid var(--line-2);border-radius:6px;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--dim)}
.ovtogglebtn:hover{border-color:var(--accent-line);color:var(--accent)}
.ovtogglebtn.on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}

/* published-map overview panel settings (js/overview.js) — map-level, not
   per-shape, so it skips .section's bordered-group treatment and just flows
   in the panel body: an intro line, a plain show/hide row, then full-width
   stacked (label-above) fields, since label-beside .prop rows are cramped
   for a photo + two text fields in a 240px column. .ov-panel/.ov-showrow/
   .ovswitch stay specific to this settings panel, but .ovfield/.ovphotozone/
   .ovimg-iconbtn below are shared with the per-shape inspector's Popup tab
   (.popup-fields further down) — same field/dropzone look, different owner. */
.ov-panel{padding:14px 16px 16px}
.ov-panel .insp-hint{padding:0 0 14px}
.ov-showrow{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:0 0 12px;border-bottom:1px solid var(--line);margin-bottom:14px}
.ov-showrow span{font-size:12.5px;color:var(--ink)}
.ovswitch{position:relative;width:34px;height:19px;border-radius:999px;background:var(--line-2);border:none;cursor:pointer;flex:none;transition:background .15s}
.ovswitch::after{content:'';position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
.ovswitch.on{background:var(--accent)}
.ovswitch.on::after{transform:translateX(15px)}
/* grays out + stops accepting input while the Show switch is off — same
   convention as .tree.disabled/.presetlist.disabled elsewhere */
.ov-fields.disabled{opacity:.45;pointer-events:none}
.ovfield{margin-bottom:12px}
.ovfield label{display:block;font-size:11px;font-weight:600;color:var(--dim);margin-bottom:5px}
.ovfield input, .ovfield textarea{width:100%;border:1px solid var(--line-2);border-radius:6px;padding:7px 9px;font:inherit;font-size:12.5px;outline:none}
.ovfield input:focus, .ovfield textarea:focus{border-color:var(--accent-line)}
.ovfield textarea{resize:vertical;min-height:56px}
.ovphotozone{position:relative;width:100%;height:100px;border-radius:6px;border:1px dashed var(--line-2);background:var(--hover);display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:pointer}
.ovphotozone.has-img{border-style:solid;cursor:default}
.ovphotozone-empty{display:flex;flex-direction:row;align-items:center;gap:12px;color:var(--faint);font-size:10.5px;pointer-events:none}
.ovphotozone.has-img .ovphotozone-empty{display:none}
/* Upload photo / Photo URL, side by side — the zone itself stays the
   "Upload photo" click target (pointer-events:none here, falls through to
   .ovphotozone's own listener), .ovpz-url opts back in as its own
   independently-clickable target. Icon/text/gap/divider all sized down
   together (design/photourl-option-k-smallicons.html, Aaron's pick after
   4 rounds of iteration — same structure as always, just smaller) so the
   pair doesn't look oversized/washed-out floating in the dashed zone. */
.ovpz-opt{display:flex;flex-direction:column;align-items:center;gap:6px}
.ovpz-opt .ico{width:14px;height:14px}
.ovpz-url{pointer-events:auto;cursor:pointer;border-radius:6px;padding:4px 6px;margin:-4px -6px}
.ovpz-url:hover{color:var(--accent)}
.ovpz-sep{width:1px;height:26px;background:var(--line-2);flex:none}
/* Rendered below .ovphotozone, not inside it (that box clips overflow for
   the preview image, which would clip this too) — a real dialog would need
   its own scrim/positioning plumbing for one input field, so this is a
   plain reveal-in-place row instead, closed by Escape, Enter (submits), or
   an outside click. */
.ovpz-urlrow{display:flex;gap:6px;margin-top:8px}
.ovpz-urlrow[hidden]{display:none} /* display:flex above would beat the hidden attr */
.ovpz-urlrow .ovpz-urlinput{flex:1;min-width:0}
.ovphotozone img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.ovphotozone .ovimg-actions{position:absolute;top:6px;right:6px;display:flex;gap:6px}
.ovimg-iconbtn{width:26px;height:26px;border-radius:50%;border:none;background:rgba(255,255,255,.92);color:var(--ink);
  display:flex;align-items:center;justify-content:center;padding:0;cursor:pointer;box-shadow:0 1px 3px rgba(15,25,40,.25)}
.ovimg-iconbtn:hover{background:#fff;color:var(--accent)}
.ovimg-iconbtn[hidden]{display:none} /* display:flex above would beat the hidden attr */
.ovimg-status{font-size:11px;color:var(--dim)}
.ovimg-status:empty{display:none}
.ovphotozone .ovimg-status{position:absolute;left:6px;bottom:6px;background:rgba(255,255,255,.9);padding:2px 6px;border-radius:4px}

/* per-shape inspector's Popup tab (Title/Details/Photo/Link — js/overlay.js
   popupPane()) — reuses the Overview settings panel's field/dropzone classes
   above verbatim (2026-07-28, Aaron's call, design/shapeinfo-*.html explored
   the options), just its own padding wrapper since it has no show/hide
   switch or .ov-fields.disabled state to support. */
.popup-fields{padding:14px 16px 16px}
.popup-fields .insp-hint{padding:0 0 14px}

/* Appearance/Popup tabs splitting the per-shape inspector — reuses the real
   .codetabs/.codetab component the Publish dialog's Links&Embeds/Analytics
   tabs already use, rather than a new tab style. */
.tabpanel{display:none}
.tabpanel.sel{display:block}

/* ======================================================================
   Mobile shell (design/mobile-option-d-hamburger-sheet.html, approved
   2026-07-29 — see planning/decisions.md). The desktop shell above is four
   position:fixed panes at fixed pixel widths with nowhere to shrink to on
   a phone; below the breakpoint the rail disappears behind a hamburger
   (js/mobile.js), and .left/.right become bottom sheets instead of side
   columns. New elements (hamburger/mobilemenu/sheethandle) are inert
   above the breakpoint so they never affect desktop layout. */
.hamburger,.mobilemenu,.sheethandle,.mobile-back,.mobile-close,.mobile-settings,.addmenu-close,.fab,.addmenu-head{display:none}

.fab{width:52px;height:52px;border-radius:50%;background:var(--accent);color:#fff;border:none;box-shadow:0 8px 20px rgba(45,127,249,.4);cursor:pointer}
.fab:active{filter:brightness(.94)}

.hamburger{width:36px;height:36px;border:none;background:none;border-radius:8px;color:var(--ink);cursor:pointer;flex:none}
.hamburger:hover,.hamburger.active{background:var(--hover)}
.hamburger svg{width:19px;height:19px;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}

/* full-screen menu page the hamburger opens — same rows as the desktop
   rail (Preset/Style/Overlay/Import/My maps) plus the top-bar actions
   that don't fit a phone-width bar (Save/Publish/Download/Help) */
.mobilemenu{position:fixed;inset:0;background:var(--panel);z-index:150;flex-direction:column;transform:translateX(-100%);transition:transform .24s ease}
body.mobile-menu-open .mobilemenu{transform:translateX(0)}
.mm-head{display:flex;align-items:center;gap:6px;height:var(--topH);padding:0 8px 0 4px;border-bottom:1px solid var(--line);flex:none}
.mm-head .ttl{font-weight:680;font-size:15px;flex:1}
.mm-body{flex:1;overflow-y:auto}
.menurow{display:flex;align-items:center;gap:12px;height:54px;padding:0 16px;border:none;border-bottom:1px solid var(--line);color:var(--ink);background:none;width:100%;text-align:left;font:600 14px var(--font);cursor:pointer;text-decoration:none}
.menurow .mic{width:30px;height:30px;border-radius:8px;background:var(--hover);display:grid;place-items:center;color:var(--dim);flex:none}
.menurow .chev{margin-left:auto;color:var(--faint);display:flex}
.menurow.active .mic{background:var(--accent-soft);color:var(--accent)}
.menurow.action .mic{background:var(--accent-soft);color:var(--accent)}
.menudivider{height:10px;background:var(--hover)}

/* shared drag-handle + close bar dropped into .left/.right for mobile —
   one instance each, not per-panel, since only one panelbody shows at a
   time (js/ui.js switchPanel) and .right's content is a single template */
.sheethandle{align-items:center;justify-content:center;position:relative;height:26px;flex:none;cursor:ns-resize;touch-action:none}
.sheethandle .grip{width:36px;height:4px;background:var(--line-2);border-radius:99px}
.sheethandle .navicon{position:absolute;right:6px;top:1px}

@media (max-width: 760px) {
  /* left-aligned, not centered — its box already starts flush with the
     search bar's edge below it (both left:12px), so centering the icon
     inside a wider tap target visually offset it from that edge */
  .hamburger{display:flex;align-items:center;justify-content:flex-start}
  .mobile-close,.mobile-settings,.addmenu-close{display:grid;place-items:center}
  /* left-aligned, not centered — the 32px box already starts flush with
     the header's own 12px padding (matching the title text and every list
     row below it), but the 18px icon centered inside that box sat 7px
     further right, reading as visually indented compared to everything
     else that starts flush at the same left edge */
  .mobile-back{display:flex;align-items:center;justify-content:flex-start}
  /* the floating "+" (#mobile-fab-add) does this job now — hide the
     desktop version, replaced by #overlay-settings-btn (Overview panel
     settings) in the same header row */
  #overlay-add{display:none}
  /* mobile keeps the #overlay-add-menu full-sheet flow (every type,
     Section included) via #mobile-fab-add instead of this canvas-docked
     toolbar — no room for a floating bar on a small screen, and it would
     sit under the bottom sheet/hamburger chrome anyway */
  #overlaybar{display:none}
  .mobilemenu{display:flex}
  .sheethandle{display:flex}

  /* ---- top bar: hamburger replaces the rail as the mode switcher;
     Save/Help/Publish/Download move into the mobile menu (below) so the
     bar itself only holds what's worth one tap at all times. Left padding
     added to match the existing 12px on the right (the hamburger used to
     sit flush at the very edge with none). ---- */
  .top{padding-left:12px}
  /* redo sits right up against the account/login button once Save/Help/
     Publish/Download and their .vsep separators are hidden below — there's
     nothing left between them but .top's 4px flex gap */
  #drop-account-wrap{margin-left:8px}
  /* desktop's padding:0 12px was sized for a big branding cell — on top of
     .top's own gap:4px after the hamburger, that read as too much space */
  .logo{width:auto;padding-left:0}
  .doctitle{display:none} /* rename/duplicate/delete still reachable via the My maps panel's own per-card icons */
  #doctitle-drop{display:none}
  .navfeedback{display:none}
  #btn-save,#drop-help-wrap,#btn-publish,#btn-export,.top>.vsep{display:none}
  /* .top has a fixed height everything else's `top:var(--topH)` depends on
     (.rail/.left/.right/.canvas) — scroll this row horizontally instead of
     letting it wrap, which would grow .top and misalign all of those */
  .exportnav{overflow-x:auto}
  .exd-instr{display:none} /* the width/height fields and Continue button are enough without the prose */

  /* ---- icon rail gone; canvas takes the full width the rail+panel used to share ---- */
  .rail{display:none}
  .canvas{left:0;right:0}
  /* no expand/collapse on mobile — js/ui.js's renderTree() always renders
     every category's children there (isMobile() check), so the chevron
     never actually toggled anything left to hide */
  .row .chev{display:none}
  /* same 12px gutter as the hamburger (left) and account button (right)
     above it, not just visually centered — left+right with no width lets
     #search-input's flex:1 fill the space between the icon buttons */
  .maptools{left:12px;right:12px}
  /* #search-input's flex:1 only fills .maptools-panel's own box — but
     .maptools-panel itself had no flex-grow relative to .maptools, so it
     never stretched to use the width .maptools now has, leaving the 3
     tool buttons stranded short of the right edge instead of flush */
  .maptools-panel{flex:1}
  .maptools-panel #search-input{min-width:0}

  /* ---- .left/.right: side columns become ONE bottom sheet on mobile, not
     two. .left is the sheet itself (slides up/down); .right is a second
     "page" of that same sheet — same position/height, sliding over it
     horizontally instead of opening its own sheet from below — so picking
     a feature/shape reads as paging forward within one sheet, and its
     back button pages back to whatever list .left is already showing
     underneath, rather than closing anything. js/ui.js's
     setInspectorVisible() adds mobile-panel-open whenever the inspector
     becomes visible, so .left (the base the page slides over) is always
     open first. ---- */
  /* --sheet-h is the one source of truth for both pages' height — set by
     class below (tap-toggle) or live by js/mobile.js's drag handler
     (inline style on body always wins over these class rules) */
  body{--sheet-h:52vh}
  body.sheet-expanded{--sheet-h:80vh}
  /* dragging sets height every pointermove frame — the .22s transition
     would fight/lag behind the finger, so it's suspended while active */
  body.sheet-dragging .left,body.sheet-dragging .right{transition:transform .22s ease}

  .left{position:fixed;top:auto;left:0;right:0;bottom:0;width:auto;height:var(--sheet-h);border-right:none;border-radius:16px 16px 0 0;box-shadow:0 -10px 30px rgba(20,30,45,.2);z-index:120;display:flex;flex-direction:column;transform:translateY(101%);transition:transform .22s ease,height .22s ease}
  body.mobile-panel-open .left{transform:translateY(0)}
  .panelbody{height:auto;flex:1;min-height:0}
  /* #overlay-add-menu is nested inside .left (needed for its desktop
     anchored-dropdown position), and .left's own transform (above) makes
     it BOTH the containing block AND a new stacking context for that
     fixed-position descendant — so #overlay-add-menu's z-index:135 only
     ever gets compared against .left's OWN z-index (120) at the top
     level, never directly against .right's (125.) .right wins that
     comparison and paints over the entire .left subtree, add-menu
     included, no matter how high a z-index the add-menu itself claims.
     Bumping .left's own z-index above .right's while the add-menu is
     specifically open (js/mobile.js watches #overlay-add-menu's hidden
     attribute) is the only fix that doesn't restructure the DOM and
     break desktop's dropdown anchoring. */
  body.mobile-addmenu-open .left{z-index:140}

  .right{position:fixed;top:auto;bottom:0;left:0;right:0;width:auto;height:var(--sheet-h);border-left:none;border-radius:16px 16px 0 0;box-shadow:-10px 0 30px rgba(20,30,45,.2);z-index:125;transform:translateX(100%);transition:transform .22s ease,height .22s ease}
  .right:not([hidden]){transform:translateX(0)}
  /* floating "+" above whichever sheet is showing — tracks --sheet-h so
     drag-resizing (above) moves it too; only meaningful in Overlay mode
     with a sheet actually open, and never over the full-screen menu.
     Opens the same add-element sheet #overlay-settings-btn's sibling
     #overlay-add would (js/mobile.js reuses its real click handler). */
  .fab{display:grid;place-items:center;position:fixed;right:16px;bottom:calc(var(--sheet-h) + 16px);z-index:130;transition:bottom .22s ease}
  body:not(.mobile-mode-overlay) .fab,
  body:not(.mobile-panel-open) .fab,
  body.mobile-menu-open .fab{display:none}
  body.sheet-dragging .fab{transition:none}

  /* Overlay lhead's own "+" (#overlay-add-menu): a full paging sheet here
     instead of a small anchored dropdown — same [data-add] buttons/click
     handlers js/overlay.js already wires, just restyled/repositioned.
     Sits above #inspector (z-index 125) so it takes over cleanly even
     while a shape's properties or Overview settings happen to be paged
     over .left already. */
  #overlay-add-menu{position:fixed;top:auto;bottom:0;left:0;right:0;width:auto;min-width:0;height:var(--sheet-h);background:var(--panel);border:none;border-radius:16px 16px 0 0;box-shadow:-10px 0 30px rgba(20,30,45,.2);padding:0;z-index:135;display:flex;flex-direction:column;transform:translateX(100%);transition:transform .22s ease,height .22s ease}
  #overlay-add-menu[hidden]{display:none} /* display:flex above would beat the hidden attr */
  #overlay-add-menu:not([hidden]){transform:translateX(0)}
  .addmenu-head{display:flex;align-items:center;justify-content:space-between;height:38px;padding:0 12px;border-bottom:1px solid var(--line);flex:none}
  /* .dropmenu button{width:100%;padding:7px 10px} (desktop, unscoped)
     otherwise wins over .navicon's own width/padding — element+class
     beats a lone class — since these buttons are still .dropmenu
     descendants. display/place-items deliberately left unset here so
     .mobile-back's left-alignment below still applies (that rule would
     lose to a display set on this more specific selector). */
  .addmenu-head .navicon{width:32px;padding:0;flex:none}
  .addmenu-head .title{font-size:13px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
  .addmenu-body{flex:1;overflow-y:auto}
  #overlay-add-menu button[data-add]{display:flex;align-items:center;gap:12px;height:54px;padding:0 16px;font-size:16px;border-radius:0;border-bottom:1px solid var(--line)}
  #overlay-add-menu button[data-add] .ico{width:18px;height:18px}

  /* back-chevron + title on the left, Close on the right — the whole
     header box matches .lhead's exactly (height/padding), the crumb is
     dropped (a single line, like .lhead's), and the title reuses
     .lhead .ttl's look — the two "pages" of one sheet share one real
     header, not two differently-sized, differently-styled ones */
  .insp-head{display:flex;align-items:center;justify-content:space-between;height:38px;padding:0 12px}
  .insp-head-left{display:flex;align-items:center;gap:8px;min-width:0}
  .insp-head .crumb{display:none}
  .insp-head .title{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}

  /* same pattern as .lhead-actions below — groups a panel's own action
     button (if any) with the new Close so .lhead's justify-content:
     space-between still only ever sees two items (title, this group) */
  .lhead-actions{display:flex;align-items:center;gap:4px}

  /* the full-screen menu suppresses both — .right needs .left open
     beneath it, so opening the menu (which closes .left) has to slide
     .right fully away too, not strand it mid-page over a closed sheet */
  body.mobile-menu-open .left{transform:translateY(101%)}
  body.mobile-menu-open .right{transform:translateX(101%)}

  /* ---- Download dialog: the order-summary rail was designed for 720px;
     stack it instead of squeezing .split-controls into a sliver ---- */
  .dialog.split{height:min(700px,88vh)}
  .split-body{flex-direction:column;overflow-y:auto}
  .split-summary{width:auto;border-left:none;border-top:1px solid var(--line)}

  /* .supportbanner only had max-width, never a real width — an
     absolutely-positioned flex row with width:auto sizes to its
     min-content in Chromium (one word per line) instead of filling the
     available space, only visible now that anything's tested this narrow */
  .supportbanner{width:min(90vw,620px)}

  /* ---- everything below was sized for a mouse at desktop distance —
     bump the base size plus every explicit font-size/icon-size that
     doesn't inherit it, so nothing on the mobile shell stays tiny ---- */
  body{font-size:15px}
  /* one size for every icon on mobile, regardless of data-icon-size —
     .sm/.md existed to fit desktop's tighter chrome, and left redo/undo +
     the search bar's icons (no size attribute, plain .ico) visibly bigger
     than everything using .sm (menurow .mic, ovrow, back/close, etc.) */
  .ico,.ico.sm,.ico.md{width:18px;height:18px}

  .row{height:36px}
  .ovrow{font-size:15px;padding:9px 8px}
  .menurow{font-size:16px}
  .lhead .ttl,.insp-head .title,.sec-top .st{font-size:13px}
  .presetcard .pname{font-size:14px}
  .fieldlabel{font-size:13px}
  .hint{font-size:13px}
  .dropzone{font-size:14px}
  .plabel{width:56px;font-size:14px}
  .colorfield .hex{font-size:14px}
  .rangefield .rv{font-size:13px;width:44px}
}

/* ---- Share map (link-based edit access, 2026-08-01) ---------------------- */
.sharedlg{width:440px}
.sharebody{padding:16px}
.sharesub{margin:0 0 14px;font-size:12.5px;line-height:1.5;color:var(--dim)}
/* the link card is .pub-linkcard, shared with the Publish panel */
#share-linkrow{margin-bottom:10px}
#share-linkrow[hidden]{display:none}
.sharebtns{display:flex;gap:8px}
.sharebtns:empty,.sharebtns:has(> button[hidden]:only-child){display:none}
.shareppl{margin-top:16px;padding-top:14px;border-top:1px solid var(--line)}
/* with nobody added the container is empty, and its divider would draw a
   section rule under nothing */
.shareppl:empty{display:none}
.sharepplhead{font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dim);margin-bottom:8px}
.sharenone{font-size:12px;color:var(--faint)}
/* turn-off sits opposite Done, not beside the link — it's the destructive
   end of the dialog, so it gets the far edge rather than a spot people can
   hit while reaching for Copy */
.sharefoot{align-items:center}
.sharefoot .spacer{flex:1}
/* needs .mini-btn.ghost's specificity to win — and stays neutral rather than
   red: turning the link off is reversible (you can make a new one), so red
   would overstate it next to a real destructive action like Delete */
.mini-btn.ghost.quiet{color:var(--dim);border-color:var(--line-2)}
.mini-btn.ghost.quiet:hover{color:var(--ink);border-color:var(--dim)}
.sharerow{display:flex;align-items:center;gap:8px;padding:5px 0}
.sharerm{width:24px;height:24px;flex:none;color:var(--faint)}
.sharerm:hover{color:#b4341c}
.sharewho{flex:1;min-width:0;font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dlg-foot{display:flex;justify-content:flex-end;padding:12px 16px;border-top:1px solid var(--line)}
/* "Shared" badge on a My-maps card you don't own — sits where the owner's
   rename/delete icons would be, so the row keeps its shape either way */
/* "this map is shared" — on the thumbnail rather than in the name row, so the
   row keeps one consistent shape (name + 3-dot) for every card, and so the
   OWNER sees it on their own map too, not just the borrower. */
.pshared-ic{position:absolute;top:7px;right:7px;width:22px;height:22px;border-radius:50%;
  background:rgba(20,30,45,.58);color:#fff;display:grid;place-items:center;pointer-events:auto}
.pshared-ic svg{width:13px;height:13px;display:block}
/* a menu row that exists but isn't yours to use (Delete, on a shared map) */
.dropmenu button:disabled{color:var(--faint);cursor:default}
.dropmenu button:disabled:hover{background:none}
.dropmenu button:disabled .ico{opacity:.5}

/* ---- presence: who else has this shared map open (2026-08-02) ------------ */
/* One avatar per person, deliberately the same initial-circle as .acct (the
   account button) so "a person" reads the same everywhere in the top bar —
   the green ring is what says "here right now" rather than a different shape.
   The row only exists when someone else IS here; a permanent "just you" chip
   would be noise on every map. */
.presence{display:inline-flex;align-items:center;margin-right:6px}
.presence[hidden]{display:none}
.pres-av{position:relative;width:26px;height:26px;border-radius:50%;
  background:linear-gradient(135deg,#2d7ff9,#7db4ff);color:#fff;
  display:grid;place-items:center;font-size:10px;font-weight:640;cursor:default;
  /* ring drawn OUTSIDE the circle, so it can't shrink the letter's box */
  box-shadow:0 0 0 2px var(--panel),0 0 0 4px #22c55e}
/* overlap when several are here, newest on top — same convention as any
   stacked-avatar row */
.pres-av + .pres-av{margin-left:-6px}
.presence:hover .pres-av{margin-left:2px}
.presence .pres-av:first-child{margin-left:0}

/* Tooltip: our own, not `title` — the native one waits a second and can't be
   styled, and this is a glanceable status, not a description. */
.pres-tip{position:absolute;top:calc(100% + 8px);right:0;white-space:nowrap;
  background:#1e2328;color:#fff;font-size:11.5px;font-weight:500;line-height:1;
  padding:6px 9px;border-radius:6px;opacity:0;pointer-events:none;
  transition:opacity .12s ease;z-index:70}
.pres-av:hover .pres-tip{opacity:1}
