:root {
  --bg: #f4f2fb;
  --surface: #ffffff;
  --ink: #2c2a3b;
  --ink-soft: #6b6880;
  --primary: #6c4fd6;
  --primary-dark: #5638c0;
  --primary-soft: #ece6fb;
  --accent: #ff6b6b;
  --border: #e2ddf5;
  --sticky-yellow: #fff3a0;
  --shadow: 0 10px 30px rgba(76, 55, 145, 0.12);
  --radius: 14px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--bg);
  /* Defensive: on some mobile WebKit builds `body.board-body`'s own
     `overflow: hidden` (below) doesn't reliably propagate to the root
     scrolling element, so `html` can stay independently horizontally
     scrollable even though `body` itself can't scroll. That showed up as
     "menus are a bit too wide for the phone screen, and you have to
     scroll sideways to reach the zoom +" - real page-level horizontal
     scroll fighting with the canvas's own touch-pan gesture. Setting it
     here too closes that gap regardless of which element the browser
     treats as the actual scroller. */
  overflow-x: hidden;
}

button {
  font-family: inherit;
  cursor: pointer;
}

input, select, textarea, button {
  font-size: 15px;
}

a { color: var(--primary); }

/* ---------- Landing page ---------- */

.landing-wrap {
  min-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
}

.landing-card {
  width: 100%;
  max-width: 640px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--shadow);
  padding: 40px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
}

.brand-logo {
  height: 44px;
  width: auto;
  display: block;
}

.landing-card p.lead {
  color: var(--ink-soft);
  margin-top: 0;
  margin-bottom: 28px;
}

.tabs {
  display: flex;
  gap: 8px;
  background: var(--primary-soft);
  border-radius: 12px;
  padding: 4px;
  margin-bottom: 24px;
}

.tab-btn {
  flex: 1;
  border: none;
  background: transparent;
  padding: 10px 14px;
  border-radius: 9px;
  font-weight: 600;
  color: var(--ink-soft);
}

.tab-btn.active {
  background: var(--surface);
  color: var(--primary-dark);
  box-shadow: 0 2px 8px rgba(76, 55, 145, 0.15);
}

.panel { display: none; }
.panel.active { display: block; }

.field { margin-bottom: 16px; }

.field label {
  display: block;
  font-weight: 600;
  font-size: 13px;
  color: var(--ink-soft);
  margin-bottom: 6px;
}

.field input[type=text],
.field input[type=email],
.field select {
  width: 100%;
  padding: 11px 13px;
  border-radius: 10px;
  border: 1.5px solid var(--border);
  background: #fbfaff;
  color: var(--ink);
}

.field input:focus, .field select:focus {
  outline: none;
  border-color: var(--primary);
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

.template-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-bottom: 18px;
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
  margin-bottom: 4px;
}
.checkbox-row input[type=checkbox] {
  width: 17px;
  height: 17px;
  accent-color: var(--primary);
  cursor: pointer;
}

.template-option {
  border: 1.5px solid var(--border);
  border-radius: 10px;
  padding: 10px 8px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  background: #fbfaff;
}

.template-option .icon { font-size: 22px; display: block; margin-bottom: 4px; }

.template-option.selected {
  border-color: var(--primary);
  background: var(--primary-soft);
  color: var(--primary-dark);
}

/* "+ Create template" tile (landing.js's buildTemplateGrid) - a navigation
   shortcut into template-editor.html, not a selectable template, so it
   deliberately never gets .selected - the dashed border (matching
   .file-drop's "this is an action, not a value" language elsewhere on this
   page) is what tells the two apart at a glance. */
.template-option-create {
  border-style: dashed;
  border-color: var(--primary);
  color: var(--primary-dark);
  cursor: pointer;
}
.template-option-create:hover { background: var(--primary-soft); }

.file-drop {
  border: 2px dashed var(--border);
  border-radius: 12px;
  padding: 22px;
  text-align: center;
  color: var(--ink-soft);
  margin-bottom: 16px;
  font-size: 14px;
}

.file-drop.has-file { border-color: var(--primary); color: var(--primary-dark); }

.primary-btn {
  width: 100%;
  padding: 13px;
  border-radius: 11px;
  border: none;
  background: var(--primary);
  color: white;
  font-weight: 700;
  font-size: 15px;
  transition: background 0.15s;
}

