:root {
  --bg: #14161a;
  --panel: #1e2128;
  --text: #e8e9ec;
  --muted: #9098a8;
  --accent: #4f8cff;
  --danger: #ff5470;
  /* Shrinks on narrow phones instead of eating the whole screen when open;
     stays a fixed 260px everywhere wider. Used by .product-sidebar,
     body.sidebar-open #app-root, and .catalog-toggle-btn.open together so
     the three always agree on how much room the sidebar actually takes. */
  --sidebar-width: min(260px, 85vw);
  --users-sidebar-width: min(320px, calc(100vw - 64px));
  --site-nav-height: 62px;
  --fullscreen-products-width: min(190px, 20vw);
  --fullscreen-tools-width: min(160px, 18vw);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
}

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid #2a2e37;
}

.app-header h1 {
  margin: 0;
  font-size: 1.25rem;
}

/* Acts as a "home" link back to the saved-quotes dashboard (see
   list_quotes in app.py) — plain text color/weight so it doesn't read as
   a typical blue underlined link, just a subtle hover to show it's
   clickable. */
.app-title-link {
  color: inherit;
  text-decoration: none;
}

.app-title-link:hover {
  color: var(--accent);
}

/* The generated quote ID, shown as a small secondary line under a
   human-given quote name — in the page heading (quote.html) and in the
   saved-quotes table (quotes_list.html) alike, so the ID stays visible
   (e.g. for support/lookup purposes) without competing with the name. */
.quote-id-sub {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--muted);
}

/* A quote's stage (see QUOTE_STAGES in app.py) — shown next to the quote
   name/title and in every quotes table (quotes_list.html). */
.quote-status-badge {
  display: inline-block;
  vertical-align: middle;
  margin-left: 0.5rem;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.quote-status-badge.paid-full {
  background: rgba(59, 178, 111, 0.18);
  color: #4ade80;
}

.quote-status-badge.paid-partial {
  background: rgba(255, 165, 59, 0.18);
  color: #ffa53b;
}

.quote-status-badge.paid-none {
  background: rgba(148, 163, 184, 0.18);
  color: var(--muted);
}

/* Paid in full *and* shipped/handed off (see QUOTE_STAGES in app.py) — a
   distinct color from paid-full so "money's in" and "the whole job is
   done" don't read the same at a glance. */
.quote-status-badge.stage-completed {
  background: rgba(79, 140, 255, 0.18);
  color: var(--accent);
}

.quote-status-badge.stage-invoice {
  background: rgba(167, 112, 239, 0.2);
  border-color: rgba(196, 155, 255, 0.3);
  color: #cda7ff;
}

/* "$X paid of $Y" line under the quote page's header toolbar (see
   quote-stage-btn/quote-stage-badge in quote.html) — kept in sync with the
   stage modal by quote.js. */
.quote-payment-summary {
  margin: 0.5rem 0 0;
  font-weight: 600;
  color: var(--muted);
}

/* Dashboard sections on /quotes (quotes_list.html): the two "needs
   follow-up" lists plus the full list underneath. */
.dashboard-section {
  margin-bottom: 2.5rem;
}

.dashboard-section h2 {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.3rem;
  font-size: 1.05rem;
}

.dashboard-section-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.6rem;
  height: 1.6rem;
  padding: 0 0.4rem;
  border-radius: 999px;
  background: var(--panel);
  color: var(--muted);
  font-size: 0.8rem;
}

.dashboard-section-hint {
  margin: 0 0 0.75rem;
  font-size: 0.82rem;
  color: var(--muted);
}

/* The ▸/▾ prefix already signals it's expandable/collapsible — plain text
   color rather than the usual blue underlined link look. */
.dashboard-section-title-link {
  color: var(--text);
  text-decoration: none;
}

.dashboard-section-title-link:hover {
  color: var(--accent);
}

.dashboard-section-more {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
}

.dashboard-section-more a {
  color: var(--accent);
}

.dashboard-pagination {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.75rem;
}

.dashboard-pagination-label {
  font-size: 0.82rem;
  color: var(--muted);
}

.quote-notes {
  margin-bottom: 1.5rem;
  padding: 1rem;
  background: var(--panel);
  border-radius: 10px;
}

.quote-notes strong {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.quote-notes p {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.toolbar {
  display: flex;
  gap: 0.75rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 1rem;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--panel);
  color: var(--text);
  font-size: 0.9rem;
  cursor: pointer;
}

.btn:hover {
  border-color: var(--accent);
}

/* Needed explicitly: at equal specificity a plain [hidden] (the browser's
   own default) loses to .btn's own `display: inline-flex` above, so
   setting .hidden on a .btn (e.g. #scene-measure-undo-btn, shown/hidden
   per-click by waitForModelClick in main.js) would silently fail to
   actually hide it without this override — same issue already hit and
   fixed for .scene-fixture-hud-btn below. */
.btn[hidden] {
  display: none;
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* Admin-only destructive actions (deleting a quote) — a distinct color so
   it doesn't sit next to Edit/Save looking like just another neutral
   action. Regular users never see a button with this class at all (see
   quotes_list.html/quote.html), so it's never a trap for them to avoid. */
.btn-danger {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
}

.btn-danger:hover {
  border-color: #fff;
}

.user-bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.4rem 1.5rem;
  background: #101216;
  border-bottom: 1px solid #2a2e37;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Goes back to the saved-quotes dashboard (see list_quotes in app.py) —
   shown on every logged-in page (this bar lives in base.html, not any one
   page's own template) so there's always a way back to it, not just from
   the main editor's own title. */
.user-bar-home-link {
  color: var(--muted);
  text-decoration: none;
}

.user-bar-home-link:hover {
  color: var(--accent);
}

/* Pushes itself (and everything after it — the logout form) flush right,
   leaving the home link alone on the left, without needing
   justify-content: space-between on the row (which would space *every*
   child apart evenly instead of just grouping these two on one side). */
.user-bar-info {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.user-bar-role {
  text-transform: capitalize;
  color: var(--accent);
}

.user-bar .link-btn {
  font-size: 0.8rem;
}

.edit-mode-banner {
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--accent);
}

.login-page {
  min-height: 80vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

.login-card {
  width: min(90vw, 340px);
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1.75rem;
  background: var(--panel);
  border-radius: 10px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}

.login-card h1 {
  margin: 0 0 0.25rem;
  font-size: 1.2rem;
  text-align: center;
}

.login-card label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.login-card input {
  padding: 0.55rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  background: var(--bg);
  color: var(--text);
  font-size: 0.9rem;
}

.login-card .btn {
  justify-content: center;
  margin-top: 0.25rem;
}

.login-error {
  padding: 0.6rem 0.75rem;
  border-radius: 6px;
  background: rgba(255, 84, 112, 0.15);
  border: 1px solid var(--danger);
  color: var(--danger);
  font-size: 0.85rem;
}

.login-intro {
  margin: -0.25rem 0 0;
  color: var(--muted);
  font-size: 0.82rem;
  line-height: 1.45;
  text-align: center;
}

.login-secondary {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding-top: 0.9rem;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  color: var(--muted);
  font-size: 0.8rem;
  text-align: center;
}

.login-secondary .btn,
.login-back-link {
  justify-content: center;
}

.login-back-link {
  color: var(--accent);
  font-size: 0.82rem;
  text-align: center;
}

.quote-row-actions {
  vertical-align: middle;
  text-align: right;
}

.quote-row-actions-inner {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  white-space: nowrap;
}

.quote-row-actions-inner .btn {
  padding: 0.35rem 0.7rem;
  font-size: 0.8rem;
}

/* Admin-only browse-by-user panel on the Saved Quotes page — an overlay
   drawer (doesn't push .quote-page's content over the way the main
   editor's product sidebar does; .quote-page's auto-centered margin isn't
   worth fighting for what's a quick admin utility panel) opened via
   users-toggle-btn, closed by clicking outside it (see quotes-list.js). */
.users-toggle-btn {
  position: fixed;
  top: 5rem;
  left: 0;
  z-index: 560;
  padding: 0.6rem 0.9rem;
  border: 1px solid #2a2e37;
  border-left: none;
  border-radius: 0 8px 8px 0;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
  transition: left 0.2s ease;
}

.users-toggle-btn.open {
  left: calc(var(--users-sidebar-width) - 1px);
  width: 52px;
  min-height: 48px;
  padding: 0;
  border-left: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 0 14px 14px 0;
}

.users-sidebar {
  position: fixed;
  top: var(--site-nav-height);
  left: 0;
  bottom: 0;
  width: var(--users-sidebar-width);
  background: var(--panel);
  border-right: 1px solid #2a2e37;
  border-radius: 0 18px 18px 0;
  padding: 1.2rem;
  transform: translateX(-100%);
  transition: transform 0.2s ease;
  z-index: 550;
  overflow-y: auto;
  box-shadow: 4px 0 24px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.users-sidebar.open {
  transform: translateX(0);
}

.users-sidebar-header h2 {
  margin: 0 0 0.55rem;
  font-size: 1.1rem;
  color: var(--text);
}

.users-sidebar-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  min-height: 54px;
  padding: 0.7rem 0.85rem;
  border-radius: 13px;
  color: var(--text);
  text-decoration: none;
  font-size: 0.9rem;
}

.users-sidebar-item:hover {
  background: var(--bg);
}

.users-sidebar-item.active {
  background: var(--accent);
  color: #fff;
}

.users-sidebar-role {
  margin-left: 0.4rem;
  font-size: 0.7rem;
  text-transform: capitalize;
  color: var(--muted);
}

.users-sidebar-item.active .users-sidebar-role {
  color: rgba(255, 255, 255, 0.85);
}

.users-sidebar-count {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  min-width: 34px;
  height: 30px;
  padding: 0 0.55rem;
  border-radius: 999px;
  background: rgba(5, 8, 14, 0.74);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  text-align: center;
}

.users-sidebar-item.active .users-sidebar-count {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
}

.users-sidebar-divider {
  height: 1px;
  margin: 0.5rem 0;
  background: #2a2e37;
}

.quotes-filter-note {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.quotes-filter-note a {
  color: var(--accent);
}

.quotes-search-row {
  margin: 0 0 1.5rem;
}

.quotes-search-input {
  width: 100%;
  max-width: 360px;
  padding: 0.6rem 0.9rem;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--panel);
  color: var(--text);
  font-size: 0.95rem;
}

#app-root {
  padding: 1.5rem;
  margin-left: 0;
  transition: margin-left 0.2s ease;
}

/* Push the whole grid clear of the open sidebar instead of letting it sit
   underneath, so photos are never partially covered while it's open. */
body.sidebar-open #app-root {
  margin-left: var(--sidebar-width);
}

#photo-stage-container {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  justify-content: center;
  align-items: flex-start;
  min-height: 60vh;
}

.empty-state {
  color: var(--muted);
  margin-top: 4rem;
}

.photo-stage {
  position: relative;
  flex: 0 0 auto;
  width: min(320px, 80vw);
  height: 240px;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
  cursor: zoom-in;
  transform-origin: center center;
  perspective: 900px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  /* Stops phones/tablets from treating a double-tap here as their own
     page-zoom gesture, so it reaches our own double-tap-to-fullscreen
     handling instead (see onDoubleClickOrTap in main.js) — also drops the
     ~300ms tap delay some mobile browsers add while they wait to see if a
     tap is the start of a double-tap. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Sized via aspect-ratio (set inline per-photo in JS) to exactly match the
   true photo, letterboxed within the stage rather than cropped. Fixtures
   and ropes are positioned as percentages of THIS element, not the outer
   stage — so those percentages stay pixel-accurate to the real photo
   whether the stage is a small grid tile, zoomed, or fullscreen, instead of
   shifting/deforming when the crop changes between those modes. */
.photo-content {
  position: relative;
  max-width: 100%;
  max-height: 100%;
}

.photo-stage-placeholder {
  flex: 0 0 auto;
  visibility: hidden;
}

.photo-stage.zoom-active {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  z-index: 40;
  cursor: default;
}

.photo-stage.fullscreen-mode {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  border-radius: 0;
  z-index: 500;
  cursor: default;
}

/* Scrolled in past the normal fit-to-screen framing (see the wheel handler
   in main.js) — draggable to look around, same as any other pannable
   image viewer. */
.photo-stage.fullscreen-mode.zoomed-in {
  cursor: grab;
}

.photo-stage.fullscreen-mode.panning {
  cursor: grabbing;
}

.photo-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.photo-fullscreen-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 60;
}

.photo-stage.fullscreen-mode .photo-fullscreen-close {
  display: flex;
}

/* Only shown once the photo is "active" (zoom-active — see initPhotoZoom in
   main.js) so it doesn't clutter every grid tile at once. Sits top-right,
   same spot as the fullscreen close button — the two never show at once
   (a stage is either zoom-active or fullscreen-mode, never both), so they
   don't collide. */
.photo-delete-btn {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1.5px solid #fff;
  background: var(--danger);
  color: #fff;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 55;
}

.photo-stage.zoom-active .photo-delete-btn {
  display: flex;
}

/* Always visible in the base grid (not gated behind zoom-active like the
   delete button) — reordering is a grid-level action, so the handle needs
   to be there before the user has clicked into any single photo. */
.photo-drag-handle {
  position: absolute;
  bottom: 0.5rem;
  left: 0.5rem;
  width: 26px;
  height: 26px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 1rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none;
  user-select: none;
  z-index: 56;
}

.photo-drag-handle:active {
  cursor: grabbing;
}

.photo-stage.fullscreen-mode .photo-drag-handle {
  display: none;
}

/* Only appears after hovering the handle for 1s (see the mouseenter timer
   in main.js) — the .visible class is what JS toggles; opacity/visibility
   (not display) so the fade transition has something to animate.
   Anchored to the handle's left edge (not centered on it) and extending
   rightward — the handle sits right at the stage's own left edge, and the
   stage clips overflow, so a centered tooltip would get cut off on the left. */
.photo-drag-handle-tooltip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  background: rgba(20, 22, 26, 0.95);
  color: var(--text);
  font-size: 0.7rem;
  font-weight: 400;
  white-space: nowrap;
  padding: 0.3rem 0.6rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease;
  z-index: 57;
}

