/* Virtual table. Colours follow the brand theme through Bootstrap variables when the theme is
   loaded, with PhaseSix-like fallbacks for the standalone dev harness. */

/* Stacking order of the table page, bottom to top. The table root makes no stacking context of
   its own, so these layers and the page's back link share one; every z-index of such a layer
   uses one of these tokens (add a new layer here, never as a bare number):
     board        auto     the viewport: layers, objects, overlay (z 0–6 inside it)
     hud          10       combat tracker, status, the HUD bar, toasts
     panel        11       inspector, map panel, token picker
     back-link    12       link back to the campaign (page template)
     character    14       character / foe fly-in
     showcase     15       the image the GM shows everyone
     dice         16       3D dice, badges, result cards; click-through except the cards
     dialog       18       modal dialogs (adventure picker): above the dice
     menu         20       context and Add menus, dice preferences popover */
:root {
  --table-z-hud: 10;
  --table-z-panel: 11;
  --table-z-back-link: 12;
  --table-z-character: 14;
  --table-z-showcase: 15;
  --table-z-dice: 16;
  --table-z-dialog: 18;
  --table-z-menu: 20;
}

.table-root {
  --table-accent: var(--bs-primary, #4f8fdb);
  --table-accent-rgb: var(--bs-primary-rgb, 79, 143, 219);
  --table-black-rgb: var(--bs-black-rgb, 0, 0, 0);
  --table-white-rgb: var(--bs-white-rgb, 255, 255, 255);
  --table-text: var(--bs-body-color, #dee2e6);
  --table-muted: var(--bs-secondary-color, rgba(222, 226, 230, 0.65));
  --table-panel-rgb: var(--bs-body-bg-rgb, 22, 26, 34);
  --table-danger: var(--bs-danger, #dc3545);
  --table-handle-size: 10px;

  position: fixed;
  inset: 0;
  overflow: hidden;
  color: var(--table-text);
  font-family: var(--bs-body-font-family, system-ui, sans-serif);
  background: #0c0e12;
}

/* ---- Surface ---------------------------------------------------------------------------- */

.table-viewport {
  --table-bg: #171b22;
  /* Settled camera scale, written by Camera when it stops moving (not every frame). */
  --cam-scale: 1;

  position: absolute;
  inset: 0;
  overflow: hidden;
  outline: none;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  /* No iOS callout (save image, copy link) under a finger held to ping. */
  -webkit-touch-callout: none;
  cursor: default;
  background-color: var(--table-bg);
  /* A soft light pool; the dot grid that moves with the camera is .table-dots. */
  background-image:
    radial-gradient(ellipse 70% 60% at 50% 45%, rgba(var(--table-accent-rgb), 0.07), transparent 70%),
    radial-gradient(ellipse 120% 90% at 50% 40%, rgba(var(--table-white-rgb), 0.04), transparent 75%);
}

/* Images on the table are never dragged out by a long press (iOS, Android). */
.table-viewport img {
  -webkit-user-drag: none;
}

/* The table cloth's image (Cloth.js): `cover` fills the viewport and stays put; `tile` follows
   the camera through its background size and position. */
.table-cloth {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-repeat: repeat;
  /* Its own layer, so following the camera repaints only the cloth. */
  will-change: transform;
}

.table-cloth[hidden] {
  display: none;
}

.table-cloth[data-mode="cover"] {
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
}

/* The dot grid would only add noise to a picture. */
.table-viewport.has-cloth-image .table-dots {
  display: none;
}

/* ---- Cloth pattern: a bundled pattern over the cloth (ClothPattern.js, PatternControls.js) -- */

/* The pattern colour, masked by the pattern's tile; size, position and opacity follow the camera. */
.table-cloth-pattern {
  position: absolute;
  inset: 0;
  pointer-events: none;
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  will-change: transform;
}

.table-cloth-pattern[hidden] {
  display: none;
}

/* A patterned cloth has its own structure; the dot grid would only add noise. */
.table-viewport.has-cloth-pattern .table-dots {
  display: none;
}

.table-pattern__details {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.table-pattern__details[hidden] {
  display: none;
}

.table-pattern__swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 6px;
}

.table-pattern__swatch {
  position: relative;
  aspect-ratio: 1;
  padding: 0;
  overflow: hidden;
  color: var(--table-text);
  font: inherit;
  background: var(--pattern-bg, #171b22);
  border: 1px solid rgba(var(--table-white-rgb), 0.15);
  border-radius: 8px;
  cursor: pointer;
}

.table-pattern__swatch:hover:not(:disabled) {
  border-color: rgba(var(--table-accent-rgb), 0.6);
}

.table-pattern__swatch.is-selected {
  border-color: var(--table-accent);
  box-shadow: 0 0 0 1px var(--table-accent), 0 0 10px rgba(var(--table-accent-rgb), 0.35);
}

.table-pattern__swatch:focus-visible {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
}

.table-pattern__swatch:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.table-pattern__sample {
  position: absolute;
  inset: 0;
  background-color: var(--pattern-fg, #9aa4b8);
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
}

/* The uploaded image shows in its own colours. */
.table-pattern__swatch[data-pattern="image"] .table-pattern__sample {
  background-color: transparent;
  background-position: center;
  background-size: cover;
}

.table-pattern__name {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 10px 4px 3px;
  overflow: hidden;
  font-size: 10px;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  text-overflow: ellipsis;
  background: linear-gradient(transparent, rgba(var(--table-black-rgb), 0.75));
  text-shadow: 0 1px 2px rgba(var(--table-black-rgb), 0.9);
}

.table-pattern__color,
.table-pattern__range {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.table-pattern__auto {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  white-space: nowrap;
}

.table-pattern__color input[type="color"] {
  width: 36px;
}

.table-pattern__color input[type="color"]:disabled {
  opacity: 0.4;
}

.table-pattern input[type="range"] {
  margin: 0;
}

.table-pattern__range input {
  flex: 1;
  min-width: 0;
}

.table-pattern__value {
  min-width: 42px;
  color: var(--table-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* The colour, opacity and scale rows need more room than the grid's 88px control column. */
.table-pattern .table-panel__field:not(.is-check) {
  grid-template-columns: 64px minmax(0, 1fr);
}

/* ---- End of cloth pattern ---------------------------------------------------------------- */

/* The camera moves this layer by transform only and sets its size and background size when it
   settles, so panning never repaints the viewport. */
.table-dots {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
  pointer-events: none;
  /* Zoomed out, Camera thins the dots out (dotLevels): the coarse period doubles and the dots
     between coarse ones (three offset layers, so they never draw over a coarse dot) fade with
     --dots-between, the fine dots with --dots-fine. */
  --dots-between: 0;
  --dots-fine: 1;
  --dot-between: radial-gradient(
    circle,
    rgba(var(--table-white-rgb), calc(0.07 * var(--dots-between))) 1px,
    transparent 1.6px
  );
  background-image:
    radial-gradient(circle, rgba(var(--table-white-rgb), 0.07) 1px, transparent 1.6px),
    var(--dot-between),
    var(--dot-between),
    var(--dot-between),
    radial-gradient(circle, rgba(var(--table-white-rgb), calc(0.035 * var(--dots-fine))) 1px, transparent 1.4px);
  background-size: 128px 128px, 128px 128px, 128px 128px, 128px 128px, 32px 32px;
  background-position: 0 0, 64px 0, 0 64px, 64px 64px, 0 0;
}

.table-viewport.is-space-held {
  cursor: grab;
}

.table-viewport.is-panning,
.table-viewport.is-panning * {
  cursor: grabbing !important;
}

.table-vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 85% 80% at 50% 50%, transparent 55%, rgba(var(--table-black-rgb), 0.55) 100%),
    linear-gradient(rgba(var(--table-black-rgb), 0.25), transparent 18%, transparent 82%, rgba(var(--table-black-rgb), 0.3));
}

.table-world {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  transform-origin: 0 0;
}

.table-world.is-moving {
  will-change: transform;
}

.table-layer {
  position: absolute;
  top: 0;
  left: 0;
}

.table-layer--map {
  z-index: 1;
}

.table-layer--objects {
  z-index: 2;
}

.table-layer--tokens {
  z-index: 3;
}

/* ---- 3D props (PropLayer.js) ----------------------------------------------------------- */

/* The shared three.js canvas covers the visible world rect; it stands above the objects layer
   (same z-index, later in the DOM) and below the tokens. The light pools lie on the maps. */
.table-props {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  transform-origin: 0 0;
  pointer-events: none;
}

.table-prop-lights {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  pointer-events: none;
}

.table-prop-light {
  --light-color: #ffb35c;
  --light-strength: 0.75;

  position: absolute;
  top: 0;
  left: 0;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--light-color) calc(var(--light-strength) * 52%), transparent) 0%,
    color-mix(in srgb, var(--light-color) calc(var(--light-strength) * 24%), transparent) 24%,
    color-mix(in srgb, var(--light-color) calc(var(--light-strength) * 8%), transparent) 50%,
    transparent 70%
  );
  mix-blend-mode: screen;
}

.table-prop-light.is-hidden {
  opacity: 0.45;
}

/* The prop's own box only catches the pointer; its mesh is on the canvas. */
.table-object__content.table-prop {
  background: none;
  border-radius: 50%;
  box-shadow: none;
}

.table-object.is-selected .table-prop:not(.table-fallback) {
  box-shadow: 0 0 0 calc(1.5px / var(--cam-scale, 1)) rgba(var(--table-accent-rgb), 0.85);
}

/* ---- Objects ---------------------------------------------------------------------------- */

.table-object {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 50% 50%;
  cursor: grab;
}

.table-object.is-readonly,
.table-object.is-locked {
  cursor: default;
}

.is-transforming .table-viewport,
.is-transforming .table-viewport * {
  cursor: grabbing !important;
}

.table-object__content {
  position: absolute;
  inset: 0;
  border-radius: 3px;
  box-shadow:
    0 2px 4px rgba(var(--table-black-rgb), 0.35),
    0 10px 28px rgba(var(--table-black-rgb), 0.45);
}

.table-object[data-layer="map"] > .table-object__content {
  border-radius: 0;
  box-shadow: 0 0 60px rgba(var(--table-black-rgb), 0.6);
}

.table-object.is-hidden > .table-object__content {
  outline: 2px dashed rgba(var(--table-white-rgb), 0.5);
  outline-offset: -2px;
}

.table-object__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: fill;
  border-radius: inherit;
  pointer-events: none;
}

.table-object__content.is-missing {
  background: repeating-linear-gradient(45deg, rgba(var(--table-white-rgb), 0.05) 0 10px, transparent 10px 20px),
    rgba(var(--table-panel-rgb), 0.9);
}

/*
 * Notes look like a slip of paper. Paddings and frames are unitless px values (--note-py,
 * --note-px, optional --note-pb/--note-pl, --note-frame) so that tiny notes can shrink them:
 * a note with room for a line of text keeps them exactly, a smaller one (a map label 44 px
 * high) gives that room to its text.
 */
.table-object[data-kind="note"] {
  container-type: size;
}

.table-note {
  --note-color: #e8dcb5;
  --note-py: 14;
  --note-px: 16;
  --note-frame: 0;

  display: flex;
  /*
   * The height a line of text needs with the full padding and frame (the line without most of
   * its leading), and the width for a few letters. Below them, padding and frame shrink linearly (to 2 px over 24 px of height or
   * 48 px of width); at or above them nothing changes.
   */
  --note-room-y: calc((var(--note-py) + var(--note-pb, var(--note-py)) + 2 * var(--note-frame)) * 1px + var(--note-size, 18px) * var(--note-font-scale, 1) * 1.3);
  --note-room-x: calc((var(--note-px) + var(--note-pl, var(--note-px)) + 2 * var(--note-frame)) * 1px + 48px);
  --note-fit-y: max(0px, calc(1px - (var(--note-room-y) - 100cqh) / 24));
  --note-fit-x: max(0px, calc(1px - (var(--note-room-x) - 100cqw) / 48));

  padding:
    min(calc(var(--note-py) * 1px), max(2px, calc(var(--note-py) * var(--note-fit-y))))
    min(calc(var(--note-px) * 1px), max(2px, calc(var(--note-px) * var(--note-fit-x))))
    min(calc(var(--note-pb, var(--note-py)) * 1px), max(2px, calc(var(--note-pb, var(--note-py)) * var(--note-fit-y))))
    min(calc(var(--note-pl, var(--note-px)) * 1px), max(2px, calc(var(--note-pl, var(--note-px)) * var(--note-fit-x))));
  border-width: min(calc(var(--note-frame) * 1px), max(min(calc(var(--note-frame) * 1px), 2px), calc(var(--note-frame) * min(var(--note-fit-y), var(--note-fit-x)))));
  overflow: hidden;
  color: #2b2620;
  background:
    linear-gradient(rgba(255, 255, 255, 0.25), transparent 30%),
    var(--note-color);
  border-radius: 2px;
}

.table-note__text {
  flex: 1;
  overflow: hidden;
  /* A note too low for a line of its text size takes a smaller one. */
  font-size: min(calc(var(--note-size, 18px) * var(--note-font-scale, 1)), 45cqh);
  line-height: 1.35;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  outline: none;
}

/* Markdown the server rendered: its own line breaks, compact blocks in the note's font. */
.table-note__text.is-markup {
  white-space: normal;
}

.table-note__text.is-markup > :first-child {
  margin-top: 0;
}

.table-note__text.is-markup > :last-child {
  margin-bottom: 0;
}

.table-note__text.is-markup :is(p, ul, ol, blockquote, pre, table) {
  margin: 0 0 0.5em;
}

.table-note__text.is-markup :is(h1, h2, h3, h4, h5) {
  margin: 0.4em 0 0.3em;
  font: inherit;
  font-weight: 700;
  line-height: 1.2;
}

.table-note__text.is-markup h1 {
  font-size: 1.45em;
}

.table-note__text.is-markup h2 {
  font-size: 1.25em;
}

.table-note__text.is-markup :is(h3, h4, h5) {
  font-size: 1.1em;
}

.table-note__text.is-markup :is(ul, ol) {
  padding-left: 1.3em;
}

.table-note__text.is-markup blockquote {
  padding-left: 0.6em;
  border-left: 3px solid color-mix(in srgb, currentColor 35%, transparent);
  font-style: italic;
}

.table-note__text.is-markup :is(code, pre) {
  font-family: ui-monospace, monospace;
  font-size: 0.9em;
}

.table-note__text.is-markup pre {
  white-space: pre-wrap;
}

.table-note__text.is-markup table {
  border-collapse: collapse;
}

.table-note__text.is-markup :is(th, td) {
  padding: 0.1em 0.4em;
  border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
}

.table-note__text.is-markup a {
  color: inherit;
  text-decoration: underline;
}

.table-object.is-editing {
  cursor: text;
}

.table-object.is-editing .table-note__text {
  user-select: text;
  -webkit-user-select: text;
  overflow: auto;
}

/* ---- Note looks: paper (data-note-style), font (data-note-font), size (--note-size) --------- */
/* Fonts are bundled: table/fonts (OFL / Apache, see the licence files there) and the site theme. */

@font-face {
  font-family: "Table Caveat";
  src: url("../fonts/caveat-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}

@font-face {
  font-family: "Table Special Elite";
  src: url("../fonts/special-elite-latin.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "Table Schoolbell";
  src: url("../fonts/schoolbell-latin.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "Table Playfair";
  src: url("../../theme/fonts/PlayfairDisplay/PlayfairDisplay-Regular.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "Table MetaMorphous";
  src: url("../../theme/fonts/MetaMorphous/metamorphous-regular-webfont.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "Table Noto Sans";
  src: url("../../theme/fonts/NotoSans/noto-sans-v28-latin-regular.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "Table Oxanium";
  src: url("../../theme/fonts/Oxanium/oxanium-v14-latin-regular.woff2") format("woff2");
  font-display: swap;
}

.table-note[data-note-font="hand"] {
  --note-font-scale: 1.25;

  font-family: "Table Caveat", "Segoe Print", cursive;
}

.table-note[data-note-font="serif"] {
  font-family: "Table Playfair", Georgia, serif;
}

.table-note[data-note-font="fantasy"] {
  --note-font-scale: 0.95;

  font-family: "Table MetaMorphous", "Table Playfair", Georgia, serif;
}

.table-note[data-note-font="typewriter"] {
  font-family: "Table Special Elite", "Courier New", monospace;
}

.table-note[data-note-font="chalk"] {
  --note-font-scale: 1.05;

  font-family: "Table Schoolbell", "Table Caveat", cursive;
}

.table-note[data-note-font="sans"] {
  font-family: "Table Noto Sans", system-ui, sans-serif;
}

.table-note[data-note-font="tech"] {
  --note-font-scale: 0.95;

  font-family: "Table Oxanium", system-ui, sans-serif;
  letter-spacing: 0.02em;
}

/* Parchment: warm, uneven skin with darkened, worn edges. */
.table-note[data-note-style="parchment"] {
  --note-py: 22;
  --note-px: 24;

  color: #3b2a17;
  background:
    radial-gradient(ellipse at center, transparent 55%, rgba(96, 58, 20, 0.35) 100%),
    radial-gradient(circle at 20% 15%, rgba(255, 245, 210, 0.55), transparent 45%),
    radial-gradient(circle at 80% 85%, rgba(150, 100, 40, 0.18), transparent 50%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0.35 0 0 0 0 0.22 0 0 0 0 0.08 0 0 0 0.16 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"),
    #e9d3a4;
  border-radius: 3px 6px 4px 7px;
  box-shadow:
    inset 0 0 18px rgba(110, 70, 25, 0.45),
    0 2px 4px rgba(var(--table-black-rgb), 0.35),
    0 10px 28px rgba(var(--table-black-rgb), 0.45);
}

/* Tablet: a glowing screen in a dark bezel with a camera dot. */
.table-note[data-note-style="tablet"] {
  --note-py: 16;
  --note-px: 18;
  --note-frame: 12;

  box-sizing: border-box;
  /* The camera dot sits on the bezel. */
  overflow: clip;
  overflow-clip-margin: 12px;
  color: #d8ecff;
  background:
    linear-gradient(160deg, rgba(255, 255, 255, 0.09), transparent 40%),
    radial-gradient(ellipse at 50% 0%, #1f4a6e, #0b1a2a 75%);
  border-style: solid;
  border-color: #16181d;
  border-radius: 16px;
  box-shadow:
    0 0 0 1px #3a3f48,
    inset 0 0 22px rgba(80, 170, 255, 0.25),
    0 2px 4px rgba(var(--table-black-rgb), 0.35),
    0 10px 28px rgba(var(--table-black-rgb), 0.5);
  text-shadow: 0 0 6px rgba(120, 190, 255, 0.35);
}

.table-note[data-note-style="tablet"]::before {
  position: absolute;
  top: -8px;
  left: 50%;
  width: 4px;
  height: 4px;
  content: "";
  background: #2b3240;
  border-radius: 50%;
  transform: translateX(-50%);
}

/* School slate: chalk on dark slate with smudges in a wooden frame. */
.table-note[data-note-style="slate"] {
  --note-py: 16;
  --note-px: 18;
  --note-frame: 11;

  box-sizing: border-box;
  color: #f2f1ea;
  background:
    radial-gradient(ellipse at 30% 40%, rgba(255, 255, 255, 0.07), transparent 55%),
    radial-gradient(ellipse at 75% 70%, rgba(255, 255, 255, 0.05), transparent 45%),
    #2e3a37;
  border-style: solid;
  border-color: #8a5a2b;
  border-image: linear-gradient(135deg, #a8723d, #6e4521 40%, #9a6634 70%, #5c3a1c) 1;
  border-radius: 0;
  box-shadow:
    inset 0 0 14px rgba(var(--table-black-rgb), 0.55),
    0 2px 4px rgba(var(--table-black-rgb), 0.35),
    0 10px 28px rgba(var(--table-black-rgb), 0.45);
  text-shadow: 0 0 1px rgba(255, 255, 255, 0.6);
}

/* Index card: white or tinted card, red margin rule, blue lines that follow the text. */
.table-note[data-note-style="index"] {
  --note-color: #fbfaf4;
  --note-py: 10;
  --note-px: 14;
  --note-pl: 18;

  color: #1f2a44;
  background:
    linear-gradient(90deg, transparent 10px, rgba(214, 74, 74, 0.55) 10px 11px, transparent 11px),
    var(--note-color);
  border-radius: 2px;
}

.table-note[data-note-style="index"] .table-note__text {
  padding-left: 4px;
  background: repeating-linear-gradient(transparent 0 calc(1lh - 1px), rgba(90, 140, 205, 0.45) calc(1lh - 1px) 1lh);
  background-attachment: local;
}

/* Sealed letter: cream paper with a fold and a wax seal in the corner. */
.table-note[data-note-style="letter"] {
  --note-py: 18;
  --note-px: 22;
  --note-pb: 30;

  color: #2a2420;
  background:
    linear-gradient(transparent calc(50% - 1px), rgba(120, 100, 70, 0.18) calc(50% - 1px), rgba(255, 255, 255, 0.35) 50%, transparent calc(50% + 2px)),
    linear-gradient(170deg, #fbf3df, #efe2c3);
  border-radius: 2px;
}

.table-note[data-note-style="letter"]::after {
  position: absolute;
  right: 12px;
  bottom: 10px;
  width: 30px;
  height: 30px;
  content: "";
  background:
    radial-gradient(circle at 50% 50%, transparent 7px, rgba(60, 0, 0, 0.35) 8px, transparent 9px),
    radial-gradient(circle at 38% 35%, #d9534f, #8c1c1c 70%);
  border-radius: 46% 54% 50% 50% / 52% 46% 54% 48%;
  box-shadow: 0 1px 3px rgba(var(--table-black-rgb), 0.45);
}

/* Typewritten page: bright paper with faint ribbon streaks. */
.table-note[data-note-style="typewriter"] {
  --note-py: 20;
  --note-px: 22;

  color: #26231f;
  background:
    repeating-linear-gradient(transparent 0 3px, rgba(0, 0, 0, 0.018) 3px 4px),
    linear-gradient(#fdfdf8, #f1efe6);
  border-radius: 1px;
}

/* Hologram: a translucent cyan projection with scan lines. */
.table-note[data-note-style="hologram"] {
  --note-py: 16;
  --note-px: 18;

  color: #b8fbff;
  background:
    repeating-linear-gradient(transparent 0 2px, rgba(120, 240, 255, 0.07) 2px 3px),
    linear-gradient(180deg, rgba(40, 200, 230, 0.22), rgba(20, 90, 140, 0.18));
  border: 1px solid rgba(120, 240, 255, 0.7);
  border-radius: 6px;
  box-shadow:
    0 0 14px rgba(70, 220, 255, 0.55),
    inset 0 0 18px rgba(70, 220, 255, 0.35);
  text-shadow: 0 0 6px rgba(90, 230, 255, 0.85);
}

.table-fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 8px;
  text-align: center;
  background: rgba(var(--table-panel-rgb), 0.92);
  border: 1px solid rgba(var(--table-white-rgb), 0.12);
}

.table-fallback__kind {
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--table-muted);
}

.table-fallback__name {
  font-size: 16px;
}

/* Battlemap grid: line width divided by the camera scale, so lines stay about 1 screen px. */
.table-grid {
  --grid-line: calc(1px / var(--cam-scale, 1));

  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--grid-opacity, 0.4);
  background-image:
    linear-gradient(to right, var(--grid-color, #000) var(--grid-line), transparent var(--grid-line)),
    linear-gradient(to bottom, var(--grid-color, #000) var(--grid-line), transparent var(--grid-line));
  background-size: var(--grid-size, 64px) var(--grid-size, 64px);
  background-position: var(--grid-x, 0) var(--grid-y, 0);
}

/* Hidden / locked badges keep a constant screen size: sizes are divided by the camera scale. */
.table-object__badges {
  --badge: calc(22px / var(--cam-scale, 1));

  position: absolute;
  top: calc(var(--badge) * -0.4);
  right: calc(var(--badge) * -0.4);
  z-index: 1;
  display: flex;
  gap: calc(var(--badge) * 0.15);
  pointer-events: none;
}

.table-object__badge {
  display: grid;
  place-items: center;
  width: var(--badge);
  height: var(--badge);
  color: var(--table-text);
  background: rgba(var(--table-panel-rgb), 0.92);
  border: calc(1px / var(--cam-scale, 1)) solid rgba(var(--table-white-rgb), 0.35);
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(var(--table-black-rgb), 0.5);
  box-sizing: border-box;
}

.table-object__badge .table-icon {
  width: 62%;
  height: 62%;
}

.table-object__badge--eyeSlash {
  color: var(--bs-warning, #ffc107);
}

/* Hidden objects: translucent for the GM, but the badge stays readable. */
.table-object.is-hidden > .table-object__content {
  opacity: 0.45;
}

/* ---- Measures ---------------------------------------------------------------------------- */

/* Only the outline and the handles take pointer input: the area never covers what lies under it. */
.table-object[data-kind="measure"] {
  pointer-events: none;
}

.table-measure {
  --measure-line: calc(1.5px / var(--cam-scale, 1));

  position: absolute;
  inset: 0;
  overflow: visible;
}

/* No card shadow and no hidden-outline: the outline of the shape is all there is. */
.table-object[data-kind="measure"] > .table-measure {
  border-radius: 0;
  box-shadow: none;
}

.table-object.is-hidden > .table-measure {
  outline: none;
}

.table-measure__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.table-measure__area {
  fill: color-mix(in srgb, var(--measure-color) 16%, transparent);
  stroke: var(--measure-color);
  stroke-width: var(--measure-line);
  stroke-linejoin: round;
}

.table-measure__guide {
  fill: none;
  stroke: var(--measure-color);
  stroke-width: var(--measure-line);
  stroke-linecap: round;
}

/* A line is all guide; circles and cones show their radius / axis faintly. */
.table-measure:not([data-shape="line"]) .table-measure__guide {
  stroke-dasharray: calc(5px / var(--cam-scale, 1)) calc(4px / var(--cam-scale, 1));
  opacity: 0.55;
}

.table-object:not(.is-readonly):not(.is-locked) .table-measure__area,
.table-object:not(.is-readonly):not(.is-locked) .table-measure__guide {
  pointer-events: visibleStroke;
  cursor: grab;
}

.table-object.is-selected .table-measure {
  --measure-line: calc(2.25px / var(--cam-scale, 1));
}

.table-object.is-selected .table-measure__svg {
  filter: drop-shadow(0 0 calc(4px / var(--cam-scale, 1)) color-mix(in srgb, var(--measure-color) 60%, transparent));
}

/* Label and handles keep their screen size and stay level whatever the measure's rotation. */
.table-measure__label,
.table-measure__handle {
  position: absolute;
  transform: translate(-50%, -50%) rotate(var(--counter-rotation, 0deg)) scale(calc(1 / var(--cam-scale, 1)));
}

.table-measure__label {
  padding: 1px 7px 2px;
  color: var(--table-text);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  white-space: nowrap;
  background: rgba(var(--table-panel-rgb), 0.82);
  border: 1px solid color-mix(in srgb, var(--measure-color) 55%, transparent);
  border-radius: 999px;
  pointer-events: none;
}

.table-measure__label[hidden],
.table-object.is-readonly .table-measure__handle,
.table-object.is-locked .table-measure__handle {
  display: none;
}

/* The origin: a ring with a dot, always there for whoever may move the measure. */
.table-measure__handle--origin {
  width: 14px;
  height: 14px;
  background: radial-gradient(circle, var(--measure-color) 0 2.5px, rgba(var(--table-panel-rgb), 0.85) 3px);
  border: 1.5px solid var(--measure-color);
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(var(--table-black-rgb), 0.5);
  pointer-events: auto;
  cursor: move;
}

/* The end: a small dot, only while the measure is selected. */
.table-measure__handle--end {
  width: 10px;
  height: 10px;
  background: var(--measure-color);
  border: 1.5px solid rgba(var(--table-panel-rgb), 0.9);
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(var(--table-black-rgb), 0.5);
  pointer-events: auto;
  cursor: crosshair;
  opacity: 0;
  visibility: hidden;
}

.table-object.is-selected .table-measure__handle--end {
  opacity: 1;
  visibility: visible;
}

.table-measure__handle:hover {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--measure-color) 30%, transparent), 0 1px 4px rgba(var(--table-black-rgb), 0.5);
}

/* ---- Paths (PathObject.js): the style draws into the SVG; a wide invisible stroke along the
   curve picks the path, so its box never covers what lies beneath. ------------------------ */

.table-object[data-kind="path"] {
  pointer-events: none;
}

.table-object__content.table-path {
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}

.table-object.is-hidden > .table-path {
  outline: none;
}

.table-object.is-hidden .table-path__drawing {
  opacity: 0.5;
}

.table-path__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.table-path__label {
  fill: var(--table-text);
  font-family: inherit;
  font-weight: 600;
  paint-order: stroke;
  stroke: rgba(var(--table-black-rgb), 0.7);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.table-path__hit {
  fill: none;
  stroke: transparent;
  stroke-width: max(calc(var(--path-hit, 16) * 1px), calc(14px / var(--cam-scale, 1)));
  stroke-linecap: round;
  stroke-linejoin: round;
}

.table-object.is-path-interactive .table-path__hit {
  pointer-events: stroke;
}

.table-object.is-selected .table-path__drawing {
  filter: drop-shadow(0 0 calc(3px / var(--cam-scale, 1)) rgba(var(--table-accent-rgb), 0.9));
}

/* Edit mode: arms from each node to its handles, grips that keep their screen size. */
.table-path__arms line {
  stroke: rgba(var(--table-accent-rgb), 0.85);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
}

.table-path__handles {
  position: absolute;
  inset: 0;
}

.table-path__node,
.table-path__control {
  position: absolute;
  box-sizing: border-box;
  transform: translate(-50%, -50%) scale(calc(1 / var(--cam-scale, 1)));
  box-shadow: 0 1px 4px rgba(var(--table-black-rgb), 0.5);
  pointer-events: auto;
  cursor: move;
}

.table-path__node {
  width: 12px;
  height: 12px;
  background: rgba(var(--table-panel-rgb), 0.95);
  border: 2px solid rgb(var(--table-accent-rgb));
}

.table-path__node.is-smooth {
  border-radius: 50%;
}

.table-path__node.is-selected {
  background: rgb(var(--table-accent-rgb));
}

.table-path__control {
  width: 9px;
  height: 9px;
  background: rgb(var(--table-accent-rgb));
  border-radius: 50%;
  cursor: crosshair;
}

.table-path__node:hover,
.table-path__control:hover {
  box-shadow: 0 0 0 4px rgba(var(--table-accent-rgb), 0.3), 0 1px 4px rgba(var(--table-black-rgb), 0.5);
}

/* The pen tool's draft, in world space with screen-size lines. */
.table-viewport.is-path-drawing,
.table-viewport.is-path-drawing .table-object {
  cursor: crosshair;
}

.table-path-draft {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: visible;
  pointer-events: none;
}

.table-path-draft__curve,
.table-path-draft__band {
  fill: none;
  stroke: rgb(var(--table-accent-rgb));
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

.table-path-draft__band {
  stroke-dasharray: 6 5;
  opacity: 0.7;
}

.table-path-draft__arm {
  stroke: rgba(var(--table-accent-rgb), 0.8);
  stroke-width: 1px;
  vector-effect: non-scaling-stroke;
}

.table-path-draft__node,
.table-path-draft__control {
  fill: rgb(var(--table-accent-rgb));
  stroke: rgba(var(--table-panel-rgb), 0.95);
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
}

.table-path-draft__node {
  fill: rgba(var(--table-panel-rgb), 0.95);
  stroke: rgb(var(--table-accent-rgb));
  stroke-width: 2px;
}

/* Clicking the first node closes the path. */
.table-path-draft__node.is-first {
  fill: rgb(var(--table-accent-rgb));
}

/* The path bar shares the measure bar's look; these are its fields. */
.table-path-bar__select,
.table-path-bar__label {
  height: 28px;
  padding: 0 8px;
  color: var(--table-text);
  font: inherit;
  font-size: 12px;
  background: rgba(var(--table-white-rgb), 0.06);
  border: 1px solid rgba(var(--table-white-rgb), 0.12);
  border-radius: 14px;
}

.table-path-bar__select {
  max-width: 150px;
}

/* The native dropdown is light whatever the bar's colours: dark text in it. */
.table-path-bar__select option,
.table-path-bar__select optgroup {
  color: #111;
}

.table-path-bar__label {
  width: 130px;
}

.table-path-bar__color {
  width: 28px;
  height: 28px;
  padding: 0;
  background: none;
  border: none;
  border-radius: 50%;
  cursor: pointer;
}

.table-path-bar__side {
  font-size: 14px;
}

.table-measure-bar__button[hidden] {
  display: none;
}

/* The bar below a selected measure: shape, colour, a cone's opening, delete. */
.table-measure-bar {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: rgba(var(--table-panel-rgb), 0.9);
  border: 1px solid rgba(var(--table-white-rgb), 0.1);
  border-radius: 999px;
  box-shadow: 0 6px 18px rgba(var(--table-black-rgb), 0.45);
  backdrop-filter: blur(6px);
  pointer-events: auto;
}

/* Narrow screens: the bar wraps onto two rows instead of running off the side. */
@media (max-width: 576px) {
  .table-measure-bar {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 4px;
    box-sizing: border-box;
    max-width: calc(100% - 16px);
    border-radius: 18px;
  }

  .table-measure-bar__angle {
    width: 72px;
  }
}

.table-measure-bar[hidden],
.table-measure-bar__group[hidden],
body.is-transforming .table-measure-bar {
  display: none;
}

.table-measure-bar__group {
  display: flex;
  align-items: center;
  gap: 2px;
}

.table-measure-bar__group + .table-measure-bar__group {
  margin-left: 3px;
  padding-left: 5px;
  border-left: 1px solid rgba(var(--table-white-rgb), 0.1);
}

.table-measure-bar__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  color: var(--table-muted);
  background: transparent;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}

.table-measure-bar__button .table-icon {
  width: 16px;
  height: 16px;
}

.table-measure-bar__button:hover,
.table-measure-bar__button:focus-visible {
  color: var(--table-text);
  background: rgba(var(--table-white-rgb), 0.08);
  outline: none;
}

.table-measure-bar__button[aria-pressed="true"] {
  color: var(--table-text);
  background: rgba(var(--table-accent-rgb), 0.22);
}

.table-measure-bar__button.is-danger:hover,
.table-measure-bar__button.is-danger:focus-visible {
  color: var(--table-danger);
}

/* Step arrows around the selected token on touch devices (StepArrows.js): one cell per tap. */
.table-step-arrows {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.table-step-arrows[hidden],
.table-step-arrows.is-off-screen,
.table-viewport.is-panning .table-step-arrows,
body.is-transforming .table-step-arrows {
  display: none;
}

.table-step-arrows__button {
  position: absolute;
  top: 0;
  left: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  color: var(--table-text);
  background: rgba(var(--table-panel-rgb), 0.88);
  border: 1px solid rgba(var(--table-accent-rgb), 0.45);
  border-radius: 50%;
  box-shadow:
    0 4px 12px rgba(var(--table-black-rgb), 0.45),
    0 0 10px rgba(var(--table-accent-rgb), 0.18);
  backdrop-filter: blur(6px);
  cursor: pointer;
  pointer-events: auto;
  touch-action: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

.table-step-arrows__button:active,
.table-step-arrows__button:focus-visible {
  color: var(--table-text);
  background: rgba(var(--table-accent-rgb), 0.3);
  border-color: var(--table-accent);
  outline: none;
}

.table-step-arrows__button .table-icon {
  width: 20px;
  height: 20px;
}

.table-step-arrows__button--right .table-icon {
  transform: rotate(90deg);
}

.table-step-arrows__button--down .table-icon {
  transform: rotate(180deg);
}

.table-step-arrows__button--left .table-icon {
  transform: rotate(-90deg);
}

/* A stepped token glides into its next cell. */
.table-object.is-stepping {
  transition: transform 0.15s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .table-object.is-stepping {
    transition: none;
  }
}

.table-measure-bar__swatch {
  width: 18px;
  height: 18px;
  margin: 0 1px;
  padding: 0;
  background: var(--swatch);
  border: 2px solid transparent;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(var(--table-black-rgb), 0.3);
  cursor: pointer;
}

.table-measure-bar__swatch[aria-pressed="true"] {
  border-color: rgba(var(--table-panel-rgb), 1);
  box-shadow: 0 0 0 1.5px var(--swatch);
}

.table-measure-bar__swatch:focus-visible {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
}

.table-measure-bar__angle {
  width: 84px;
  accent-color: var(--table-accent);
}

.table-measure-bar__value {
  min-width: 34px;
  padding-right: 6px;
  color: var(--table-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ---- Tokens ----------------------------------------------------------------------------- */

.table-token {
  --token-color: #c9a227;
  --token-width: 4px;

  overflow: visible;
  background: none;
  border-radius: 0;
  box-shadow: none;
}

.table-token__frame {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: rgba(var(--table-panel-rgb), 0.95);
  border: var(--token-width) solid var(--token-color);
  box-shadow: 0 4px 14px rgba(var(--table-black-rgb), 0.55);
  box-sizing: border-box;
  container-type: size;
}

.table-token[data-shape="round"] .table-token__frame {
  border-radius: 50%;
}

.table-token[data-shape="square"] .table-token__frame {
  border-radius: 12%;
}

.table-token__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

.table-token__sigil {
  color: var(--token-color);
  font-size: 40cqmin;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.table-token__name {
  position: absolute;
  top: calc(100% + 4px * var(--token-scale, 1));
  left: 50%;
  max-width: 180%;
  padding: calc(1px * var(--token-scale, 1)) calc(7px * var(--token-scale, 1));
  overflow: hidden;
  color: var(--table-text);
  font-size: calc(12px * var(--token-scale, 1));
  white-space: nowrap;
  text-overflow: ellipsis;
  background: rgba(var(--table-panel-rgb), 0.85);
  border-radius: 999px;
  transform: translateX(-50%);
  pointer-events: none;
}

.table-token__health {
  position: absolute;
  bottom: calc(-3px * var(--token-scale, 1));
  left: 50%;
  width: min(76%, 76px * var(--token-scale, 1));
  height: calc(6px * var(--token-scale, 1));
  overflow: hidden;
  background: rgba(var(--table-black-rgb), 0.7);
  border: 1px solid rgba(var(--table-black-rgb), 0.8);
  border-radius: calc(3px * var(--token-scale, 1));
  transform: translateX(-50%);
}

.table-token__health[hidden],
.table-token__name[hidden] {
  display: none;
}

.table-token__health-fill {
  height: 100%;
  background: var(--bs-success, #198754);
}

.table-token__health[data-level="mid"] .table-token__health-fill {
  background: var(--bs-warning, #ffc107);
}

.table-token__health[data-level="low"] .table-token__health-fill {
  background: var(--bs-danger, #dc3545);
}

/* The combat turn marker under the active token (TokenObject.setTurn): a dashed track with
   three arcs, one turn a minute; it stands still with reduced motion. */
.table-token__turn {
  position: absolute;
  top: -24%;
  left: -24%;
  width: 148%;
  height: 148%;
  overflow: visible;
  color: var(--table-accent);
  pointer-events: none;
  filter: drop-shadow(0 0 3px rgba(var(--table-accent-rgb), 0.75));
  animation: table-turn-spin 60s linear infinite;
}

.table-token__turn circle {
  fill: none;
  stroke: currentColor;
}

.table-token__turn-inner {
  stroke-width: 0.8;
  opacity: 0.45;
}

.table-token__turn-ticks {
  stroke-width: 1.6;
  stroke-dasharray: 0.6 2.4;
  opacity: 0.8;
}

.table-token__turn-arcs {
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-dasharray: 26 14;
}

@keyframes table-turn-spin {
  to {
    transform: rotate(1turn);
  }
}

@media (prefers-reduced-motion: reduce) {
  .table-token__turn {
    animation: none;
  }
}

/* ---- Token frames (objects/tokenFrames.js) --------------------------------------------------
   A user's frame around their tokens: two inline SVG layers next to the frame, reaching 14 % past
   the token. Each frame sets its colours (--tf-a…d, mixed with the token's --token-color) and the
   animated layer's motion. Only transform and opacity animate, on the whole layer; the motion
   pauses while the camera moves and stops with reduced motion or the "reduce effects" switch. */

.table-token[data-frame]:not([data-frame="classic"]) .table-token__frame {
  border: 0;
  box-shadow:
    0 4px 14px rgba(var(--table-black-rgb), 0.55),
    inset 0 0 0 1px rgba(var(--table-black-rgb), 0.45);
}

.tf-layer {
  position: absolute;
  top: -14%;
  left: -14%;
  width: 128%;
  height: 128%;
  overflow: visible;
  pointer-events: none;
}

.tf-base {
  filter: drop-shadow(0 1px 1.5px rgba(var(--table-black-rgb), 0.6));
}

.tf-anim {
  opacity: var(--tf-rest, 0.7);
  transform-origin: 50% 50%;
  animation: var(--tf-motion, none) var(--tf-duration, 8s) ease-in-out infinite;
}

.tf-fa { fill: var(--tf-a); }
.tf-fb { fill: var(--tf-b); }
.tf-fc { fill: var(--tf-c); }
.tf-fd { fill: var(--tf-d); }
.tf-sa { stroke: var(--tf-a); }
.tf-sb { stroke: var(--tf-b); }
.tf-sc { stroke: var(--tf-c); }
.tf-sd { stroke: var(--tf-d); }
.tf-fe { fill: var(--tf-e); }
.tf-se { stroke: var(--tf-e); }
.tf-faint { opacity: 0.55; }
.tf-dim { opacity: 0.4; }

.tf-anim--spin { --tf-motion: tf-pulse; }
.table-token[data-shape="round"] .tf-anim--spin {
  --tf-motion: tf-spin;
  opacity: var(--tf-spin-opacity, 0.8);
  animation-timing-function: linear;
}
.tf-anim--pulse { --tf-motion: tf-pulse; }
.tf-anim--sway { --tf-motion: tf-sway; --tf-rest: 1; }
.tf-anim--twinkle { --tf-motion: tf-twinkle; }
.tf-anim--flicker { --tf-motion: tf-flicker; --tf-rest: 0.9; }
.tf-anim--rise { --tf-motion: tf-rise; --tf-rest: 0.5; animation-timing-function: linear; }
.tf-anim--breathe { --tf-motion: tf-breathe; --tf-rest: 0.3; }
.tf-anim--strike { --tf-motion: tf-strike; --tf-rest: 0.15; animation-timing-function: linear; }

.table-token[data-frame="vine"] {
  --tf-a: color-mix(in srgb, var(--token-color) 22%, #4f8a3c);
  --tf-b: color-mix(in srgb, var(--token-color) 15%, #2f4a22);
  --tf-c: var(--token-color);
  --tf-duration: 7s;
}

.table-token[data-shape="square"][data-frame="vine"],
.table-token[data-shape="square"][data-frame="thorns"] {
  --tf-sway: 0.8deg;
}

.table-token[data-frame="wood"] {
  --tf-a: color-mix(in srgb, var(--token-color) 14%, #6b4424);
  --tf-b: color-mix(in srgb, var(--token-color) 10%, #b88a55);
  --tf-c: #24160b;
  --tf-d: var(--token-color);
  --tf-low: 0.05;
  --tf-high: 0.3;
  --tf-rest: 0.12;
  --tf-duration: 9s;
}

.table-token[data-frame="circuit"] {
  --tf-a: color-mix(in srgb, var(--token-color) 40%, #35d0c0);
  --tf-b: #0e1d22;
  --tf-c: color-mix(in srgb, var(--token-color) 25%, #c4fff7);
  --tf-duration: 26s;
}

.table-token[data-frame="circuit"] .tf-anim,
.table-token[data-frame="neon"] .tf-layer {
  filter: drop-shadow(0 0 1.5px var(--tf-a));
}

.table-token[data-frame="runestone"] {
  --tf-a: color-mix(in srgb, var(--token-color) 8%, #6f6b64);
  --tf-b: #2b2926;
  --tf-c: color-mix(in srgb, var(--token-color) 70%, #e9f7ff);
  --tf-low: 0.1;
  --tf-high: 0.95;
  --tf-rest: 0.5;
  --tf-duration: 6s;
}

.table-token[data-frame="runestone"] .tf-anim {
  filter: drop-shadow(0 0 1.2px var(--tf-c));
}

.table-token[data-frame="filigree"] {
  --tf-a: color-mix(in srgb, var(--token-color) 35%, #d8ae3c);
  --tf-b: #6f5212;
  --tf-c: #fff4cc;
  --tf-low: 0.1;
  --tf-high: 0.6;
  --tf-spin-opacity: 0.45;
  --tf-duration: 16s;
}

.table-token[data-frame="rivets"] {
  --tf-a: color-mix(in srgb, var(--token-color) 12%, #3d4248);
  --tf-b: #1a1d20;
  --tf-c: #8f98a1;
  --tf-d: #edf3f7;
  --tf-low: 0.05;
  --tf-high: 0.35;
  --tf-spin-opacity: 0.3;
  --tf-duration: 18s;
}

.table-token[data-frame="crystal"] {
  --tf-a: color-mix(in srgb, var(--token-color) 20%, rgb(190 235 255 / 0.6));
  --tf-b: color-mix(in srgb, var(--token-color) 25%, #bfe9ff);
  --tf-c: #f5fdff;
  --tf-duration: 4.5s;
}

.table-token[data-frame="crystal"] .tf-base {
  filter: drop-shadow(0 0 1.5px rgb(190 235 255 / 0.7));
}

.table-token[data-frame="ember"] {
  --tf-a: color-mix(in srgb, var(--token-color) 15%, #3a1408);
  --tf-b: color-mix(in srgb, var(--token-color) 35%, #ff7a1a);
  --tf-c: #ffd36b;
  --tf-duration: 2.6s;
}

.table-token[data-frame="ember"] .tf-layer {
  filter: drop-shadow(0 0 1.5px rgb(255 140 40 / 0.6));
}

.table-token[data-frame="arcane"] {
  --tf-a: var(--token-color);
  --tf-b: color-mix(in srgb, var(--token-color) 50%, #ffffff);
  --tf-c: color-mix(in srgb, var(--token-color) 70%, #ffffff);
  --tf-duration: 40s;
}

.table-token[data-frame="arcane"] .tf-layer {
  filter: drop-shadow(0 0 1.2px var(--token-color));
}

.table-token[data-frame="bone"] {
  --tf-a: color-mix(in srgb, var(--token-color) 8%, #e6dcc3);
  --tf-b: #4a4136;
  --tf-c: var(--token-color);
  --tf-duration: 6s;
}

.table-token[data-frame="coral"] {
  --tf-a: color-mix(in srgb, var(--token-color) 20%, #2f8f9d);
  --tf-b: color-mix(in srgb, var(--token-color) 25%, #ff7f6b);
  --tf-c: color-mix(in srgb, var(--token-color) 20%, #f2a65a);
  --tf-d: #dcf7ff;
  --tf-duration: 5s;
}

.table-token[data-frame="celestial"] {
  --tf-a: color-mix(in srgb, var(--token-color) 14%, #1b2a5c);
  --tf-b: color-mix(in srgb, var(--token-color) 40%, #ffe9a8);
  --tf-c: #ffffff;
  --tf-duration: 5.5s;
}

.table-token[data-frame="thorns"] {
  --tf-a: color-mix(in srgb, var(--token-color) 12%, #6a5638);
  --tf-b: color-mix(in srgb, var(--token-color) 25%, #a3192e);
  --tf-c: color-mix(in srgb, var(--token-color) 20%, #e8506a);
  --tf-d: color-mix(in srgb, var(--token-color) 10%, #3c3122);
  --tf-duration: 8s;
}

.table-token[data-frame="gears"] {
  --tf-a: color-mix(in srgb, var(--token-color) 30%, #b5893b);
  --tf-b: #4a3412;
  --tf-c: color-mix(in srgb, var(--token-color) 25%, #8c6a2e);
  --tf-low: 0.5;
  --tf-high: 1;
  --tf-spin-opacity: 1;
  --tf-duration: 30s;
}

.table-token[data-frame="neon"] {
  --tf-a: color-mix(in srgb, var(--token-color) 85%, #ffffff);
  --tf-b: color-mix(in srgb, var(--token-color) 30%, #c04dff);
  --tf-c: #ffffff;
  --tf-d: rgb(0 0 0 / 0.55);
  --tf-duration: 6s;
}

.table-token[data-frame="stainedglass"] {
  --tf-a: var(--token-color);
  --tf-b: color-mix(in srgb, var(--token-color) 25%, #e3a93a);
  --tf-c: color-mix(in srgb, var(--token-color) 25%, #2f4fb8);
  --tf-d: #16120e;
  --tf-e: #fff6dc;
  --tf-low: 0.05;
  --tf-high: 0.3;
  --tf-spin-opacity: 0.28;
  --tf-duration: 24s;
}

.table-token[data-frame="stainedglass"] .tf-glass {
  stroke-opacity: 0.85;
}

.table-token[data-frame="rope"] {
  --tf-a: color-mix(in srgb, var(--token-color) 15%, #b8975e);
  --tf-b: #5b4325;
  --tf-c: #efdcae;
  --tf-d: var(--token-color);
  --tf-sway: 4deg;
  --tf-duration: 6s;
}

.table-token[data-frame="feathers"] {
  --tf-a: color-mix(in srgb, var(--token-color) 30%, #eee4d2);
  --tf-b: color-mix(in srgb, var(--token-color) 20%, #4a2f1e);
  --tf-c: #fffaf0;
  --tf-d: #2a1d14;
  --tf-e: var(--token-color);
  --tf-sway: 1.2deg;
  --tf-duration: 7s;
}

.table-token[data-frame="scales"] {
  --tf-a: color-mix(in srgb, var(--token-color) 45%, #2e6a4c);
  --tf-b: #0d1a14;
  --tf-c: color-mix(in srgb, var(--token-color) 30%, #dfffe9);
  --tf-low: 0.1;
  --tf-high: 0.8;
  --tf-rest: 0.4;
  --tf-duration: 5s;
}

.table-token[data-frame="hexshield"] {
  --tf-a: color-mix(in srgb, var(--token-color) 30%, rgb(70 210 255 / 0.2));
  --tf-b: color-mix(in srgb, var(--token-color) 35%, #62e4ff);
  --tf-c: color-mix(in srgb, var(--token-color) 20%, rgb(220 252 255 / 0.55));
  --tf-duration: 4s;
}

.table-token[data-frame="hexshield"] .tf-layer {
  filter: drop-shadow(0 0 1.2px var(--tf-b));
}

.table-token[data-frame="tentacles"] {
  --tf-a: color-mix(in srgb, var(--token-color) 30%, #6a3d7a);
  --tf-b: #241327;
  --tf-c: color-mix(in srgb, var(--token-color) 20%, #e8bfe6);
  --tf-d: rgb(12 0 18 / 0.6);
  --tf-sway: 3deg;
  --tf-duration: 9s;
}

.table-token[data-shape="square"][data-frame="tentacles"],
.table-token[data-shape="square"][data-frame="rope"],
.table-token[data-shape="square"][data-frame="web"] {
  --tf-sway: 0.8deg;
}

.table-token[data-frame="chains"] {
  --tf-a: color-mix(in srgb, var(--token-color) 12%, #8a9096);
  --tf-b: #1b1e21;
  --tf-c: #f3f7fa;
  --tf-low: 0.1;
  --tf-high: 0.85;
  --tf-rest: 0.4;
  --tf-duration: 7s;
}

.table-token[data-frame="lightning"] {
  --tf-a: color-mix(in srgb, var(--token-color) 15%, rgb(24 30 56 / 0.6));
  --tf-b: color-mix(in srgb, var(--token-color) 45%, #8fc8ff);
  --tf-c: #ffffff;
  --tf-duration: 3.4s;
}

.table-token[data-frame="lightning"] .tf-anim {
  filter: drop-shadow(0 0 1.5px var(--tf-b));
}

.table-token[data-frame="radiant"] {
  --tf-a: color-mix(in srgb, var(--token-color) 25%, #f0c24a);
  --tf-b: color-mix(in srgb, var(--token-color) 15%, #9a6a14);
  --tf-c: #fff6d0;
  --tf-low: 0.2;
  --tf-high: 0.8;
  --tf-spin-opacity: 0.75;
  --tf-duration: 50s;
}

.table-token[data-frame="radiant"] .tf-base {
  filter: drop-shadow(0 0 1.5px color-mix(in srgb, var(--tf-a) 70%, transparent));
}

.table-token[data-frame="web"] {
  --tf-a: color-mix(in srgb, var(--token-color) 12%, rgb(236 240 245 / 0.8));
  --tf-b: color-mix(in srgb, var(--token-color) 25%, #2a2622);
  --tf-c: var(--token-color);
  --tf-d: #ffffff;
  --tf-sway: 5deg;
  --tf-duration: 8s;
}

@keyframes tf-spin {
  to {
    transform: rotate(1turn);
  }
}

@keyframes tf-pulse {
  0%,
  100% {
    opacity: var(--tf-low, 0.25);
  }

  50% {
    opacity: var(--tf-high, 0.9);
  }
}

@keyframes tf-sway {
  0%,
  100% {
    transform: rotate(calc(var(--tf-sway, 1.6deg) * -1));
  }

  50% {
    transform: rotate(var(--tf-sway, 1.6deg));
  }
}

@keyframes tf-twinkle {
  0%,
  100% {
    opacity: 0.15;
    transform: scale(0.995);
  }

  45% {
    opacity: 1;
    transform: scale(1.005);
  }

  65% {
    opacity: 0.7;
  }
}

@keyframes tf-flicker {
  0%,
  100% {
    opacity: 0.9;
    transform: scale(1);
  }

  9% {
    opacity: 0.55;
  }

  11% {
    opacity: 1;
    transform: scale(1.01);
  }

  45% {
    opacity: 0.7;
    transform: scale(0.995);
  }

  48% {
    opacity: 1;
  }

  72% {
    opacity: 0.8;
    transform: scale(1.008);
  }
}

@keyframes tf-rise {
  0% {
    opacity: 0;
    transform: translateY(3%);
  }

  30% {
    opacity: 0.9;
  }

  100% {
    opacity: 0;
    transform: translateY(-5%);
  }
}

/* Lightning: two quick strikes, then about three dark seconds (never three flashes a second). */
@keyframes tf-strike {
  0%,
  16%,
  100% {
    opacity: 0.15;
  }

  3% {
    opacity: 1;
  }

  6% {
    opacity: 0.35;
  }

  9% {
    opacity: 0.95;
  }
}

@keyframes tf-breathe {
  0%,
  100% {
    opacity: 0.08;
    transform: scale(0.99);
  }

  50% {
    opacity: 0.45;
    transform: scale(1.01);
  }
}

.table-world.is-moving .tf-anim {
  animation-play-state: paused;
}

.table-root.is-effects-reduced .tf-anim {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .tf-anim {
    animation: none;
  }
}

/* The "Appearance" tab of the settings (LooksPanel): collapsible sections, each with its title
   and the current choice in the summary, a one-line hint and a grid of uniform radio tiles (a
   small picture over the name). Frames, cursors and pings share the tile. */
.table-looks {
  display: flex;
  flex-direction: column;
}

.table-looks__section + .table-looks__section {
  border-top: 1px solid rgba(var(--table-white-rgb), 0.08);
}

.table-looks__summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 2px;
  list-style: none;
  cursor: pointer;
}

.table-looks__summary::-webkit-details-marker {
  display: none;
}

/* A chevron that turns when the section opens. */
.table-looks__summary::after {
  width: 7px;
  height: 7px;
  margin: 0 3px 3px 0;
  border-right: 1.5px solid var(--table-muted);
  border-bottom: 1.5px solid var(--table-muted);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
  content: "";
}

.table-looks__section[open] > .table-looks__summary::after {
  margin-bottom: -2px;
  transform: rotate(225deg);
}

.table-looks__summary:focus-visible {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.table-looks__title {
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.table-looks__current {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  color: var(--table-text);
  font-size: 12px;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-looks__dot {
  flex: none;
  width: 12px;
  height: 12px;
  background: var(--swatch);
  border-radius: 50%;
  box-shadow: 0 0 0 1.5px rgba(var(--table-black-rgb), 0.5);
}

.table-looks__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 0 12px;
}

.table-looks__hint {
  margin: 0;
  color: var(--table-muted);
  font-size: 12px;
}

.table-looks__tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
  gap: 5px;
}

.table-looks__tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
  padding: 4px 2px 5px;
  background: rgba(var(--table-white-rgb), 0.04);
  border: 1px solid rgba(var(--table-white-rgb), 0.1);
  border-radius: 8px;
  cursor: pointer;
}

/* The radio stays focusable for the keyboard but the tile shows the state. */
.table-looks__tile input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.table-looks__tile:has(input:checked) {
  background: rgba(var(--table-accent-rgb), 0.16);
  border-color: var(--table-accent);
}

.table-looks__tile:has(input:focus-visible) {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
}

.table-looks__pic {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 40px;
  overflow: hidden;
}

.table-looks__pic .table-token {
  position: relative;
  width: 28px;
  height: 28px;
  --token-width: 2.5px;
}

/* A ping, low in the picture so beacons and arrow drops have room; still unless just chosen. */
.table-looks__pic .table-ping {
  top: 72%;
  left: 50%;
  transform: scale(0.42);
}

.table-looks__pic:not(.is-playing) .table-ping,
.table-looks__pic:not(.is-playing) .table-ping * {
  animation-delay: -0.6s;
  animation-play-state: paused;
}

/* The feather is still high above the picture then: show it as it lands. */
.table-looks__pic:not(.is-playing) .table-ping--feather .table-ping__shape {
  animation-delay: -1.6s;
}

.table-looks__pic .table-cursor {
  top: 50%;
  left: 50%;
}

/* Up to two lines, so longer names ("Gilded filigree") stay whole. */
.table-looks__name {
  display: -webkit-box;
  max-width: 100%;
  overflow: hidden;
  font-size: 11px;
  line-height: 1.2;
  text-align: center;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

/* The user's colour: "Automatic", the palette and a custom colour as round swatches. */
.table-looks__colors {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(28px, 1fr));
  gap: 6px;
}

.table-looks__swatch {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  background: var(--swatch, transparent);
  border: 2px solid rgba(var(--table-white-rgb), 0.15);
  border-radius: 50%;
  cursor: pointer;
}

.table-looks__swatch input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.table-looks__swatch:has(input:checked) {
  border-color: var(--table-text);
  box-shadow: 0 0 0 2px var(--table-accent);
}

.table-looks__swatch:has(input:focus-visible) {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
}

.table-looks__swatch-text {
  color: rgba(var(--table-black-rgb), 0.75);
  font-size: 11px;
  font-weight: 700;
}

/* The custom swatch is the colour input itself, showing the custom colour. */
.table-looks__custom-color {
  width: 100%;
  height: 100%;
  padding: 0;
  overflow: hidden;
  background: conic-gradient(#ff6b6b, #feca57, #4cd964, #3dd6d0, #6c8cff, #c77dff, #ff6b6b);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}

.table-looks__swatch.is-custom:has(input:checked) .table-looks__custom-color {
  background: none;
}

.table-looks__custom-color::-webkit-color-swatch-wrapper {
  padding: 0;
}

.table-looks__custom-color::-webkit-color-swatch {
  border: 0;
  border-radius: 50%;
}

.table-looks__custom-color::-moz-color-swatch {
  border: 0;
  border-radius: 50%;
}

.table-looks__swatch.is-custom:not(:has(input:checked)) .table-looks__custom-color::-webkit-color-swatch {
  opacity: 0;
}

.table-looks__swatch.is-custom:not(:has(input:checked)) .table-looks__custom-color::-moz-color-swatch {
  opacity: 0;
}

.table-looks__status {
  min-height: 1.2em;
  margin: 0;
  color: var(--table-muted);
  font-size: 12px;
}

.table-looks__status[data-state="error"] {
  color: var(--bs-danger, #dc3545);
}

/* ---- Image frames (objects/imageFrames.js) --------------------------------------------------
   A frame the GM puts around an image or battlemap: one layer, the last child of the image's
   content, so it lies above the image, its effect and its grid. "inner" frames cover the image's
   edge, "outer" ones lie around it; neither takes pointer input or changes the object's bounds.
   The layer carries --if-w/--if-h (image), --if-t (thickness) and --if-lw/--if-lh (layer) in
   world px; each frame sets its palette (--if-a…) on .if-<name>. Frames 6–15 have their own block
   at the end of this file. Motion is transform and opacity only; it pauses while the camera moves
   and stops with reduced motion or the "reduce effects" switch. */

.table-image-frame {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.table-image-frame[data-placement="outer"] {
  inset: calc(-1 * var(--if-t));
}

.table-image-frame > *,
.if-svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* A strip along one edge (edgeStrips), turned around its top left corner; it clips its content. */
.table-image-frame > .if-strip {
  right: auto;
  bottom: auto;
  overflow: hidden;
  transform-origin: 0 0;
}

.table-world.is-moving .table-image-frame,
.table-world.is-moving .table-image-frame *,
.table-world.is-moving .table-image-frame *::before,
.table-world.is-moving .table-image-frame *::after {
  animation-play-state: paused !important;
}

.table-root.is-effects-reduced .table-image-frame,
.table-root.is-effects-reduced .table-image-frame *,
.table-root.is-effects-reduced .table-image-frame *::before,
.table-root.is-effects-reduced .table-image-frame *::after {
  animation-play-state: paused !important;
}

@media (prefers-reduced-motion: reduce) {
  .table-image-frame,
  .table-image-frame *,
  .table-image-frame *::before,
  .table-image-frame *::after {
    animation-play-state: paused !important;
  }
}

/* Shared shading: the moulding's light from the top left, a dark rim. */
.table-image-frame .if-light {
  opacity: 0.1;
}

.table-image-frame .if-rim {
  stroke: rgba(0, 0, 0, 0.6);
}

/* 1 Wood: a carved dark moulding with its grain. */
.if-wood {
  --if-a: #5b3a21;
  --if-b: #7f5332;
  --if-c: #2a180c;
  --if-d: #a8774a;
}

.if-wood .if-grain {
  stroke: var(--if-c);
  stroke-opacity: 0.35;
}

/* 2 Gilded: an ornate gold moulding with corner flourishes; a glint runs around it now and then. */
.if-gilded {
  --if-a: #a67a28;
  --if-b: #d9b45a;
  --if-c: #4f3409;
  --if-d: #f6e3a0;
  --if-glint: rgba(255, 249, 222, 0.75);
}

.if-gilded .if-beads {
  stroke: var(--if-d);
  stroke-linecap: round;
  stroke-opacity: 0.85;
}

.if-gilded .if-flourish {
  stroke-linecap: round;
  stroke-linejoin: round;
}

.if-gilded .if-flourish-edge {
  stroke: var(--if-c);
}

.if-gilded .if-flourish-rose {
  fill: var(--if-d);
  stroke: var(--if-c);
}

.if-gilded .if-glint {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(3 * var(--if-t));
  height: 100%;
  background: linear-gradient(105deg, transparent 25%, var(--if-glint) 50%, transparent 75%);
  transform: translateX(-100%);
  animation: if-glint 14s ease-in-out infinite both;
}

.if-gilded .if-strip--right .if-glint {
  animation-delay: 2.4s;
}

.if-gilded .if-strip--bottom .if-glint {
  animation-delay: 4.8s;
}

.if-gilded .if-strip--left .if-glint {
  animation-delay: 7.2s;
}

/* Sweep along the strip (--if-len) in the first 17 % (2.4 s), then rest out of sight. */
@keyframes if-glint {
  0% {
    transform: translateX(-100%);
  }

  17%,
  100% {
    transform: translateX(var(--if-len));
  }
}

/* 3 Iron: dark bands with rivets and riveted corner plates. */
.if-iron {
  --if-a: #43484e;
  --if-b: #6c737b;
  --if-c: #1a1d21;
  --if-d: #9ca3ab;
}

.if-iron .if-plate {
  stroke: var(--if-c);
}

.if-iron .if-rivet-shadow {
  fill: rgba(0, 0, 0, 0.5);
}

/* 4 Neon: a glowing tube around the image with a backlight; it flickers now and then. */
.if-neon {
  --if-a: #ff4fd8;
  --if-b: #fff0fb;
  --if-cap: #2b2b33;
}

.if-neon .if-neon-glass {
  stroke: var(--if-a);
  stroke-linecap: round;
}

.if-neon .if-neon-core {
  stroke: var(--if-b);
  stroke-linecap: round;
  stroke-opacity: 0.9;
}

.if-neon .if-neon-cap {
  fill: var(--if-cap);
}

.if-neon .if-neon-tube,
.if-neon .if-neon-backlight {
  animation: if-neon-flicker 9s linear infinite;
}

@keyframes if-neon-flicker {
  0%,
  78%,
  80.5%,
  83%,
  100% {
    opacity: 1;
  }

  79% {
    opacity: 0.45;
  }

  81.5% {
    opacity: 0.7;
  }

  82% {
    opacity: 0.3;
  }
}

/* 5 Instant photo: a paper border, wider at the bottom, and its shadow. Pure CSS. */
.if-polaroid {
  --if-a: #f1ede3;
}

.if-polaroid .if-polaroid__paper {
  /* The paper reaches two thicknesses below the layer for the wide bottom margin. */
  height: auto;
  bottom: calc(-2 * var(--if-t));
  border: var(--if-t) solid var(--if-a);
  border-bottom-width: calc(3 * var(--if-t));
  box-sizing: border-box;
  box-shadow:
    0 calc(0.4 * var(--if-t)) calc(1.4 * var(--if-t)) rgba(0, 0, 0, 0.55),
    inset 0 0 0 max(1px, calc(0.05 * var(--if-t))) rgba(0, 0, 0, 0.2),
    inset 0 calc(0.08 * var(--if-t)) calc(0.2 * var(--if-t)) rgba(0, 0, 0, 0.3);
}

/* ---- End of image frames (set 1) ----------------------------------------------------------- */

/* ---- Image frames, set 3 (objects/imageFramesSet3.js): the quiet ones -------------------------
   TV backlight, cracked glass, vignette, mat board and floating. Low contrast and thin; only the
   backlight moves (a slow breath, opacity only) and the shared rule above pauses it. */

/* 16 TV backlight: a small canvas of the image's edge colours, stretched around it. */
.if-ambilight .if-ambilight__glow {
  opacity: 0.9;
  animation: if-ambilight-breathe 11s ease-in-out infinite;
}

@keyframes if-ambilight-breathe {
  0%,
  100% {
    opacity: 0.9;
  }

  50% {
    opacity: 0.62;
  }
}

/* 17 Cracked glass: fine light cracks with a dark edge from one corner (chosen per image), a faint glare. */
.if-crackedglass {
  --if-crack: rgba(255, 255, 255, 0.55);
  --if-crack-shade: rgba(0, 0, 0, 0.3);
  --if-glare: rgba(255, 255, 255, 0.09);
}

.if-crackedglass .if-crackedglass__pane {
  overflow: hidden;
}

.if-crackedglass .if-crack,
.if-crackedglass .if-crack-shade {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.if-crackedglass .if-crack {
  stroke: var(--if-crack);
}

.if-crackedglass .if-crack-shade {
  stroke: var(--if-crack-shade);
}

.if-crackedglass .if-crack-impact {
  fill: var(--if-crack);
  opacity: 0.5;
}

/* 18 Vignette: a soft dark falloff towards the corners, like an old lens. */
.if-vignette {
  --if-a: rgba(0, 0, 0, 0.5);
}

.if-vignette .if-vignette__shade {
  background: radial-gradient(ellipse farthest-corner at 50% 50%, transparent 48%, rgba(0, 0, 0, 0.12) 68%, var(--if-a) 100%);
}

/* 19 Mat board: a pale board reaching 1.8 thicknesses past the image, a bevelled cut at the
   opening and a soft shadow under the board. */
.if-passepartout {
  --if-a: #ddd6c6;
  --if-b: #ece6da;
  --if-bevel-lit: #faf7f0;
  --if-bevel-shade: #cfc6b3;
}

.if-passepartout .if-passepartout__shadow {
  box-shadow: 0 calc(0.25 * var(--if-t)) calc(min(1.2 * var(--if-t), 120px)) rgba(0, 0, 0, 0.45);
}

.if-passepartout .if-bevel-lit {
  fill: var(--if-bevel-lit);
}

.if-passepartout .if-bevel-shade {
  fill: var(--if-bevel-shade);
}

.if-passepartout .if-mat-edge {
  stroke: rgba(0, 0, 0, 0.25);
}

/* 20 Floating: the image lifts off the table, layered shadows under it and a faint light rim. */
.if-floating .if-floating__lift {
  top: var(--if-t);
  left: var(--if-t);
  width: var(--if-w);
  height: var(--if-h);
  box-shadow:
    0 calc(0.12 * var(--if-t)) calc(0.25 * var(--if-t)) rgba(0, 0, 0, 0.3),
    0 calc(0.5 * var(--if-t)) calc(min(1.2 * var(--if-t), 100px)) rgba(0, 0, 0, 0.3),
    0 calc(1.2 * var(--if-t)) calc(min(2.6 * var(--if-t), 180px)) rgba(0, 0, 0, 0.25),
    inset 0 0 0 max(1px, calc(0.025 * var(--if-t))) rgba(255, 255, 255, 0.16),
    inset 0 max(1px, calc(0.04 * var(--if-t))) 0 rgba(255, 255, 255, 0.2);
}

/* ---- End of image frames (set 3) ----------------------------------------------------------- */

/* ---- Audio and video -------------------------------------------------------------------- */

.table-icon {
  display: block;
  width: 18px;
  height: 18px;
}

.table-audio {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 4px;
  padding: 8px 12px 6px;
  overflow: hidden;
  background:
    radial-gradient(ellipse 80% 120% at 0% 0%, rgba(var(--table-accent-rgb), 0.22), transparent 70%),
    rgba(var(--table-panel-rgb), 0.94);
  border: 1px solid rgba(var(--table-accent-rgb), 0.35);
  border-radius: 10px;
}

.table-audio audio {
  display: none;
}

.table-audio__header {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.table-audio__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  color: var(--table-accent);
  background: rgba(var(--table-accent-rgb), 0.15);
  border-radius: 50%;
}

.is-playing .table-audio__icon {
  box-shadow: 0 0 0 0 rgba(var(--table-accent-rgb), 0.6);
  animation: table-pulse 1.8s ease-out infinite;
}

.table-audio__title {
  overflow: hidden;
  font-size: 15px;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-video {
  overflow: hidden;
  background: #000;
}

.table-video__media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}

.table-video__controls {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 18px 10px 6px;
  background: linear-gradient(transparent, rgba(var(--table-black-rgb), 0.8));
  opacity: 0;
  transition: opacity 0.15s ease;
}

.table-object:hover .table-video__controls,
.table-object.is-selected .table-video__controls,
.table-video__controls:focus-within {
  opacity: 1;
}

.table-media-controls {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--table-text);
  font-size: 12px;
}

.table-media-controls__row {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
}

.table-media-controls__button {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  color: inherit;
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

.table-media-controls__button:hover:not(:disabled),
.table-media-controls__button:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.22);
  outline: none;
}

.table-media-controls__button.is-active {
  color: var(--table-accent);
}

.table-media-controls__button:disabled {
  cursor: default;
  opacity: 0.45;
}

.table-media-controls__time {
  flex: 1;
  min-width: 0;
  padding: 0 6px;
  overflow: hidden;
  color: var(--table-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.table-media-controls input[type="range"] {
  min-width: 0;
  height: 14px;
  margin: 0;
  accent-color: var(--table-accent);
  cursor: pointer;
}

.table-media-controls input[type="range"]:disabled {
  cursor: default;
}

.table-media-controls__progress {
  width: 100%;
}

.table-media-controls__volume {
  flex: 0 1 70px;
}

@keyframes table-pulse {
  70% {
    box-shadow: 0 0 0 10px rgba(var(--table-accent-rgb), 0);
  }

  100% {
    box-shadow: 0 0 0 0 rgba(var(--table-accent-rgb), 0);
  }
}

/* ---- Selection and handles (screen space, constant size) -------------------------------- */

.table-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
}

.table-selection {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 50% 50%;
  border: 1.5px solid var(--table-accent);
  box-shadow:
    0 0 0 1px rgba(var(--table-black-rgb), 0.35),
    0 0 14px rgba(var(--table-accent-rgb), 0.45);
  box-sizing: border-box;
}

.table-selection.is-readonly {
  border-style: dashed;
  border-color: rgba(var(--table-white-rgb), 0.55);
  box-shadow: none;
}

.table-handle {
  position: absolute;
  display: none;
  width: var(--table-handle-size);
  height: var(--table-handle-size);
  margin: calc(var(--table-handle-size) / -2) 0 0 calc(var(--table-handle-size) / -2);
  pointer-events: auto;
  background: #fff;
  border: 1.5px solid var(--table-accent);
  border-radius: 2px;
  box-shadow: 0 1px 3px rgba(var(--table-black-rgb), 0.5);
  box-sizing: border-box;
  touch-action: none;
}

/* A larger invisible hit area around each handle. */
.table-handle::before {
  content: "";
  position: absolute;
  inset: -6px;
}

.table-selection.has-handles .table-handle {
  display: block;
}

.table-selection.has-handles.is-small .table-handle--n,
.table-selection.has-handles.is-small .table-handle--e,
.table-selection.has-handles.is-small .table-handle--s,
.table-selection.has-handles.is-small .table-handle--w {
  display: none;
}

.table-handle--nw { top: 0; left: 0; }
.table-handle--n { top: 0; left: 50%; }
.table-handle--ne { top: 0; left: 100%; }
.table-handle--e { top: 50%; left: 100%; }
.table-handle--se { top: 100%; left: 100%; }
.table-handle--s { top: 100%; left: 50%; }
.table-handle--sw { top: 100%; left: 0; }
.table-handle--w { top: 50%; left: 0; }

.table-handle--rotate {
  top: -28px;
  left: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: var(--table-accent);
  border-color: #fff;
  cursor: grab;
}

/* Stem between the rotate handle and the frame. */
.table-handle--rotate::after {
  content: "";
  position: absolute;
  top: 12px;
  left: 50%;
  width: 1.5px;
  height: 16px;
  margin-left: -0.75px;
  background: var(--table-accent);
}

.table-marquee {
  position: absolute;
  top: 0;
  left: 0;
  border: 1px solid rgba(var(--table-accent-rgb), 0.9);
  background: rgba(var(--table-accent-rgb), 0.12);
}

/* ---- Pings (Ping.js) -------------------------------------------------------------------- */

/* Screen-space layer above the world; each ping is placed by transform only. */
.table-pings {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.table-ping,
.table-ping-arrow {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  animation: table-ping-fade 2.6s ease-in forwards;
}

.table-ping__ring,
.table-ping__dot {
  position: absolute;
  border-radius: 50%;
}

.table-ping__ring {
  top: -36px;
  left: -36px;
  width: 72px;
  height: 72px;
  border: 3px solid var(--ping-color);
  box-shadow: 0 0 12px var(--ping-color);
  opacity: 0;
  animation: table-ping-ripple 1.1s ease-out 2;
}

.table-ping__ring + .table-ping__ring {
  animation-delay: 0.35s;
}

.table-ping__dot {
  top: -6px;
  left: -6px;
  width: 12px;
  height: 12px;
  background: var(--ping-color);
  box-shadow: 0 0 0 2px rgba(var(--table-black-rgb), 0.5);
}

.table-ping__label {
  position: absolute;
  top: 18px;
  left: 0;
  max-width: 160px;
  padding: 1px 7px;
  overflow: hidden;
  color: var(--table-text);
  font-size: 12px;
  white-space: nowrap;
  text-overflow: ellipsis;
  background: rgba(var(--table-panel-rgb), 0.85);
  border-left: 3px solid var(--ping-color);
  border-radius: 4px;
  transform: translateX(-50%);
}

/* The arrow points from the viewport centre towards the ping (--ping-angle). */
.table-ping-arrow__head {
  position: absolute;
  top: -11px;
  left: -11px;
  width: 22px;
  height: 22px;
  background: var(--ping-color);
  clip-path: polygon(100% 50%, 0 0, 25% 50%, 0 100%);
  filter: drop-shadow(0 0 4px rgba(var(--table-black-rgb), 0.6));
  transform: rotate(var(--ping-angle, 0rad));
  animation: table-ping-nudge 0.55s ease-in-out 4 alternate;
}

.table-ping-arrow .table-ping__label {
  top: -9px;
  left: 18px;
  transform: none;
}

.table-ping-arrow.is-right .table-ping__label {
  right: 18px;
  left: auto;
}

.table-ping-arrow.is-bottom .table-ping__label {
  top: auto;
  bottom: -9px;
}

/* Rulers: the ruler tool's line, everyone else's and a dragged token's trail (Ruler.js). Thin,
   in the measurer's ping colour, with a dark halo so they read on any battlemap. */
.table-rulers {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.table-rulers__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.table-ruler {
  transition: opacity 0.3s ease-out;
}

.table-ruler__halo {
  stroke: rgba(var(--table-black-rgb), 0.45);
  stroke-width: 4.5px;
  stroke-linecap: round;
}

.table-ruler__line {
  stroke: var(--ruler-color);
  stroke-width: 1.5px;
  stroke-linecap: round;
}

.table-ruler.is-dashed .table-ruler__line {
  stroke-dasharray: 6px 5px;
}

.table-ruler__start {
  fill: rgba(var(--table-panel-rgb), 0.85);
  stroke: var(--ruler-color);
  stroke-width: 1.5px;
}

.table-ruler__end {
  fill: var(--ruler-color);
}

.table-ruler.is-fading,
.table-ruler__label.is-fading {
  opacity: 0;
}

.table-ruler__label {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  gap: 6px;
  align-items: baseline;
  padding: 1px 8px 2px;
  color: var(--table-text);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  white-space: nowrap;
  background: rgba(var(--table-panel-rgb), 0.82);
  border: 1px solid color-mix(in srgb, var(--ruler-color) 55%, transparent);
  border-radius: 999px;
  translate: 0 -50%;
  transition: opacity 0.3s ease-out;
}

.table-ruler__label.is-left {
  translate: -100% -50%;
}

.table-ruler__label[hidden] {
  display: none;
}

.table-ruler__value:empty,
.table-ruler__name:empty {
  display: none;
}

.table-ruler__name {
  max-width: 140px;
  overflow: hidden;
  color: var(--table-muted);
  text-overflow: ellipsis;
}

/* After release the line lingers with a quiet offer to keep it as a measure. */
.table-ruler__label.has-keep {
  pointer-events: auto;
}

.table-ruler__keep {
  padding: 0 0 0 7px;
  color: var(--ruler-color);
  font: inherit;
  background: none;
  border: 0;
  border-left: 1px solid rgba(var(--table-white-rgb), 0.15);
  cursor: pointer;
}

.table-ruler__keep:hover,
.table-ruler__keep:focus-visible {
  text-decoration: underline;
  outline: none;
}

.table-viewport.is-measuring,
.table-viewport.is-measuring .table-object,
.table-viewport.is-measuring .table-object * {
  cursor: crosshair !important;
}

@media (prefers-reduced-motion: reduce) {
  .table-ruler,
  .table-ruler__label {
    transition: none;
  }
}

@keyframes table-ping-ripple {
  from {
    opacity: 1;
    transform: scale(0.15);
  }

  to {
    opacity: 0;
    transform: scale(1.5);
  }
}

@keyframes table-ping-fade {
  0%,
  75% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@keyframes table-ping-nudge {
  to {
    translate: calc(cos(var(--ping-angle, 0rad)) * 6px) calc(sin(var(--ping-angle, 0rad)) * 6px);
  }
}

/* Ping styles (Ping.js `pingElement`): the parts sit around the spot, which is the 0×0 box of
   .table-ping; SVG shapes use a viewBox of -40 -80 80 120 at 1 px per unit, so their (0 0) is
   the spot too. Everything animates transform and opacity only and ends within PING_MS. */
.table-ping__shape,
.table-ping__circle {
  position: absolute;
  top: -80px;
  left: -40px;
  width: 80px;
  height: 120px;
  overflow: visible;
  fill: var(--ping-color);
  stroke: var(--ping-color);
  transform-origin: 40px 80px;
  filter: drop-shadow(0 0 5px var(--ping-color)) drop-shadow(0 1px 2px rgba(var(--table-black-rgb), 0.6));
}

.table-ping__shape {
  stroke: none;
}

.table-ping__flash {
  position: absolute;
  top: -36px;
  left: -36px;
  width: 72px;
  height: 72px;
  background: radial-gradient(circle, rgba(var(--table-white-rgb), 0.9), var(--ping-color) 30%, transparent 70%);
  border-radius: 50%;
  opacity: 0;
  animation: table-ping-flash 0.7s ease-out;
}

.table-ping__ground {
  position: absolute;
  top: -10px;
  left: -30px;
  width: 60px;
  height: 20px;
  border: 2px solid var(--ping-color);
  border-radius: 50%;
  box-shadow: 0 0 10px var(--ping-color);
  opacity: 0;
  animation: table-ping-ripple 1.1s ease-out 2;
}

.table-ping--beacon .table-ping__pillar {
  position: absolute;
  top: -140px;
  left: -7px;
  width: 14px;
  height: 140px;
  background: linear-gradient(to top, var(--ping-color), transparent);
  border-radius: 7px 7px 3px 3px;
  box-shadow: 0 0 16px var(--ping-color);
  transform-origin: 50% 100%;
  animation: table-ping-pillar 2.4s ease-out forwards;
}

.table-ping__ray {
  position: absolute;
  top: -9px;
  left: -1.5px;
  width: 3px;
  height: 18px;
  background: var(--ping-color);
  border-radius: 2px;
  box-shadow: 0 0 6px var(--ping-color);
  opacity: 0;
  animation: table-ping-ray 0.9s ease-out 2;
}

.table-ping__ray:nth-child(odd) {
  animation-delay: 0.08s;
}

.table-ping__sight {
  position: absolute;
  top: -26px;
  left: -26px;
  width: 52px;
  height: 52px;
  border: 2.5px solid;
  border-color: var(--ping-color) transparent;
  border-radius: 50%;
  filter: drop-shadow(0 0 4px var(--ping-color));
  animation: table-ping-sight 2.2s ease-out forwards;
}

.table-ping__tick {
  position: absolute;
  top: -7px;
  left: -1.25px;
  width: 2.5px;
  height: 14px;
  background: var(--ping-color);
  box-shadow: 0 0 4px var(--ping-color);
  animation: table-ping-tick 0.6s ease-out forwards;
}

.table-ping--heart .table-ping__shape {
  animation: table-ping-beat 1.1s ease-in-out 2;
}

.table-ping__float {
  position: absolute;
  top: -8px;
  left: -5px;
  color: var(--ping-color);
  font-size: 11px;
  line-height: 1;
  opacity: 0;
  animation: table-ping-float 1.6s ease-out forwards;
  animation-delay: calc(var(--i) * 0.3s + 0.2s);
}

.table-ping__float::before {
  content: "♥";
}

.table-ping--runes .table-ping__circle {
  animation: table-ping-runes 2.4s ease-out forwards;
}

.table-ping__drop {
  position: absolute;
  top: -4px;
  left: -4px;
  width: 8px;
  height: 8px;
  background: var(--ping-color);
  border-radius: 50% 50% 50% 0;
  opacity: 0;
  animation: table-ping-drop 0.9s ease-out 2;
}

.table-ping--lightning .table-ping__shape {
  fill: color-mix(in srgb, var(--ping-color) 55%, #ffffff);
  transform-origin: 40px 0;
  animation: table-ping-strike 1.3s linear forwards;
}

.table-ping--lightning .table-ping__flash {
  animation-delay: 0.12s;
}

.table-ping--lightning .table-ping__ring {
  animation-delay: 0.15s;
}

.table-ping--arrow .table-ping__shape {
  animation: table-ping-arrow-drop 1.2s ease-in forwards;
}

.table-ping--arrow .table-ping__ground {
  animation-delay: 0.45s;
}

.table-ping--flame .table-ping__shape {
  transform-origin: 40px 89px;
  animation:
    table-ping-flame-in 0.35s ease-out,
    table-ping-flame 0.32s ease-in-out 0.35s infinite alternate;
}

.table-ping__core {
  fill: color-mix(in srgb, var(--ping-color) 35%, #fff4c0);
}

.table-ping__ember {
  position: absolute;
  top: -2px;
  left: calc((var(--i) - 2) * 6px - 2px);
  width: 4px;
  height: 4px;
  background: color-mix(in srgb, var(--ping-color) 50%, #ffd36b);
  border-radius: 50%;
  opacity: 0;
  animation: table-ping-ember 1.3s ease-out forwards;
  animation-delay: calc(var(--i) * 0.22s + 0.2s);
}

/* Shared light details: the compass's north point, the bell's gleam, the eye's glint. */
.table-ping__north {
  fill: color-mix(in srgb, var(--ping-color) 35%, #ffffff);
}

.table-ping__void {
  fill: rgba(var(--table-black-rgb), 0.82);
}

/* Compass rose: it spins in, overshoots and settles pointing north. */
.table-ping--compass .table-ping__circle {
  animation: table-ping-compass 1.5s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}

.table-ping--compass .table-ping__ring {
  animation-iteration-count: 1;
  animation-delay: 0.4s;
}

/* Portal: rim and whirl open, the whirl turns (the rotate property, apart from the scale of the
   opening), motes are drawn in, then it closes. */
.table-ping--portal .table-ping__portal {
  animation: table-ping-portal 2.4s ease-in-out forwards;
}

.table-ping--portal .table-ping__whirl {
  animation:
    table-ping-portal 2.4s ease-in-out forwards,
    table-ping-whirl 1.1s linear infinite;
}

.table-ping__mote {
  position: absolute;
  top: -2.5px;
  left: -2.5px;
  width: 5px;
  height: 5px;
  background: color-mix(in srgb, var(--ping-color) 45%, #ffffff);
  border-radius: 50%;
  box-shadow: 0 0 5px var(--ping-color);
  opacity: 0;
  rotate: calc(var(--i) * 360deg / var(--n));
  animation: table-ping-mote 0.9s ease-in 2;
  animation-delay: calc(var(--i) * 0.12s + 0.3s);
}

/* Bell: it swings on its knob while sound waves ripple out to both sides. */
.table-ping--bell .table-ping__shape {
  transform-origin: 40px 33px;
  animation: table-ping-bell 1.6s ease-in-out forwards;
}

.table-ping__wave {
  position: absolute;
  top: -42px;
  left: 4px;
  width: 22px;
  height: 30px;
  border: 2.5px solid transparent;
  border-right-color: var(--ping-color);
  border-radius: 50%;
  filter: drop-shadow(0 0 3px var(--ping-color)) drop-shadow(0 1px 1px rgba(var(--table-black-rgb), 0.6));
  opacity: 0;
  transform-origin: 0 50%;
  animation: table-ping-wave 0.9s ease-out forwards;
  animation-delay: calc(var(--i) * 0.28s + 0.1s);
}

.table-ping__wave--left {
  left: -26px;
  border-right-color: transparent;
  border-left-color: var(--ping-color);
  transform-origin: 100% 50%;
}

.table-ping--bell .table-ping__ground {
  animation-iteration-count: 1;
  animation-delay: 0.2s;
}

/* Eye: it opens, looks left and right, blinks once and closes. */
.table-ping--eye .table-ping__eye {
  transform-origin: 40px 50px;
  animation: table-ping-eye 2.4s ease-in-out forwards;
}

.table-ping__iris {
  transform-box: fill-box;
  transform-origin: center;
  animation: table-ping-look 2.4s ease-in-out forwards;
}

.table-ping--eye .table-ping__dot {
  opacity: 0.8;
}

/* Feather: it drifts down in a swaying fall, tips over as it lands and raises a soft puff. */
.table-ping--feather .table-ping__shape {
  transform-origin: 40px 80px;
  animation: table-ping-feather 1.7s ease-in-out forwards;
}

.table-ping__rachis {
  stroke: color-mix(in srgb, var(--ping-color) 40%, #ffffff);
}

.table-ping--feather .table-ping__ground {
  animation-iteration-count: 1;
  animation-delay: 1.5s;
}

.table-ping__puff {
  position: absolute;
  top: -3px;
  left: -3px;
  width: 6px;
  height: 6px;
  background: color-mix(in srgb, var(--ping-color) 50%, #ffffff);
  border-radius: 50%;
  opacity: 0;
  rotate: calc(var(--i) * 360deg / var(--n) + 45deg);
  animation: table-ping-puff 0.8s ease-out forwards;
  animation-delay: 1.55s;
}

/* With reduced motion: a still ring and dot that only fade with the ping. */
.table-ping--still .table-ping__ring {
  opacity: 0.85;
  animation: none;
}

@keyframes table-ping-flash {
  from {
    opacity: 1;
    transform: scale(0.2);
  }

  to {
    opacity: 0;
    transform: scale(1.6);
  }
}

@keyframes table-ping-compass {
  0% {
    opacity: 0;
    transform: rotate(-220deg) scale(0.3);
  }

  45% {
    opacity: 1;
    transform: rotate(14deg) scale(1.06);
  }

  62% {
    transform: rotate(-7deg) scale(1);
  }

  78% {
    transform: rotate(3deg);
  }

  100% {
    opacity: 1;
    transform: rotate(0deg) scale(1);
  }
}

@keyframes table-ping-portal {
  0% {
    opacity: 0;
    scale: 0.1;
  }

  20% {
    opacity: 1;
    scale: 1.05;
  }

  30% {
    scale: 1;
  }

  78% {
    opacity: 1;
    scale: 1;
  }

  100% {
    opacity: 0;
    scale: 0.05;
  }
}

@keyframes table-ping-whirl {
  to {
    rotate: 360deg;
  }
}

@keyframes table-ping-mote {
  0% {
    opacity: 0;
    transform: translateY(-40px) scale(1.2);
  }

  25% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translateY(-3px) scale(0.3);
  }
}

@keyframes table-ping-bell {
  0% {
    opacity: 0;
    transform: rotate(0deg) translateY(-10px);
  }

  12% {
    opacity: 1;
    transform: rotate(24deg) translateY(0);
  }

  32% {
    transform: rotate(-20deg);
  }

  52% {
    transform: rotate(13deg);
  }

  72% {
    transform: rotate(-7deg);
  }

  88% {
    transform: rotate(3deg);
  }

  100% {
    opacity: 1;
    transform: rotate(0deg);
  }
}

@keyframes table-ping-wave {
  0% {
    opacity: 0;
    transform: scale(0.5);
  }

  25% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: scale(1.9);
  }
}

@keyframes table-ping-eye {
  0% {
    opacity: 0;
    transform: scaleY(0.05);
  }

  14% {
    opacity: 1;
    transform: scaleY(1);
  }

  56% {
    transform: scaleY(1);
  }

  61% {
    transform: scaleY(0.08);
  }

  66%,
  86% {
    transform: scaleY(1);
  }

  100% {
    opacity: 1;
    transform: scaleY(0.05);
  }
}

@keyframes table-ping-look {
  0%,
  16% {
    translate: 0 0;
  }

  30%,
  42% {
    translate: -6px 0;
  }

  52%,
  70% {
    translate: 6px 0;
  }

  82%,
  100% {
    translate: 0 0;
  }
}

@keyframes table-ping-feather {
  0% {
    opacity: 0;
    transform: translate(-16px, -96px) rotate(-32deg);
  }

  12% {
    opacity: 1;
  }

  28% {
    transform: translate(12px, -70px) rotate(26deg);
  }

  48% {
    transform: translate(-10px, -44px) rotate(-20deg);
  }

  68% {
    transform: translate(8px, -20px) rotate(14deg);
  }

  84% {
    transform: translate(-3px, -4px) rotate(-6deg);
  }

  /* It lands on its quill and tips over onto its side. */
  92% {
    transform: translate(0, 0) rotate(0deg);
  }

  100% {
    opacity: 1;
    transform: translate(0, 0) rotate(68deg);
  }
}

@keyframes table-ping-puff {
  0% {
    opacity: 0.9;
    transform: translateY(-2px) scale(0.6);
  }

  100% {
    opacity: 0;
    transform: translateY(-16px) scale(1.3);
  }
}

@keyframes table-ping-pillar {
  0% {
    opacity: 0;
    transform: scaleY(0);
  }

  18% {
    opacity: 1;
    transform: scaleY(1);
  }

  60% {
    opacity: 0.85;
  }

  100% {
    opacity: 0;
    transform: scaleY(1.05);
  }
}

@keyframes table-ping-ray {
  from {
    opacity: 1;
    transform: rotate(calc(var(--i) / var(--n) * 1turn)) translateY(-10px) scaleY(0.3);
  }

  to {
    opacity: 0;
    transform: rotate(calc(var(--i) / var(--n) * 1turn)) translateY(-46px) scaleY(1);
  }
}

@keyframes table-ping-sight {
  0% {
    opacity: 0;
    transform: scale(1.9) rotate(-90deg);
  }

  30% {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }

  100% {
    opacity: 1;
    transform: scale(1) rotate(45deg);
  }
}

@keyframes table-ping-tick {
  from {
    opacity: 0;
    transform: rotate(calc(var(--i) * 90deg)) translateY(-48px);
  }

  to {
    opacity: 1;
    transform: rotate(calc(var(--i) * 90deg)) translateY(-26px);
  }
}

@keyframes table-ping-beat {
  0% {
    transform: scale(0);
  }

  30% {
    transform: scale(1.25);
  }

  50% {
    transform: scale(0.95);
  }

  70% {
    transform: scale(1.12);
  }

  100% {
    transform: scale(1);
  }
}

@keyframes table-ping-float {
  0% {
    opacity: 0;
    transform: translate(0, 0) scale(0.6);
  }

  25% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translate(calc((var(--i) - 1) * 16px), -56px) scale(1.1);
  }
}

@keyframes table-ping-runes {
  0% {
    opacity: 0;
    transform: scale(0.3) rotate(-120deg);
  }

  25% {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }

  100% {
    opacity: 0.6;
    transform: scale(1.05) rotate(70deg);
  }
}

@keyframes table-ping-drop {
  from {
    opacity: 1;
    transform: rotate(calc(var(--i) / var(--n) * 1turn)) translateY(-6px) rotate(-45deg);
  }

  to {
    opacity: 0;
    transform: rotate(calc(var(--i) / var(--n) * 1turn)) translateY(-36px) rotate(-45deg) scale(0.4);
  }
}

@keyframes table-ping-strike {
  0% {
    opacity: 0;
    transform: scaleY(0);
  }

  12% {
    opacity: 1;
    transform: scaleY(1);
  }

  22% {
    opacity: 0.3;
  }

  30% {
    opacity: 1;
  }

  40% {
    opacity: 0.5;
  }

  50%,
  80% {
    opacity: 1;
    transform: scaleY(1);
  }

  100% {
    opacity: 0;
    transform: scaleY(1);
  }
}

@keyframes table-ping-arrow-drop {
  0% {
    opacity: 0;
    transform: translateY(-70px);
  }

  35% {
    opacity: 1;
    transform: translateY(0);
  }

  48% {
    transform: translateY(-12px);
  }

  60%,
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes table-ping-flame-in {
  from {
    opacity: 0;
    transform: scale(0.2);
  }
}

@keyframes table-ping-flame {
  from {
    transform: scale(1, 1) skewX(-2deg);
  }

  to {
    transform: scale(1.06, 0.92) skewX(3deg);
  }
}

@keyframes table-ping-ember {
  0% {
    opacity: 0;
    transform: translateY(0);
  }

  20% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translate(calc((var(--i) - 2) * 3px), -60px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .table-ping__ring {
    opacity: 0.8;
    animation: none;
  }

  .table-ping__ring + .table-ping__ring {
    display: none;
  }

  .table-ping-arrow__head {
    animation: none;
  }
}

.table-root.is-effects-reduced .table-ping-arrow__head {
  animation: none;
}

/* Fills under the cursor while a press is held for a ping (Table._startLongPress). It shows
   only after a moment, so ordinary clicks and drags never flash it. */
.table-press-ring {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

.table-press-ring svg {
  position: absolute;
  top: -20px;
  left: -20px;
  width: 40px;
  height: 40px;
  opacity: 0;
  transform: rotate(-90deg);
  animation: table-press-show 0.1s linear 0.15s forwards;
}

.table-press-ring circle {
  fill: none;
  stroke-width: 3.5;
}

.table-press-ring__track {
  stroke: rgba(var(--table-black-rgb), 0.45);
}

.table-press-ring__fill {
  stroke: var(--ping-color, var(--table-accent));
  stroke-linecap: round;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation: table-press-fill 0.5s linear forwards;
}

@keyframes table-press-show {
  to {
    opacity: 1;
  }
}

@keyframes table-press-fill {
  to {
    stroke-dashoffset: 0;
  }
}

.table-viewport.is-drop-target::after {
  content: "";
  position: absolute;
  inset: 12px;
  z-index: 6;
  pointer-events: none;
  border: 2px dashed rgba(var(--table-accent-rgb), 0.8);
  border-radius: 12px;
  background: rgba(var(--table-accent-rgb), 0.07);
}

.table-fit-rect {
  position: absolute;
  top: 0;
  left: 0;
  border: 1.5px solid var(--bs-warning, #ffc107);
  background: rgba(255, 193, 7, 0.15);
}

.table-viewport.is-fitting,
.table-viewport.is-fitting * {
  cursor: crosshair !important;
}

/* ---- Ghost cursors (GhostCursors.js, cursorStyles.js) ------------------------------------ */

/* Everyone else's pointers: above the objects, below the HUD and menus. Placed by transform. */
.table-ghost-cursors {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.table-cursor {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  transition: opacity 0.35s ease;
  will-change: transform;
}

.table-cursor.is-fading {
  opacity: 0;
}

.table-cursor__shape {
  position: absolute;
  overflow: visible;
  filter: drop-shadow(0 1px 2px rgba(var(--table-black-rgb), 0.55));
}

/* On the table the others' cursors stay in the background: the pointer and its trail are
   translucent, with a thin dark halo so they still read on light maps; the name chip is a
   little see-through too, its text solid. */
.table-ghost-cursors .table-cursor__shape {
  opacity: 0.65;
  filter:
    drop-shadow(0 0 1px rgba(var(--table-black-rgb), 0.55))
    drop-shadow(0 1px 2px rgba(var(--table-black-rgb), 0.35));
}

.table-ghost-cursors .table-cursor__name {
  background: color-mix(in srgb, var(--cursor-color) 70%, transparent);
  box-shadow: 0 1px 3px rgba(var(--table-black-rgb), 0.3);
}

.table-ghost-cursors .table-cursor-trail {
  filter: opacity(0.6);
}

.table-ghost-cursors .table-cursor-trail--sparkle {
  filter: opacity(0.6) drop-shadow(0 0 3px var(--cursor-color));
}

/* Shape parts: the user's colour, a pale edge drawn under it, dark ink and pale highlights. */
.table-cursor .c-fill {
  fill: var(--cursor-color);
}

.table-cursor .c-edge {
  stroke: rgba(var(--table-white-rgb), 0.95);
  stroke-width: 1.6;
  stroke-linejoin: round;
  paint-order: stroke;
}

.table-cursor .c-ink {
  fill: rgba(var(--table-black-rgb), 0.85);
}

.table-cursor .c-light {
  fill: rgba(var(--table-white-rgb), 0.9);
}

.table-cursor .c-ink-line,
.table-cursor .c-outline,
.table-cursor .c-stroke,
.table-cursor .c-outline-wide,
.table-cursor .c-ink-line-wide,
.table-cursor .c-stroke-wide {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.table-cursor .c-ink-line {
  stroke: rgba(var(--table-black-rgb), 0.5);
  stroke-width: 1.3;
}

.table-cursor .c-outline {
  stroke: rgba(var(--table-black-rgb), 0.6);
  stroke-width: 4;
}

.table-cursor .c-stroke {
  stroke: var(--cursor-color);
  stroke-width: 2;
}

.table-cursor .c-outline-wide {
  stroke: rgba(var(--table-white-rgb), 0.9);
  stroke-width: 5.5;
}

.table-cursor .c-ink-line-wide {
  stroke: rgba(var(--table-black-rgb), 0.85);
  stroke-width: 3;
}

.table-cursor .c-stroke-wide {
  stroke: var(--cursor-color);
  stroke-width: 3.2;
}

.table-cursor .c-glow {
  fill: var(--cursor-color);
  opacity: 0.4;
  filter: blur(2.5px);
  transform-box: fill-box;
  transform-origin: center;
  animation: table-cursor-pulse 1.8s ease-in-out infinite;
}

/* A dark edge under pale parts (a blade, the white of an eye). */
.table-cursor .c-dark-edge {
  stroke: rgba(var(--table-black-rgb), 0.7);
  stroke-width: 1.4;
  stroke-linejoin: round;
  paint-order: stroke;
}

/* Glass: a pale wash of the user's colour (a lens, a lantern's panes). */
.table-cursor .c-glass {
  fill: color-mix(in srgb, var(--cursor-color) 30%, rgba(var(--table-white-rgb), 0.35));
}

.table-cursor .c-glass-dark {
  fill: color-mix(in srgb, var(--cursor-color) 35%, rgba(var(--table-black-rgb), 0.55));
}

.table-cursor .c-flame {
  fill: color-mix(in srgb, var(--cursor-color) 25%, #fff1b8);
  transform-box: fill-box;
  transform-origin: 50% 90%;
}

.table-cursor .c-light-line {
  fill: none;
  stroke: rgba(var(--table-white-rgb), 0.9);
  stroke-width: 1.4;
  stroke-linecap: round;
}

.table-cursor .c-soft {
  fill-opacity: 0.85;
}

.table-cursor__flicker {
  animation: table-cursor-flicker 1.6s ease-in-out infinite;
}

.table-cursor__bob {
  animation: table-cursor-bob 2.6s ease-in-out infinite alternate;
}

.table-cursor__blink {
  transform-origin: 16px 16px;
  animation: table-cursor-blink 6s ease-in-out infinite;
}

.table-cursor__spin {
  animation: table-cursor-spin 7s linear infinite;
}

/* The name chip, beside the pointer; centred styles keep it clear of their shape. */
.table-cursor__name {
  position: absolute;
  top: 20px;
  left: 14px;
  max-width: 180px;
  padding: 1px 7px;
  overflow: hidden;
  color: var(--cursor-ink);
  font-size: 11px;
  font-weight: 600;
  line-height: 16px;
  white-space: nowrap;
  text-overflow: ellipsis;
  background: var(--cursor-color);
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(var(--table-black-rgb), 0.45);
}

.table-cursor--crosshair .table-cursor__name,
.table-cursor--orb .table-cursor__name,
.table-cursor--comet .table-cursor__name,
.table-cursor--rune .table-cursor__name,
.table-cursor--magnifier .table-cursor__name,
.table-cursor--lantern .table-cursor__name,
.table-cursor--wisp .table-cursor__name,
.table-cursor--eye .table-cursor__name {
  top: 12px;
  left: 16px;
}

.table-cursor--quill .table-cursor__name {
  top: 4px;
  left: 12px;
}

/* Trails: sparkles behind the wand, a glowing tail behind the comet. */
.table-cursor-trail {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  will-change: transform, opacity;
}

.table-cursor-trail--comet {
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  background: var(--cursor-color);
  border-radius: 50%;
  box-shadow: 0 0 8px 2px var(--cursor-color);
}

.table-cursor-trail--sparkle {
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  filter: drop-shadow(0 0 3px var(--cursor-color));
}

.table-cursor-trail--sparkle::before {
  position: absolute;
  inset: 0;
  background: var(--cursor-color);
  clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  animation: table-cursor-twinkle 0.7s ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * -0.13s);
  content: "";
}

@keyframes table-cursor-pulse {
  50% {
    opacity: 0.6;
    transform: scale(1.25);
  }
}

@keyframes table-cursor-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes table-cursor-flicker {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }

  30% {
    opacity: 0.8;
    transform: scale(0.92, 1.06);
  }

  60% {
    opacity: 0.95;
    transform: scale(1.04, 0.95);
  }
}

@keyframes table-cursor-bob {
  from {
    transform: translateY(1px);
  }

  to {
    transform: translateY(-1.5px);
  }
}

@keyframes table-cursor-blink {
  0%,
  92%,
  100% {
    transform: scaleY(1);
  }

  95% {
    transform: scaleY(0.1);
  }
}

@keyframes table-cursor-twinkle {
  from {
    transform: scale(0.6) rotate(0deg);
  }

  to {
    transform: scale(1.15) rotate(45deg);
  }
}

/* Still cursors without trails (GhostCursors.js also stops laying them). */
.table-root.is-effects-reduced .table-cursor .c-glow,
.table-root.is-effects-reduced .table-cursor__spin,
.table-root.is-effects-reduced .table-cursor__flicker,
.table-root.is-effects-reduced .table-cursor__bob,
.table-root.is-effects-reduced .table-cursor__blink,
.table-root.is-effects-reduced .table-cursor-trail--sparkle::before {
  animation: none;
}

.table-root.is-effects-reduced .table-cursor-trail {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .table-cursor .c-glow,
  .table-cursor__spin,
  .table-cursor__flicker,
  .table-cursor__bob,
  .table-cursor__blink,
  .table-cursor-trail--sparkle::before {
    animation: none;
  }

  .table-cursor-trail {
    display: none;
  }

  .table-cursor {
    transition: none;
  }
}

/* The Appearance tab's cursor switch. */
.table-looks__switch {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

/* ---- Panels ------------------------------------------------------------------------------ */

.table-panel {
  position: absolute;
  top: 64px;
  left: 16px;
  z-index: var(--table-z-panel);
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 220px;
  max-height: calc(100% - 96px);
  padding: 10px 12px 12px;
  overflow: auto;
  font-size: 13px;
  background: rgba(var(--table-panel-rgb), 0.88);
  border: 1px solid rgba(var(--table-accent-rgb), 0.3);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(var(--table-black-rgb), 0.45);
  backdrop-filter: blur(6px);
}

.table-panel,
.table-panel * {
  box-sizing: border-box;
}

.table-panel[hidden],
.table-panel [hidden] {
  display: none !important;
}

.table-panel__title {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--table-accent);
}

.table-panel__fields {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.table-panel__field {
  display: grid;
  grid-template-columns: 1fr 88px;
  align-items: center;
  gap: 8px;
  margin: 0;
  color: var(--table-muted);
}

.table-panel__field.is-check {
  grid-template-columns: 1fr auto;
}

.table-panel__field input {
  width: 100%;
  min-width: 0;
  color: var(--table-text);
  font: inherit;
  accent-color: var(--table-accent);
}

.table-panel__field input[type="number"] {
  padding: 2px 6px;
  background: rgba(var(--table-black-rgb), 0.3);
  border: 1px solid rgba(var(--table-white-rgb), 0.15);
  border-radius: 5px;
}

.table-panel__field input[type="color"] {
  height: 26px;
  padding: 0 2px;
  background: transparent;
  border: 1px solid rgba(var(--table-white-rgb), 0.15);
  border-radius: 5px;
}

.table-panel__field input[type="checkbox"] {
  width: 16px;
  height: 16px;
}

.table-panel__button {
  padding: 5px 10px;
  color: var(--table-text);
  font: inherit;
  background: rgba(var(--table-accent-rgb), 0.18);
  border: 1px solid rgba(var(--table-accent-rgb), 0.45);
  border-radius: 6px;
  cursor: pointer;
}

.table-panel__button:hover,
.table-panel__button:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.32);
  outline: none;
}

.table-panel__hint {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  color: var(--table-text);
  background: rgba(255, 193, 7, 0.1);
  border: 1px solid rgba(255, 193, 7, 0.45);
  border-radius: 6px;
}

/* Inspector on the right, token picker as a drawer on the left. */
.table-inspector {
  right: 16px;
  left: auto;
  width: 240px;
}

/* The token frame picker: "Default" and the 16 frames as small tokens, five to a row; the
   caption below names the choice. */
.table-inspector__frame-field {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}

.table-inspector__frames {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 4px;
}

.table-inspector__frame {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  background: rgba(var(--table-white-rgb), 0.04);
  border: 1px solid rgba(var(--table-white-rgb), 0.12);
  border-radius: 6px;
  cursor: pointer;
}

/* The radio stays focusable for the keyboard but the tile shows the state. */
.table-inspector__frame input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.table-inspector__frame:has(input:checked) {
  background: rgba(var(--table-accent-rgb), 0.16);
  border-color: var(--table-accent);
}

.table-inspector__frame:has(input:focus-visible) {
  outline: 2px solid var(--table-accent);
  outline-offset: 1px;
}

/* "Default" follows a user's choice: a dashed tile tells it apart from the frames. */
.table-inspector__frame.is-default {
  border-style: dashed;
}

.table-inspector__frame .table-token {
  position: relative;
  width: 24px;
  height: 24px;
  --token-width: 2px;
}

.table-inspector__frame-name {
  color: var(--table-muted);
  font-size: 12px;
}

/* A player's panel: only the look of the notes they control (Inspector.js, permissions.js). */
.table-inspector.is-player > :not(.table-inspector__head, .table-inspector__notes) {
  display: none;
}

/* A player who edits everything: all but visibility, lock, layer and controllers. */
.table-inspector.is-editor .table-inspector__gm {
  display: none;
}

.table-inspector__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* The bottom sheet's handle, phones only. */
.table-inspector__toggle {
  display: none;
}

/* Choices like "Typewritten page" need more room than a number: after a short label column,
   so the selects line up, the select takes the rest. */
.table-inspector .table-panel__field:has(> select) {
  grid-template-columns: 64px minmax(0, 1fr);
}

.table-panel__row {
  display: flex;
  gap: 6px;
}

.table-panel__row > * {
  flex: 1;
}

.table-panel__field.is-wide {
  grid-template-columns: 1fr;
  gap: 2px;
}

.table-panel__field input[type="text"],
.table-panel__field select,
.table-token-picker__search {
  width: 100%;
  min-width: 0;
  padding: 3px 6px;
  color: var(--table-text);
  font: inherit;
  background: rgba(var(--table-black-rgb), 0.3);
  border: 1px solid rgba(var(--table-white-rgb), 0.15);
  border-radius: 5px;
}

.table-panel__field select option,
.table-panel__field select optgroup {
  color: #111;
}

.table-panel__group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  margin: 2px 0 0;
  padding: 8px 0 0;
  border: 0;
  border-top: 1px solid rgba(var(--table-white-rgb), 0.1);
}

.table-panel__group legend {
  float: left;
  width: 100%;
  margin-bottom: 2px;
  padding: 0;
  color: var(--table-accent);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.table-panel__muted {
  color: var(--table-muted);
  font-size: 12px;
}

.table-token-picker {
  top: 64px;
  bottom: 80px;
  left: 16px;
  width: 260px;
  max-height: none;
}

.table-root.has-token-picker .table-map-panel,
.table-root.has-prop-picker .table-map-panel {
  left: 292px;
}

/* The prop picker: preview tiles in the token picker's drawer. */
.table-prop-picker__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 8px;
  min-height: 0;
  overflow: auto;
}

.table-prop-picker__tile {
  position: relative;
}

.table-prop-picker__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 100%;
  padding: 6px 4px 8px;
  color: var(--table-text);
  font: inherit;
  font-size: 12px;
  text-align: center;
  background: rgba(var(--table-white-rgb), 0.04);
  border: 1px solid rgba(var(--table-white-rgb), 0.1);
  border-radius: 8px;
  cursor: grab;
}

.table-prop-picker__item:hover,
.table-prop-picker__item:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.14);
  border-color: rgba(var(--table-accent-rgb), 0.55);
  outline: none;
}

.table-prop-picker__picture {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 1;
  overflow: hidden;
  background: radial-gradient(circle at 50% 60%, rgba(var(--table-white-rgb), 0.08), transparent 70%);
  border-radius: 6px;
}

.table-prop-picker__picture img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.table-prop-picker__picture img:not([src]) {
  visibility: hidden;
}

.table-prop-picker__picture.is-rendering {
  background-image: linear-gradient(100deg, transparent 30%, rgba(var(--table-white-rgb), 0.08) 50%, transparent 70%);
  background-size: 200% 100%;
  animation: table-prop-shimmer 1.2s linear infinite;
}

@keyframes table-prop-shimmer {
  to {
    background-position: -200% 0;
  }
}

/* A gentle turn of the picture on hover. */
.table-prop-picker__item:hover img {
  animation: table-prop-turn 2.4s ease-in-out infinite;
}

@keyframes table-prop-turn {
  0%,
  100% {
    transform: perspective(240px) rotateY(-14deg);
  }

  50% {
    transform: perspective(240px) rotateY(14deg);
  }
}

.table-prop-picker__name {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-prop-picker__info {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px !important;
  height: 22px !important;
  font-size: 13px !important;
}

@media (prefers-reduced-motion: reduce) {
  .table-prop-picker__item:hover img,
  .table-prop-picker__picture.is-rendering {
    animation: none;
  }
}

/* Credits of a prop's model: a popover (picker, context menu) and the settings' list. */
.table-credits-popover {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--table-z-menu);
  width: min(300px, calc(100% - 16px));
  max-height: none;
}

.table-credit {
  display: grid;
  gap: 2px;
  font-size: 13px;
}

.table-credit + .table-credit {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(var(--table-white-rgb), 0.08);
}

.table-credit__title {
  font-weight: 600;
}

.table-credit__by {
  color: var(--table-muted);
  font-weight: 400;
}

.table-credit__row {
  display: flex;
  gap: 6px;
  min-width: 0;
}

.table-credit__label {
  flex: none;
  min-width: 52px;
  color: var(--table-muted);
}

.table-credit a {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--table-accent);
}

.table-settings__credits {
  display: grid;
}

.table-token-picker__header {
  display: flex;
  align-items: center;
  gap: 4px;
}

.table-token-picker__header .table-panel__title {
  flex: 1;
}

.table-token-picker__icon-button {
  width: 26px;
  height: 26px;
  padding: 0;
  color: var(--table-text);
  font: inherit;
  font-size: 15px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

.table-token-picker__icon-button:hover,
.table-token-picker__icon-button:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.2);
  outline: none;
}

.table-token-picker__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 0;
  margin: 0 -6px;
  overflow: auto;
}

.table-token-picker__group {
  margin: 8px 6px 2px;
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.table-token-picker__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 6px;
  color: var(--table-text);
  font: inherit;
  text-align: left;
  background: transparent;
  border: 0;
  border-radius: 8px;
  cursor: grab;
}

.table-token-picker__item:hover,
.table-token-picker__item:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.16);
  outline: none;
}

.table-token-picker__portrait {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  overflow: hidden;
  color: var(--table-accent);
  font-weight: 600;
  background: rgba(var(--table-accent-rgb), 0.15);
  border: 2px solid rgba(var(--table-accent-rgb), 0.55);
  border-radius: 50%;
}

.table-token-picker__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.table-token-picker__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-token-picker__health {
  color: var(--table-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* ---- Context menu ------------------------------------------------------------------------ */

.table-menu {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--table-z-menu);
  display: flex;
  flex-direction: column;
  min-width: 190px;
  padding: 4px;
  font-size: 13px;
  background: rgba(var(--table-panel-rgb), 0.97);
  border: 1px solid rgba(var(--table-accent-rgb), 0.3);
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(var(--table-black-rgb), 0.55);
}

.table-menu[hidden] {
  display: none;
}

/* A long list (the GM's object menu on a phone) scrolls inside the table. */
.table-menu {
  max-height: calc(100% - 16px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-touch-callout: none;
  user-select: none;
}

/* Fingers (the long-press menu): items big enough to hit. */
@media (pointer: coarse) {
  .table-menu {
    min-width: 220px;
    font-size: 15px;
  }

  .table-menu__item {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 8px 12px;
  }
}

.table-menu__item {
  padding: 6px 10px;
  color: var(--table-text);
  font: inherit;
  text-align: left;
  background: transparent;
  border: 0;
  border-radius: 5px;
  cursor: pointer;
}

.table-menu__item:hover,
.table-menu__item:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.2);
  outline: none;
}

/* Add menu entries lead with an icon. */
.table-menu__item.has-icon {
  display: flex;
  align-items: center;
  gap: 8px;
}

.table-menu__item .table-icon {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--table-accent);
}

.table-menu__item.is-danger {
  color: var(--table-danger);
}

.table-menu__separator {
  height: 1px;
  margin: 4px 6px;
  background: rgba(var(--table-white-rgb), 0.1);
}

/* Link back to the campaign; part of the page template, above the table. */
.table-back-link {
  position: fixed;
  top: 12px;
  left: 16px;
  z-index: var(--table-z-back-link);
  max-width: calc(50% - 120px);
  padding: 6px 12px;
  overflow: hidden;
  color: var(--bs-body-color, #dee2e6);
  font-family: var(--bs-body-font-family, system-ui, sans-serif);
  font-size: 13px;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-decoration: none;
  user-select: none;
  -webkit-user-select: none;
  background: rgba(var(--bs-body-bg-rgb, 22, 26, 34), 0.78);
  border: 1px solid rgba(var(--bs-primary-rgb, 79, 143, 219), 0.25);
  border-radius: 8px;
  backdrop-filter: blur(6px);
}

.table-back-link:hover,
.table-back-link:focus-visible {
  color: var(--bs-primary, #4f8fdb);
}

.table-back-link:focus-visible {
  outline: 2px solid var(--bs-primary, #4f8fdb);
  outline-offset: 2px;
}

/* A master plot's table: not a campaign's, so the link back says what is being edited. */
.table-back-link--master {
  max-width: calc(100% - 32px - 240px);
  color: var(--bs-warning-text-emphasis, #ffda6a);
  font-weight: 600;
  border-color: rgba(var(--bs-warning-rgb, 255, 193, 7), 0.6);
}

body:has(.table-root) {
  margin: 0;
  background: #0c0e12;
  /* No page pinch or double-tap zoom anywhere on the table page; panels still scroll. The table
     viewport itself takes no browser gestures at all (touch-action: none). */
  touch-action: pan-x pan-y;
}

/* ---- Dice ------------------------------------------------------------------------------- */

/* Above the board, the character panel (14) and the showcase (15), so a roll is seen while the
   GM shows an image or a phone user rolls in the full-width panel; below dialogs and menus.
   Only the result cards take clicks, so the HUD under it stays usable; trays and cards keep clear
   of the HUD bar and of a side panel on wide screens (DiceTray._freeWidth). */
.table-dice {
  --dice-one: var(--table-danger, #e5484d);
  --dice-six: #f2c14e;
  --dice-explode: #ff9f43;
  --dice-crit: #46c97a;
  --dice-megacrit: #b07cff;
  --dice-fail: rgba(var(--table-white-rgb), 0.45);
  --dice-success: var(--table-text);
  position: absolute;
  inset: 0;
  z-index: var(--table-z-dice);
  overflow: hidden;
  pointer-events: none;
}

.table-dice[hidden] {
  display: none;
}

.table-dice__canvas {
  position: absolute;
  top: 0;
  left: 0;
}

/* Running sum of an exploding chain, above its first die. */
.dice-badge {
  position: absolute;
  top: 0;
  left: 0;
  padding: 2px 9px;
  color: var(--table-text);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  background: rgba(var(--table-panel-rgb), 0.92);
  border: 1px solid var(--dice-six);
  border-radius: 999px;
  box-shadow: 0 0 12px color-mix(in srgb, var(--dice-six) 45%, transparent);
  transition: opacity 0.6s ease;
}

.dice-badge.is-done {
  border-color: var(--dice-crit);
  box-shadow: 0 4px 14px rgba(var(--table-black-rgb), 0.4);
}

.dice-card {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: max-content;
  min-width: 200px;
  max-width: min(340px, calc(100% - 32px));
  padding: 10px 12px;
  color: var(--table-text);
  font-size: 13px;
  pointer-events: auto;
  background: rgba(var(--table-panel-rgb), 0.94);
  border: 1px solid rgba(var(--table-accent-rgb), 0.35);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(var(--table-black-rgb), 0.5);
  backdrop-filter: blur(6px);
  cursor: pointer;
  animation: dice-card-in 0.25s ease-out;
  transition: opacity 0.6s ease;
}

.dice-card.is-crit {
  border-color: var(--dice-crit);
}

.dice-card.is-megacrit {
  border-color: var(--dice-megacrit);
  box-shadow: 0 0 22px color-mix(in srgb, var(--dice-megacrit) 45%, transparent), 0 10px 30px rgba(var(--table-black-rgb), 0.5);
}

.dice-card.is-fail {
  border-color: color-mix(in srgb, var(--dice-one) 55%, transparent);
}

.dice-card.is-leaving,
.dice-badge.is-leaving {
  opacity: 0;
}

.dice-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
}

.dice-card__header {
  font-size: 14px;
  font-weight: 600;
}

.dice-card__who {
  color: var(--table-muted);
}

/* The roller's colour (userColors.js) as a dot before their name. */
.dice-card__who.has-color::before {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 5px;
  vertical-align: 1px;
  background: var(--user-color);
  border-radius: 50%;
  content: "";
}

.dice-card__tag {
  flex-basis: 100%;
  color: var(--dice-crit);
  font-weight: 600;
}

.dice-card.is-megacrit .dice-card__tag {
  color: var(--dice-megacrit);
}

.dice-card__dice {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.dice-chip {
  min-width: 26px;
  height: 26px;
  padding: 0 5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 24px;
  text-align: center;
  border: 1px solid rgba(var(--table-white-rgb), 0.2);
  border-radius: 6px;
}

.dice-chip.is-fail {
  color: var(--dice-fail);
}

.dice-chip.is-success {
  color: var(--dice-success);
  border-color: rgba(var(--table-accent-rgb), 0.6);
}

.dice-chip.is-crit {
  color: var(--dice-crit);
  border-color: var(--dice-crit);
}

.dice-chip.is-megacrit {
  color: var(--dice-megacrit);
  border-color: var(--dice-megacrit);
  box-shadow: 0 0 8px color-mix(in srgb, var(--dice-megacrit) 50%, transparent);
}

.dice-card__foot {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  color: var(--table-muted);
}

.dice-card__total {
  color: var(--table-text);
  font-weight: 600;
}

.dice-card__description {
  max-height: 4.5em;
  overflow: hidden;
  color: var(--table-muted);
  font-size: 12px;
}

.dice-card--batch {
  min-width: 180px;
}

.dice-batch {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: min(40vh, 220px);
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.dice-batch__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.dice-batch__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@keyframes dice-card-in {
  from {
    opacity: 0;
  }
}

.dice-fx {
  position: absolute;
  pointer-events: none;
}

.dice-fx--ring {
  border: 3px solid currentColor;
  border-radius: 50%;
  box-shadow: 0 0 16px currentColor, inset 0 0 12px currentColor;
}

.dice-fx--spark {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  box-shadow: 0 0 6px currentColor;
}

.dice-fx--flash {
  inset: 0;
  mix-blend-mode: screen;
}

/* Embers: hot sparks in the event colour, grey flakes of ash. */
.dice-fx--ember,
.dice-fx--ash {
  width: 5px;
  height: 5px;
  border-radius: 50%;
}

.dice-fx--ember {
  background: #fff3d6;
  box-shadow: 0 0 4px 1px currentColor, 0 0 10px currentColor;
}

.dice-fx--ash {
  width: 4px;
  height: 3px;
  background: rgba(190, 185, 180, 0.8);
  border-radius: 1px;
}

/* Runes: a turning circle framing the die. */
.dice-fx--runes svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  filter: drop-shadow(0 0 3px currentColor);
}

.dice-fx__rune-rim,
.dice-fx__rune-ticks {
  fill: none;
  stroke: currentColor;
}

.dice-fx__rune-rim {
  stroke-width: 0.8;
}

.dice-fx__rune-ticks {
  stroke-width: 2.4;
  stroke-dasharray: 0.4 3.6;
  opacity: 0.7;
}

.dice-fx__rune-glyphs {
  fill: currentColor;
  font: 9px "Segoe UI Historic", "Noto Sans Runic", "DejaVu Sans", sans-serif;
  text-anchor: middle;
}

/* Shockwave: rings and dust that rush outwards. */
.dice-fx--wave {
  border: 3px solid currentColor;
  border-radius: 50%;
  box-shadow: 0 0 10px currentColor;
}

.dice-fx--core {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.9), currentColor 35%, transparent 70%);
}

.dice-fx--dust {
  width: 4px;
  height: 4px;
  background: rgba(225, 215, 195, 0.85);
  border-radius: 50%;
}

/* Crack: dark fissures with a glowing edge, and a puff of smoke. */
.dice-fx--crack svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.dice-fx--crack polyline {
  fill: none;
  stroke: rgba(12, 8, 8, 0.85);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  filter: drop-shadow(0 0 1.5px currentColor);
}

.dice-fx--crack .dice-fx__branch {
  stroke-width: 0.9;
}

.dice-fx--smoke {
  border-radius: 50%;
  background: radial-gradient(circle, rgba(70, 66, 64, 0.75), rgba(70, 66, 64, 0) 70%);
}

@media (prefers-reduced-motion: reduce) {
  .dice-card {
    animation: none;
  }
}

/* ---- Dice preferences (section of the settings dialog) ------------------------------------ */

.table-dice-prefs {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.table-dice-prefs__preview:hover,
.table-dice-prefs__preview:focus-visible {
  color: var(--table-text);
  background: rgba(var(--table-accent-rgb), 0.2);
  outline: none;
}

.table-dice-prefs__hint {
  margin: 0;
  color: var(--table-muted);
  font-size: 12px;
}

.table-dice-prefs__group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.table-dice-prefs__group legend {
  margin-bottom: 4px;
  padding: 0;
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The shown skin: a large 3D picture with its name and family. It stays in view while the
   swatches scroll under it, so a hovered or focused skin is always visible. */
.table-dice-prefs__stage {
  position: sticky;
  /* The dialog's top padding (.table-panel), so no swatch shows above it. */
  top: -10px;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 10px;
  background:
    linear-gradient(rgba(var(--table-white-rgb), 0.04), rgba(var(--table-white-rgb), 0.04)),
    rgb(var(--table-panel-rgb));
  border: 1px solid rgba(var(--table-accent-rgb), 0.35);
  border-radius: 10px;
}

.table-dice-prefs__stage-picture {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 200px;
  aspect-ratio: 5 / 3;
}

.table-dice-prefs__stage-caption {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.table-dice-prefs__stage-name {
  font-size: 15px;
  overflow-wrap: anywhere;
}

.table-dice-prefs__stage-family {
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

@media (max-width: 576px) {
  .table-dice-prefs__stage-picture {
    width: 140px;
  }
}

.table-dice-prefs__families {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.table-dice-prefs__family {
  min-height: 28px;
  padding: 2px 10px;
  color: var(--table-muted);
  font: inherit;
  font-size: 12px;
  background: transparent;
  border: 1px solid rgba(var(--table-white-rgb), 0.18);
  border-radius: 999px;
  cursor: pointer;
}

.table-dice-prefs__family:hover {
  color: var(--table-text);
}

.table-dice-prefs__family[aria-pressed="true"] {
  color: var(--table-text);
  background: rgba(var(--table-accent-rgb), 0.2);
  border-color: var(--table-accent);
}

.table-dice-prefs__family:focus-visible {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
}

/* The family chips and the search field: one row when there is room, the search below otherwise. */
.table-dice-prefs__filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.table-dice-prefs__filters .table-dice-prefs__families {
  flex: 1 1 260px;
}

.table-dice-prefs__count {
  margin-left: 5px;
  color: var(--table-muted);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}

.table-dice-prefs__search {
  flex: 1 1 140px;
  min-width: 0;
  min-height: 28px;
  padding: 3px 10px;
  color: var(--table-text);
  font: inherit;
  font-size: 12px;
  background: rgba(var(--table-black-rgb), 0.25);
  border: 1px solid rgba(var(--table-white-rgb), 0.16);
  border-radius: 999px;
}

.table-dice-prefs__search:focus-visible {
  border-color: var(--table-accent);
  outline: none;
}

/* A family's name over its run of swatches ("All" only). */
.table-dice-prefs__heading {
  grid-column: 1 / -1;
  margin: 6px 0 0;
  padding-bottom: 2px;
  color: var(--table-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-bottom: 1px solid rgba(var(--table-accent-rgb), 0.25);
}

.table-dice-prefs__heading:first-child {
  margin-top: 0;
}

.table-dice-prefs__heading[hidden],
.table-dice-prefs__empty[hidden] {
  display: none;
}

.table-dice-prefs__empty {
  margin: 0;
  color: var(--table-muted);
  font-size: 12px;
}

/* Small swatches, as many to a row as fit. */
.table-dice-prefs__skins {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(68px, 1fr));
  gap: 4px;
}

.table-dice-prefs__skin {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
  padding: 4px 2px;
  background: rgba(var(--table-white-rgb), 0.04);
  border: 1px solid rgba(var(--table-white-rgb), 0.12);
  border-radius: 8px;
  cursor: pointer;
}

.table-dice-prefs__skin[hidden] {
  display: none;
}

.table-dice-prefs__skin:hover {
  border-color: rgba(var(--table-accent-rgb), 0.5);
}

/* The radio stays focusable for the keyboard but the tile shows the state. */
.table-dice-prefs__skin input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.table-dice-prefs__skin:has(input:checked) {
  background: rgba(var(--table-accent-rgb), 0.16);
  border-color: var(--table-accent);
}

.table-dice-prefs__skin:has(input:focus-visible) {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
}

.table-dice-prefs__swatch {
  display: block;
  width: 44px;
  height: 44px;
  overflow: hidden;
  background: rgba(var(--table-white-rgb), 0.08);
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(var(--table-black-rgb), 0.5);
}

.table-dice-prefs__swatch canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.table-dice-prefs__flat {
  width: 72px;
  height: 72px;
  border-radius: 12px;
}

/* Fills the 5:3 picture box without ever stretching the dice. */
.table-dice-prefs__render {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.table-dice-prefs__name {
  max-width: 100%;
  font-size: 11px;
  line-height: 1.2;
  text-align: center;
  overflow-wrap: anywhere;
}

.table-dice-prefs__effect {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 30px;
  align-items: center;
  gap: 6px;
}

.table-dice-prefs__effect select {
  min-width: 96px;
  padding: 3px 6px;
  color: var(--table-text);
  font: inherit;
  background: rgba(var(--table-white-rgb), 0.06);
  border: 1px solid rgba(var(--table-white-rgb), 0.18);
  border-radius: 6px;
}

/* The native dropdown is light: dark text for the options and their group labels. */
.table-dice-prefs__effect select option,
.table-dice-prefs__effect select optgroup {
  color: #000;
}

.table-dice-prefs__preview {
  width: 30px;
  height: 28px;
  color: var(--table-accent);
  font-size: 11px;
  background: transparent;
  border: 1px solid rgba(var(--table-accent-rgb), 0.35);
  border-radius: 6px;
  cursor: pointer;
}

.table-dice-prefs__reduce {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

/* The effects' colour: a switch with the user's colour as a dot and a short explanation. */
.table-dice-prefs__tint {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 4px;
  cursor: pointer;
}

.table-dice-prefs__tint input {
  margin-top: 3px;
}

.table-dice-prefs__tint-dot {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 2px;
  border: 1px solid rgba(var(--table-white-rgb), 0.5);
  border-radius: 50%;
  box-shadow: 0 0 6px rgba(var(--table-black-rgb), 0.5);
}

.table-dice-prefs__tint-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.table-dice-prefs__tint-text small {
  color: var(--table-muted);
  font-size: 11px;
}

.table-dice-prefs__status {
  min-height: 1.2em;
  margin: 0;
  color: var(--table-muted);
  font-size: 12px;
}

.table-dice-prefs__status[data-state="ok"] {
  color: var(--bs-success, #198754);
}

.table-dice-prefs__status[data-state="error"] {
  color: var(--table-danger);
}

/* ---- Settings dialog ------------------------------------------------------------------- */

/* Light backdrop and the dialog on the right, so dice previews stay visible on the table. */
.table-settings {
  position: absolute;
  inset: 0;
  z-index: var(--table-z-dialog);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 16px;
  background: rgba(var(--table-black-rgb), 0.25);
}

.table-settings[hidden] {
  display: none;
}

.table-settings__dialog {
  position: static;
  gap: 10px;
  width: min(380px, 100%);
  max-height: min(86vh, 820px);
  max-height: min(86dvh, 820px);
  background: rgba(var(--table-panel-rgb), 0.97);
}

@media (max-width: 576px) {
  .table-settings {
    justify-content: center;
  }

  /* Not the bottom sheet of the other panels. */
  .table-settings .table-settings__dialog {
    width: 100%;
    max-height: min(86dvh, 820px);
  }
}

.table-settings__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.table-settings__title {
  margin: 0;
}

.table-settings__close {
  width: 28px;
  height: 28px;
  padding: 0;
  color: var(--table-text);
  font: inherit;
  font-size: 18px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

.table-settings__close:hover,
.table-settings__close:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.2);
  outline: none;
}

/* Five tabs for the GM: they wrap rather than widen the dialog (a scrollbar takes its room). */
.table-settings__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0 2px;
  border-bottom: 1px solid rgba(var(--table-white-rgb), 0.12);
}

.table-settings__tabs[hidden],
.table-settings__tab[hidden],
.table-settings__pane[hidden] {
  display: none;
}

.table-settings__tab {
  padding: 6px 10px;
  color: var(--table-muted);
  font: inherit;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}

.table-settings__tab:hover,
.table-settings__tab:focus-visible {
  color: var(--table-text);
  outline: none;
}

.table-settings__tab[aria-selected="true"] {
  color: var(--table-text);
  border-bottom-color: var(--table-accent);
}

.table-settings__pane {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.table-settings__hint {
  margin: 0;
  font-size: 12px;
}

/* The players section: per kind "May create", then the properties in columns. */
.table-settings__subtitle {
  margin: 4px 0 0;
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.table-settings__checks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 4px 16px;
}

.table-settings__create {
  font-weight: 600;
}

/* A checklist: the box before its label. */
.table-settings__permissions .table-panel__field.is-check {
  grid-template-columns: auto minmax(0, 1fr);
  justify-content: start;
}

.table-settings__permissions .table-panel__field.is-check input {
  order: -1;
  width: auto;
}

.table-settings__gm-only {
  margin: 0;
  padding-left: 18px;
  color: var(--table-muted);
  font-size: 12px;
}

.table-settings__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.table-settings__cloth-preview {
  width: 44px;
  height: 30px;
  background-position: center;
  background-size: cover;
  border: 1px solid rgba(var(--table-white-rgb), 0.2);
  border-radius: 4px;
}

.table-settings__cloth-preview[hidden],
.table-settings .table-panel__button[hidden] {
  display: none;
}

.table-settings .table-panel__field {
  grid-template-columns: 1fr 150px;
}

.table-settings .table-panel__field.is-wide {
  grid-template-columns: 1fr;
}

/* Amount and unit of the distance per cell side by side (settings and map panel). */
.table-settings__distance,
.table-panel__distance {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px;
}

.table-panel__distance select,
.table-settings__distance select {
  padding: 2px 4px;
  color: var(--table-text);
  font: inherit;
  background: rgba(var(--table-black-rgb), 0.3);
  border: 1px solid rgba(var(--table-white-rgb), 0.15);
  border-radius: 5px;
}

.table-panel__distance select option,
.table-settings__distance select option {
  color: #111;
}

.table-hud__settings {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.table-hud__settings .table-icon {
  width: 15px;
  height: 15px;
  color: var(--table-accent);
}

.table-hud__settings.is-active {
  background: rgba(var(--table-accent-rgb), 0.2) !important;
}

/* On a phone the gear alone. */
@media (max-width: 576px) {
  .table-hud__settings > span {
    display: none;
  }
}

/* ---- HUD -------------------------------------------------------------------------------- */

.table-hud {
  position: absolute;
  inset: 0;
  z-index: var(--table-z-hud);
  pointer-events: none;
  font-size: 13px;
}

.table-hud__bar,
.table-toast {
  background: rgba(var(--table-panel-rgb), 0.78);
  border: 1px solid rgba(var(--table-accent-rgb), 0.25);
  border-radius: 8px;
  box-shadow: 0 6px 20px rgba(var(--table-black-rgb), 0.4);
  backdrop-filter: blur(6px);
}

.table-hud__bar {
  position: absolute;
  right: 16px;
  bottom: 16px;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  pointer-events: auto;
}

.table-hud__bar button {
  min-width: 30px;
  height: 28px;
  padding: 0 8px;
  color: var(--table-text);
  font: inherit;
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

.table-hud__bar button:hover,
.table-hud__bar button:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.2);
  outline: none;
}

.table-hud__add::before {
  content: "+";
  content: "+" / "";
  margin-right: 4px;
  color: var(--table-accent);
  font-weight: 600;
}

.table-hud__add.is-active {
  background: rgba(var(--table-accent-rgb), 0.2) !important;
}

/* The "Add" menu opens above the HUD bar (see AddMenu._position). */
.table-add-menu {
  min-width: 210px;
}

.table-hud__ruler {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.table-hud__ruler .table-icon {
  width: 16px;
  height: 16px;
}

/* Icon-only HUD buttons: the GM's camera (save the start view) and "go to the start view". */
.table-hud__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--table-muted) !important;
}

.table-hud__icon[hidden] {
  display: none;
}

.table-hud__icon .table-icon {
  width: 16px;
  height: 16px;
}

.table-hud__icon:hover,
.table-hud__icon:focus-visible {
  color: var(--table-text) !important;
}

/* A shutter: the camera button flashes once when the view was saved. */
.table-hud__save-view.is-flashing {
  animation: table-shutter 0.45s ease-out;
}

@keyframes table-shutter {
  0% {
    color: rgb(var(--table-panel-rgb));
    background: rgba(var(--table-white-rgb), 0.9);
  }

  100% {
    background: transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .table-hud__save-view.is-flashing {
    animation: none;
  }
}

.table-hud__ruler.is-active,
.table-hud__snap.is-active {
  color: var(--table-accent) !important;
  background: rgba(var(--table-accent-rgb), 0.2) !important;
}

/* A player's own character: a portrait badge in the top left corner, below the link back to
   the campaign, like a game's player frame. On phones only the round portrait. */
.table-character-badge {
  position: absolute;
  top: 52px;
  left: 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(260px, calc(50% - 32px));
  padding: 4px 16px 4px 4px;
  color: var(--table-text);
  font: inherit;
  text-align: left;
  pointer-events: auto;
  cursor: pointer;
  background: rgba(var(--table-panel-rgb), 0.78);
  border: 1px solid rgba(var(--table-accent-rgb), 0.25);
  border-radius: 999px;
  box-shadow: 0 6px 20px rgba(var(--table-black-rgb), 0.4);
  backdrop-filter: blur(6px);
}

.table-character-badge[hidden] {
  display: none;
}

.table-character-badge:hover {
  border-color: rgba(var(--table-accent-rgb), 0.55);
}

.table-character-badge:focus-visible {
  outline: 2px solid var(--table-accent);
  outline-offset: 2px;
}

.table-character-badge.is-active {
  background: rgba(var(--table-panel-rgb), 0.9);
  border-color: var(--table-accent);
  box-shadow:
    0 6px 20px rgba(var(--table-black-rgb), 0.4),
    0 0 0 3px rgba(var(--table-accent-rgb), 0.2);
}

.table-character-badge__portrait {
  position: relative;
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
}

.table-character-badge__portrait img,
.table-character-badge__sigil {
  position: absolute;
  inset: 5px;
  width: 38px;
  height: 38px;
  object-fit: cover;
  border-radius: 50%;
}

.table-character-badge__portrait img[hidden],
.table-character-badge__sigil[hidden] {
  display: none;
}

.table-character-badge__sigil {
  display: grid;
  place-items: center;
  color: var(--table-text);
  font-size: 14px;
  font-weight: 700;
  background: rgba(var(--table-accent-rgb), 0.3);
}

/* Health as a ring round the portrait; without health only a faint track. */
.table-character-badge__ring {
  position: absolute;
  inset: 0;
  width: 48px;
  height: 48px;
  transform: rotate(-90deg);
}

.table-character-badge__track,
.table-character-badge__fill {
  fill: none;
  stroke-width: 3;
}

.table-character-badge__track {
  stroke: rgba(var(--table-white-rgb), 0.14);
}

.table-character-badge__fill {
  stroke: var(--bs-success, #198754);
  stroke-dasharray: var(--health, 0) 100;
  stroke-linecap: round;
  transition: stroke-dasharray 0.4s ease;
}

.table-character-badge[data-level="mid"] .table-character-badge__fill {
  stroke: var(--bs-warning, #ffc107);
}

.table-character-badge[data-level="low"] .table-character-badge__fill {
  stroke: var(--bs-danger, #dc3545);
}

.table-character-badge__ring.is-empty .table-character-badge__fill {
  display: none;
}

.table-character-badge.is-active .table-character-badge__track {
  stroke: rgba(var(--table-accent-rgb), 0.45);
}

.table-character-badge__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.table-character-badge__name {
  overflow: hidden;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-character-badge__health {
  color: var(--table-muted);
  font-size: 11px;
}

.table-character-badge__health[hidden] {
  display: none;
}

@media (max-width: 576px) {
  .table-character-badge {
    left: 12px;
    padding: 2px;
  }

  .table-character-badge__text {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .table-character-badge__fill {
    transition: none;
  }
}

/* ---- Combat tracker: the turn order at the top centre (CombatTracker.js) ----------------- */

.table-combat {
  position: absolute;
  top: 12px;
  left: 50%;
  display: flex;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  max-width: min(880px, calc(100% - 32px));
  padding: 4px 6px;
  font-size: 13px;
  pointer-events: auto;
  background: rgba(var(--table-panel-rgb), 0.78);
  border: 1px solid rgba(var(--table-accent-rgb), 0.25);
  border-radius: 8px;
  box-shadow: 0 6px 20px rgba(var(--table-black-rgb), 0.4);
  backdrop-filter: blur(6px);
  translate: -50% 0;
  animation: table-combat-in 0.32s cubic-bezier(0.2, 0.9, 0.3, 1);
}

.table-combat[hidden],
.table-combat [hidden] {
  display: none !important;
}

@keyframes table-combat-in {
  from {
    opacity: 0;
    transform: translateY(-24px);
  }
}

.table-combat button {
  color: var(--table-text);
  font: inherit;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.table-combat button:focus-visible {
  outline: 2px solid var(--table-accent);
  outline-offset: 1px;
}

.table-combat__status {
  display: flex;
  flex: none;
  align-items: center;
  gap: 2px;
  padding-right: 8px;
  border-right: 1px solid rgba(var(--table-accent-rgb), 0.2);
}

.table-combat__step {
  width: 24px;
  height: 28px;
  padding: 0;
  font-size: 11px !important;
  border-radius: 6px;
}

.table-combat__step:hover {
  background: rgba(var(--table-accent-rgb), 0.2) !important;
}

.table-combat__round {
  display: grid;
  justify-items: center;
  min-width: 44px;
  padding: 0 4px;
  line-height: 1.1;
}

.table-combat__round-label {
  color: var(--table-muted);
  font-size: 11px;
}

.table-combat__round-number {
  font-size: 18px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.table-combat:not(.is-started) .table-combat__round-label {
  color: var(--table-text);
  font-size: 12px;
}

/* The cards scroll sideways; the padding leaves room for the active card's lift and glow. */
.table-combat__cards {
  position: relative;
  display: flex;
  flex: 0 1 auto;
  gap: 4px;
  min-width: 0;
  padding: 7px 7px 3px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--table-accent-rgb), 0.4) transparent;
}

.table-combat.is-empty .table-combat__cards {
  display: none;
}

.table-combat__card {
  --card-color: #c9a227;

  position: relative;
  display: grid;
  flex: none;
  justify-items: center;
  gap: 3px;
  width: 64px;
}

.table-combat__card.is-leaving {
  position: absolute;
  pointer-events: none;
}

.table-combat__portrait {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0 !important;
  overflow: hidden;
  background: rgb(var(--table-panel-rgb)) !important;
  border: 2px solid var(--card-color) !important;
  border-radius: 50%;
  box-sizing: border-box;
  transition:
    transform 0.25s ease,
    box-shadow 0.25s ease,
    filter 0.25s ease;
}

.table-combat__card[data-shape="square"] .table-combat__portrait {
  border-radius: 12%;
}

.table-combat__portrait:disabled {
  cursor: default !important;
}

.table-combat__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

.table-combat__sigil {
  color: var(--card-color);
  font-size: 15px;
  font-weight: 600;
}

/* Still waiting for initiative: dimmed until the number is in. */
.table-combat__card:not(.is-rolled) .table-combat__portrait {
  filter: saturate(0.5) brightness(0.75);
}

.table-combat__card.is-hidden .table-combat__portrait {
  border-style: dashed !important;
  opacity: 0.65;
}

.table-combat__card.is-active .table-combat__portrait {
  box-shadow:
    0 0 0 2px rgb(var(--table-panel-rgb)),
    0 0 0 4px var(--table-accent),
    0 0 18px rgba(var(--table-accent-rgb), 0.65);
  transform: translateY(1px) scale(1.06);
}

.table-combat__name {
  max-width: 100%;
  overflow: hidden;
  color: var(--table-muted);
  font-size: 11px;
  line-height: 1.2;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-combat__card.is-active .table-combat__name {
  color: var(--table-text);
  font-weight: 600;
}

.table-combat__badge,
.table-combat__roll {
  position: absolute;
  top: 28px;
  right: 0;
  display: grid;
  place-items: center;
  min-width: 22px;
  height: 18px;
  padding: 0 4px !important;
  font-size: 11px !important;
  font-weight: 600 !important;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  background: rgb(var(--table-panel-rgb)) !important;
  border: 1px solid rgba(var(--table-accent-rgb), 0.55) !important;
  border-radius: 9px;
  box-sizing: border-box;
}

.table-combat__badge:disabled {
  color: var(--table-text) !important;
  cursor: default !important;
}

.table-combat__badge:not(:disabled):hover {
  border-color: var(--table-accent) !important;
}

.table-combat__card.is-active .table-combat__badge {
  color: #fff !important;
  background: var(--table-accent) !important;
  border-color: var(--table-accent) !important;
}

/* The dice button of an unrolled card the user may roll for. */
.table-combat__roll {
  top: 25px;
  right: -2px;
  width: 24px;
  height: 22px;
  padding: 0 !important;
  color: #fff !important;
  background: var(--table-accent) !important;
  border-color: rgba(var(--table-white-rgb), 0.35) !important;
  border-radius: 6px;
  box-shadow: 0 2px 6px rgba(var(--table-black-rgb), 0.5);
}

.table-combat__roll .table-icon {
  width: 15px;
  height: 15px;
}

.table-combat__roll:hover {
  filter: brightness(1.15);
}

.table-combat__enter,
.table-combat__remove {
  position: absolute;
  top: 0;
  width: 18px;
  height: 18px;
  padding: 0 !important;
  font-size: 12px !important;
  line-height: 1;
  background: rgb(var(--table-panel-rgb)) !important;
  border: 1px solid rgba(var(--table-accent-rgb), 0.4) !important;
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.table-combat__enter {
  left: 0;
}

.table-combat__remove {
  right: 0;
}

.table-combat__card:hover .table-combat__enter,
.table-combat__card:hover .table-combat__remove,
.table-combat__card:focus-within .table-combat__enter,
.table-combat__card:focus-within .table-combat__remove {
  opacity: 1;
}

.table-combat__remove:hover {
  color: var(--table-danger) !important;
  border-color: var(--table-danger) !important;
}

.table-combat__actions {
  display: flex;
  flex: none;
  align-items: center;
  gap: 4px;
}

.table-combat__actions:has(> :not([hidden])) {
  padding-left: 8px;
  border-left: 1px solid rgba(var(--table-accent-rgb), 0.2);
}

.table-combat__action,
.table-combat__done {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px !important;
  white-space: nowrap;
  border: 1px solid rgba(var(--table-accent-rgb), 0.35) !important;
  border-radius: 6px;
}

.table-combat__action .table-icon {
  width: 16px;
  height: 16px;
  color: var(--table-accent);
}

.table-combat__action:hover {
  background: rgba(var(--table-accent-rgb), 0.2) !important;
}

.table-combat__done {
  color: #fff !important;
  font-weight: 600 !important;
  background: var(--table-accent) !important;
  border-color: var(--table-accent) !important;
}

.table-combat__done:hover {
  filter: brightness(1.12);
}

.table-combat__end {
  width: 28px;
  height: 28px;
  padding: 0 !important;
  color: var(--table-muted) !important;
  font-size: 18px !important;
  line-height: 1;
  border-radius: 6px;
}

.table-combat__end:hover {
  color: var(--table-danger) !important;
  background: rgba(220, 53, 69, 0.15) !important;
}

.table-combat__turn {
  padding: 3px 10px;
  color: var(--table-accent);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  background: rgba(var(--table-accent-rgb), 0.14);
  border: 1px solid rgba(var(--table-accent-rgb), 0.45);
  border-radius: 999px;
}

.table-combat__turn--gm {
  color: var(--table-text);
  box-shadow: 0 0 12px rgba(var(--table-accent-rgb), 0.35);
}

/* Manual entry: under its card, for campaigns that roll real dice and for corrections. */
.table-combat__entry {
  position: absolute;
  top: calc(100% + 8px);
  display: grid;
  gap: 8px;
  width: 220px;
  padding: 10px 12px 12px;
  background: rgba(var(--table-panel-rgb), 0.96);
  border: 1px solid rgba(var(--table-accent-rgb), 0.35);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(var(--table-black-rgb), 0.5);
  animation: table-toast-in 0.16s ease-out;
}

.table-combat__entry-hint {
  font-size: 12px;
  line-height: 1.35;
}

.table-combat__entry-name {
  overflow: hidden;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-combat__entry-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  color: var(--table-muted);
  font-size: 12px;
}

.table-combat__entry-row input {
  width: 84px;
  padding: 4px 8px;
  color: var(--table-text);
  font: inherit;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  background: rgba(var(--table-black-rgb), 0.3);
  border: 1px solid rgba(var(--table-accent-rgb), 0.35);
  border-radius: 6px;
}

.table-combat__entry-row input:focus {
  border-color: var(--table-accent);
  outline: none;
}

.table-combat__entry-row input.is-invalid {
  border-color: var(--table-danger);
}

.table-combat__entry-submit {
  height: 30px;
  color: #fff !important;
  font-weight: 600 !important;
  background: var(--table-accent) !important;
  border-radius: 6px;
}

/* The sound hint moves below the tracker. */
.table-root.has-combat .table-hud__sound {
  top: 104px;
}

@media (prefers-reduced-motion: reduce) {
  .table-combat,
  .table-combat__entry {
    animation: none;
  }

  .table-combat__portrait {
    transition: none;
  }
}

/* Below the back link where the top row gets tight. */
@media (max-width: 900px) {
  .table-combat {
    top: 52px;
  }

  .table-root.has-combat .table-hud__sound {
    top: 144px;
  }
}

/* Phones: avatars only, the cards on a row of their own, the controls below them. */
@media (max-width: 576px) {
  .table-combat {
    right: 16px;
    left: 16px;
    flex-wrap: wrap;
    gap: 4px 6px;
    max-width: none;
    translate: none;
  }

  .table-combat__cards {
    flex: 1 0 100%;
    order: -1;
    padding-top: 5px;
  }

  .table-combat__card {
    width: 40px;
  }

  .table-combat__portrait {
    width: 36px;
    height: 36px;
  }

  .table-combat__name,
  .table-combat__enter,
  .table-combat__remove,
  .table-combat__action > span {
    display: none;
  }

  .table-combat__badge {
    top: 22px;
    right: -4px;
    min-width: 20px;
    height: 16px;
  }

  .table-combat__roll {
    top: 18px;
    right: -5px;
    width: 22px;
    height: 20px;
  }

  .table-combat__status {
    padding-right: 0;
    border-right: 0;
  }

  .table-combat__actions {
    margin-left: auto;
  }

  .table-combat__actions:has(> :not([hidden])) {
    padding-left: 0;
    border-left: 0;
  }

  .table-combat__entry {
    top: calc(100% + 6px);
  }
}

/* ---- Initiative prompt: a player's call to roll for their tokens (InitiativePrompt.js) ---- */

.table-initiative {
  position: absolute;
  bottom: 16px;
  left: 16px;
  z-index: var(--table-z-hud);
  display: grid;
  gap: 10px;
  box-sizing: border-box;
  width: 300px;
  max-width: calc(100% - 32px);
  padding: 12px 14px 14px;
  overflow: hidden;
  font-size: 13px;
  pointer-events: auto;
  background:
    radial-gradient(ellipse 90% 70% at 0% 0%, rgba(var(--table-accent-rgb), 0.16), transparent 70%),
    rgba(var(--table-panel-rgb), 0.92);
  border: 1px solid rgba(var(--table-accent-rgb), 0.45);
  border-radius: 12px;
  box-shadow:
    0 12px 32px rgba(var(--table-black-rgb), 0.5),
    0 0 24px rgba(var(--table-accent-rgb), 0.18);
  backdrop-filter: blur(8px);
}

/* A thin line of light along the top edge. */
.table-initiative::before {
  position: absolute;
  top: 0;
  right: 18px;
  left: 18px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--table-accent-rgb), 0.9), transparent);
  content: "";
}

.table-initiative[hidden],
.table-initiative [hidden] {
  display: none !important;
}

.table-initiative.is-entering {
  animation:
    table-initiative-in 0.52s cubic-bezier(0.2, 1.25, 0.4, 1),
    table-initiative-flare 1.4s ease-out;
}

@keyframes table-initiative-in {
  from {
    opacity: 0;
    transform: translateY(28px) scale(0.92);
  }
}

@keyframes table-initiative-flare {
  0% {
    box-shadow:
      0 12px 32px rgba(var(--table-black-rgb), 0.5),
      0 0 0 rgba(var(--table-accent-rgb), 0);
  }

  35% {
    box-shadow:
      0 12px 32px rgba(var(--table-black-rgb), 0.5),
      0 0 44px rgba(var(--table-accent-rgb), 0.55);
  }
}

.table-initiative button {
  color: var(--table-text);
  font: inherit;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.table-initiative button:focus-visible,
.table-initiative input:focus-visible {
  outline: 2px solid var(--table-accent);
  outline-offset: 1px;
}

.table-initiative__head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.table-initiative__die {
  display: grid;
  flex: none;
  place-items: center;
  width: 34px;
  height: 34px;
  color: var(--table-accent);
  filter: drop-shadow(0 0 6px rgba(var(--table-accent-rgb), 0.6));
}

.table-initiative__die svg {
  width: 100%;
  height: 100%;
}

.table-initiative__die-face {
  fill: rgba(var(--table-accent-rgb), 0.28);
}

/* The die tumbles in, then rests with a slow glint. */
.table-initiative.is-entering .table-initiative__die {
  animation: table-initiative-tumble 0.9s cubic-bezier(0.2, 0.8, 0.3, 1.15);
}

.table-initiative__die svg {
  animation: table-initiative-glint 3.2s ease-in-out 1s infinite;
}

@keyframes table-initiative-tumble {
  0% {
    opacity: 0;
    transform: translate(-18px, -14px) rotate(-300deg) scale(0.4);
  }

  60% {
    opacity: 1;
    transform: translate(2px, 1px) rotate(18deg) scale(1.08);
  }

  80% {
    transform: rotate(-6deg) scale(0.98);
  }
}

@keyframes table-initiative-glint {
  0%,
  100% {
    transform: rotate(0);
  }

  50% {
    transform: rotate(8deg);
  }
}

.table-initiative__title {
  flex: 1;
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.01em;
}

.table-initiative__close {
  flex: none;
  align-self: flex-start;
  width: 26px;
  height: 26px;
  padding: 0;
  color: var(--table-muted) !important;
  font-size: 18px !important;
  line-height: 1;
  border-radius: 6px;
}

.table-initiative__close:hover {
  color: var(--table-text) !important;
  background: rgba(var(--table-white-rgb), 0.08) !important;
}

.table-initiative__hint {
  color: var(--table-muted);
  font-size: 12px;
  line-height: 1.35;
}

.table-initiative__rows {
  display: grid;
  gap: 6px;
  max-height: 40vh;
  overflow-y: auto;
}

.table-initiative__row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 4px 4px 4px 6px;
  background: rgba(var(--table-white-rgb), 0.04);
  border: 1px solid rgba(var(--table-accent-rgb), 0.18);
  border-radius: 10px;
}

.table-initiative__portrait {
  display: grid;
  flex: none;
  place-items: center;
  width: 36px;
  height: 36px;
  overflow: hidden;
  font-size: 13px;
  font-weight: 600;
  background: rgba(var(--table-black-rgb), 0.35);
  border: 2px solid var(--token-color, var(--table-accent));
  border-radius: 50%;
  box-shadow: 0 0 10px rgba(var(--table-accent-rgb), 0.3);
}

.table-initiative__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.table-initiative__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-initiative__roll,
.table-initiative__all {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px !important;
  color: #fff !important;
  font-weight: 600 !important;
  background: var(--table-accent) !important;
  border: 1px solid rgba(var(--table-white-rgb), 0.3) !important;
  border-radius: 8px;
  box-shadow: 0 3px 10px rgba(var(--table-black-rgb), 0.45);
}

.table-initiative__roll .table-icon,
.table-initiative__all .table-icon {
  width: 17px;
  height: 17px;
}

/* Waiting for the player: the button breathes softly. */
.table-initiative__roll:not(:disabled) {
  animation: table-initiative-pulse 1.8s ease-out 0.6s infinite;
}

@keyframes table-initiative-pulse {
  0% {
    box-shadow:
      0 3px 10px rgba(var(--table-black-rgb), 0.45),
      0 0 0 0 rgba(var(--table-accent-rgb), 0.6);
  }

  70%,
  100% {
    box-shadow:
      0 3px 10px rgba(var(--table-black-rgb), 0.45),
      0 0 0 9px rgba(var(--table-accent-rgb), 0);
  }
}

.table-initiative__roll:hover:not(:disabled),
.table-initiative__all:hover:not(:disabled) {
  filter: brightness(1.15);
}

.table-initiative__roll:disabled,
.table-initiative__all:disabled {
  cursor: default;
  opacity: 0.7;
}

/* Rolling: the dice in the button spin until the result is in. */
.table-initiative__row.is-rolling .table-initiative__roll .table-icon {
  animation: table-initiative-spin 0.7s linear infinite;
}

@keyframes table-initiative-spin {
  to {
    transform: rotate(360deg);
  }
}

.table-initiative__all {
  justify-self: stretch;
  background: transparent !important;
  border-color: rgba(var(--table-accent-rgb), 0.55) !important;
  color: var(--table-text) !important;
  box-shadow: none;
}

.table-initiative__all:hover:not(:disabled) {
  background: rgba(var(--table-accent-rgb), 0.18) !important;
}

.table-initiative__entry {
  display: flex;
  flex: none;
  gap: 4px;
}

.table-initiative__entry input {
  width: 64px;
  height: 34px;
  padding: 0 8px;
  box-sizing: border-box;
  color: var(--table-text);
  font: inherit;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  background: rgba(var(--table-black-rgb), 0.3);
  border: 1px solid rgba(var(--table-accent-rgb), 0.45);
  border-radius: 8px;
}

.table-initiative__entry input:focus {
  border-color: var(--table-accent);
  outline: none;
}

.table-initiative__entry input.is-invalid {
  border-color: var(--table-danger);
}

.table-initiative__submit {
  width: 34px;
  height: 34px;
  padding: 0 !important;
  color: #fff !important;
  font-weight: 600 !important;
  background: var(--table-accent) !important;
  border-radius: 8px;
}

.table-initiative__live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .table-initiative.is-entering,
  .table-initiative.is-entering .table-initiative__die,
  .table-initiative__die svg,
  .table-initiative__roll:not(:disabled),
  .table-initiative__row.is-rolling .table-initiative__roll .table-icon {
    animation: none;
  }
}

/* Phones: across the bottom, above the HUD bar; only the dice icon on the roll buttons. */
@media (max-width: 576px) {
  .table-initiative {
    right: 16px;
    bottom: 60px;
    width: auto;
    max-width: none;
    padding: 10px 12px 12px;
  }

  .table-initiative__rows {
    max-height: 30vh;
  }

  .table-initiative__roll {
    padding: 0 10px !important;
  }

  .table-initiative__roll > span {
    display: none;
  }
}

/* ---- Character panel: the sheet fly-in on the right (CharacterPanel.js) -------------------- */

.table-root {
  --table-character-panel-width: 420px;
}

.table-character-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  /* Above the HUD and panels, below the showcase (15) and menus (20). */
  z-index: var(--table-z-character);
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  width: min(var(--table-character-panel-width), 100%);
  background: rgb(var(--table-panel-rgb));
  border-left: 1px solid rgba(var(--table-accent-rgb), 0.3);
  box-shadow: -10px 0 30px rgba(var(--table-black-rgb), 0.45);
  transform: translateX(100%);
  visibility: hidden;
  transition:
    transform 0.25s ease,
    visibility 0s linear 0.25s;
}

.table-character-panel.is-open {
  transform: none;
  visibility: visible;
  transition: transform 0.25s ease;
}

.table-character-panel__header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 8px 8px 14px;
  font-size: 13px;
  border-bottom: 1px solid rgba(var(--table-accent-rgb), 0.25);
}

.table-character-panel__title {
  flex: 1;
  overflow: hidden;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-character-panel__close {
  width: 30px;
  height: 30px;
  padding: 0;
  color: var(--table-text);
  font: inherit;
  font-size: 20px;
  line-height: 1;
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

.table-character-panel__close:hover,
.table-character-panel__close:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.2);
  outline: none;
}

.table-character-panel__frame {
  flex: 1;
  width: 100%;
  border: 0;
}

/* A roll of the shown character or foe on a phone: the panel slides aside to a handle at the
   edge until the result card has faded (CharacterPanel.stepAsideFor). */
.table-character-panel.is-open.is-aside {
  transform: translateX(calc(100% - 36px));
}

.table-character-panel__handle {
  position: absolute;
  top: 50%;
  left: 0;
  display: none;
  width: 36px;
  height: 72px;
  padding: 0;
  color: var(--table-text);
  font: inherit;
  font-size: 22px;
  background: rgba(var(--table-accent-rgb), 0.25);
  border: 0;
  border-radius: 8px 0 0 8px;
  transform: translateY(-50%);
  cursor: pointer;
}

.table-character-panel.is-aside .table-character-panel__handle {
  display: block;
}

/* While the panel is aside only its handle strip is on screen: the HUD bar keeps clear of it. */
.table-root.has-character-panel:has(.table-character-panel.is-aside) .table-hud__bar {
  right: 52px;
}

.table-character-panel.is-aside .table-character-panel__frame,
.table-character-panel.is-aside .table-character-panel__header {
  visibility: hidden;
}

/* Keep the inspector and the HUD bar beside the open panel. */
.table-root.has-character-panel .table-inspector,
.table-root.has-character-panel .table-hud__bar {
  right: calc(var(--table-character-panel-width) + 16px);
}

.table-hud__sound {
  position: absolute;
  top: 56px;
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 14px;
  color: var(--table-text);
  font: inherit;
  pointer-events: auto;
  background: rgba(var(--table-panel-rgb), 0.9);
  border: 1px solid rgba(255, 193, 7, 0.6);
  border-radius: 999px;
  box-shadow: 0 6px 20px rgba(var(--table-black-rgb), 0.4);
  cursor: pointer;
}

.table-hud__sound[hidden] {
  display: none;
}

.table-hud__zoom {
  min-width: 56px !important;
  font-variant-numeric: tabular-nums;
}

.table-hud__status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px 0 8px;
  color: var(--table-muted);
}

.table-hud__status::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--table-muted);
}

.table-hud__status[data-status="open"]::before {
  background: var(--bs-success, #198754);
  box-shadow: 0 0 6px var(--bs-success, #198754);
}

.table-hud__status[data-status="closed"]::before {
  background: var(--bs-warning, #ffc107);
}

.table-hud__status[data-status="offline"]::before {
  background: var(--table-accent);
}

.table-hud__status[data-status="denied"]::before {
  background: var(--table-danger);
}

/* ---- Who is at the table (PresenceDots.js) ----------------------------------------------- */

/* Small dots in the members' colours, close together. */
.table-hud__bar .table-hud__presence {
  display: inline-flex;
  gap: 3px;
  align-items: center;
  min-width: 0;
  padding: 0 8px 0 6px;
}

.table-hud__presence[hidden] {
  display: none;
}

.table-presence__dot {
  flex: none;
  width: 8px;
  height: 8px;
  background: var(--presence-color);
  border-radius: 50%;
}

.table-presence__dot.is-new {
  animation: table-presence-in 0.3s ease-out;
}

@keyframes table-presence-in {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
}

.table-presence__more {
  margin-left: 2px;
  color: var(--table-muted);
  font-size: 11px;
}

.table-presence {
  min-width: 240px;
  max-width: min(340px, calc(100% - 16px));
  padding: 8px 10px;
  user-select: text;
}

.table-presence__title {
  margin-bottom: 6px;
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.table-presence__list {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 6px 10px;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.table-presence__row {
  display: contents;
}

.table-presence__name {
  overflow: hidden;
  color: var(--table-text);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-presence__you,
.table-presence__tabs,
.table-presence__role {
  color: var(--table-muted);
}

.table-presence__role {
  font-size: 12px;
}

.table-presence__latency {
  color: var(--table-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.table-presence__latency[data-quality="good"] {
  color: var(--bs-success, #198754);
}

.table-presence__latency[data-quality="fair"] {
  color: var(--bs-warning, #ffc107);
}

.table-presence__latency[data-quality="poor"] {
  color: var(--table-danger);
}

@media (prefers-reduced-motion: reduce) {
  .table-presence__dot.is-new {
    animation: none;
  }
}

@media (max-width: 576px) {
  .table-hud__bar .table-hud__presence {
    padding: 0 6px 0 4px;
  }
}

/* ---- View only and no access ------------------------------------------------------------- */

/* A viewer keeps the camera controls; every tool that changes something is gone. */
.table-root.is-viewer .table-hud__add,
.table-root.is-viewer .table-character-badge,
.table-root.is-viewer .table-hud__ruler,
.table-root.is-viewer .table-hud__snap,
.table-root.is-viewer .table-hud__save-view {
  display: none !important;
}

.table-root.is-viewer .table-viewport {
  cursor: grab;
}

.table-root.is-viewer .table-viewport.is-panning {
  cursor: grabbing;
}

.table-hud__view-only {
  display: inline-flex;
  align-items: center;
  align-self: center;
  margin: 0 4px;
  padding: 2px 9px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--table-accent);
  background: rgba(var(--table-accent-rgb), 0.12);
  border: 1px solid rgba(var(--table-accent-rgb), 0.45);
  border-radius: 999px;
}

.table-hud__view-only[hidden] {
  display: none;
}

/* The access dialog covers everything, the back link included: its actions lead away. */
.table-access {
  position: absolute;
  inset: 0;
  z-index: var(--table-z-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(var(--table-black-rgb), 0.55);
  backdrop-filter: blur(4px);
}

.table-access[hidden] {
  display: none;
}

.table-access__dialog {
  position: static;
  align-items: center;
  gap: 12px;
  width: min(400px, 100%);
  max-height: none;
  padding: 24px 24px 22px;
  text-align: center;
  background: rgba(var(--table-panel-rgb), 0.97);
  border-color: rgba(var(--table-accent-rgb), 0.45);
  box-shadow:
    0 12px 40px rgba(var(--table-black-rgb), 0.55),
    0 0 0 1px rgba(var(--table-accent-rgb), 0.08),
    0 0 32px rgba(var(--table-accent-rgb), 0.12);
}

.table-access__sigil {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  color: var(--table-accent);
  background: radial-gradient(circle, rgba(var(--table-accent-rgb), 0.18), transparent 70%);
  border: 1px solid rgba(var(--table-accent-rgb), 0.4);
  border-radius: 50%;
}

.table-access__sigil svg {
  width: 24px;
  height: 24px;
  fill: currentColor;
}

.table-access__title {
  margin: 0;
  font-size: 13px;
}

.table-access__text {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--table-text);
}

.table-access__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
}

.table-access__actions .table-panel__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 8px 18px;
  font-size: 14px;
  text-decoration: none;
}

/* "Log in" is the way in; "Back to campaign" stays quieter next to it. */
.table-access__login {
  color: var(--bs-white, #fff) !important;
  background: rgba(var(--table-accent-rgb), 0.85) !important;
  border-color: rgba(var(--table-accent-rgb), 1) !important;
}

.table-access__login:hover,
.table-access__login:focus-visible {
  background: rgba(var(--table-accent-rgb), 1) !important;
}

.table-access__login + .table-access__back {
  background: transparent;
}

/* Nothing behind the dialog takes input; the back link is one of its actions now. */
.table-root.is-denied > :not(.table-access) {
  pointer-events: none;
}

body:has(.table-root.is-denied) .table-back-link {
  display: none;
}

.table-hud__toasts {
  position: absolute;
  left: 50%;
  bottom: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: min(480px, calc(100% - 32px));
  transform: translateX(-50%);
}

.table-toast {
  padding: 8px 14px;
  animation: table-toast-in 0.18s ease-out;
}

.table-toast--error {
  border-color: rgba(220, 53, 69, 0.6);
  border-color: color-mix(in srgb, var(--table-danger) 60%, transparent);
}

@keyframes table-toast-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}

@media (max-width: 576px) {
  .table-hud__status {
    display: none;
  }

  /* The bar has to fit a phone: "Add" shows only its "+", labels never wrap. */
  .table-hud__bar button {
    white-space: nowrap;
  }

  .table-hud__bar .table-hud__add {
    font-size: 0;
  }

  .table-hud__bar .table-hud__add::before {
    margin: 0;
    font-size: 18px;
  }

  .table-hud__toasts {
    bottom: 64px;
  }
}

@media (max-width: 576px) {
  .table-panel {
    top: auto;
    bottom: 64px;
    width: calc(100% - 32px);
    max-height: 45%;
  }

  /* A bottom sheet, collapsed to its title by default, so the selected token and its step
     arrows stay in sight above it (Inspector pans them there). */
  .table-inspector {
    right: 16px;
    left: 16px;
    width: auto;
    max-height: min(40dvh, 360px);
    padding-top: 0;
  }

  .table-inspector__head {
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 0 -12px;
    padding: 0 4px 0 12px;
    background: rgba(var(--table-panel-rgb), 0.96);
  }

  .table-inspector__toggle {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    color: var(--table-accent);
    background: none;
    border: 0;
    cursor: pointer;
  }

  .table-inspector__toggle svg {
    width: 20px;
    height: 20px;
    transition: rotate 0.2s ease;
  }

  .table-inspector:not(.is-collapsed) .table-inspector__toggle svg {
    rotate: 180deg;
  }

  .table-inspector.is-collapsed {
    padding-bottom: 0;
  }

  .table-inspector.is-collapsed > :not(.table-inspector__head) {
    display: none;
  }

  /* The map panel makes way for the sheet at the top. */
  .table-root:has(> .table-inspector:not([hidden])) .table-map-panel {
    top: 64px;
    bottom: auto;
    max-height: 30%;
  }

  .table-token-picker {
    top: 64px;
    bottom: 64px;
    max-height: none;
  }

  .table-root.has-token-picker .table-map-panel,
  .table-root.has-token-picker .table-inspector,
  .table-root.has-prop-picker .table-map-panel,
  .table-root.has-prop-picker .table-inspector {
    display: none;
  }

  /* A bottom sheet: the table stays in sight above it for placing. */
  .table-prop-picker {
    top: auto;
    bottom: 64px;
    max-height: 55%;
  }

  .table-prop-picker__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Phones and narrow windows: the panel covers the whole table. */
@media (max-width: 576px) {
  .table-character-panel {
    width: 100%;
  }

  .table-root.has-character-panel .table-inspector,
  .table-root.has-character-panel .table-map-panel,
  body:has(.table-root.has-character-panel) .table-back-link {
    display: none;
  }
}

/* Too narrow for the HUD bar beside the panel: it stays in place, under the panel. */
@media (max-width: 800px) {
  .table-root.has-character-panel .table-hud__bar {
    right: 16px;
  }
}

/* ---- Image showcase --------------------------------------------------------------------- */

.table-showcase {
  position: absolute;
  inset: 0;
  z-index: var(--table-z-showcase);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 24px 16px;
  background: rgba(var(--table-black-rgb), 0.72);
  backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity 600ms ease;
}

.table-showcase.is-open {
  opacity: 1;
}

.table-showcase[hidden] {
  display: none;
}

/* Cards are stacked in one grid cell, so a new one crossfades over the old one. */
.table-showcase__stage {
  display: grid;
  place-items: center;
  max-width: 100%;
  min-height: 0;
}

.table-showcase__card {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  max-width: 100%;
  margin: 0;
  opacity: 0;
  transform: scale(0.96);
  transition:
    opacity 600ms ease,
    transform 600ms ease;
}

.table-showcase__card.is-visible {
  opacity: 1;
  transform: none;
}

.table-showcase__image {
  display: block;
  max-width: min(100%, 1100px);
  max-height: calc(100vh - 190px);
  max-height: calc(100dvh - 190px);
  object-fit: contain;
  border: 1px solid rgba(var(--table-white-rgb), 0.25);
  border-radius: 8px;
  box-shadow: 0 20px 70px rgba(var(--table-black-rgb), 0.7);
}

/* Without an image: the first letter, like the token picker's portraits. */
.table-showcase__sigil {
  display: grid;
  place-items: center;
  width: min(60vw, 260px);
  aspect-ratio: 1;
  color: var(--table-accent);
  font-size: min(30vw, 130px);
  font-weight: 600;
  background: rgba(var(--table-accent-rgb), 0.15);
  border: 3px solid rgba(var(--table-accent-rgb), 0.55);
  border-radius: 50%;
}

.table-showcase__name {
  max-width: 100%;
  padding: 4px 16px;
  overflow: hidden;
  color: var(--table-text);
  font-size: clamp(18px, 3vw, 28px);
  font-weight: 600;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(var(--table-panel-rgb), 0.9);
  border: 1px solid rgba(var(--table-white-rgb), 0.2);
  border-radius: 999px;
}

.table-showcase__controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

.table-showcase__button {
  padding: 6px 14px;
  color: var(--table-text);
  font: inherit;
  background: rgba(var(--table-panel-rgb), 0.92);
  border: 1px solid rgba(var(--table-white-rgb), 0.25);
  border-radius: 8px;
  cursor: pointer;
}

.table-showcase__button:hover,
.table-showcase__button:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.25);
  outline: none;
}

.table-showcase__button[hidden] {
  display: none;
}

.table-token-picker__row {
  display: flex;
  align-items: center;
}

.table-token-picker__row > .table-token-picker__item {
  flex: 1;
  min-width: 0;
}

.table-token-picker__showcase {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--table-muted);
}

.table-token-picker__showcase .table-icon {
  width: 18px;
  height: 18px;
}

/* ---- Adventure picker ------------------------------------------------------------------- */

.table-adventure-picker {
  position: absolute;
  inset: 0;
  z-index: var(--table-z-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(var(--table-black-rgb), 0.45);
}

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

.table-adventure-picker__dialog {
  position: static;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(640px, 100%);
  max-height: min(80vh, 760px);
  max-height: min(80dvh, 760px);
}

.table-adventure-picker__dialog.is-busy {
  cursor: progress;
}

.table-adventure-picker__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* "Add hidden": next to the title in the adventure picker, under the search in the world picker. */
.table-adventure-picker__hidden {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--table-muted);
  font-size: 13px;
  cursor: pointer;
}

.table-adventure-picker__header .table-adventure-picker__hidden {
  margin-left: auto;
}

.table-adventure-picker__close {
  width: 28px;
  height: 28px;
  padding: 0;
  color: var(--table-text);
  font: inherit;
  font-size: 18px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

.table-adventure-picker__close:hover,
.table-adventure-picker__close:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.2);
  outline: none;
}

.table-adventure-picker__list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
  overflow: auto;
}

.table-adventure-picker__element {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* The element's name opens and closes its body. */
.table-adventure-picker__toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 4px 6px;
  color: var(--table-text);
  font: inherit;
  text-align: left;
  background: transparent;
  border: 0;
  border-radius: 4px;
  cursor: pointer;
}

.table-adventure-picker__toggle:hover,
.table-adventure-picker__toggle:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.14);
  outline: none;
}

.table-adventure-picker__toggle .table-icon {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--table-accent);
  transition: transform 0.15s ease;
}

.table-adventure-picker__toggle[aria-expanded="true"] .table-icon {
  transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
  .table-adventure-picker__toggle .table-icon {
    transition: none;
  }
}

.table-adventure-picker__element-name {
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Child elements sit in their parent's body and are indented under its name. */
.table-adventure-picker__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-left: 28px;
}

.table-adventure-picker__body[hidden] {
  display: none;
}

.table-adventure-picker__body > .table-adventure-picker__element {
  margin-left: -14px;
}

.table-adventure-picker__section {
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.table-adventure-picker__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  gap: 8px;
}

.table-adventure-picker__item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  padding: 4px;
  font-size: 12px;
  background: rgba(var(--table-white-rgb), 0.04);
  border: 1px solid rgba(var(--table-white-rgb), 0.1);
  border-radius: 8px;
}

/* The picture: adds the image. */
.table-adventure-picker__thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 2;
  padding: 0;
  overflow: hidden;
  color: rgba(var(--table-accent-rgb), 0.6);
  background: rgba(var(--table-accent-rgb), 0.12);
  border: 1px solid transparent;
  border-radius: 5px;
}

button.table-adventure-picker__thumb {
  cursor: pointer;
}

button.table-adventure-picker__thumb:hover,
button.table-adventure-picker__thumb:focus-visible {
  border-color: rgba(var(--table-accent-rgb), 0.8);
  outline: none;
  box-shadow: 0 0 0 2px rgba(var(--table-accent-rgb), 0.35);
}

.table-adventure-picker__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.table-adventure-picker__thumb.is-empty .table-icon {
  width: 32px;
  height: 32px;
}

/* Texts that become notes: the element's summary and GM notes, an item's description. */
.table-adventure-picker__texts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.table-adventure-picker__text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  color: var(--table-text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  background: rgba(var(--table-white-rgb), 0.04);
  border: 1px solid rgba(var(--table-white-rgb), 0.12);
  border-radius: 6px;
  cursor: pointer;
}

.table-adventure-picker__text .table-icon {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--table-accent);
}

.table-adventure-picker__text:hover,
.table-adventure-picker__text:focus-visible {
  background: rgba(var(--table-accent-rgb), 0.18);
  border-color: rgba(var(--table-accent-rgb), 0.5);
  outline: none;
}

.table-adventure-picker__item .table-adventure-picker__text {
  padding: 3px 6px;
  font-size: 11px;
}

/* The world picker (WorldPicker.js) shares the adventure picker's dialog and the settings tabs. */
.table-world-picker__pane {
  min-height: 0;
}

.table-world-picker__pane:not([hidden]) {
  flex: 1 1 auto;
}

.table-world-picker__search {
  width: 100%;
  padding: 6px 10px;
  color: var(--table-text);
  font: inherit;
  background: rgba(var(--table-black-rgb), 0.25);
  border: 1px solid rgba(var(--table-white-rgb), 0.16);
  border-radius: 6px;
}

.table-world-picker__search:focus-visible {
  border-color: var(--table-accent);
  outline: none;
}

.table-world-picker__parent {
  overflow: hidden;
  color: var(--table-muted);
  font-size: 11px;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.table-adventure-picker__dialog.is-busy button:disabled {
  cursor: progress;
  opacity: 0.6;
}

.table-adventure-picker__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .table-showcase,
  .table-showcase__card {
    transition: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .table-toast,
  .is-playing .table-audio__icon {
    animation: none;
  }

  .table-character-panel,
  .table-character-panel.is-open {
    transition: none;
  }
}

/* Path decorations (objects/pathStyles.js, pathDecor.js) ----------------------------------- */

/* Gentle, CSS-only animations of animatable path styles. A stamp's wrapper carries its
   placement, so transforms here move the stamp around its own centre. */
.table-path-anim {
  transform-box: fill-box;
  transform-origin: center;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

.table-path-anim--flicker {
  animation-name: table-path-flicker;
  animation-direction: alternate;
}

.table-path-anim--twinkle {
  animation-name: table-path-twinkle;
}

.table-path-anim--pulse {
  animation-name: table-path-pulse;
}

.table-path-anim--wave {
  animation-name: table-path-wave;
}

/* Dashes that move along the stroke (the river's highlight). */
.table-path-anim--flow {
  animation-name: table-path-flow;
  animation-timing-function: linear;
}

@keyframes table-path-flicker {
  0% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }

  60% {
    opacity: 0.55;
    transform: translateY(-12%) scale(0.92);
  }

  100% {
    opacity: 0.85;
    transform: translateY(-4%) scale(1.04);
  }
}

@keyframes table-path-twinkle {
  0%,
  100% {
    opacity: 0.25;
    transform: scale(0.6);
  }

  50% {
    opacity: 1;
    transform: scale(1.1);
  }
}

@keyframes table-path-pulse {
  0%,
  100% {
    opacity: 0.55;
  }

  30% {
    opacity: 1;
  }
}

@keyframes table-path-wave {
  0%,
  100% {
    opacity: 0.35;
  }

  25% {
    opacity: 1;
  }
}

@keyframes table-path-flow {
  to {
    stroke-dashoffset: var(--dash-period, -24px);
  }
}

/* Lightning crackles: two bolts take turns, snapping rather than fading. */
.table-path-anim--crackle,
.table-path-anim--crackle-alt {
  animation-timing-function: steps(1, end);
}

.table-path-anim--crackle {
  animation-name: table-path-crackle;
}

.table-path-anim--crackle-alt {
  animation-name: table-path-crackle-alt;
}

@keyframes table-path-crackle {
  0%,
  55%,
  100% {
    opacity: 1;
  }

  42% {
    opacity: 0.15;
  }

  78% {
    opacity: 0.6;
  }
}

@keyframes table-path-crackle-alt {
  0%,
  55%,
  100% {
    opacity: 0;
  }

  42% {
    opacity: 1;
  }

  78% {
    opacity: 0.45;
  }
}

/* Motions any path can play (objects/pathMotions.js). "Step by step" reveals stamps in turn:
   each stamp's delay is its place on the path and the cycle is the whole path; the variant sets
   how much of the cycle a stamp stays visible (about six stamps at once). */
.table-path-anim[class*="table-path-anim--trail-"] {
  animation-timing-function: linear;
  animation-fill-mode: backwards;
}

.table-path-anim--trail-50 {
  animation-name: table-path-trail-50;
}

@keyframes table-path-trail-50 {
  0% {
    opacity: 0;
    transform: scale(1.18);
  }

  2% {
    opacity: 1;
    transform: scale(1);
  }

  22% {
    opacity: 1;
  }

  50%,
  100% {
    opacity: 0;
  }
}

.table-path-anim--trail-25 {
  animation-name: table-path-trail-25;
}

@keyframes table-path-trail-25 {
  0% {
    opacity: 0;
    transform: scale(1.18);
  }

  1.5% {
    opacity: 1;
    transform: scale(1);
  }

  11% {
    opacity: 1;
  }

  25%,
  100% {
    opacity: 0;
  }
}

.table-path-anim--trail-12 {
  animation-name: table-path-trail-12;
}

@keyframes table-path-trail-12 {
  0% {
    opacity: 0;
    transform: scale(1.18);
  }

  1% {
    opacity: 1;
    transform: scale(1);
  }

  5.4% {
    opacity: 1;
  }

  12%,
  100% {
    opacity: 0;
  }
}

.table-path-anim--trail-6 {
  animation-name: table-path-trail-6;
}

@keyframes table-path-trail-6 {
  0% {
    opacity: 0;
    transform: scale(1.18);
  }

  0.6% {
    opacity: 1;
    transform: scale(1);
  }

  2.7% {
    opacity: 1;
  }

  6%,
  100% {
    opacity: 0;
  }
}

.table-path-anim--trail-3 {
  animation-name: table-path-trail-3;
}

@keyframes table-path-trail-3 {
  0% {
    opacity: 0;
    transform: scale(1.18);
  }

  0.4% {
    opacity: 1;
    transform: scale(1);
  }

  1.4% {
    opacity: 1;
  }

  3%,
  100% {
    opacity: 0;
  }
}

.table-path-anim--wave-side {
  animation-name: table-path-wave-side;
}

@keyframes table-path-wave-side {
  0%,
  100% {
    transform: translateX(-22%);
  }

  50% {
    transform: translateX(22%);
  }
}

.table-path-anim--sway {
  animation-name: table-path-sway;
}

@keyframes table-path-sway {
  0%,
  100% {
    transform: rotate(-11deg);
  }

  50% {
    transform: rotate(11deg);
  }
}

.table-path-anim--breathe {
  animation-name: table-path-breathe;
}

@keyframes table-path-breathe {
  0%,
  100% {
    opacity: 0.7;
    transform: scale(0.84);
  }

  50% {
    opacity: 1;
    transform: scale(1.08);
  }
}

.table-path-anim--breathe-line {
  animation-name: table-path-breathe-line;
}

@keyframes table-path-breathe-line {
  0%,
  100% {
    opacity: 0.5;
  }

  50% {
    opacity: 1;
  }
}

.table-path-anim--blink {
  animation-name: table-path-blink;
  animation-timing-function: steps(1, end);
}

@keyframes table-path-blink {
  0% {
    opacity: 1;
  }

  14% {
    opacity: 0.45;
  }

  22% {
    opacity: 0.95;
  }

  48% {
    opacity: 0.6;
  }

  57% {
    opacity: 1;
  }

  80% {
    opacity: 0.35;
  }

  88% {
    opacity: 0.85;
  }
}

.table-path-anim--spin,
.table-path-anim--spin-back {
  animation-name: table-path-spin;
  animation-timing-function: linear;
}

.table-path-anim--spin-back {
  animation-direction: reverse;
}

@keyframes table-path-spin {
  to {
    transform: rotate(360deg);
  }
}

.table-path-anim--turbulence {
  animation-name: table-path-turbulence;
}

@keyframes table-path-turbulence {
  0%,
  100% {
    transform: translate(0, 0) rotate(0);
  }

  17% {
    transform: translate(7%, -9%) rotate(6deg);
  }

  34% {
    transform: translate(-6%, 4%) rotate(-5deg);
  }

  51% {
    transform: translate(9%, 6%) rotate(3deg);
  }

  68% {
    transform: translate(-8%, -5%) rotate(-7deg);
  }

  85% {
    transform: translate(4%, 8%) rotate(4deg);
  }
}

.table-path-anim--float {
  animation-name: table-path-float;
}

@keyframes table-path-float {
  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(var(--path-float, -4px));
  }
}

/* "Draw itself" in 24 slots of a cycle (pathMotions.js DRAW_SLOTS): step k (stamp k/16 of the
   way along the path, or corridor k of the clip around a line) shows from slot k on, the drawing
   holds, then fades in slots 20–23. Stepped and on a shared grid of instants, so nothing repaints
   between the steps. */
.table-path-anim[class*="table-path-anim--draw-in-"] {
  animation-timing-function: steps(1, end);
  /* A stamp is placed by its transform attribute: keep SVG's own origin for it (these steps
     only switch visibility, they never transform). */
  transform-box: view-box;
  transform-origin: 0 0;
}

.table-path-anim--draw-in-0 {
  animation-name: table-path-draw-in-0;
}

@keyframes table-path-draw-in-0 {
  0%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-1 {
  animation-name: table-path-draw-in-1;
}

@keyframes table-path-draw-in-1 {
  0% {
    visibility: hidden;
  }

  4.167%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-2 {
  animation-name: table-path-draw-in-2;
}

@keyframes table-path-draw-in-2 {
  0% {
    visibility: hidden;
  }

  8.333%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-3 {
  animation-name: table-path-draw-in-3;
}

@keyframes table-path-draw-in-3 {
  0% {
    visibility: hidden;
  }

  12.5%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-4 {
  animation-name: table-path-draw-in-4;
}

@keyframes table-path-draw-in-4 {
  0% {
    visibility: hidden;
  }

  16.667%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-5 {
  animation-name: table-path-draw-in-5;
}

@keyframes table-path-draw-in-5 {
  0% {
    visibility: hidden;
  }

  20.833%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-6 {
  animation-name: table-path-draw-in-6;
}

@keyframes table-path-draw-in-6 {
  0% {
    visibility: hidden;
  }

  25%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-7 {
  animation-name: table-path-draw-in-7;
}

@keyframes table-path-draw-in-7 {
  0% {
    visibility: hidden;
  }

  29.167%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-8 {
  animation-name: table-path-draw-in-8;
}

@keyframes table-path-draw-in-8 {
  0% {
    visibility: hidden;
  }

  33.333%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-9 {
  animation-name: table-path-draw-in-9;
}

@keyframes table-path-draw-in-9 {
  0% {
    visibility: hidden;
  }

  37.5%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-10 {
  animation-name: table-path-draw-in-10;
}

@keyframes table-path-draw-in-10 {
  0% {
    visibility: hidden;
  }

  41.667%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-11 {
  animation-name: table-path-draw-in-11;
}

@keyframes table-path-draw-in-11 {
  0% {
    visibility: hidden;
  }

  45.833%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-12 {
  animation-name: table-path-draw-in-12;
}

@keyframes table-path-draw-in-12 {
  0% {
    visibility: hidden;
  }

  50%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-13 {
  animation-name: table-path-draw-in-13;
}

@keyframes table-path-draw-in-13 {
  0% {
    visibility: hidden;
  }

  54.167%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-14 {
  animation-name: table-path-draw-in-14;
}

@keyframes table-path-draw-in-14 {
  0% {
    visibility: hidden;
  }

  58.333%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-in-15 {
  animation-name: table-path-draw-in-15;
}

@keyframes table-path-draw-in-15 {
  0% {
    visibility: hidden;
  }

  62.5%,
  100% {
    visibility: visible;
  }
}

.table-path-anim--draw-fade {
  animation-name: table-path-draw-fade;
  animation-timing-function: steps(1, end);
}

@keyframes table-path-draw-fade {
  0%,
  83.333% {
    opacity: 1;
  }

  87.5% {
    opacity: 0.67;
  }

  91.667% {
    opacity: 0.33;
  }

  95.833%,
  100% {
    opacity: 0;
  }
}

/* "Glint": a bead of light running along the path, then a pause. */
.table-path-glint {
  opacity: 0;
  pointer-events: none;
}

.table-path-anim--glint {
  animation-name: table-path-glint;
  animation-timing-function: ease-in-out;
}

@keyframes table-path-glint {
  0% {
    opacity: 0;
    stroke-dashoffset: var(--glint-from, 0.05);
  }

  6%,
  62% {
    opacity: 0.85;
  }

  70%,
  100% {
    opacity: 0;
    stroke-dashoffset: -1;
  }
}

/* "March": marching ants. One in three stamps (or dashes along a line) is bright, the bright
   one jumps forward every third of the cycle. Stepped, so nothing repaints between the steps. */
.table-path-anim--march {
  animation-name: table-path-march;
  animation-timing-function: steps(1, end);
}

@keyframes table-path-march {
  0% {
    opacity: 1;
  }

  33.3%,
  100% {
    opacity: var(--path-march-dim, 0.3);
  }
}

/* Dashes along a line only show while bright: dimmed they would smudge the line's gaps. */
.table-path-extras--march {
  --path-march-dim: 0;
}

/* "Heartbeat": lub-dub, then rest. Stamps thump; a line brightens over a swelling glow. */
.table-path-anim--heartbeat {
  animation-name: table-path-heartbeat;
}

@keyframes table-path-heartbeat {
  0%,
  42%,
  100% {
    transform: scale(1);
  }

  8% {
    transform: scale(1.28);
  }

  16% {
    transform: scale(1.04);
  }

  25% {
    transform: scale(1.2);
  }
}

.table-path-aura {
  opacity: 0;
  pointer-events: none;
}

.table-path-anim--heartbeat-aura {
  animation-name: table-path-heartbeat-aura;
}

@keyframes table-path-heartbeat-aura {
  0%,
  45%,
  100% {
    opacity: 0;
  }

  8% {
    opacity: 0.35;
  }

  16% {
    opacity: 0.1;
  }

  25% {
    opacity: 0.28;
  }
}

.table-path-anim--heartbeat-line {
  animation-name: table-path-heartbeat-line;
}

@keyframes table-path-heartbeat-line {
  0%,
  45%,
  100% {
    opacity: 0.72;
  }

  8%,
  25% {
    opacity: 1;
  }

  16% {
    opacity: 0.85;
  }
}

/* "Sparkle", "Ripple", "Rise": small extras along the path, hidden between their moments. */
.table-path-extras {
  pointer-events: none;
}

.table-path-extras .table-path-anim {
  opacity: 0;
}

.table-path-anim--sparkle {
  animation-name: table-path-sparkle;
}

@keyframes table-path-sparkle {
  0% {
    opacity: 0;
    transform: scale(0.2) rotate(0deg);
  }

  10% {
    opacity: 1;
    transform: scale(1) rotate(30deg);
  }

  30% {
    opacity: 0.8;
    transform: scale(0.8) rotate(60deg);
  }

  42%,
  100% {
    opacity: 0;
    transform: scale(0.3) rotate(90deg);
  }
}

.table-path-anim--ripple {
  animation-name: table-path-ripple;
  animation-timing-function: ease-out;
}

@keyframes table-path-ripple {
  0% {
    opacity: 0.8;
    transform: scale(0.2);
  }

  70%,
  100% {
    opacity: 0;
    transform: scale(1.5);
  }
}

.table-path-anim--rise {
  animation-name: table-path-rise;
  animation-timing-function: linear;
}

@keyframes table-path-rise {
  0% {
    opacity: 0;
    transform: translateY(0);
  }

  20% {
    opacity: 0.85;
  }

  100% {
    opacity: 0;
    transform: translateY(var(--path-rise, -24px));
  }
}

/* Runes glow softly in their own colour. */
.table-path-runes {
  filter: drop-shadow(0 0 2px currentColor);
}

/* Paused while the page is hidden; off when the system asks for less motion. */
.is-page-hidden .table-path-anim {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .table-path-anim {
    animation: none;
  }
}

/* A path's label: light text with a dark halo, readable on maps and on the bare table. */
.table-path__label {
  fill: var(--table-text, #ece8e1);
  stroke: rgba(var(--table-black-rgb, 0, 0, 0), 0.55);
  stroke-width: 0.14em;
  stroke-linejoin: round;
  paint-order: stroke;
  font-family: var(--bs-body-font-family, system-ui, sans-serif);
  font-weight: 600;
  letter-spacing: 0.05em;
  pointer-events: none;
}

/* The path bar's style picker: the current style as a little preview, and a menu of all
   styles drawn by the table's own renderer. */
.table-path-picker {
  position: relative;
}

.table-path-picker__button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  max-width: 170px;
  padding: 0 10px 0 4px;
  color: var(--table-text);
  font: inherit;
  font-size: 12px;
  background: rgba(var(--table-white-rgb), 0.06);
  border: 1px solid rgba(var(--table-white-rgb), 0.12);
  border-radius: 14px;
  cursor: pointer;
}

.table-path-picker__button:hover,
.table-path-picker__button:focus-visible,
.table-path-picker__button[aria-expanded="true"] {
  border-color: rgba(var(--table-accent-rgb), 0.6);
  outline: none;
}

.table-path-picker__preview .table-path-swatch {
  display: block;
  width: 46px;
  height: 18px;
}

.table-path-picker__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.table-path-picker__menu {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 0;
  z-index: 1;
  box-sizing: border-box;
  width: min(450px, calc(100vw - 24px));
  max-height: min(480px, 62vh);
  padding: 0 8px 8px;
  overflow: auto;
  background: rgba(var(--table-panel-rgb), 0.97);
  border: 1px solid rgba(var(--table-white-rgb), 0.12);
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(var(--table-black-rgb), 0.5);
}

.table-path-picker__menu.is-below {
  top: calc(100% + 10px);
  bottom: auto;
}

.table-path-picker__menu[hidden] {
  display: none;
}

/* A tab per group, sticking to the top of the menu while the previews scroll under it. */
.table-path-picker__tabs {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 -8px 2px;
  padding: 8px 8px 6px;
  background: rgb(var(--table-panel-rgb));
  border-bottom: 1px solid rgba(var(--table-white-rgb), 0.08);
}

.table-path-picker__tab {
  min-height: 24px;
  padding: 1px 9px;
  color: var(--table-muted);
  font: inherit;
  font-size: 11px;
  white-space: nowrap;
  background: transparent;
  border: 1px solid rgba(var(--table-white-rgb), 0.16);
  border-radius: 999px;
  cursor: pointer;
}

.table-path-picker__tab:hover {
  color: var(--table-text);
}

.table-path-picker__tab[aria-current="true"] {
  color: var(--table-text);
  background: rgba(var(--table-accent-rgb), 0.2);
  border-color: var(--table-accent);
}

.table-path-picker__group {
  margin: 6px 4px 4px;
  color: var(--table-muted);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.table-path-picker__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(98px, 1fr));
  gap: 4px;
}

.table-path-picker__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
  padding: 4px 4px 5px;
  color: var(--table-text);
  font: inherit;
  font-size: 11px;
  background: rgba(var(--table-white-rgb), 0.03);
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
}

.table-path-picker__item .table-path-swatch {
  width: 90px;
  height: 35px;
}

.table-path-picker__item > span {
  overflow: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.table-path-picker__item:hover,
.table-path-picker__item:focus-visible {
  background: rgba(var(--table-white-rgb), 0.08);
  outline: none;
}

.table-path-picker__item[aria-pressed="true"] {
  background: rgba(var(--table-accent-rgb), 0.18);
  border-color: rgba(var(--table-accent-rgb), 0.6);
}

.table-path-bar__select.table-path-bar__motion {
  max-width: 180px;
}

.table-path-bar__select[hidden] {
  display: none;
}

.table-path-bar__spacing[hidden] {
  display: none;
}

/* Narrow screens: two columns. */
@media (max-width: 576px) {
  .table-path-picker__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ---- Reveal fades: objects the GM shows or hides (TableObject.fadeIn / destroy) ----------- */

/* Players: a revealed object fades in, a hidden or deleted one fades out before it goes. */
.table-object.is-appearing {
  animation: table-object-appear 0.35s ease-out;
}

.table-object.is-vanishing {
  animation: table-object-vanish 0.35s ease-in forwards;
  pointer-events: none;
}

.table-object.is-vanishing * {
  pointer-events: none;
}

@keyframes table-object-appear {
  from {
    opacity: 0;
  }
}

@keyframes table-object-vanish {
  to {
    opacity: 0;
  }
}

/* The GM: the hidden look comes and goes gradually. */
.table-object > .table-object__content,
.table-path__drawing,
.table-prop-light {
  transition: opacity 0.35s ease;
}

@media (prefers-reduced-motion: reduce) {
  .table-object.is-appearing,
  .table-object.is-vanishing {
    animation: none;
  }

  .table-object > .table-object__content,
  .table-path__drawing,
  .table-prop-light {
    transition: none;
  }
}

/* ---- Animated table effect (TableEffects.js) ----------------------------------------------- */

/* One screen-space canvas: before the world (below the objects) or, `is-above`, over everything
   on the table but below the overlay (marquee, pings) and the HUD. Never takes pointer input. */
.table-effects {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.table-effects.is-above {
  z-index: 4;
}

.table-effects[hidden] {
  display: none;
}

/* An image's own effect (ImageEffects.js): inside the image, below its grid. */
.table-image-effect {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  pointer-events: none;
}

/* ---- Image frames, set 2: frames 6–15 (objects/imageFramesSet2.js) --------------------------
   Sheets (.if-sheet) are SVGs in world px over the frame layer (placed by the shared
   `.table-image-frame > *` rule); edge strips (.if-edge) hold one
   side of an outer frame and scale from the image's edge (--x: left and right, --y: top and
   bottom). Each frame sets its palette here. Only whole sheets, strips and plain elements move,
   on transform and opacity; with the animations off they show their resting state. */

/* An inner frame stays inside the image. */
.table-image-frame[data-placement="inner"] .if-sheet {
  overflow: hidden;
}

/* Parchment: a sepia vignette under the charred, torn rim. */
.table-image-frame.if-parchment {
  --if-pa-char: #130b06;
  --if-pa-scorch: rgba(86, 45, 14, 0.62);
  --if-pa-ember: rgba(226, 118, 42, 0.6);
  --if-pa-shade: rgba(78, 42, 12, 0.7);
  --if-pa-tint: rgba(150, 104, 44, 0.24);

  background: radial-gradient(ellipse at center, transparent 55%, var(--if-pa-tint));
  box-shadow: inset 0 0 calc(var(--if-t) * 1.4) calc(var(--if-t) * 0.2) var(--if-pa-shade);
}

.if-parchment .if-pa-char { fill: var(--if-pa-char); }
.if-parchment .if-pa-scorch { fill: var(--if-pa-scorch); }
.if-parchment .if-pa-ember { stroke: var(--if-pa-ember); }

/* Stone: two sets of carved runes glow in turn. */
.table-image-frame.if-stone {
  --if-st-a: #6b6964;
  --if-st-b: #5c5a56;
  --if-st-c: #78756f;
  --if-st-joint: #2b2a28;
  --if-st-rune: #93e6f0;
  --if-st-halo: rgba(110, 220, 236, 0.32);
}

.if-stone .if-st-face { stroke: var(--if-st-joint); }
.if-stone .if-st-face--0 { fill: var(--if-st-a); }
.if-stone .if-st-face--1 { fill: var(--if-st-b); }
.if-stone .if-st-face--2 { fill: var(--if-st-c); }
.if-stone .if-st-sheen-hi { stop-color: rgba(255, 255, 255, 0.2); }
.if-stone .if-st-sheen-mid { stop-color: rgba(255, 255, 255, 0); }
.if-stone .if-st-sheen-lo { stop-color: rgba(0, 0, 0, 0.28); }
.if-stone .if-st-cast { stroke: rgba(0, 0, 0, 0.45); }
.if-stone .if-st-light { stroke: rgba(255, 255, 255, 0.26); }
.if-stone .if-st-shade { stroke: rgba(0, 0, 0, 0.38); }
.if-stone .if-st-crack { stroke: rgba(22, 20, 18, 0.65); }
.if-stone .if-st-speck { stroke: rgba(232, 226, 214, 0.22); }
.if-stone .if-st-carve { stroke: #22211e; }
.if-stone .if-st-halo { stroke: var(--if-st-halo); }
.if-stone .if-st-glow { stroke: var(--if-st-rune); }

.if-stone .if-st-runes {
  opacity: 0.4;
  animation: if-st-pulse 6s ease-in-out infinite;
}

.if-stone .if-st-runes--1 {
  animation-delay: -3s;
}

@keyframes if-st-pulse {
  0%,
  100% {
    opacity: 0.12;
  }

  50% {
    opacity: 0.9;
  }
}

/* Vines: the crest leaves and the small ones sway against each other. */
.table-image-frame.if-vines {
  --if-vi-stem: #3e5c26;
  --if-vi-stem2: #2b4019;
  --if-vi-leaf-a: #5e9340;
  --if-vi-leaf-b: #7cae4e;
  --if-vi-vein: rgba(32, 56, 20, 0.7);
  --if-vi-blossom: #f1e4ee;
  --if-vi-heart: #e9c54b;
}

.if-vines .if-vi-stem { stroke: var(--if-vi-stem); }
.if-vines .if-vi-stem2 { stroke: var(--if-vi-stem2); }
.if-vines .if-vi-shadow { fill: rgba(0, 0, 0, 0.32); }
.if-vines .if-vi-shadow-line { stroke: rgba(0, 0, 0, 0.3); }
.if-vines .if-vi-leaf--0 { fill: var(--if-vi-leaf-a); }
.if-vines .if-vi-leaf--1 { fill: var(--if-vi-leaf-b); }
.if-vines .if-vi-vein { stroke: var(--if-vi-vein); }
.if-vines .if-vi-blossom { fill: var(--if-vi-blossom); }
.if-vines .if-vi-heart { stroke: var(--if-vi-heart); }

.if-vines .if-vi-leaves--0 {
  animation: if-vi-sway-a 6s ease-in-out infinite;
}

.if-vines .if-vi-leaves--1 {
  animation: if-vi-sway-b 7.5s ease-in-out infinite;
}

@keyframes if-vi-sway-a {
  0%,
  100% {
    transform: translate(0, 0);
  }

  50% {
    transform: translate(calc(var(--if-t) * 0.05), calc(var(--if-t) * -0.035));
  }
}

@keyframes if-vi-sway-b {
  0%,
  100% {
    transform: translate(0, 0);
  }

  50% {
    transform: translate(calc(var(--if-t) * -0.045), calc(var(--if-t) * 0.03));
  }
}

/* Frost: a cold haze at the edges and corners; the crystals shimmer, their tips sparkle. */
.table-image-frame.if-frost {
  --if-fr-line: rgba(226, 245, 255, 0.78);
  --if-fr-haze: rgba(188, 228, 255, 0.22);
  --if-fr-mist: rgba(214, 238, 255, 0.42);

  background:
    radial-gradient(circle at 0 0, var(--if-fr-mist), transparent calc(var(--if-t) * 2.6)),
    radial-gradient(circle at 100% 0, var(--if-fr-mist), transparent calc(var(--if-t) * 2.6)),
    radial-gradient(circle at 100% 100%, var(--if-fr-mist), transparent calc(var(--if-t) * 2.6)),
    radial-gradient(circle at 0 100%, var(--if-fr-mist), transparent calc(var(--if-t) * 2.6));
  box-shadow: inset 0 0 calc(var(--if-t) * 1.1) calc(var(--if-t) * 0.12) var(--if-fr-mist);
}

.if-frost .if-fr-line { stroke: var(--if-fr-line); }
.if-frost .if-fr-haze { stroke: var(--if-fr-haze); }
.if-frost .if-fr-bright { stroke: #fff; }
.if-frost .if-fr-sparkle { fill: #fff; }

.if-frost .if-fr-shimmer {
  opacity: 0.25;
  animation: if-fr-shimmer 8s ease-in-out infinite;
}

.if-frost .if-fr-sparkles {
  opacity: 0.6;
  animation: if-fr-twinkle 4s ease-in-out infinite;
}

.if-frost .if-fr-sparkles--1 {
  animation-delay: -2s;
}

@keyframes if-fr-shimmer {
  0%,
  100% {
    opacity: 0;
  }

  50% {
    opacity: 0.65;
  }
}

@keyframes if-fr-twinkle {
  0%,
  100% {
    opacity: 0;
  }

  50% {
    opacity: 1;
  }
}

/* HUD: a scan line runs down the image every few seconds; a status light blinks. */
.table-image-frame.if-hud {
  --if-hud: #5ef2ff;
  --if-hud-rgb: 94, 242, 255;
  --if-hud-light: #ffb347;
}

.if-hud .if-hud-line,
.if-hud .if-hud-marker { stroke: var(--if-hud); }
.if-hud .if-hud-marker { fill: var(--if-hud); stroke: none; }
.if-hud .if-hud-glow { stroke: rgba(var(--if-hud-rgb), 0.22); }
.if-hud .if-hud-tick { stroke: rgba(var(--if-hud-rgb), 0.6); }
.if-hud .if-hud-rim { stroke: rgba(var(--if-hud-rgb), 0.22); }

.if-hud .if-hud-text {
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  letter-spacing: 0.08em;
  fill: rgba(var(--if-hud-rgb), 0.85);
}

.if-hud .if-hud-scanbox {
  overflow: hidden;
}

/* The line is the scan element's bottom edge: from above the image down to its bottom. */
.if-hud .if-hud-scan {
  position: absolute;
  inset: 0;
  opacity: 0;
  background: linear-gradient(
    to bottom,
    transparent calc(100% - var(--if-t) * 2.5),
    rgba(var(--if-hud-rgb), 0.12) calc(100% - var(--if-t) * 0.12),
    rgba(var(--if-hud-rgb), 0.7) 100%
  );
  transform: translateY(-100%);
  animation: if-hud-scan 9s linear infinite;
}

.if-hud .if-hud-blink {
  top: calc(var(--if-t) * 0.62);
  right: calc(var(--if-t) * 0.62);
  left: auto;
  width: calc(var(--if-t) * 0.16);
  height: calc(var(--if-t) * 0.16);
  border-radius: 50%;
  background: var(--if-hud-light);
  animation: if-hud-blink 2s steps(1, end) infinite;
}

@keyframes if-hud-scan {
  0% {
    opacity: 0;
    transform: translateY(-100%);
  }

  4% {
    opacity: 1;
  }

  46% {
    opacity: 1;
  }

  50%,
  100% {
    opacity: 0;
    transform: translateY(0);
  }
}

@keyframes if-hud-blink {
  0% {
    opacity: 1;
  }

  50% {
    opacity: 0.15;
  }
}

/* Arcane: three sets of the same sigils, each a third of a step further on, shown in turn so
   the band creeps clockwise; the inner ring breathes. */
.table-image-frame.if-arcane {
  --if-ar-sigil: #e8d6ff;
  --if-ar-rgb: 170, 112, 255;
}

.if-arcane .if-ar-aura {
  inset: var(--if-t);
  width: auto;
  height: auto;
  box-shadow:
    0 0 calc(var(--if-t) * 0.8) calc(var(--if-t) * 0.12) rgba(var(--if-ar-rgb), 0.45),
    inset 0 0 calc(var(--if-t) * 0.3) rgba(var(--if-ar-rgb), 0.3);
}

.if-arcane .if-ar-ring { stroke: rgba(var(--if-ar-rgb), 0.7); }
.if-arcane .if-ar-ring--outer { stroke: rgba(var(--if-ar-rgb), 0.4); }
.if-arcane .if-ar-diamond { fill: var(--if-ar-sigil); }
.if-arcane .if-ar-halo { stroke: rgba(var(--if-ar-rgb), 0.32); }
.if-arcane .if-ar-sigil { stroke: var(--if-ar-sigil); }

.if-arcane .if-ar-band {
  opacity: 0;
  animation: if-ar-step 4.5s linear infinite;
}

.if-arcane .if-ar-band--0 {
  opacity: 0.85;
}

.if-arcane .if-ar-band--1 {
  animation-delay: -3s;
}

.if-arcane .if-ar-band--2 {
  animation-delay: -1.5s;
}

.if-arcane .if-ar-pulse {
  opacity: 0.4;
  animation: if-ar-breathe 6s ease-in-out infinite;
}

@keyframes if-ar-step {
  0% {
    opacity: 0;
  }

  8%,
  33% {
    opacity: 0.9;
  }

  41%,
  100% {
    opacity: 0;
  }
}

@keyframes if-ar-breathe {
  0%,
  100% {
    opacity: 0.2;
  }

  50% {
    opacity: 0.8;
  }
}

/* Flames: two sets of tongues flicker from the image's edge, embers rise in three waves. */
.table-image-frame.if-flames {
  --if-fl-outer: #b9270f;
  --if-fl-middle: #ff6b1a;
  --if-fl-core: #ffd25a;
  --if-fl-ember: #ffb347;
  --if-fl-hot: #fff0b0;
  --if-fl-rgb: 255, 96, 20;
}

.if-flames .if-fl-glow {
  inset: var(--if-t);
  width: auto;
  height: auto;
  box-shadow:
    0 0 calc(var(--if-t) * 0.8) calc(var(--if-t) * 0.15) rgba(var(--if-fl-rgb), 0.5),
    inset 0 0 calc(var(--if-t) * 0.25) rgba(var(--if-fl-rgb), 0.35);
}

.if-flames .if-fl-outer { fill: var(--if-fl-outer); }
.if-flames .if-fl-middle { fill: var(--if-fl-middle); }
.if-flames .if-fl-core { fill: var(--if-fl-core); }
.if-flames .if-fl-ember { stroke: var(--if-fl-ember); }
.if-flames .if-fl-ember--hot { stroke: var(--if-fl-hot); }

.if-flames .if-fl-tongues--0.if-edge--y { animation: if-fl-flicker-y 1.9s ease-in-out infinite; }
.if-flames .if-fl-tongues--0.if-edge--x { animation: if-fl-flicker-x 1.9s ease-in-out infinite; }

.if-flames .if-fl-tongues--1 {
  opacity: 0.7;
}

.if-flames .if-fl-tongues--1.if-edge--y {
  animation:
    if-fl-flicker-y 2.6s ease-in-out -1.1s infinite,
    if-fl-fade 1.7s ease-in-out infinite;
}

.if-flames .if-fl-tongues--1.if-edge--x {
  animation:
    if-fl-flicker-x 2.6s ease-in-out -1.1s infinite,
    if-fl-fade 1.7s ease-in-out infinite;
}

.if-flames .if-fl-embers {
  opacity: 0;
  animation: if-fl-rise 3.6s linear infinite;
}

.if-flames .if-fl-embers--0 {
  opacity: 0.6;
}

.if-flames .if-fl-embers--1 {
  animation-delay: -1.2s;
}

.if-flames .if-fl-embers--2 {
  animation-delay: -2.4s;
}

@keyframes if-fl-flicker-y {
  0%,
  100% {
    transform: scaleY(1);
  }

  18% {
    transform: scaleY(1.12);
  }

  34% {
    transform: scaleY(0.9);
  }

  52% {
    transform: scaleY(1.07);
  }

  70% {
    transform: scaleY(0.94);
  }

  86% {
    transform: scaleY(1.1);
  }
}

@keyframes if-fl-flicker-x {
  0%,
  100% {
    transform: scaleX(1);
  }

  18% {
    transform: scaleX(1.12);
  }

  34% {
    transform: scaleX(0.9);
  }

  52% {
    transform: scaleX(1.07);
  }

  70% {
    transform: scaleX(0.94);
  }

  86% {
    transform: scaleX(1.1);
  }
}

@keyframes if-fl-fade {
  0%,
  100% {
    opacity: 0.85;
  }

  40% {
    opacity: 0.35;
  }

  65% {
    opacity: 0.75;
  }
}

@keyframes if-fl-rise {
  0% {
    opacity: 0;
    transform: translateY(0);
  }

  20% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translateY(calc(var(--if-t) * -1.4));
  }
}

/* Shadow: a dark pool under the image; two sets of tendrils breathe outward. */
.table-image-frame.if-shadow {
  --if-sh-rgb: 6, 4, 12;
  --if-sh-wisp: rgba(8, 6, 14, 0.78);
  --if-sh-wisp-b: rgba(34, 22, 50, 0.62);
}

.if-shadow .if-sh-pool {
  inset: var(--if-t);
  width: auto;
  height: auto;
  box-shadow: 0 0 calc(var(--if-t) * 0.9) calc(var(--if-t) * 0.35) rgba(var(--if-sh-rgb), 0.85);
}

.if-shadow .if-sh-puff { fill: rgba(var(--if-sh-rgb), 0.7); }
.if-shadow .if-sh-wisp--0 { fill: var(--if-sh-wisp); }
.if-shadow .if-sh-wisp--1 { fill: var(--if-sh-wisp-b); }

.if-shadow .if-sh-tendrils--0.if-edge--y { animation: if-sh-breathe-y 7s ease-in-out infinite; }
.if-shadow .if-sh-tendrils--0.if-edge--x { animation: if-sh-breathe-x 7s ease-in-out infinite; }

.if-shadow .if-sh-tendrils--1.if-edge--y {
  animation:
    if-sh-breathe-y 9s ease-in-out -4s infinite,
    if-sh-fade 9s ease-in-out infinite;
}

.if-shadow .if-sh-tendrils--1.if-edge--x {
  animation:
    if-sh-breathe-x 9s ease-in-out -4s infinite,
    if-sh-fade 9s ease-in-out infinite;
}

@keyframes if-sh-breathe-y {
  0%,
  100% {
    transform: scaleY(0.9);
  }

  50% {
    transform: scaleY(1.18);
  }
}

@keyframes if-sh-breathe-x {
  0%,
  100% {
    transform: scaleX(0.9);
  }

  50% {
    transform: scaleX(1.18);
  }
}

@keyframes if-sh-fade {
  0%,
  100% {
    opacity: 0.9;
  }

  50% {
    opacity: 0.4;
  }
}

/* Lightning: three sets of arcs flash in steps (most frames change nothing), on durations that
   are whole multiples of 1.6 s. */
.table-image-frame.if-lightning {
  --if-li-core: #f4f9ff;
  --if-li-rgb: 110, 180, 255;
}

.if-lightning .if-li-glow-rim {
  inset: var(--if-t);
  width: auto;
  height: auto;
  box-shadow: 0 0 calc(var(--if-t) * 0.45) rgba(var(--if-li-rgb), 0.4);
}

.if-lightning .if-li-rim { stroke: rgba(var(--if-li-rgb), 0.55); }
.if-lightning .if-li-glow { stroke: rgba(var(--if-li-rgb), 0.42); }
.if-lightning .if-li-core { stroke: var(--if-li-core); }

.if-lightning .if-li-arcs {
  opacity: 0;
  animation: if-li-flash 3.2s steps(1, end) infinite;
}

.if-lightning .if-li-arcs--0 {
  opacity: 0.7;
}

.if-lightning .if-li-arcs--1 {
  animation-duration: 4.8s;
  animation-delay: -1.6s;
}

.if-lightning .if-li-arcs--2 {
  animation-duration: 6.4s;
  animation-delay: -4.8s;
}

@keyframes if-li-flash {
  0% {
    opacity: 0;
  }

  60% {
    opacity: 1;
  }

  62% {
    opacity: 0.3;
  }

  64% {
    opacity: 1;
  }

  67%,
  100% {
    opacity: 0;
  }
}

/* Stained glass: six jewel colours, each a gradient that repeats along the panes. */
.table-image-frame.if-stainedglass {
  --if-sg-0: #b3122e;
  --if-sg-1: #1f4fb5;
  --if-sg-2: #1f8a4c;
  --if-sg-3: #d9a21b;
  --if-sg-4: #6b2fa3;
  --if-sg-5: #198f9c;
  --if-sg-lead: #161514;
}

.if-stainedglass .if-sg-pane,
.if-stainedglass .if-sg-rose { fill-opacity: 0.88; }
.if-stainedglass .if-sg-stop-hi { stop-color: color-mix(in srgb, var(--if-sg) 62%, #fff); }
.if-stainedglass .if-sg-stop { stop-color: var(--if-sg); }
.if-stainedglass .if-sg-stop-lo { stop-color: color-mix(in srgb, var(--if-sg) 70%, #000); }
.if-stainedglass .if-sg-sheen { stop-color: rgba(255, 255, 255, 0.2); }
.if-stainedglass .if-sg-sheen--clear { stop-color: rgba(255, 255, 255, 0); }
.if-stainedglass .if-sg-lead { stroke: var(--if-sg-lead); }

/* The shared pause freezes a frame where it is. Where that could stop mid-flash (lightning),
   with the scan line across the image (HUD) or between two sets of sigils (arcane), reduced
   effects and reduced motion show the resting state instead; moving the camera only pauses. */
.table-root.is-effects-reduced .table-image-frame .if-li-arcs,
.table-root.is-effects-reduced .table-image-frame .if-hud-scan,
.table-root.is-effects-reduced .table-image-frame .if-ar-band {
  animation-name: none;
}

@media (prefers-reduced-motion: reduce) {
  .table-image-frame .if-li-arcs,
  .table-image-frame .if-hud-scan,
  .table-image-frame .if-ar-band {
    animation-name: none;
  }
}

/* ---- Image frames, set 4: frames 21–25 (objects/imageFramesSet4.js) -------------------------
   The quiet frames. Low contrast, slow, small motion on transform and opacity; paused at any
   moment they still look calm, so none of them needs a resting state of its own. */

/* Old film: grain at the edges, the gate's round corners, a rare scratch, a gentle flicker. */
.table-image-frame.if-oldfilm {
  --if-of-dark: rgba(20, 14, 8, 0.85);
  --if-of-shade: rgba(24, 17, 10, 0.38);
  --if-of-light: #fff6e2;

  box-shadow: inset 0 0 calc(var(--if-t) * 1.2) calc(var(--if-t) * 0.1) var(--if-of-shade);
}

.if-oldfilm .if-of-gate { fill: var(--if-of-dark); }
.if-oldfilm .if-of-grain { opacity: 0.55; }
.if-oldfilm .if-of-grain--inner { opacity: 0.25; }
.if-oldfilm .if-of-grain-light { stroke: rgba(255, 248, 230, 0.7); }
.if-oldfilm .if-of-grain-dark { stroke: rgba(10, 8, 5, 0.7); }
.if-oldfilm .if-of-scratch { stroke: rgba(255, 248, 230, 0.32); }
.if-oldfilm .if-of-scratch--dark { stroke: rgba(10, 8, 5, 0.28); }

/* Each set of scratches shows for a moment once per cycle. */
.if-oldfilm .if-of-scratches {
  opacity: 0;
  animation: if-of-scratch 7.2s steps(1, end) infinite;
}

.if-oldfilm .if-of-scratches--1 {
  animation-duration: 10.8s;
  animation-delay: -4.8s;
}

.if-oldfilm .if-of-flicker {
  opacity: 0.03;
  background: var(--if-of-light);
  animation: if-of-flicker 2.7s linear infinite;
}

@keyframes if-of-scratch {
  0% {
    opacity: 0;
  }

  70% {
    opacity: 1;
  }

  73% {
    opacity: 0.5;
  }

  76%,
  100% {
    opacity: 0;
  }
}

@keyframes if-of-flicker {
  0%,
  100% {
    opacity: 0.03;
  }

  12% {
    opacity: 0.06;
  }

  25% {
    opacity: 0.02;
  }

  41% {
    opacity: 0.055;
  }

  58% {
    opacity: 0.03;
  }

  73% {
    opacity: 0.065;
  }

  87% {
    opacity: 0.025;
  }
}

/* Candlelight: warm pools of light in the corners over a dim warm rim, flickering softly. */
.table-image-frame.if-candlelight {
  --if-cl-light: rgba(255, 170, 74, 0.3);
  --if-cl-rim: rgba(255, 150, 60, 0.2);

  box-shadow: inset 0 0 calc(var(--if-t) * 1.6) calc(var(--if-t) * 0.2) rgba(38, 18, 6, 0.42);
}

.if-candlelight .if-cl-glow {
  opacity: 0.75;
  animation: if-cl-flicker 3.1s ease-in-out infinite;
}

.if-candlelight .if-cl-glow--a {
  background:
    radial-gradient(circle at 0 100%, var(--if-cl-light), transparent calc(var(--if-t) * 5)),
    radial-gradient(circle at 100% 0, var(--if-cl-light), transparent calc(var(--if-t) * 3.5));
}

.if-candlelight .if-cl-glow--b {
  background:
    radial-gradient(circle at 100% 100%, var(--if-cl-light), transparent calc(var(--if-t) * 5)),
    radial-gradient(circle at 0 0, var(--if-cl-light), transparent calc(var(--if-t) * 3.5));
  animation-duration: 2.3s;
  animation-delay: -1.1s;
}

.if-candlelight .if-cl-glow--rim {
  box-shadow: inset 0 0 calc(var(--if-t) * 0.9) var(--if-cl-rim);
  animation-duration: 4.3s;
  animation-delay: -2s;
}

@keyframes if-cl-flicker {
  0%,
  100% {
    opacity: 0.75;
  }

  17% {
    opacity: 0.9;
  }

  31% {
    opacity: 0.68;
  }

  52% {
    opacity: 0.86;
  }

  69% {
    opacity: 0.62;
  }

  84% {
    opacity: 0.82;
  }
}

/* Mist: one slim strip per side, clipped to the image; the bank inside drifts slowly along the
   side and thickens and thins. */
.table-image-frame.if-mist {
  --if-mi-fog: rgba(226, 233, 241, 0.42);

  box-shadow: inset 0 0 calc(var(--if-t) * 1.4) rgba(226, 233, 241, 0.14);
}

.if-mist .if-mi-strip {
  overflow: hidden;
}

.table-image-frame.if-mist .if-mi-strip > .if-sheet {
  position: absolute;
  overflow: visible;
}

.if-mist .if-mi-puff { stop-color: var(--if-mi-fog); }
.if-mist .if-mi-puff--half { stop-opacity: 0.5; }
.if-mist .if-mi-puff--clear { stop-opacity: 0; }

.if-mist .if-mi-strip--top .if-mi-bank {
  animation:
    if-mi-drift-x 24s ease-in-out infinite alternate,
    if-mi-breathe 13s ease-in-out infinite;
}

.if-mist .if-mi-strip--bottom .if-mi-bank {
  animation:
    if-mi-drift-x 27s ease-in-out -11s infinite alternate-reverse,
    if-mi-breathe 15s ease-in-out -6s infinite;
}

.if-mist .if-mi-strip--left .if-mi-bank {
  animation:
    if-mi-drift-y 29s ease-in-out -9s infinite alternate,
    if-mi-breathe 14s ease-in-out -3s infinite;
}

.if-mist .if-mi-strip--right .if-mi-bank {
  animation:
    if-mi-drift-y 25s ease-in-out -4s infinite alternate-reverse,
    if-mi-breathe 16s ease-in-out -9s infinite;
}

@keyframes if-mi-drift-x {
  from {
    transform: translateX(calc(var(--if-t) * -0.6));
  }

  to {
    transform: translateX(calc(var(--if-t) * 0.6));
  }
}

@keyframes if-mi-drift-y {
  from {
    transform: translateY(calc(var(--if-t) * -0.6));
  }

  to {
    transform: translateY(calc(var(--if-t) * 0.6));
  }
}

@keyframes if-mi-breathe {
  0%,
  100% {
    opacity: 0.75;
  }

  50% {
    opacity: 1;
  }
}

/* CRT: faint scanlines, round dark corners, a glare and a slow refresh band. */
.table-image-frame.if-crt {
  --if-crt-line: max(1px, calc(var(--if-t) * 0.035));

  box-shadow: inset 0 0 calc(var(--if-t) * 1.3) calc(var(--if-t) * 0.15) rgba(0, 0, 0, 0.42);
}

.if-crt .if-crt-lines {
  opacity: 0.6;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.18) 0,
    rgba(0, 0, 0, 0.18) var(--if-crt-line),
    transparent var(--if-crt-line),
    transparent calc(var(--if-crt-line) * 2)
  );
}

.if-crt .if-crt-bezel { fill: rgba(6, 7, 8, 0.92); }
.if-crt .if-crt-glass { stroke: rgba(255, 255, 255, 0.07); }
.if-crt .if-crt-glare { stop-color: rgba(255, 255, 255, 0.07); }
.if-crt .if-crt-glare--clear { stop-color: rgba(255, 255, 255, 0); }

.if-crt .if-crt-refreshbox {
  overflow: hidden;
}

/* The band sits at the bottom of a box as tall as the image, which runs from above the image
   to below it. */
.if-crt .if-crt-refresh {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    transparent calc(100% - var(--if-t) * 3),
    rgba(255, 255, 255, 0.05) calc(100% - var(--if-t) * 1.2),
    transparent 100%
  );
  transform: translateY(-100%);
  animation: if-crt-refresh 11s linear infinite;
}

@keyframes if-crt-refresh {
  from {
    transform: translateY(-100%);
  }

  to {
    transform: translateY(calc(var(--if-t) * 3));
  }
}

/* Tape: translucent strips across the corners. */
.table-image-frame.if-tape {
  --if-tp-tape: rgba(240, 234, 214, 0.42);
}

.if-tape .if-tp-strip {
  fill: var(--if-tp-tape);
  stroke: rgba(255, 255, 255, 0.2);
}

.if-tape .if-tp-shadow { fill: rgba(0, 0, 0, 0.18); }
.if-tape .if-tp-shine { stroke: rgba(255, 255, 255, 0.07); }