.primary-btn:hover { background: var(--primary-dark); }
.primary-btn:disabled { background: #c9c2e8; cursor: not-allowed; }

.error-msg {
  color: #c0392b;
  background: #fdecea;
  border: 1px solid #f6c1bb;
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 13px;
  margin-bottom: 14px;
  display: none;
}
.error-msg.show { display: block; }

.hint { font-size: 12px; color: var(--ink-soft); margin-top: 8px; }

/* ---------- Join modal (participant on the board page) ---------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(44, 42, 59, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 500;
}

.modal-card {
  position: relative;
  background: var(--surface);
  border-radius: 18px;
  box-shadow: var(--shadow);
  padding: 32px;
  width: 100%;
  max-width: 420px;
}
/* Wider variant for modals that list board rows (my boards / overwrite
   picker) - a board name plus the "oldest" badge needs more room than the
   default modal width gives it, see .myboards-item .meta below. */
.modal-card.modal-card-wide { max-width: 560px; }

.modal-card h2 { margin-top: 0; font-size: 19px; padding-right: 130px; }
.modal-card p.board-name { color: var(--ink-soft); margin-top: -6px; }

/* ---------- Language switcher ---------- */

.lang-switch {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 800;
}
.lang-switch.lang-switch-inline {
  position: absolute;
  top: 20px;
  right: 20px;
}
.lang-switch select {
  border: 1.5px solid var(--border);
  background: var(--surface);
  color: var(--ink);
  border-radius: 999px;
  padding: 7px 12px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.lang-switch select:focus { outline: none; border-color: var(--primary); }

/* ---------- Account bar (landing page) ---------- */

.account-bar {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 800;
  display: flex;
  align-items: center;
  gap: 8px;
}
.account-btn {
  border: 1.5px solid var(--border);
  background: var(--surface);
  color: var(--ink);
  border-radius: 999px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 600;
}
.account-btn:hover { border-color: var(--primary); color: var(--primary-dark); }
.account-btn.primary { background: var(--primary); color: #fff; border-color: var(--primary); }
.account-btn.primary:hover { background: var(--primary-dark); color: #fff; }
.account-chip-name {
  display: flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1.5px solid var(--border); border-radius: 999px;
  padding: 6px 14px; font-size: 13px; font-weight: 700; color: var(--ink);
}

/* ---------- Auth modal ---------- */

.modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: none;
  background: var(--primary-soft);
  color: var(--primary-dark);
  font-size: 13px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-close:hover { background: var(--primary); color: #fff; }

.auth-tabs { display: flex; gap: 6px; margin-bottom: 14px; }
.auth-tabs .tab-btn { flex: 1; padding: 8px 6px; font-size: 12.5px; }

.auth-panel { display: none; }
.auth-panel.active { display: block; }

.auth-note {
  background: var(--primary-soft);
  color: var(--primary-dark);
  border-radius: 10px;
  padding: 12px 14px;
  font-size: 13px;
  margin-bottom: 14px;
  line-height: 1.5;
  word-break: break-word;
}
.auth-note a { color: var(--primary-dark); font-weight: 700; }

.account-fac-note { background: var(--primary-soft); border-radius: 10px; padding: 10px 12px; }

/* ---------- My boards / overwrite picker lists ---------- */

.myboards-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 340px;
  overflow-y: auto;
  margin: 12px 0 4px;
}
.myboards-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
  padding: 10px 12px;
}
.myboards-item.oldest { border-color: var(--accent); }
.myboards-item .meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
/* Name + "oldest" badge sit in their own flex row so the badge is never
   the thing that gets clipped: only the name text truncates (min-width:0
   lets it shrink inside the flex row), the badge always renders in full
   at a fixed size next to it. Previously the badge was a child of `.name`
   itself, which was the whole row's nowrap/ellipsis box - so a long board
   name plus the badge together got cut off mid-badge ("olde..."). */
.myboards-item .meta .name-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
.myboards-item .meta .name { font-weight: 700; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.myboards-item .meta .sub { font-size: 11px; color: var(--ink-soft); }
.myboards-item .badge-oldest {
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  background: var(--accent);
  border-radius: 999px;
  padding: 2px 8px;
  flex-shrink: 0;
}
.myboards-item .actions { display: flex; gap: 6px; flex-shrink: 0; }
.myboards-item .actions button {
  border: 1.5px solid var(--border);
  background: var(--surface);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
}
.myboards-item .actions button.danger { color: var(--accent); border-color: var(--accent); }
.myboards-item .actions button.danger:hover { background: var(--accent); color: #fff; }
.myboards-item .actions button.primary { background: var(--primary); color: #fff; border-color: var(--primary); }
.myboards-item .actions button.primary:hover { background: var(--primary-dark); }

/* ---------- Board page ---------- */

body.board-body { overflow: hidden; height: 100vh; }

#board-root { position: relative; width: 100vw; height: 100vh; overflow: hidden; background: #eae7f7; }

#canvas-viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  cursor: grab;
  /* Without this, a touch browser intercepts a two-finger gesture here as
     its own native pinch-zoom-the-page (or a one-finger drag as page
     scroll) before board.js's pointer-event handlers ever see it - the
     custom pinch-to-zoom/pan logic in wireInteractions() needs to own
     every touch gesture that starts on the canvas itself. .sticky and
     .shape-el already set this for the same reason on their own drag
     handles. */
  touch-action: none;
}
#canvas-viewport.panning { cursor: grabbing; }

#world {
  position: absolute;
  top: 0;
  left: 0;
  width: 3600px;
  height: 2200px;
  transform-origin: 0 0;
  /* The watermark tile (RetroTeamJam icon at ~8% opacity, pre-baked as a
     seamless 2-row brick pattern so it doesn't line up in a rigid grid) is
     listed first so it paints above the dot grid but is still just part of
     #world's own background - it's not a DOM layer, so it never intercepts
     clicks/drags and needs no pointer-events/z-index bookkeeping relative
     to #shapes-layer / #stickies-layer. It pans and zooms together with
     the board content since it lives on #world itself. */
  background:
    url('/img/watermark-tile.png') 0 0 / 390px 748px repeat,
    linear-gradient(90deg, rgba(108,79,214,0.06) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(rgba(108,79,214,0.06) 1px, transparent 1px) 0 0 / 40px 40px,
    #f8f7fd;
}

.template-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }

.sticky {
  position: absolute;
  pointer-events: auto;
  width: 190px;
  min-height: 150px;
  background: var(--sticky-yellow);
  border-radius: 4px;
  box-shadow: 0 6px 16px rgba(0,0,0,0.15);
  padding: 12px;
  font-size: 14px;
  line-height: 1.35;
  color: #3a3520;
  cursor: grab;
  user-select: none;
  touch-action: none;
  transform: rotate(var(--tilt, -1deg));
}
.sticky:active { cursor: grabbing; }
.sticky textarea {
  width: 100%;
  height: 100%;
  min-height: 120px;
  border: none;
  background: transparent;
  resize: none;
  font-family: inherit;
  font-size: 14px;
  color: inherit;
  cursor: text;
}
.sticky textarea:focus { outline: none; }
.sticky .author {
  font-size: 10px;
  opacity: 0.55;
  margin-top: 6px;
  font-weight: 600;
}
.sticky .del-btn {
  position: absolute;
  top: 4px;
  right: 6px;
  border: none;
  background: transparent;
  font-size: 13px;
  opacity: 0.4;
  padding: 2px 4px;
}
.sticky .del-btn:hover { opacity: 0.9; }

.sticky.masked .scribble { width: 100%; height: 76px; }
.sticky.masked textarea { display: none; }

.cursor-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 400; }
.remote-cursor {
  position: absolute;
  transform: translate(-2px, -2px);
  display: flex;
  align-items: center;
  gap: 5px;
  transition: left 0.06s linear, top 0.06s linear;
}
.remote-cursor svg { filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)); }
.remote-cursor .label {
  font-size: 11px;
  font-weight: 700;
  color: white;
  padding: 2px 7px;
  border-radius: 999px;
  white-space: nowrap;
}
.remote-cursor.hide-label .label { display: none; }

/* toolbars */

.toolbar {
  position: fixed;
  z-index: 600;
  background: var(--surface);
  border-radius: 14px;
  box-shadow: var(--shadow);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px;
}

#top-toolbar { top: 16px; left: 16px; max-width: calc(100vw - 32px); flex-wrap: wrap; }
#zoom-toolbar { bottom: 16px; right: 16px; flex-direction: column; }
#presence-toolbar { top: 16px; right: 16px; flex-direction: column; align-items: stretch; min-width: 180px; }

/* ---------- Collapsible toolbars (top-left + participants) ----------
   Daniel: "het deelnemersblok geminimaliseerd kunnen worden en het blok
   linksboven moet ook geminimaliseerd kunnen worden". .toolbar-body wraps
   everything except the always-visible header bits (board title /
   participant count + the toggle button itself). display:contents makes
   the wrapper invisible to layout - its children act as if they were
   direct children of the flex .toolbar - so collapsing is just one
   display:none away, without needing to unpick either toolbar's own flex
   layout (#top-toolbar is a row, #presence-toolbar is a column). */
.toolbar-body { display: contents; }
.toolbar.collapsed .toolbar-body { display: none; }

.toolbar-collapse-btn {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: none;
  background: var(--primary-soft);
  color: var(--primary-dark);
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  transition: transform 0.15s;
}
.toolbar-collapse-btn:hover { background: var(--primary); color: #fff; }
/* Arrow points "away from the content" - down while expanded (content is
   below/inline and can be tucked away), rotated to point up once
   collapsed (nothing left to tuck, would open back downward). */
.toolbar.collapsed .toolbar-collapse-btn { transform: rotate(180deg); }

/* Always visible (both collapsed and expanded) - it's what carries the
   toggle button itself, so it can never be the thing display:none hides. */
.presence-toolbar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
}
.presence-count {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: nowrap;
}

.tb-btn {
  border: none;
  background: transparent;
  border-radius: 9px;
  padding: 8px 12px;
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.tb-btn:hover { background: var(--primary-soft); }
.tb-btn.active { background: var(--primary); color: white; }
.tb-btn.danger:hover { background: #fdecea; color: #c0392b; }
/* "Save template" in template-editor.html - a filled call-to-action button
   among otherwise-transparent toolbar buttons, so it reads as the one
   action that actually commits something (mirrors .primary-btn's colors,
   just sized to sit inline in a toolbar rather than full-width). */
.tb-btn.primary { background: var(--primary); color: white; }
.tb-btn.primary:hover { background: var(--primary-dark); }

/* Template-editor's board-name field: same visual footprint as the
   read-only .board-title span on board.html, but an actual input since
   there's no separate "rename" flow here - typing directly in the
   toolbar is the whole UI for it. */
.board-title-input {
  font-weight: 700;
  font-size: 14px;
  padding: 6px 8px;
  color: var(--ink);
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  min-width: 160px;
}
.board-title-input:hover, .board-title-input:focus {
  border-color: var(--border);
  background: white;
}
.board-title-input:focus { outline: none; border-color: var(--primary); }

.tb-sep { width: 1px; align-self: stretch; background: var(--border); margin: 4px 2px; }

.board-title { font-weight: 700; font-size: 14px; padding: 0 8px; color: var(--ink); }
.board-title .role-badge {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  background: var(--primary-soft);
  color: var(--primary-dark);
  padding: 2px 7px;
  border-radius: 999px;
  margin-left: 6px;
}

.switch {
  position: relative;
  width: 38px;
  height: 22px;
  border-radius: 999px;
  background: #d8d3ee;
  display: inline-block;
  flex-shrink: 0;
}
.switch::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: white;
  transition: left 0.15s;
}
.switch.on { background: var(--primary); }
.switch.on::after { left: 18px; }

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 9px;
  cursor: pointer;
}
.toggle-row:hover { background: var(--primary-soft); }
.toggle-row span { font-size: 13px; font-weight: 600; }
.board-id-value {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--primary-dark);
}