.photo-drag-handle-tooltip.visible {
  opacity: 1;
  visibility: visible;
}

/* Shown on whichever other photo tile the dragged photo is currently
   hovering directly over, telling the user a drop here swaps the two
   photos' positions instead of inserting into a gap. */
.photo-stage.drag-swap-target::after {
  content: "Swap";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(79, 140, 255, 0.4);
  color: #fff;
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  z-index: 46;
  pointer-events: none;
}

/* A thin glowing bar marking exactly where the dragged photo would land if
   dropped in the gap it's currently hovering (as opposed to swapping —
   see .drag-swap-target above). Lives on <body>, not inside any stage, so
   its fixed positioning follows the true viewport. */
.photo-insert-indicator {
  position: fixed;
  width: 4px;
  border-radius: 2px;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
  z-index: 300;
  pointer-events: none;
}

.photo-insert-indicator[hidden] {
  display: none;
}

.fullscreen-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  z-index: 400;
}

.quote-page {
  max-width: 800px;
  margin: 2rem auto;
  padding: 0 1.5rem;
}

/* /quotes only (see quotes_list.html) — .quote-page alone is also the
   single-quote view (quote.html), which still wants the narrower reading
   width above; the dashboard's own tables read better with real width to
   stretch into instead of being squeezed into an 800px column. */
.quote-page.quotes-dashboard {
  max-width: 1600px;
}

.quote-photos {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.quote-photo {
  width: 100%;
  border-radius: 10px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}

.quote-table {
  background: var(--panel);
  border-collapse: separate;
  border-spacing: 0;
  border-radius: 18px;
  padding: 0;
  overflow: hidden;
}

.quote-table th,
.quote-table td {
  padding: 0.8rem 0.75rem;
  border-right: 0;
}

.quote-table thead th {
  background: rgba(255, 255, 255, 0.035);
}

.quote-table tbody tr:last-child td {
  border-bottom: 0;
}

.quote-table th:first-child,
.quote-table td:first-child {
  padding-left: 1rem;
}

.quote-table th:last-child,
.quote-table td:last-child {
  padding-right: 1rem;
}

.review-section {
  max-width: 720px;
  margin: 2rem auto;
  padding: 1.5rem;
  background: var(--panel);
  border-radius: 10px;
}

.review-details {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid #2a2e37;
}

.review-details label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.review-details input,
.review-details textarea {
  padding: 0.55rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  background: var(--bg);
  color: var(--text);
  font-size: 0.9rem;
  font-family: inherit;
  resize: vertical;
}

.review-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 1rem;
}

.review-table th,
.review-table td {
  text-align: left;
  padding: 0.6rem 0.5rem;
  border-bottom: 1px solid #2a2e37;
}

/* Manufacturer and the spec one-liner ("40W · 120V · 4200 lm · 3000K ·
   IP65"), tucked under a line item's product name on both the Review screen
   and the saved quote — five more columns would push the prices off the edge
   of the table, and these read fine stacked. Same treatment as
   .quote-id-sub, which plays the same role in the quotes tables. */
.line-item-sub {
  margin-top: 0.15rem;
  font-size: 0.75rem;
  color: var(--muted);
}

.review-total {
  margin-top: 1rem;
  font-size: 1.1rem;
  text-align: right;
}

/* Past versions of a quote, kept because they'd already been sent (see
   archive_current_revision in app.py). Listed under the line items on the
   live quote page only. */
.quote-revisions {
  margin-top: 2.5rem;
}

.quote-revisions-hint {
  margin: 0.25rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* The live version, pinned to the bottom of the history table so the list
   reads as a complete lineage rather than only the superseded parts. */
.quote-revision-current {
  font-weight: 600;
}

/* Which revision a quote is on — only shown once there's more than one, so
   a quote that's never been revised doesn't carry a meaningless "Rev 1". */
.quote-status-badge.quote-rev-badge {
  background: rgba(148, 163, 184, 0.18);
  color: var(--muted);
}

/* Banner across the top of an archived revision, in place of the payment
   summary the live quote shows there. */
.quote-rev-banner {
  margin: 0.5rem 0 0;
  padding: 0.6rem 0.8rem;
  border-left: 3px solid var(--accent);
  background: rgba(79, 140, 255, 0.08);
  border-radius: 4px;
  color: var(--muted);
  max-width: 70ch;
}

.review-actions {
  margin-top: 1.5rem;
  display: flex;
  justify-content: space-between;
}

.photo-badge {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  background: rgba(0, 0, 0, 0.6);
  color: var(--text);
  font-size: 0.75rem;
  padding: 0.2rem 0.5rem;
  border-radius: 6px;
  pointer-events: none;
}

.photo-measure-point {
  position: absolute;
  width: 18px;
  height: 18px;
  transform: translate(-50%, -50%);
  border: 3px solid #fff;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px rgba(79, 140, 255, 0.3), 0 4px 14px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  z-index: 65;
}

.fixture-count-btn {
  position: absolute;
  right: 0.55rem;
  bottom: 0.55rem;
  z-index: 58;
  padding: 0.38rem 0.65rem;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 999px;
  background: rgba(12, 16, 25, 0.68);
  color: var(--muted);
  font: 600 0.7rem/1 system-ui, sans-serif;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28), inset 0 1px rgba(255, 255, 255, 0.1);
  -webkit-backdrop-filter: blur(12px) saturate(145%);
  backdrop-filter: blur(12px) saturate(145%);
}

