html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--color-canvas-bg);
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}

canvas {

}

#viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--color-canvas-bg);
  cursor: grab;
  touch-action: none;
}

#viewport.panning {
  cursor: grabbing;
}

/* ------------------------------------------------------------------
 * "Are you lost?" dog speech bubble - shown when the user has panned
 * away from all images; clicking it flies the view back to the content.
 * ------------------------------------------------------------------ */
#dog-lost-bubble {
  position: absolute;
  display: none;
  transform: translate(-50%, -100%);
  max-width: 220px;
  background: #ffffff;
  color: var(--color-tertiary);
  border: 3px solid var(--color-tertiary);
  border-radius: 14px;
  padding: 10px 14px;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 13px;
  line-height: 1.35;
  text-align: center;
  box-shadow: 6px 6px 0px 0px var(--color-primary);
  cursor: pointer;
  z-index: 900;
  pointer-events: auto;
  touch-action: manipulation;
  user-select: none;
}

#dog-lost-bubble::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -13px;
  transform: translateX(-50%);
  border: 8px solid transparent;
  border-top-color: var(--color-tertiary);
}

#dog-lost-bubble:hover {
  background: #fffdf0;
}

#bg-canvas {
  position: absolute;
  inset: 0;
}

#world {
    position: absolute;
    top: 0;
    left: 0;
    transform-origin: 0 0;
}

/* Promoting the world to its own compositing layer keeps a pan smooth,
   but the browser then has to keep a rasterized backing store for the
   whole *scaled* layer. Zoomed in, that layer is enormous - many times
   the screen - and both Chrome and WebKit give up on parts of it, which
   is what showed as images rendering only partially or blinking out for
   a frame while zooming.

   So the hint is applied only while a gesture is actually in progress
   (canvas.js adds .interacting on pointer/wheel activity and drops it a
   moment after it stops). During the gesture there is something to
   optimise; at rest the browser rasterizes normally and everything is
   simply there. */
#world.interacting {
    will-change: transform;
}

.canvas-image {
    position: absolute;
    top: 0;
    left: 0;
    border-radius: 2px;
    background: #fff;
    user-select: none;
    /* Default fallback; each image gets its own random magenta/yellow/black
       shadow color set inline by canvas.js (see shadowColorFor()). */
    box-shadow: 15px 10px 0px 0px var(--color-tertiary);
    border: 4px solid black;
    /* Hidden until its <img> has actually decoded (canvas.js adds
       --loaded on load), so panning into new images fades them in instead
       of flashing this background then popping the photo on top of it. */
    opacity: 0;
    transition: opacity 0.25s ease;
}

.canvas-image--loaded {
    opacity: 1;
}

/* The incoming half of a variant swap (see swapTo in canvas.js): it is
   stacked exactly over the one already on screen and takes its place
   only once it has decoded, so the swap never shows the card's empty
   background. Sized off the element rather than the old <img> so a
   restyled .canvas-image img rule cannot leave it mismatched. */
.canvas-image img.canvas-image-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
}

.canvas-image.selected {
  outline: 3px solid #3f6fe0;
  outline-offset: 2px;
}

/* Admin only (canvas.js adds this class only when IS_ADMIN): the work
   behind this image has no material, substrate or size recorded yet.
   Reusing the frame that is already there keeps the canvas layout
   identical - only the colour changes. */
.canvas-image.needs-details {
  border-color: #ff8c00;
}

/* ------------------------------------------------------------------
 * Image groups: a hero image that carries more images uploaded with it.
 * Sparkles hint at them on hover; the siblings themselves fly out of the
 * hero as ".canvas-image--satellite" elements (see canvas.js).
 * ------------------------------------------------------------------ */
.group-sparkles {
  position: absolute;
  inset: -6%;
  pointer-events: none;
  z-index: 3;
}

.group-sparkles span {
  position: absolute;
  width: 9%;
  aspect-ratio: 1 / 1;
  background: var(--color-secondary);
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%,
                     50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
  opacity: 0;
  transform: scale(0.2);
}

.canvas-image.has-group:hover .group-sparkles span,
.canvas-image.has-group.group-open .group-sparkles span {
  animation: kompendi-sparkle 1.6s ease-in-out infinite;
}