.participant-list { padding: 6px 4px 2px; }
.participant-chip { display: flex; align-items: center; gap: 8px; padding: 4px 6px; font-size: 12px; font-weight: 600; }
.participant-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.participant-dot-mine {
  cursor: pointer;
  box-shadow: 0 0 0 2px rgba(44,42,59,0.12);
  transition: box-shadow 0.15s;
}
.participant-dot-mine:hover { box-shadow: 0 0 0 3px rgba(44,42,59,0.24); }

/* Facilitator-only "kick" button on another participant's row (Daniel:
   "Facilitator moeten mensen eruit kunnen kicken") - hover-reveal like
   .shape-controls, so the list doesn't look cluttered with a red X next
   to every name at all times. */
.participant-kick-btn {
  /* margin-left:auto (not justify-content:space-between on the row -
     that would also misplace the 2-item rows that have no kick button,
     e.g. your own row) pushes only this button to the far right, when
     it's present at all. */
  margin-left: auto;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--ink-soft);
  font-size: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.participant-chip:hover .participant-kick-btn { opacity: 1; }
.participant-kick-btn:hover { background: #fdecea; color: #c0392b; }
body.is-touch .participant-kick-btn { opacity: 1; } /* no hover on touch - always show */

.color-swatch-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 2px 8px 10px;
}
.color-swatch {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 8px;
  border: none;
  cursor: pointer;
  padding: 0;
  box-shadow: 0 0 0 1.5px rgba(44,42,59,0.12);
  transition: transform 0.1s, box-shadow 0.1s;
}
.color-swatch:hover { transform: scale(1.08); }
.color-swatch.selected { box-shadow: 0 0 0 2.5px var(--ink); }