.fixture-count-btn:hover {
  color: var(--text);
  border-color: rgba(114, 167, 255, 0.5);
}

.fixture-count-btn.active {
  border-color: rgba(134, 184, 255, 0.7);
  background: linear-gradient(180deg, rgba(83, 145, 246, 0.94), rgba(43, 99, 196, 0.9));
  color: #fff;
  box-shadow: 0 8px 22px rgba(37, 100, 211, 0.34), inset 0 1px rgba(255, 255, 255, 0.35);
}

.photo-stage.fullscreen-mode .fixture-count-btn {
  position: fixed;
  right: calc(var(--fullscreen-tools-width) + 0.75rem);
  bottom: 0.75rem;
  font-size: 0.78rem;
}

.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.modal[hidden] {
  display: none;
}

.add-photo-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: min(90vw, 320px);
  height: 220px;
  margin-top: 2rem;
  border: 2px dashed #3a3f4a;
  border-radius: 10px;
  background: #23262d;
  color: var(--muted);
  cursor: pointer;
}

.add-photo-tile:hover {
  border-color: var(--accent);
  color: var(--text);
}

.add-photo-icon {
  font-size: 2rem;
  line-height: 1;
}

.modal-content {
  background: var(--panel);
  padding: 1rem;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  align-items: center;
}

#camera-video {
  max-width: min(90vw, 640px);
  border-radius: 8px;
}

/* Row layout (canvas left, everything else in a side column to the
   right) — overrides .modal-content's default centered column so the
   measure panel below never sits on top of the model it's asking about. */
.model-3d-view-content {
  flex-direction: row;
  align-items: flex-start;
}

.model-3d-view-canvas {
  max-width: min(60vw, 520px);
  max-height: min(70vh, 520px);
  width: min(60vw, 520px);
  height: min(70vh, 520px);
  border-radius: 8px;
  background: #101216;
  cursor: grab;
  flex-shrink: 0;
}

.model-3d-view-canvas:active {
  cursor: grabbing;
}

.model-3d-view-side {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: 220px;
  flex-shrink: 0;
}

/* The "how many feet is that" step — replaces a plain prompt() (see
   measureModelDimensions in model-3d-view.js), which is always screen-
   centered and can't be moved off of the model it's asking about. In-flow
   here (not position:fixed like .rope-hud) since .model-3d-view-side
   already puts it beside the canvas rather than on top of it. */
.model-3d-view-measure-panel {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: rgba(20, 22, 26, 0.95);
  border: 1px solid #2a2e37;
  border-radius: 10px;
  padding: 0.9rem;
}

.model-3d-view-measure-panel[hidden] {
  display: none;
}

/* Shared by the Add Product modal's real-world-size field and the bigger
   3D view's per-axis measure panel — a whole-feet input next to a 0-11.99
   inches input, so a fixture smaller than a foot (or an odd size like
   1'4") can be entered precisely instead of forcing it into one decimal-
   feet field. */
.feet-inches-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.feet-inches-row input {
  flex: 1;
  min-width: 0;
  padding: 0.5rem 0.6rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  background: var(--panel);
  color: var(--text);
  font-size: 0.95rem;
}

.feet-inches-unit {
  color: var(--muted);
  font-size: 0.85rem;
}

.model-3d-view-measure-input {
  padding: 0.5rem 0.6rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  background: var(--panel);
  color: var(--text);
  font-size: 0.95rem;
}

.model-3d-view-measure-panel .btn {
  width: 100%;
  justify-content: center;
}

#send-modal .modal-content {
  align-items: stretch;
  width: min(90vw, 360px);
}

#send-modal label {
  font-size: 0.85rem;
  color: var(--muted);
}

#send-modal input {
  padding: 0.5rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  background: var(--bg);
  color: var(--text);
}

/* One row per recipient (see addEmailRow in quote.js) — built up
   dynamically as "+ Add recipient" is clicked. */
.send-email-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.send-email-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.send-email-row .send-email-input {
  flex: 1;
  min-width: 0;
}

.send-price-tier {
  flex: 0 0 150px;
  padding: 0.5rem;
}

.price-tier-cell {
  font-size: 0.78rem;
  line-height: 1.65;
  white-space: nowrap;
}

.price-tier-totals {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0.55rem 1rem;
}

.send-email-remove-btn {
  flex-shrink: 0;
  padding: 0.3rem 0.7rem;
  font-size: 0.9rem;
  line-height: 1;
}

#send-email-add-btn {
  align-self: flex-start;
  padding: 0.35rem 0.7rem;
  font-size: 0.8rem;
}

.send-status {
  min-height: 1.2rem;
  font-size: 0.8rem;
  color: var(--danger);
}

#stage-modal .modal-content {
  align-items: stretch;
  width: min(90vw, 360px);
}

.stage-modal-options {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.stage-modal-options label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
}

.stage-modal-amount-row {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.stage-modal-amount-row label {
  font-size: 0.85rem;
  color: var(--muted);
}

.stage-modal-amount-row input {
  padding: 0.5rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  background: var(--bg);
  color: var(--text);
}

.stage-modal-amount-preview {
  margin: 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* An author-level `display` (above) always beats the browser's default
   `[hidden] { display: none }` regardless of selector specificity — same
   pitfall as .add-product-modal-content label[hidden] elsewhere in this
   file. */
.stage-modal-amount-row[hidden] {
  display: none;
}

.add-product-modal-content {
  align-items: stretch;
  width: min(90vw, 420px);
  max-height: 85vh;
  overflow-y: auto;
}

.add-product-modal-content h2 {
  margin: 0;
  font-size: 1.1rem;
}

.add-product-modal-content label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--muted);
}

/* An author-level `display` (above) always beats the browser's default
   `[hidden] { display: none }` regardless of selector specificity — so
   without this, a hidden label here would still render. */
.add-product-modal-content label[hidden] {
  display: none;
}

.add-product-modal-content input[type="text"],
.add-product-modal-content input[type="number"],
.add-product-modal-content select {
  padding: 0.5rem;
  border-radius: 6px;
  border: 1px solid #2a2e37;
  background: var(--bg);
  color: var(--text);
  font-size: 0.9rem;
}

.add-product-checkbox-row {
  flex-direction: row !important;
  align-items: center;
  gap: 0.5rem !important;
}

/* Visually subordinate to "Use rope-style placement" above it — only
   relevant/shown when that's checked (see product-editor.js). */
.add-product-nested-checkbox {
  margin-left: 1.4rem;
  font-size: 0.8rem;
}

.add-product-category-field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.link-btn {
  align-self: flex-start;
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font-size: 0.8rem;
  cursor: pointer;
}

.link-btn:hover {
  text-decoration: underline;
}

.add-product-image-source {
  border: 1px solid #2a2e37;
  border-radius: 8px;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.product-price-tiers {
  margin: 0;
  padding: 0.8rem;
  border: 1px solid var(--glass-border);
  border-radius: 12px;
  background: rgba(7, 10, 17, 0.28);
}

.product-price-tiers legend {
  padding: 0 0.35rem;
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 700;
}

.product-price-tiers > p {
  margin: 0 0 0.7rem;
}

.product-price-tier-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.55rem;
}

.product-price-tier-grid label {
  min-width: 0;
}

@media (max-width: 520px) {
  .product-price-tier-grid { grid-template-columns: 1fr; }
}

.add-product-image-source legend {
  padding: 0 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Part number, manufacturer, and the electrical figures a commercial buyer
   needs off the finished quote — grouped into their own box so the fixture's
   own placement/pricing settings above stay easy to scan. */
.add-product-specs {
  border: 1px solid #2a2e37;
  border-radius: 8px;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.add-product-specs legend {
  padding: 0 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.add-product-specs-optional {
  font-weight: 400;
  opacity: 0.8;
}

/* One user-defined spec: its name, its value, and a button to drop the row.
   The name column is narrower than the value since spec names ("Wattage",
   "Finish") are short and predictable while values can run long. */
.spec-rows {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.spec-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
  gap: 0.4rem;
  align-items: center;
}

.spec-row input {
  width: 100%;
}

.spec-row-remove-btn {
  padding: 0.3rem 0.6rem;
  line-height: 1;
  color: var(--muted);
}

.spec-row-remove-btn:hover {
  color: #ff6b6b;
}

.spec-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* Preset bundles sit to the right of "+ Add Spec", pushed to the far edge so
   the one-at-a-time and all-at-once paths read as distinct choices rather
   than one run-on row of buttons. */
.spec-presets {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
}

.spec-presets-label {
  font-size: 0.75rem;
  color: var(--muted);
}

.spec-preset-btn {
  padding: 0.3rem 0.6rem;
  font-size: 0.8rem;
}

.image-source-tabs {
  display: flex;
  gap: 0.5rem;
}

.image-source-tab {
  flex: 1;
  justify-content: center;
  font-size: 0.8rem;
  padding: 0.4rem 0.6rem;
}

.image-source-tab[hidden] {
  display: none;
}

.image-source-tab.active {
  border-color: var(--accent);
  color: var(--accent);
}

.shape-picker {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
}

.shape-picker[hidden] {
  display: none;
}

.shape-option {
  aspect-ratio: 1;
  padding: 0.4rem;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--bg);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.shape-option:hover {
  border-color: var(--accent);
}

.shape-option.selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.shape-option img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.upload-picker {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
}

.upload-picker[hidden] {
  display: none;
}

.model3d-picker {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
}

.model3d-picker[hidden] {
  display: none;
}

.model3d-hint {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
}

.new-product-model-preview {
  width: 160px;
  height: 160px;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--bg);
}

.new-product-model-preview[hidden] {
  display: none;
}

.bend-config {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  margin-top: -0.4rem;
  padding: 0.7rem 0.8rem;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--bg);
}

.bend-config[hidden] {
  display: none;
}

.bend-axis-options {
  display: flex;
  gap: 0.5rem;
}

.bend-axis-options[hidden] {
  display: none;
}

.bend-axis-btn {
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 1px solid #2a2e37;
  background: transparent;
  color: var(--text);
  font-size: 0.82rem;
  cursor: pointer;
}

.bend-axis-btn:hover {
  border-color: var(--accent);
}

.bend-axis-btn.active {
  border-color: var(--accent);
  background: var(--accent);
  color: #101216;
}

.bend-preview-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 160px;
  height: 160px;
}