@keyframes kompendi-sparkle {
  0%, 100% { opacity: 0; transform: scale(0.2) rotate(0deg); }
  45% { opacity: 1; transform: scale(1) rotate(75deg); }
}

.canvas-image--satellite {
  transform-origin: 50% 50%;
  transition: transform 0.55s cubic-bezier(0.2, 0.85, 0.3, 1),
              opacity 0.35s ease;
  will-change: transform, opacity;
}

@media (prefers-reduced-motion: reduce) {
  .canvas-image { transition-duration: 0.01s; }
  .canvas-image--satellite { transition-duration: 0.01s; }
  .canvas-image.has-group:hover .group-sparkles span { animation: none; opacity: 1; transform: scale(1); }
}

.canvas-image img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
  border-radius: 2px;
  pointer-events: none;
  -webkit-user-drag: none;
}

.canvas-image.admin-draggable {
  cursor: move;
}

.canvas-image.dragging {
  opacity: 0.9;
}

.resize-handle {
  position: absolute;
  right: -24px;
  bottom: -24px;
  width: 48px;
  height: 48px;
  background: #111111;
  border: 2px solid #ffffff;
  border-radius: 50%;
  cursor: nwse-resize;
}

/* ------------------------------------------------------------------
 * Image caption
 * ------------------------------------------------------------------ */
.canvas-caption {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
  font-family: "Times New Roman", Times, serif;
  font-size: 13px;
  line-height: 1.6;
  color: #111;
  background: #fff;
  word-break: break-word;
  white-space: pre-wrap;
  padding: 0 6px;
  box-sizing: border-box;
  width: 100%;
  outline: none;
  user-select: none;
}

.canvas-caption--visible {
  max-height: 2000px;
  padding: 6px;
}

.canvas-caption--edit {
  cursor: text;
  user-select: text;
  -webkit-user-select: text;
  background: #fffef8;
  outline: 1px dashed #ccc;
  min-height: 28px;
}

/* ------------------------------------------------------------------
 * Footer links (Impressum / Datenschutz)
 * ------------------------------------------------------------------ */
#footer-links {
  position: fixed;
  bottom: 18px;
  right: 26px;
  z-index: 1000;
  display: flex;
  gap: 16px;
  pointer-events: auto;
}

#admin-exit {
  position: fixed;
  top: 18px;
  left: 26px;
  z-index: 1000;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 2px;
  color: var(--color-primary);
  text-decoration: none;
  text-transform: uppercase;
  user-select: none;
}

#admin-exit:hover {
  text-decoration: underline;
}

#admin-generate-pdf {
  position: fixed;
  top: 46px;
  left: 26px;
  z-index: 1000;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 2px;
  color: var(--color-primary);
  text-decoration: none;
  text-transform: uppercase;
  user-select: none;
}

#admin-generate-pdf:hover {
  text-decoration: underline;
}

#footer-links a {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 2px;
  color: #111111;
  text-decoration: none;
  text-transform: uppercase;
  user-select: none;
}

#footer-links a:hover {
  text-decoration: underline;
}

/* ------------------------------------------------------------------
 * Admin context menu (right click / long press on an image)
 * ------------------------------------------------------------------ */
#image-context-menu {
  position: fixed;
  z-index: 1500;
  display: none;
  flex-direction: column;
  min-width: 180px;
  background: #ffffff;
  border: 3px solid var(--color-tertiary);
  box-shadow: 6px 6px 0px 0px var(--color-primary);
}

#image-context-menu.open {
  display: flex;
}

#image-context-menu button {
  padding: 12px 16px;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: left;
  color: #111111;
  background: #ffffff;
  border: none;
  cursor: pointer;
}

#image-context-menu button[hidden] {
  display: none;
}

/* `~` rather than `+` so a hidden entry (the details one, on a group's
   satellites) doesn't leave a stray divider above the first visible
   button. */
#image-context-menu button:not([hidden]) ~ button:not([hidden]) {
  border-top: 1px solid #eeeeee;
}

#image-context-menu button:hover {
  background: var(--color-secondary);
}

#image-context-menu button.danger:hover {
  background: #e63946;
  color: #ffffff;
}