#timer-display {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: 15px;
  min-width: 54px;
  text-align: center;
}
#timer-display.warn { color: var(--accent); }
#timer-display.timeup-flash { animation: timeup-blink 0.6s steps(1, end) infinite; }
@keyframes timeup-blink {
  0%, 49% { color: var(--accent); }
  50%, 100% { color: var(--ink); }
}

.timer-input-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 8px 12px;
}
.timer-input-row label { font-size: 13px; font-weight: 600; }
.timer-input-row input[type=number] {
  width: 76px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 6px 8px;
  font-size: 14px;
  font-weight: 700;
  text-align: center;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.timer-input-row input[type=number]:focus { outline: none; border-color: var(--primary); }

.menu-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--shadow);
  padding: 10px;
  min-width: 230px;
  max-height: calc(100vh - 90px);
  overflow-y: auto;
  display: none;
  z-index: 700;
}
.menu-popover.open { display: block; }
.menu-popover h4 { margin: 4px 8px 8px; font-size: 12px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.03em; }
.tb-wrap { position: relative; }

/* Every full-row action button in a menu popover (reveal, add-shape, copy
   link, export, ...) shares this so they all line up flush and left-aligned
   instead of each button improvising its own inline width/alignment.
   NOTE: this used to be justify-content:center, which visually "staircased"
   rows of different label lengths (each button's icon+text starts at a
   different x depending on how wide its own text is) - left-aligning fixes
   that since every row now starts from the same edge. */