.bend-preview-canvas {
  width: 160px;
  height: 160px;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--bg);
}

.bend-preview-canvas[hidden] {
  display: none;
}

.model3d-status {
  font-size: 0.8rem;
  color: var(--muted);
}

.current-image-picker {
  display: flex;
  align-items: flex-start;
}

.current-image-picker[hidden] {
  display: none;
}

.new-product-image-preview {
  width: 80px;
  height: 80px;
  object-fit: contain;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--bg);
}

.fixture-sharing-fieldset {
  display: grid;
  gap: 0.65rem;
  padding: 0.9rem;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.035);
}

.fixture-sharing-fieldset > label {
  display: grid;
  gap: 0.4rem;
}

.fixture-sharing-fieldset select {
  width: 100%;
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--glass-border);
  border-radius: 9px;
  background: var(--panel);
  color: var(--text);
}

.fixture-audience-users {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.45rem;
  padding: 0.65rem;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.16);
}

.fixture-audience-users[hidden] {
  display: none;
}

.fixture-audience-user {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.add-product-error {
  min-height: 1.1rem;
  font-size: 0.8rem;
  color: var(--danger);
}

.toast {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translate(-50%, 1rem);
  background: var(--panel);
  border: 1px solid #2a2e37;
  padding: 0.6rem 1rem;
  border-radius: 8px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 300;
}

.toast.visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

.modal-actions {
  display: flex;
  gap: 0.75rem;
}

.catalog-toggle-btn {
  position: fixed;
  top: 5rem;
  left: 0;
  z-index: 560;
  padding: 0.6rem 0.9rem;
  border: 1px solid #2a2e37;
  border-left: none;
  border-radius: 0 8px 8px 0;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
  transition: left 0.2s ease;
}

.catalog-toggle-btn.open {
  left: var(--sidebar-width);
}

.product-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--sidebar-width);
  background: var(--panel);
  border-right: 1px solid #2a2e37;
  padding: 1.25rem 1rem;
  transform: translateX(-100%);
  transition: transform 0.2s ease;
  z-index: 550;
  overflow-y: auto;
  box-shadow: 4px 0 24px rgba(0, 0, 0, 0.4);
}

.product-sidebar.open {
  transform: translateX(0);
}

/* Mirrors .catalog-toggle-btn/.product-sidebar above, flipped to the right
   edge — only shown while a photo is fullscreen (see
   showFullscreenToolsPanel/hideFullscreenToolsPanel in main.js), since
   that's the one time the main toolbar these buttons normally live in is
   covered and unreachable. */
.fullscreen-tools-toggle-btn {
  position: fixed;
  top: 5rem;
  right: 0;
  z-index: 560;
  padding: 0.6rem 0.9rem;
  border: 1px solid #2a2e37;
  border-right: none;
  border-radius: 8px 0 0 8px;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
  transition: right 0.2s ease;
}

.fullscreen-tools-toggle-btn.open {
  right: var(--sidebar-width);
}

.fullscreen-tools-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--sidebar-width);
  background: var(--panel);
  border-left: 1px solid #2a2e37;
  padding: 1.25rem 1rem;
  transform: translateX(100%);
  transition: transform 0.2s ease;
  z-index: 550;
  overflow-y: auto;
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.fullscreen-tools-panel.open {
  transform: translateX(0);
}

.product-sidebar h2 {
  margin: 0;
  font-size: 1rem;
  color: var(--muted);
}

.product-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.product-asset-tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.3rem;
  margin: 0 0 0.85rem;
  padding: 0.25rem;
  border: 1px solid var(--glass-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.045);
}

.product-asset-tab {
  min-width: 0;
  padding: 0.48rem 0.35rem;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 650;
  cursor: pointer;
}

.product-asset-tab:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.07);
}

.product-asset-tab.active {
  color: #fff;
  background: linear-gradient(180deg, rgba(91, 150, 245, 0.82), rgba(49, 104, 198, 0.76));
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.25), 0 5px 14px rgba(28, 83, 178, 0.25);
}

.add-product-btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid #2a2e37;
  background: var(--panel);
  color: var(--text);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.add-product-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.catalog-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.category-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.category-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: var(--panel);
  color: var(--text);
  font-size: 0.9rem;
  cursor: pointer;
  text-align: left;
}

.category-header:hover {
  border-color: var(--accent);
}

.category-arrow {
  color: var(--muted);
  font-size: 0.75rem;
}

.category-items[hidden] {
  display: none;
}

.category-items {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.5rem 0 0.25rem 0.75rem;
}

.catalog-item {
  position: relative;
  display: grid;
  grid-template-columns: 48px 1fr auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 1.4rem 0.5rem 0.5rem;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.catalog-item[hidden] {
  display: none;
}

body.fullscreen-panels-open .product-asset-tabs {
  grid-template-columns: 1fr;
  margin-bottom: 0.55rem;
}

body.fullscreen-panels-open .product-asset-tab {
  padding: 0.4rem 0.25rem;
  font-size: 0.65rem;
}

.catalog-item:hover {
  border-color: var(--accent);
}

.catalog-item img {
  width: 48px;
  height: 48px;
  object-fit: contain;
}

.catalog-item-name {
  font-size: 0.85rem;
}

.catalog-item-price {
  font-size: 0.8rem;
  color: var(--muted);
}

/* Opens the Add Product modal pre-filled for this product (see
   product-editor.js's openForEdit) — extra right padding on .catalog-item
   above keeps this clear of the price text. */
.catalog-item-menu-btn {
  position: absolute;
  top: 0.15rem;
  right: 0.15rem;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.catalog-item-menu-btn:hover {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
}

.catalog-item-menu-btn[hidden] {
  display: none;
}

.placement-ghost {
  position: fixed;
  width: 60px;
  height: 60px;
  object-fit: contain;
  pointer-events: none;
  transform: translate(-50%, -50%);
  opacity: 0.85;
  z-index: 200;
}

/* Overrides .placement-ghost's fixed 60x60 product-icon size with something
   that actually looks like a dragged photo thumbnail. */
.photo-drag-ghost {
  width: 140px;
  height: auto;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.placed-item {
  position: absolute;
  touch-action: none;
  cursor: grab;
}

.rope-layer,
.rope-draft-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.rope-hit {
  stroke: transparent;
  stroke-width: 6;
  vector-effect: non-scaling-stroke;
  pointer-events: stroke;
  cursor: pointer;
}

.rope-glow {
  stroke: #ffdd78;
  stroke-width: 10;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  opacity: 0.45;
  filter: blur(4px);
}

/* Tube look: dark casing (base) wider than the lit core leaves a rim of
   shadow along the core's edges, reading as round instead of a flat line.
   A thin bright highlight on top sells it further. */
.rope-base {
  stroke: #2a2118;
  stroke-width: 7;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.rope-core {
  stroke: #f0a93c;
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.rope-highlight {
  stroke: #fff6d2;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  opacity: 0.85;
}

.rope-item.selected .rope-core {
  stroke: #ffc35c;
}

.rope-item.selected .rope-highlight {
  stroke: #ffffff;
}

.rope-item.selected .rope-glow {
  opacity: 0.85;
  stroke-width: 13;
}

/* Selection swaps the path's `d` to straight point-to-point segments (see
   updatePaths() in placement.js) — this dash pattern breaks that single
   straight path visually into a chain of short, separate-looking bulbs so
   it reads as "many little rope lights attached to each other" rather than
   one solid straight strand, hinting that each stretch is independently
   grabbable. Deselecting drops the dasharray and swaps back to the smoothed
   `d`, so it reads as one solid rope again. */
.rope-item.selected .rope-base,
.rope-item.selected .rope-core,
.rope-item.selected .rope-highlight,
.rope-item.selected .rope-glow {
  stroke-dasharray: 4.5 2.2;
}

.rope-delete-btn {
  position: absolute;
  transform: translate(-50%, calc(-100% - 8px));
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--danger);
  border: 1.5px solid #fff;
  color: #fff;
  font-size: 8px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  z-index: 12;
}

.rope-delete-btn[hidden] {
  display: none;
}

.photo-stage.fullscreen-mode .rope-delete-btn {
  width: 30px;
  height: 30px;
  font-size: 16px;
  border-width: 3px;
  transform: translate(-50%, calc(-100% - 14px));
}

.rope-footage-btn {
  position: absolute;
  transform: translate(-50%, 10px);
  padding: 0.15rem 0.5rem;
  border-radius: 10px;
  background: rgba(20, 22, 26, 0.9);
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: 0.65rem;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  z-index: 12;
}

.rope-footage-btn[hidden] {
  display: none;
}

.photo-stage.fullscreen-mode .rope-footage-btn {
  font-size: 0.9rem;
  padding: 0.3rem 0.8rem;
  transform: translate(-50%, 18px);
}

/* Sits to the side of the midpoint (delete is above, footage is below)
   rather than stacked with them, so all three controls stay clear of one
   another. --label-shift-x/y (set by clampLabelToStage in
   label-position.js) nudge it back onscreen if that would push it past the
   photo's edge — same mechanism as the fixture/rope name labels. */
.rope-edit-points-btn {
  position: absolute;
  transform: translate(calc(8px + var(--label-shift-x, 0px)), calc(-50% + var(--label-shift-y, 0px)));
  padding: 0.15rem 0.5rem;
  border-radius: 10px;
  background: rgba(20, 22, 26, 0.9);
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: 0.65rem;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  z-index: 12;
}

.rope-edit-points-btn[hidden] {
  display: none;
}

.photo-stage.fullscreen-mode .rope-edit-points-btn {
  font-size: 0.9rem;
  padding: 0.3rem 0.8rem;
  transform: translate(calc(14px + var(--label-shift-x, 0px)), calc(-50% + var(--label-shift-y, 0px)));
}

/* While actually editing, anchored to the top-left corner of the rope's own
   bounding box instead of the midpoint (which a drag can easily land a dot
   on top of) — pushed up and to the left from that corner so it stays right
   next to the rope without ever sitting on one of its points, wherever they
   end up. */
.rope-edit-points-btn.active {
  transform: translate(
    calc(-100% - 6px + var(--label-shift-x, 0px)),
    calc(-100% - 6px + var(--label-shift-y, 0px))
  );
  background: var(--accent);
  color: #fff;
}

.photo-stage.fullscreen-mode .rope-edit-points-btn.active {
  transform: translate(
    calc(-100% - 10px + var(--label-shift-x, 0px)),
    calc(-100% - 10px + var(--label-shift-y, 0px))
  );
}

.rope-name-label {
  position: absolute;
  /* Offset fully to the left of its point instead of centered on it — a
     centered label would sit directly on top of the rope's line, covering
     it. Delete/footage sit at the midpoint offset up/down, so offsetting
     the name label sideways at the start point also keeps it clear of
     those on short ropes where the two anchor points are close together.
     --label-shift-x/y (set by clampLabelToStage in label-position.js) pull
     the label back inside the photo's frame if this offset would otherwise
     push it past the edge. */
  transform: translate(calc(-100% - 8px + var(--label-shift-x, 0px)), calc(-50% + var(--label-shift-y, 0px)));
  font-size: 0.65rem;
  font-weight: 600;
  white-space: nowrap;
  background: rgba(0, 0, 0, 0.8);
  padding: 0.1rem 0.4rem;
  border-radius: 3px;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.7);
  pointer-events: none;
  z-index: 12;
}

/* Shows regardless of selection when the toolbar toggle is on — this rule
   deliberately outranks the [hidden] state set by selection syncing. */
body.show-fixture-names .rope-name-label {
  display: block;
}

.photo-stage.fullscreen-mode .rope-name-label {
  font-size: 0.95rem;
  padding: 0.2rem 0.6rem;
  transform: translate(calc(-100% - 14px + var(--label-shift-x, 0px)), calc(-50% + var(--label-shift-y, 0px)));
}

/* Ropes don't have an .item-img to target, so the fixture highlighter gets
   its own rule: the same pulsing glow filter applied to the tube's stroke
   layers instead. */
body.highlight-fixtures .rope-item .rope-base,
body.highlight-fixtures .rope-item .rope-core,
body.highlight-fixtures .rope-item .rope-highlight {
  filter: drop-shadow(0 0 8px rgba(255, 241, 118, 0.85)) brightness(1.35) saturate(1.3);
  animation: fixture-highlight-pulse 1.1s ease-in-out infinite alternate;
}

.rope-draft-path {
  stroke: #ffdd78;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 3 2;
  vector-effect: non-scaling-stroke;
  opacity: 0.9;
}

.rope-draft-dot {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #4f8cff;
  border: 1.5px solid #fff;
  cursor: grab;
  touch-action: none;
  z-index: 41;
}

.rope-draft-dot::before {
  /* Enlarges the actual hit-area well beyond the visible dot without
     making the dot itself look bigger — small dots are easy to miss. */
  content: "";
  position: absolute;
  inset: -9px;
}

.rope-draft-dot.dragging {
  cursor: grabbing;
  background: #7badff;
}

.photo-stage.fullscreen-mode .rope-draft-dot {
  width: 13px;
  height: 13px;
  border-width: 2px;
}

/* Fixed to the viewport's right edge (not the photo) so it never sits on
   top of — or has to be worked around on — the picture the user is placing
   points on. */
.rope-hud {
  position: fixed;
  top: 50%;
  right: 1.5rem;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.6rem;
  width: 210px;
  padding: 0.9rem;
  background: rgba(20, 22, 26, 0.95);
  border: 1px solid #2a2e37;
  border-radius: 10px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
  z-index: 570;
}

/* Needed explicitly: at equal specificity a plain [hidden] (the browser's
   own default) loses to .rope-hud's own `display: flex` above — never an
   issue for placement.js's own rope-placement HUD, which is only ever
   added to the DOM while active and removed (not hidden) otherwise, but
   #scene-measure-hud in index.html is static markup that stays in the
   page permanently and relies on toggling `hidden` (see waitForModelClick/
   cancelSceneMeasurement in main.js) — without this override, setting
   .hidden on it did nothing, which is why it could appear to be "stuck on
   screen" even on a fresh page load that never touched the measuring tool
   at all. Same issue already hit and fixed for .scene-fixture-hud-btn and
   .btn elsewhere in this file. */
.rope-hud[hidden] {
  display: none;
}

.rope-hud-hint {
  font-size: 0.8rem;
  color: var(--text);
  line-height: 1.35;
}

.rope-hud .btn {
  width: 100%;
  justify-content: center;
}

.placed-item.selected {
  outline: 1px dashed var(--accent);
  outline-offset: 3px;
}

.photo-stage.fullscreen-mode .placed-item.selected {
  outline-width: 2px;
  outline-offset: 6px;
}

/* A rope's bounding box can span most of the photo — a box outline around
   it would cover huge unrelated areas. Its own thicker/brighter stroke
   (.rope-item.selected .rope-core / .rope-glow above) is the indicator
   instead, so it hugs the actual line. */
.photo-stage.fullscreen-mode .rope-item.selected,
.rope-item.selected {
  outline: none;
}

body.highlight-fixtures .placed-item .item-img {
  outline: 3px solid #fff176;
  outline-offset: 3px;
  border-radius: 6px;
  filter: drop-shadow(0 0 10px rgba(255, 241, 118, 0.85)) brightness(1.35) saturate(1.3);
  animation: fixture-highlight-pulse 1.1s ease-in-out infinite alternate;
}

@keyframes fixture-highlight-pulse {
  from {
    filter: drop-shadow(0 0 6px rgba(255, 241, 118, 0.7)) brightness(1.25) saturate(1.2);
  }
  to {
    filter: drop-shadow(0 0 20px rgba(255, 241, 118, 1)) brightness(1.5) saturate(1.4);
  }
}

.placed-item .item-img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
  backface-visibility: hidden;
}

.handle {
  position: absolute;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--accent);
  border: 1.5px solid #fff;
  color: #fff;
  font-size: 8px;
  line-height: 1;
  text-align: center;
  display: none;
  align-items: center;
  justify-content: center;
  user-select: none;
  z-index: 10;
}

.placed-item.selected .handle {
  display: flex;
}

.handle-rotate {
  top: -18px;
  left: 50%;
  transform: translateX(-50%);
  cursor: grab;
}

.handle-resize {
  bottom: -5px;
  right: -5px;
  cursor: nwse-resize;
}

.handle-turn {
  bottom: -5px;
  left: -5px;
  cursor: move;
  background: #b16bff;
}

.handle-copy {
  top: -5px;
  left: -5px;
  cursor: pointer;
  background: #3bb26f;
}

.handle-delete {
  top: -5px;
  right: -5px;
  background: var(--danger);
  cursor: pointer;
}

/* Right-middle edge — drag to make the fixture longer/shorter along its own
   length without changing its overall size (handle-resize does that). */
.handle-stretch {
  top: 50%;
  right: -5px;
  transform: translateY(-50%);
  cursor: ew-resize;
  background: #ffa53b;
}

/* Bottom-center, mirroring handle-rotate's top-center position — drag
   perpendicular to the fixture to push its middle that way and bow it
   into an arc (see wireBend in placement.js). Only present for a product
   marked bendable (see renderPlacedItem in placement.js). */
.handle-bend {
  bottom: -18px;
  left: 50%;
  transform: translateX(-50%);
  cursor: ns-resize;
  background: #22d3c5;
}

/* The photo (and everything on it) renders much bigger in fullscreen, so the
   tiny grid-sized handles become hard to grab — scale them up to match. */
.photo-stage.fullscreen-mode .handle {
  width: 30px;
  height: 30px;
  font-size: 16px;
  border-width: 3px;
}

.photo-stage.fullscreen-mode .handle-rotate {
  top: -40px;
}

.photo-stage.fullscreen-mode .handle-resize {
  bottom: -12px;
  right: -12px;
}

.photo-stage.fullscreen-mode .handle-turn {
  bottom: -12px;
  left: -12px;
}

.photo-stage.fullscreen-mode .handle-copy {
  top: -12px;
  left: -12px;
}

.photo-stage.fullscreen-mode .handle-delete {
  top: -12px;
  right: -12px;
}

.photo-stage.fullscreen-mode .handle-stretch {
  right: -12px;
}

.photo-stage.fullscreen-mode .handle-bend {
  bottom: -40px;
}

/* Only present at all when the product is sold by the foot (see
   renderPlacedItem in placement.js) — sits above the fixture, clear of the
   rotate handle, and only shows on selection like the other handles. */
.item-footage-btn {
  position: absolute;
  top: -34px;
  left: 50%;
  transform: translateX(-50%);
  padding: 0.15rem 0.5rem;
  border-radius: 10px;
  background: rgba(20, 22, 26, 0.9);
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: 0.65rem;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  z-index: 12;
  display: none;
}

.placed-item.selected .item-footage-btn {
  display: block;
}

.photo-stage.fullscreen-mode .item-footage-btn {
  font-size: 0.9rem;
  padding: 0.3rem 0.8rem;
  top: -62px;
}

/* Position only — kept separate from .item-label-text's own transform (see
   below) so the edge-clamp shift (JS: clampLabelToStage in
   label-position.js) and the small-fixture readability counter-scale don't
   fight over the same `transform` property. --label-shift-x/y default to
   0px until JS has actually measured this label against its stage. */
.item-label {
  position: absolute;
  bottom: -1.05rem;
  left: 50%;
  transform: translate(calc(-50% + var(--label-shift-x, 0px)), var(--label-shift-y, 0px));
  pointer-events: none;
  display: none;
}

.placed-item.selected .item-label,
body.show-fixture-names .item-label {
  display: block;
}

.item-label-text {
  display: inline-block;
  white-space: nowrap;
  font-size: 0.65rem;
  font-weight: 600;
  background: rgba(0, 0, 0, 0.8);
  padding: 0.1rem 0.4rem;
  border-radius: 3px;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.7);
  /* Counteracts the fixture's own scale so the name stays readable even
     when the fixture has been shrunk down small — never shrinks below
     normal size, and is capped so a very tiny fixture doesn't blow the
     label up too far. */
  transform: scale(clamp(1, calc(1 / var(--item-scale, 1)), 2.2));
}