.menu-btn-full { width: 100%; justify-content: flex-start; box-sizing: border-box; }
.menu-btn-stack { display: flex; flex-direction: column; gap: 4px; padding-bottom: 8px; }

.template-pick { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; padding: 4px; }
.template-pick button {
  border: 1.5px solid var(--border);
  background: #fbfaff;
  border-radius: 9px;
  padding: 8px 6px;
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-soft);
}
.template-pick button.selected { border-color: var(--primary); background: var(--primary-soft); color: var(--primary-dark); }

.toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: white;
  padding: 10px 18px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  z-index: 900;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}
.toast.show { opacity: 1; }

.help-hint {
  position: fixed;
  bottom: 16px;
  /* Bottom-right rather than bottom-left: the board-menu dropdown (and
     the other menu-popovers) hang down from the top-left toolbar and,
     on a short/zoomed-out browser window, can reach all the way to the
     bottom of the screen - a bottom-left hint used to end up underneath
     that open menu. The right edge has no such tall dropdown to collide
     with. Stops short of the full right edge to clear #zoom-toolbar
     (56px wide + its own 16px margin, plus a 16px gap of our own). */
  right: 88px;
  z-index: 600;
  background: rgba(44,42,59,0.75);
  color: white;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 12px;
}

/* Primary "add a sticky" affordance on touch - hidden by default (shown
   only for pointer:coarse, see the rule at the end of this file next to
   the other touch-only overrides) since a mouse user already has a
   perfectly discoverable double-click. Bottom-center: #zoom-toolbar owns
   the bottom-right corner and .help-hint/.presence-toolbar the rest of
   the bottom strip on a narrow screen, so center is the one clear spot,
   and it's the natural thumb-reach position anyway. */
.fab-add-sticky {
  display: none;
  position: fixed;
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%);
  width: 56px;
  height: 56px;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: var(--primary);
  color: white;
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
  box-shadow: var(--shadow);
  z-index: 620;
}
.fab-add-sticky:active { background: var(--primary-dark); }

/* ---------- Narrow / phone-sized screens ----------
   Driven by `body.is-narrow` rather than a raw `@media (max-width:700px)`
   query, so the same layout can also be forced on a wide desktop window
   (or suppressed on an actual phone) via the manual desktop/mobile
   switch in the top toolbar (see updateDeviceClasses() in board.js) -
   `.is-narrow` mirrors the live matchMedia result by default, but the
   manual override takes precedence once set. Every selector here is
   prefixed with `body.is-narrow`, which also happens to out-specificity
   every unprefixed base rule it touches, so (unlike the old @media
   version) none of this depends on source order in the file.
   The four corner-anchored toolbars that work fine on a spacious desktop
   window would overlap on a phone, so #top-toolbar and #presence-toolbar
   are restacked as two full-width bars (see #mobile-toolbar-stack in
   board.html - the wrapper does nothing without body.is-narrow, since
   position:fixed on the toolbars themselves ignores DOM nesting). */
body.is-narrow #mobile-toolbar-stack {
  position: fixed;
  top: 8px;
  left: 8px;
  right: 8px;
  /* Higher than #zoom-toolbar's 600: an open bottom-sheet popover
     (nested inside this stack) can reach all the way to the bottom
     edge, the same corner #zoom-toolbar is pinned to. A fixed
     descendant does not escape its ancestor's stacking context, so
     without this the later-in-DOM #zoom-toolbar (same z-index, sibling
     stacking contexts resolve by DOM order) would paint on top of the
     sheet instead of being covered by it. */
  z-index: 650;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
body.is-narrow #mobile-toolbar-stack .toolbar {
  position: static;
  width: 100%;
  max-width: none;
  box-sizing: border-box;
  flex-wrap: wrap;
}
/* The private-writing banner (see board.html - it sits between
   #top-toolbar and #presence-toolbar inside #mobile-toolbar-stack) was
   still using its desktop position:fixed/top:70px placement here, which
   no longer clears the taller, higher z-index (650) toolbar stack it now
   overlaps on a phone - Daniel's "private writing notificatie achter de
   menu's" report. Turning it into a normal static flex child (like the
   two toolbars above) puts it in the actual flow between them instead,
   so it can never be covered. */