.photo-stage.fullscreen-mode .item-label-text {
  font-size: 0.95rem;
  padding: 0.2rem 0.6rem;
}

/* A touch device (phone, tablet, or a desktop with a touchscreen) has much
   less precision than a mouse pointer — bump the tiny drag handles up to a
   size between their normal 13px and their fullscreen-mode 30px, so they're
   still grabbable before the user has zoomed/gone fullscreen at all.
   Keyed off input precision (pointer: coarse), not screen size, since a
   touchscreen laptop is just as imprecise as a phone at any window width. */
@media (pointer: coarse) {
  .handle {
    width: 22px;
    height: 22px;
    font-size: 12px;
    border-width: 2px;
  }

  .handle-rotate {
    top: -28px;
  }

  .handle-resize {
    bottom: -9px;
    right: -9px;
  }

  .handle-turn {
    bottom: -9px;
    left: -9px;
  }

  .handle-copy {
    top: -9px;
    left: -9px;
  }

  .handle-delete {
    top: -9px;
    right: -9px;
  }

  .handle-stretch {
    right: -9px;
  }

  .handle-bend {
    bottom: -28px;
  }
}

/* Narrow phone/tablet viewports: the header/toolbar and off-canvas sidebar
   are laid out assuming a desktop-width window — wrap and shrink them
   instead of letting the toolbar's five buttons overflow the screen. */
@media (max-width: 700px) {
  #app-root {
    padding: 1rem;
  }

  .app-header {
    flex-wrap: wrap;
    gap: 0.6rem;
    padding: 0.75rem 1rem;
  }

  .app-header h1 {
    font-size: 1.05rem;
  }

  .toolbar {
    flex-wrap: wrap;
    width: 100%;
    gap: 0.5rem;
  }

  .toolbar .btn {
    flex: 1 1 auto;
    justify-content: center;
    font-size: 0.82rem;
    padding: 0.5rem 0.6rem;
  }

  /* Anchored to the header on wide screens (top: 5rem) so it clears a
     single-row toolbar — that assumption breaks once the toolbar wraps to
     two or three rows here, so pin it to the bottom of the screen instead,
     where it's clear of the header at any wrapped height. */
  .catalog-toggle-btn {
    top: auto;
    bottom: 1rem;
  }

  /* Same reasoning as .catalog-toggle-btn above, mirrored to the right
     edge — stacked slightly higher so the two don't overlap if a photo is
     somehow fullscreen while the product sidebar's toggle is also showing. */
  .fullscreen-tools-toggle-btn {
    top: auto;
    bottom: 4.5rem;
  }
}

/* Fullscreen is a focused workspace: the compact product catalog and tool
   strip stay pinned open, and the photo/scene fits in the space between
   them rather than being covered by either panel. */
body.fullscreen-panels-open .catalog-toggle-btn,
body.fullscreen-panels-open .fullscreen-tools-toggle-btn {
  display: none;
}

body.fullscreen-panels-open .product-sidebar {
  top: var(--site-nav-height);
  width: var(--fullscreen-products-width);
  padding: 0.85rem 0.65rem;
  transform: translateX(0);
}

body.fullscreen-panels-open .fullscreen-tools-panel {
  top: var(--site-nav-height);
  width: var(--fullscreen-tools-width);
  padding: 0.9rem 0.7rem;
  transform: translateX(0);
  justify-content: flex-start;
  gap: 0.55rem;
}

body.fullscreen-panels-open .fullscreen-tools-panel .btn {
  width: 100%;
  min-height: 42px;
  justify-content: flex-start;
  padding: 0.55rem 0.65rem;
  border-radius: 11px;
  font-size: 0.73rem;
  line-height: 1.2;
  text-align: left;
  white-space: normal;
}

.fullscreen-tools-header {
  margin: -0.05rem 0 0.25rem;
  padding: 0.15rem 0.2rem 0.75rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.fullscreen-tools-header h2 {
  margin: 0.15rem 0 0;
  color: var(--text);
  font-size: 1rem;
  letter-spacing: -0.02em;
}

.fullscreen-tools-kicker {
  color: var(--accent);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

body.fullscreen-panels-open #highlight-fixtures-btn::before,
body.fullscreen-panels-open #show-fixture-names-btn::before {
  width: 1.15rem;
  flex: 0 0 1.15rem;
  color: #a9c8ff;
  font-size: 0.9rem;
  text-align: center;
}

body.fullscreen-panels-open #highlight-fixtures-btn::before {
  content: "✦";
}

body.fullscreen-panels-open #show-fixture-names-btn::before {
  content: "Aa";
  font-size: 0.68rem;
  font-weight: 800;
}

body.fullscreen-panels-open .product-sidebar-header {
  margin-bottom: 0.65rem;
}

body.fullscreen-panels-open .product-sidebar h2 {
  font-size: 0.85rem;
}

body.fullscreen-panels-open .catalog-list,
body.fullscreen-panels-open .category-list {
  gap: 0.4rem;
}

body.fullscreen-panels-open .category-header {
  padding: 0.45rem 0.5rem;
  font-size: 0.78rem;
}

body.fullscreen-panels-open .category-items {
  gap: 0.35rem;
  padding-left: 0.35rem;
}

body.fullscreen-panels-open .catalog-item {
  grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: 0.35rem;
  padding: 0.35rem 1rem 0.35rem 0.35rem;
}

body.fullscreen-panels-open .catalog-item img {
  width: 34px;
  height: 34px;
}

body.fullscreen-panels-open .catalog-item-name,
body.fullscreen-panels-open .catalog-item-price {
  font-size: 0.7rem;
}

body.fullscreen-panels-open .photo-stage.fullscreen-mode {
  inset: var(--site-nav-height) var(--fullscreen-tools-width) 0 var(--fullscreen-products-width);
  width: auto;
  height: auto;
}

@media (max-width: 700px) {
  :root {
    --fullscreen-products-width: 24vw;
    --fullscreen-tools-width: 20vw;
  }

  body.fullscreen-panels-open .product-sidebar {
    padding-inline: 0.4rem;
  }

  body.fullscreen-panels-open .catalog-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 0.3rem;
    text-align: center;
  }

  body.fullscreen-panels-open .catalog-item-price,
  body.fullscreen-panels-open .catalog-item-menu-btn {
    display: none;
  }
}

/* --- 3D Scenes ---------------------------------------------------------
   A scene tile reuses the .photo-stage grid/fullscreen chrome (see main.js's
   renderScene) but is driven by a <canvas> + SceneViewer instead of a plain
   <img>, so it needs its own sizing (a canvas has no natural aspect ratio
   the way an uploaded photo does) and a few behavior tweaks. */

.scene-content {
  width: 100%;
  height: 100%;
}

.scene-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.scene-stage {
  cursor: pointer;
}

.scene-stage.fullscreen-mode {
  cursor: default;
}

/* .photo-delete-btn normally only shows once a photo is zoom-active (see
   initPhotoZoom in main.js) — a scene tile never enters that inline-zoom
   mode at all, so without this it could never be deleted from the grid. */
.scene-stage .photo-delete-btn {
  display: flex;
}

.scene-stage.fullscreen-mode .photo-delete-btn {
  display: none;
}

/* Only relevant to a scene stage, shown/hidden alongside fullscreen (see
   enterFullscreen/exitFullscreen in main.js) — sits opposite corner from
   .photo-fullscreen-close and .fullscreen-tools-toggle-btn so it never
   collides with either. Its click-to-measure hint HUD (#scene-measure-hud)
   reuses .rope-hud below rather than needing its own styling. */
.scene-measure-btn {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 560;
  padding: 0.5rem 0.85rem;
  border: 1px solid #2a2e37;
  border-radius: 999px;
  background: var(--panel);
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
}

.scene-measure-btn[hidden] {
  display: none;
}

/* Floating toolbar that tracks whichever fixture is selected inside an
   open scene (see getScreenPositionForItem in scene-viewer.js and
   updateSceneHudPosition in main.js) — positioned via left/top set directly
   in JS every frame, not by CSS. */
.scene-fixture-hud {
  position: fixed;
  transform: translate(-50%, -120%);
  display: flex;
  gap: 0.35rem;
  background: rgba(20, 22, 28, 0.92);
  border: 1px solid #2a2e37;
  border-radius: 999px;
  padding: 0.35rem;
  z-index: 520;
  pointer-events: auto;
}