body.is-narrow #mobile-toolbar-stack .private-banner {
  position: static;
  top: auto;
  left: auto;
  transform: none;
  align-self: center;
  max-width: calc(100vw - 32px);
}
body.is-narrow #presence-toolbar {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  min-width: 0;
}
body.is-narrow #presence-toolbar .participant-list { display: flex; flex-wrap: wrap; padding: 0; }
body.is-narrow .board-title {
  max-width: 42vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.is-narrow #zoom-toolbar { bottom: 8px; right: 8px; }

/* Every menu-popover (board menu, timer, color picker) is normally an
   absolutely-positioned dropdown anchored right below its trigger
   button - min-width 230px. On a 375px-wide phone that risks running
   off whichever edge is closer to the trigger (the color-picker sits
   near the right edge, the board menu near the left). Turning it into
   a full-width bottom sheet sidesteps that entirely: it no longer
   matters where the trigger button is. No JS changes needed - the
   existing open/close wiring (toggle .open, close on outside click)
   already works the same regardless of how .menu-popover is
   positioned. */
body.is-narrow .menu-popover {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: auto;
  width: 100%;
  min-width: 0;
  max-width: none;
  max-height: 75vh;
  border-radius: 16px 16px 0 0;
  box-sizing: border-box;
  padding-bottom: max(14px, env(safe-area-inset-bottom, 14px));
}
/* Dims the board behind an open bottom sheet. :has() degrades safely -
   browsers without it simply skip the dimming, the sheet itself still
   works. pointer-events:auto so a tap on the dimmed area is what the
   user expects to close the sheet with, and it still does: the click
   bubbles from this body-level pseudo-element up to `document`, which
   is exactly what the existing outside-click-closes-the-popover
   listener is already listening for.
   z-index must stay BELOW 600 (not between 600 and 700): the popover
   is a descendant of #mobile-toolbar-stack, which is itself a
   position:fixed + z-index:600 element - and a fixed descendant does
   NOT escape its ancestor's stacking context (fixed only exempts it
   from layout containment, not z-index comparison). So the popover's
   own z-index:700 only wins *inside* that stacking context; from the
   backdrop's point of view the whole stack is one box ranked at 600.
   A backdrop above 600 would cover the sheet, not sit behind it. */
body.is-narrow:has(.menu-popover.open)::after {
  content: '';
  position: fixed;
  inset: 0;
  background: rgba(30, 27, 46, 0.4);
  z-index: 590;
  pointer-events: auto;
}

body.is-narrow .help-hint {
  left: 8px;
  /* #zoom-toolbar is pinned bottom-right (~53px wide plus its own 8px
     margin) - left at right:8px like everything else here, the hint's
     dark banner runs underneath it. */
  right: 69px;
  max-width: calc(100vw - 77px);
  box-sizing: border-box;
  text-align: center;
}

/* ---------- Private writing banner ---------- */

.private-banner {
  position: fixed;
  top: 70px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 550;
  background: #fff0f0;
  color: #b3372c;
  border: 1.5px solid #f3b8b0;
  padding: 9px 12px 9px 18px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 700;
  box-shadow: var(--shadow);
  max-width: min(560px, calc(100vw - 32px));
  text-align: center;
  display: flex;
  align-items: center;
  gap: 10px;
}
/* Daniel: "de private writing melding moet je met een kruisje kunnen
   afsluiten" - a small round X at the end of the pill, styled to match
   the banner's own red tone rather than the neutral .modal-close look. */
.private-banner-close {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: none;
  background: rgba(179,55,44,0.12);
  color: #b3372c;
  font-size: 11px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
}
.private-banner-close:hover { background: rgba(179,55,44,0.22); }

/* ---------- Board menu sub-hint ---------- */

.menu-subhint {
  font-size: 11px;
  color: var(--ink-soft);
  padding: 2px 8px 8px;
  line-height: 1.35;
}

/* ---------- Layer stacking ----------
   #shapes-layer and #stickies-layer are two absolutely-positioned sibling
   divs inside #world (see board.html). Neither had its own z-index, so
   neither established its own stacking context - individual shape elements
   bump their own inline z-index (see bringShapeToFront() in board.js) to
   stay grabbable when several shapes overlap, and without a stacking
   context boundary that bump lets a shape's z-index compete directly
   against sticky notes too, so a shape could end up painted (and
   hit-tested) ABOVE a post-it, blocking it from being read/dragged. Giving
   each layer its own z-index scopes those per-shape z-index bumps to
   *within* #shapes-layer only, so stickies always render and receive
   pointer events above every shape, no matter what a shape's own z-index
   is. */
#shapes-layer { z-index: 10; }
#stickies-layer { z-index: 20; }

/* ---------- Background shapes ---------- */

.shape-el {
  position: absolute;
  pointer-events: auto;
  border: 2px solid rgba(108,79,214,0.35);
  border-radius: 10px;
  box-sizing: border-box;
  cursor: grab;
  /* Without this, some browsers (notably Safari/trackpad input) can hijack
     a pointerdown-drag as a native scroll/selection gesture partway through,
     which made dragging/resizing feel broken even though the JS ran fine. */
  touch-action: none;
  user-select: none;
}
.shape-el:active { cursor: grabbing; }
.shape-el.ellipse { border-radius: 50%; }
.shape-el.locked { border-style: solid; box-shadow: inset 0 0 0 1px rgba(108,79,214,0.2); cursor: default; }
.shape-el.view-only { cursor: default; pointer-events: none; }
.shape-el.just-created { animation: shape-pop 0.5s ease-out; }
@keyframes shape-pop {
  0% { transform: scale(0.85); box-shadow: 0 0 0 6px rgba(108,79,214,0.35); }
  100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(108,79,214,0); }
}

.shape-el.text-shape {
  border-style: dashed;
  background: transparent !important;
}
.shape-el.text-shape.locked { border-style: solid; }
/* Inset from the shape's own edges (rather than filling it 100%) so a
   thin border strip of the parent .shape-el stays uncovered - that strip
   is what lets a text shape actually be dragged. Bug found 2026-09-05
   (Daniel: "het tekstveld lijkt ook niet sleepbaar"): with the textarea
   covering the full box, EVERY pointerdown landed on it, and its own
   pointerdown handler deliberately calls stopPropagation() (so a click
   places the text cursor instead of starting a drag) - .shape-el's own
   drag listener (which explicitly ignores clicks that target
   .shape-text-content) never even ran, so there was no way left to grab
   the shape at all. .shape-el is already position:absolute, so this
   inset positions relative to it directly, no extra wrapper needed. */
.shape-text-content {
  position: absolute;
  left: 7px; right: 7px; top: 7px; bottom: 7px;
  box-sizing: border-box;
  border: none;
  background: transparent;
  resize: none;
  outline: none;
  padding: 3px 4px;
  font: inherit;
  font-size: 15px;
  line-height: 1.35;
  color: var(--ink, #2c2a3b);
  cursor: text;
}
.shape-text-content:disabled { cursor: default; opacity: 0.7; }
.shape-text-readonly {
  position: absolute;
  left: 7px; right: 7px; top: 7px; bottom: 7px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  cursor: default;
}

.shape-controls {
  position: absolute;
  top: -14px;
  right: 6px;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.12s;
}
.shape-el:hover .shape-controls { opacity: 1; }

.shape-btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.shape-btn.shape-del:hover { background: #fdecea; color: #c0392b; }

/* Per-shape color picker (fill color for rect/ellipse, font color for a
   text shape) - reuses .color-swatch-grid/.color-swatch from the
   participant "my color" popover for a consistent look, but is anchored
   to the shape itself (a child of .shape-el) so it pans/zooms together
   with it instead of needing separate on-screen positioning math. */
.shape-color-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--shadow);
  padding: 8px;
  min-width: 168px;
  display: none;
  cursor: default;
}
.shape-color-popover.open { display: block; }
.shape-color-popover .color-swatch-grid { padding: 2px 2px 8px; }
/* Keep the 🎨/🔓/✕ row visible (normally hover-only, see
   .shape-el:hover .shape-controls below) while its own popover is open,
   so it doesn't fade away out from under the cursor mid-pick. */
.shape-el.showing-color-popover .shape-controls { opacity: 1; }

.shape-resize {
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 16px;
  height: 16px;
  border-radius: 4px;
  background: var(--primary);
  border: 2px solid white;
  box-shadow: var(--shadow);
  cursor: nwse-resize;
  opacity: 0;
  transition: opacity 0.12s;
}
.shape-el:hover .shape-resize { opacity: 1; }
.shape-el.locked .shape-resize { display: none; }
.shape-el.locked .shape-controls { opacity: 1; }

/* ---------- Bigger tap targets for touch pointers ----------
   Driven by `body.is-touch` rather than a raw `@media (pointer: coarse)`
   query - same reasoning as body.is-narrow above: this lets the manual
   desktop/mobile switch force these touch-sized targets on a mouse-driven
   desktop (to preview the mobile variant) or suppress them on an actual
   touchscreen (to preview the desktop variant), and every selector below
   out-specificities the plain single-class base rule it overrides
   (.del-btn, .shape-resize, .tb-btn, .fab-add-sticky, .help-hint), so -
   unlike the old @media version - none of this depends on being placed
   at the end of the file or on source order at all anymore.
   A few controls are sized fine for a mouse (which can click a specific
   pixel) but too small to reliably hit with a fingertip: the sticky
   delete button, the shape resize handle, and especially the 9px
   participant-color dot. Buttons already backed by a big click target
   (.toggle-row, .color-swatch) are left alone. */
body.is-touch .sticky .del-btn {
  padding: 10px 12px;
  font-size: 15px;
}
body.is-touch .shape-resize {
  width: 26px;
  height: 26px;
  right: -11px;
  bottom: -11px;
}
/* Keeps the dot itself visually a small 9px color swatch - only the
   invisible ::before hit area grows, via a negative inset rather than
   padding (padding would inherit the dot's own background and visibly
   balloon it). A pseudo-element's click still targets its host element
   for JS purposes, so the existing click handler on .participant-dot-
   mine (see renderParticipantList() in board.js) fires exactly as
   before - nothing to change on the JS side. */
body.is-touch .participant-dot-mine { position: relative; }
body.is-touch .participant-dot-mine::before {
  content: '';
  position: absolute;
  inset: -17px;
}
body.is-touch .tb-btn { padding: 10px 14px; }
body.is-touch .fab-add-sticky { display: flex; }
/* .help-hint and the FAB are both bottom-anchored, so showing the FAB
   (touch only) means the hint needs to move out of its way rather than
   sit underneath it - lifted by the FAB's own height plus clearance, on
   top of whatever bottom offset was already in effect (16px on a
   spacious touch tablet, 8px on the narrow-screen override above).
   calc() reading env() twice like this is redundant work, not a
   correctness issue - env() is a plain constant lookup. */
body.is-touch .help-hint { bottom: calc(16px + 56px + 12px + env(safe-area-inset-bottom, 0px)); }
body.is-touch.is-narrow .help-hint { bottom: calc(8px + 56px + 12px + env(safe-area-inset-bottom, 0px)); }