.scene-fixture-hud[hidden] {
  display: none;
}

.scene-fixture-hud-btn {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: none;
  background: #2a2e37;
  color: #fff;
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Needed explicitly: at equal specificity a plain [hidden] (the browser's
   own default) loses to this class's own `display: flex` above, so setting
   .hidden on an individual button (see updateSceneHud in main.js, which
   hides spin/scale for a selected rope — they'd otherwise do nothing) would
   silently fail to actually hide it without this override. */
.scene-fixture-hud-btn[hidden] {
  display: none;
}

.scene-fixture-hud-btn:hover {
  background: var(--accent);
  color: #101216;
}

.scene-fixture-hud-delete {
  background: var(--danger);
}

.scene-fixture-hud-delete:hover {
  background: var(--danger);
  opacity: 0.85;
}

/* Mirrors body.show-fixture-names' effect on a flat photo's .item-label-text
   (see style.css below and syncSceneNameLabels in main.js) — same pill
   look, just positioned via left/top in JS every frame instead of CSS
   percentages, since there's no DOM element per 3D fixture to attach a
   percentage-based position to. */
.scene-fixture-name-label {
  position: fixed;
  transform: translate(-50%, 8px);
  display: inline-block;
  white-space: nowrap;
  font-size: 0.85rem;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, 0.8);
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.7);
  pointer-events: none;
  z-index: 510;
}

.scene-fixture-name-label[hidden] {
  display: none;
}

/* Shown instead of .scene-fixture-hud when the selected scene fixture is a
   real 3D model (see updateSceneHud in main.js) — a fixed panel on the
   right edge, same slide-in-from-the-edge treatment as
   .fullscreen-tools-panel, holding the Move/Rotate/Scale mode picker (see
   setInteractionMode in scene-viewer.js). */
.scene-modify-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(280px, 85vw);
  background: var(--panel);
  border-left: 1px solid #2a2e37;
  padding: 1.25rem 1rem;
  z-index: 560;
  overflow-y: auto;
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.scene-modify-panel[hidden] {
  display: none;
}

.scene-modify-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.scene-modify-panel-header h3 {
  margin: 0;
  font-size: 1rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.scene-modify-close-btn {
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: none;
  background: #2a2e37;
  color: #fff;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.scene-modify-close-btn:hover {
  background: var(--accent);
  color: #101216;
}

.scene-modify-modes {
  display: flex;
  gap: 0.4rem;
}

.scene-modify-mode-btn {
  flex: 1;
  padding: 0.55rem 0.3rem;
  border-radius: 8px;
  border: 1px solid #2a2e37;
  background: transparent;
  color: var(--text);
  font-size: 0.82rem;
  cursor: pointer;
}

.scene-modify-mode-btn:hover {
  border-color: var(--accent);
}

.scene-modify-mode-btn.active {
  border-color: var(--accent);
  background: var(--accent);
  color: #101216;
}

.scene-modify-panel-actions {
  margin-top: auto;
  display: flex;
  gap: 0.5rem;
}

.scene-modify-panel-actions .btn {
  flex: 1;
}

/* --- Review screen: per-scene "include interactive 3D view" ------------ */

.review-scenes-section {
  margin-bottom: 1.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid #2a2e37;
}

.review-scenes-section[hidden] {
  display: none;
}

.review-scenes-section h3 {
  margin: 0 0 0.4rem;
  font-size: 1rem;
}

.review-scenes-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.review-scene-row {
  display: flex !important;
  flex-direction: row !important;
  align-items: center;
  gap: 0.5rem !important;
  font-size: 0.88rem;
}

/* --- Liquid glass visual system ---------------------------------------
   A shared, Apple-inspired glass treatment for every piece of application
   chrome. Photo and 3D canvases remain opaque so their color and contrast
   are not altered by the interface material around them. */
:root {
  color-scheme: dark;
  --bg: #080b12;
  --panel: rgba(30, 35, 48, 0.58);
  --panel-strong: rgba(38, 44, 60, 0.76);
  --panel-soft: rgba(255, 255, 255, 0.065);
  --glass-border: rgba(255, 255, 255, 0.14);
  --glass-border-bright: rgba(255, 255, 255, 0.24);
  --glass-highlight: rgba(255, 255, 255, 0.09);
  --text: #f5f7fb;
  --muted: #aeb7c8;
  --accent: #72a7ff;
  --danger: #ff647c;
  --glass-blur: blur(24px) saturate(150%);
  --glass-shadow: 0 18px 48px rgba(0, 0, 0, 0.34),
                  inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

html {
  min-height: 100%;
  background: #080b12;
}

body {
  min-height: 100vh;
  background:
    radial-gradient(circle at 12% 8%, rgba(70, 118, 255, 0.2), transparent 31rem),
    radial-gradient(circle at 88% 18%, rgba(143, 72, 255, 0.16), transparent 27rem),
    radial-gradient(circle at 52% 92%, rgba(32, 186, 202, 0.12), transparent 32rem),
    linear-gradient(145deg, #080b12 0%, #111522 52%, #090c14 100%);
  background-attachment: fixed;
}

/* Main glass surfaces */
.app-header,
.user-bar,
.login-card,
.quote-notes,
.quote-table,
.review-section,
.modal-content,
.users-sidebar,
.product-sidebar,
.fullscreen-tools-panel,
.scene-modify-panel,
.model-3d-view-measure-panel,
.rope-hud,
.scene-fixture-hud,
.toast {
  background: linear-gradient(145deg, var(--glass-highlight), rgba(255, 255, 255, 0.025)),
              var(--panel);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}

.app-header {
  border-width: 0 0 1px;
  background: rgba(18, 22, 32, 0.62);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.2),
              inset 0 -1px 0 rgba(255, 255, 255, 0.05);
}

.user-bar {
  background: rgba(10, 13, 21, 0.68);
  border-width: 0 0 1px;
}

.login-card,
.quote-notes,
.quote-table,
.review-section,
.modal-content {
  border-radius: 20px;
}

.users-sidebar,
.product-sidebar {
  border-width: 0 1px 0 0;
  background-color: rgba(18, 22, 32, 0.76);
}

.fullscreen-tools-panel,
.scene-modify-panel {
  border-width: 0 0 0 1px;
  background-color: rgba(18, 22, 32, 0.76);
}

/* Interactive glass */
.btn,
.users-toggle-btn,
.catalog-toggle-btn,
.fullscreen-tools-toggle-btn,
.category-header,
.add-product-btn,
.scene-measure-btn,
.scene-modify-mode-btn,
.scene-modify-close-btn,
.scene-fixture-hud-btn {
  border-color: var(--glass-border);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.13), rgba(255, 255, 255, 0.055));
  color: var(--text);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.13),
              0 7px 18px rgba(0, 0, 0, 0.2);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  backdrop-filter: blur(16px) saturate(150%);
  transition: transform 0.18s ease, border-color 0.18s ease,
              background 0.18s ease, box-shadow 0.18s ease;
}

.btn,
.category-header,
.scene-modify-mode-btn {
  border-radius: 12px;
}

.btn:hover,
.users-toggle-btn:hover,
.catalog-toggle-btn:hover,
.fullscreen-tools-toggle-btn:hover,
.category-header:hover,
.add-product-btn:hover,
.scene-measure-btn:hover,
.scene-modify-mode-btn:hover,
.scene-modify-close-btn:hover,
.scene-fixture-hud-btn:hover {
  border-color: var(--glass-border-bright);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.085));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
              0 10px 24px rgba(0, 0, 0, 0.26),
              0 0 0 1px rgba(114, 167, 255, 0.15);
  transform: translateY(-1px);
}

.btn:active,
.category-header:active,
.scene-modify-mode-btn:active {
  transform: translateY(0) scale(0.985);
}

.btn-primary,
.scene-modify-mode-btn.active,
.users-sidebar-item.active {
  border-color: rgba(150, 191, 255, 0.56);
  background: linear-gradient(180deg, rgba(121, 174, 255, 0.95), rgba(60, 120, 230, 0.86));
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.42),
              0 9px 24px rgba(35, 102, 220, 0.3);
}

.btn-danger,
.scene-fixture-hud-delete {
  border-color: rgba(255, 132, 151, 0.5);
  background: linear-gradient(180deg, rgba(255, 111, 133, 0.96), rgba(211, 52, 81, 0.9));
}

/* Inputs and grouped controls */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
textarea,
select,
.add-product-image-source,
.spec-fields,
.current-image-picker,
.review-scene-row {
  border-color: var(--glass-border);
  background-color: rgba(7, 10, 17, 0.44);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.055),
              0 5px 16px rgba(0, 0, 0, 0.14);
  -webkit-backdrop-filter: blur(14px) saturate(135%);
  backdrop-filter: blur(14px) saturate(135%);
}

input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
textarea,
select {
  border-radius: 10px !important;
  outline: none;
  transition: border-color 0.18s ease, box-shadow 0.18s ease,
              background-color 0.18s ease;
}

input:focus,
textarea:focus,
select:focus {
  border-color: rgba(114, 167, 255, 0.72) !important;
  background-color: rgba(18, 25, 40, 0.68) !important;
  box-shadow: 0 0 0 3px rgba(75, 138, 255, 0.18),
              inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
}

input::placeholder,
textarea::placeholder {
  color: rgba(174, 183, 200, 0.7);
}

/* Tables, tiles, badges, and smaller layers */
.review-table th,
.review-table td,
.quote-table th,
.quote-table td,
.review-details,
.review-scenes-section {
  border-color: rgba(255, 255, 255, 0.09);
}

.review-table tbody tr,
.quote-table tbody tr {
  transition: background 0.18s ease;
}

.review-table tbody tr:hover,
.quote-table tbody tr:hover {
  background: rgba(255, 255, 255, 0.05);
}

.add-photo-tile,
.catalog-item,
.users-sidebar-item,
.photo-badge,
.quote-status-badge,
.dashboard-section-count {
  border: 1px solid var(--glass-border);
  background-color: var(--panel-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
}

.add-photo-tile {
  border-style: dashed;
  border-color: rgba(255, 255, 255, 0.23);
  border-radius: 20px;
  background: linear-gradient(145deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.025));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09),
              0 16px 38px rgba(0, 0, 0, 0.2);
}

.catalog-item,
.users-sidebar-item {
  border-radius: 12px;
}

.catalog-item:hover,
.users-sidebar-item:hover {
  border-color: var(--glass-border-bright);
  background-color: rgba(255, 255, 255, 0.1);
}

.photo-badge,
.scene-fixture-name-label,
.item-label-text,
.rope-name-label {
  background: rgba(12, 15, 23, 0.58);
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 5px 18px rgba(0, 0, 0, 0.25),
              inset 0 1px 0 rgba(255, 255, 255, 0.1);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
}

.modal {
  background: rgba(2, 4, 9, 0.56);
  -webkit-backdrop-filter: blur(10px) saturate(115%);
  backdrop-filter: blur(10px) saturate(115%);
}

.photo-stage {
  border: 1px solid rgba(255, 255, 255, 0.13);
  border-radius: 18px;
  box-shadow: 0 22px 55px rgba(0, 0, 0, 0.42),
              inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.model-3d-view-canvas {
  border: 1px solid var(--glass-border);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08),
              0 15px 36px rgba(0, 0, 0, 0.3);
}

@media (prefers-reduced-motion: reduce) {
  .btn,
  .category-header,
  .scene-modify-mode-btn,
  .catalog-item,
  .users-sidebar-item {
    transition: none;
  }
}

/* --- Final quote page: interactive 3D view block ------------------------ */

.quote-scenes {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.quote-scene-block {
  border: 1px solid #2a2e37;
  border-radius: 10px;
  padding: 1rem;
}

.quote-scene-block h3 {
  margin: 0 0 0.6rem;
}

.quote-scene-canvas {
  display: block;
  width: 100%;
  max-width: 900px;
  aspect-ratio: 900 / 560;
  border-radius: 8px;
  background: #000;
  margin-top: 0.75rem;
}

.quote-scene-canvas[hidden] {
  display: none;
}

/* --- Drag-and-drop file upload -------------------------------------------
   Highlight shown while dragging a file over a drop zone (see
   wireFileDropZone in drag-drop.js) — an outline rather than a border on
   each, so it doesn't shift any layout when it toggles on/off. */
#photo-stage-container.drag-over {
  outline: 3px dashed var(--accent);
  outline-offset: -3px;
  background: rgba(110, 168, 254, 0.08);
  border-radius: 12px;
}

.upload-picker.drag-over,
.model3d-picker.drag-over {
  outline: 2px dashed var(--accent);
  outline-offset: 6px;
  border-radius: 8px;
}

/* --- Shared navigation and home --------------------------------------- */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 600;
  min-height: var(--site-nav-height);
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.55rem 1.5rem;
  border-bottom: 1px solid var(--glass-border);
  background: rgba(10, 13, 21, 0.7);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.22), inset 0 -1px 0 rgba(255, 255, 255, 0.04);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  backdrop-filter: blur(24px) saturate(160%);
}

/* The product drawer header contains the Add Product button. Keep the
   entire drawer below the sticky shared navigation instead of letting its
   first row sit hidden underneath that bar. */
.product-sidebar {
  top: var(--site-nav-height);
}

/* These drawer handles originally sat below a single page header. The
   shared site navigation adds another bar above it, so include that height
   and keep the handles clear of the page title and toolbar. */
.catalog-toggle-btn,
.users-toggle-btn {
  top: calc(var(--site-nav-height) + 5rem);
}

.site-nav-brand {
  color: var(--text);
  font-size: 1rem;
  font-weight: 750;
  letter-spacing: -0.02em;
  text-decoration: none;
}

.site-nav-tabs {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 13px;
  background: rgba(255, 255, 255, 0.045);
}

.site-nav-tab {
  padding: 0.45rem 0.8rem;
  border-radius: 9px;
  color: var(--muted);
  font-size: 0.84rem;
  font-weight: 600;
  text-decoration: none;
  transition: color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
}

.site-nav-tab:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.075);
}

.site-nav-tab.active {
  color: #fff;
  background: rgba(114, 167, 255, 0.22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 5px 14px rgba(0, 0, 0, 0.15);
}

.site-nav .user-bar-info {
  margin-left: auto;
}

.home-hero {
  position: relative;
  min-height: calc(100vh - 58px);
  overflow: hidden;
  padding: clamp(5rem, 10vw, 9rem) clamp(1.25rem, 6vw, 6rem) 7rem;
}

.home-title-backdrop {
  position: absolute;
  top: clamp(1rem, 5vw, 4rem);
  left: 50%;
  width: 110%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.025);
  font-size: clamp(5rem, 15vw, 15rem);
  font-weight: 850;
  line-height: 0.78;
  letter-spacing: -0.085em;
  text-align: center;
  text-transform: uppercase;
  pointer-events: none;
  user-select: none;
}

.home-intro,
.home-users,
.home-demo {
  position: relative;
  z-index: 1;
}

.home-intro {
  max-width: 880px;
  margin: 0 auto clamp(5rem, 10vw, 9rem);
  text-align: center;
}

.home-eyebrow {
  margin: 0 0 0.8rem;
  color: #9fc2ff;
  font-size: 0.78rem;
  font-weight: 750;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.home-intro h1 {
  margin: 0;
  font-size: clamp(3.2rem, 8vw, 7.4rem);
  line-height: 0.95;
  letter-spacing: -0.065em;
  background: linear-gradient(180deg, #fff 12%, #bfc9dc 86%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.home-lead {
  max-width: 680px;
  margin: 1.5rem auto 0;
  color: var(--muted);
  font-size: clamp(1rem, 2vw, 1.2rem);
  line-height: 1.65;
}

.home-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

.home-cta {
  min-height: 46px;
  padding-inline: 1.25rem;
  justify-content: center;
}

.home-users {
  width: min(1180px, 100%);
  margin: 0 auto clamp(5rem, 9vw, 8rem);
  padding: clamp(1.15rem, 3vw, 2rem);
  border: 1px solid var(--glass-border);
  border-radius: 22px;
  background: linear-gradient(145deg, rgba(255, 255, 255, 0.095), rgba(255, 255, 255, 0.035));
  box-shadow: var(--glass-shadow), inset 0 1px rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}

.home-users-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.2rem;
}

.home-users-heading h2 {
  margin: 0;
  font-size: clamp(1.8rem, 4vw, 3rem);
  letter-spacing: -0.045em;
}

.home-users-heading p:last-child {
  margin: 0.5rem 0 0;
  color: var(--muted);
}

.home-user-total {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.82rem;
}

.home-user-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0.7rem;
}

.home-user-card {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 0.8rem;
  min-width: 0;
  padding: 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.11);
  border-radius: 14px;
  background: rgba(7, 10, 17, 0.38);
  color: var(--text);
  text-decoration: none;
  transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}

.home-user-card:hover {
  transform: translateY(-2px);
  border-color: var(--glass-border-bright);
  background: rgba(114, 167, 255, 0.1);
}

.home-user-avatar {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 50%;
  background: linear-gradient(145deg, rgba(114, 167, 255, 0.42), rgba(117, 76, 212, 0.35));
  font-weight: 800;
}

.home-user-details {
  display: grid;
  min-width: 0;
}

.home-user-details strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.home-user-details span,
.home-user-quotes {
  color: var(--muted);
  font-size: 0.76rem;
}

.home-user-arrow {
  color: #9fc2ff;
  font-size: 1.5rem;
}

.home-demo {
  width: min(1180px, 100%);
  margin: 0 auto;
}

.home-demo-copy {
  max-width: 600px;
  margin-bottom: 1.5rem;
}

.home-demo-copy h2 {
  margin: 0;
  font-size: clamp(1.8rem, 4vw, 3.4rem);
  line-height: 1.05;
  letter-spacing: -0.045em;
}

.home-demo-copy > p:last-child {
  color: var(--muted);
  line-height: 1.6;
}

.home-demo-window {
  overflow: hidden;
  border: 1px solid var(--glass-border-bright);
  border-radius: 20px;
  background: rgba(9, 12, 20, 0.8);
  box-shadow: 0 36px 90px rgba(0, 0, 0, 0.48), inset 0 1px 0 rgba(255, 255, 255, 0.15);
}

.home-demo-browser-bar {
  height: 38px;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 0.85rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.055);
}

.home-demo-browser-bar > span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.23);
}

.home-demo-browser-bar div {
  min-width: 190px;
  margin: 0 auto;
  padding: 0.3rem 1rem;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.2);
  color: var(--muted);
  font-size: 0.7rem;
  text-align: center;
}

.home-demo-video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #080b12;
  object-fit: cover;
}

@media (max-width: 700px) {
  .site-nav {
    gap: 0.5rem;
    padding-inline: 0.75rem;
    flex-wrap: wrap;
  }

  .home-users-heading {
    align-items: flex-start;
    flex-direction: column;
  }

  .catalog-toggle-btn,
  .users-toggle-btn {
    top: auto;
    bottom: 1rem;
  }

  .site-nav-brand {
    display: none;
  }

  .site-nav-tabs {
    order: 1;
  }

  .site-nav .user-bar-info {
    margin-left: auto;
    font-size: 0.75rem;
  }

  .home-hero {
    padding-top: 4.5rem;
  }

  .home-intro h1 {
    font-size: clamp(3rem, 16vw, 5rem);
  }

  .home-demo-window {
    border-radius: 16px;
  }
}
