/* Editors: the GM config dialogs and the controls they
   share. Loaded after stage.css and uses its tokens. */

/* Folder tree (FolderTree.mjs), the left pane of the scene and document
   editors. Shoelace's tree with our fonts and its own carets on folders;
   plain items have no caret column. */

.folder-tree sl-tree {
  --indent-size: 26px;
  --indent-guide-width: 0;
  font-size: 17px;
}

.folder-tree sl-tree-item::part(item) {
  padding-block: 4px;
  border-inline-start: 0;
  border-radius: 0;
}

.folder-tree sl-tree-item::part(item--selected) {
  background: var(--surface-hover);
}

.folder-tree sl-tree-item.is-item::part(expand-button) {
  display: none;
}

.folder-tree sl-tree-item.is-folder::part(expand-button) {
  color: var(--on-surface-muted);
}

.folder-tree .tree-label {
  flex: 1;
  min-width: 0;
  padding-inline-start: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A FolderTree rowIcon (the dashboard's note on scenes with music). */
.folder-tree .tree-icon {
  flex: none;
  margin-inline: 4px;
  font-size: 16px;
  opacity: .7;
}

/* Shoelace's label part only takes its content's width; stretched, the
   row's icons sit at its right edge instead of against the name. */
.folder-tree sl-tree-item::part(label) {
  flex: 1;
  min-width: 0;
}

/* The row icons: a folder's pencil and trash show on hover of its own line
   (is-hovered, set by FolderTree, because :hover on the folder element
   includes its children); an item's trash shows on hover and on the
   selected row (items have no children, so plain :hover does). Where the
   host refuses, it is a trash with an X that explains why when clicked. */
.folder-tree .tree-action {
  display: grid;
  place-items: center;
  flex: none;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--on-surface-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .15s;
}

.folder-tree .tree-delete { margin-inline-end: 4px; }

.folder-tree .tree-action .material-symbols-outlined { font-size: 20px; }

.folder-tree sl-tree-item.is-hovered > .tree-action,
.folder-tree sl-tree-item.is-item:hover > .tree-delete,
.folder-tree sl-tree-item.is-item[selected] > .tree-delete,
.folder-tree .tree-action:focus-visible {
  opacity: 1;
}

.folder-tree .tree-action:hover { background: var(--surface-hover); color: var(--on-surface); }

/* While dragging: the source row fades and the ghost is a translucent pill
   (FolderTree hands it to setDragImage from off screen). */
.folder-tree sl-tree-item.is-dragging::part(item) {
  opacity: .35;
}

.folder-tree .drag-ghost {
  position: fixed;
  top: -1000px;
  left: -1000px;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: rgb(45 46 49 / .6);
  color: var(--on-surface);
  font-size: 17px;
  white-space: nowrap;
  opacity: .6;
}

/* Drop markers: a line above or below the row, or a ring for "into". The
   line is 2px centred on the boundary between rows (1px outside the row,
   1px inside), so "after A" and "before B" draw in exactly the same place
   and the marker does not hop as the pointer crosses from one row to the
   next. */
.folder-tree sl-tree-item.is-drop-before::part(item) {
  box-shadow: 0 -1px 0 0 var(--accent-soft), inset 0 1px 0 0 var(--accent-soft);
}

.folder-tree sl-tree-item.is-drop-after::part(item) {
  box-shadow: 0 1px 0 0 var(--accent-soft), inset 0 -1px 0 0 var(--accent-soft);
}

.folder-tree sl-tree-item.is-drop-into::part(item) {
  box-shadow: inset 0 0 0 2px var(--accent-soft);
}

/* Edit Folder dialog. */

.folder-dialog::part(panel) { max-width: 330px; }

.folder-dialog form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.folder-dialog .actions {
  display: flex;
  gap: 12px;
  justify-content: center;
}

.folder-dialog .pill[hidden] { display: none; }

/* A captioned field: small caption above a dark input. */

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--on-surface-muted);
}

.field input,
.field textarea {
  padding: 10px 12px;
  border: 1px solid var(--divider);
  border-radius: var(--radius-menu);
  background: var(--stage-bg);
  color: var(--on-surface);
  font: 17px var(--font-ui);
}

/* Buttons the editors add to the stage's pills: a round icon button
   (folder-plus, upload, trash) and a red pill for destructive yeses. */

.round-button {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  background: var(--surface-hover);
  color: var(--on-surface);
  cursor: pointer;
  box-shadow: var(--shadow-button);
}

.round-button:hover { background: var(--divider); }
.round-button.accent { background: var(--accent); color: var(--on-accent); }
.round-button.accent:hover { background: var(--accent-soft); }

.pill.danger { background: #b3261e; color: var(--on-accent); }
.pill.danger:hover { background: #d33b32; }

.error {
  margin: 0;
  color: var(--err);
  font-size: 14px;
}

.error[hidden], .pill[hidden], .round-button[hidden] { display: none; }

/* Editor dialog (EditorDialog.mjs): fills the stage
   with a side pane and a main pane. The heading is the editor's own, in
   the side pane, so Shoelace's title line is kept small. */

/* No title line: the heading is the editor's own, so the header collapses
   and the X floats in the corner over the body. */
/* At most 1000px wide, so a big screen (a link game in a full window)
   does not stretch the form into long empty rows. */
.editor-dialog {
  --width: min(calc(100% - 48px), 1000px);
  --header-spacing: 0;
  --body-spacing: 24px 32px 24px;
}

.editor-dialog::part(header) {
  position: absolute;
  top: 4px;
  right: 16px;
  padding: 0;
  z-index: 1;
}

.editor-dialog::part(close-button) { margin: 12px 0 0; }

/* The top right of an editor's main pane sits under that X: the scene
   preview and the role portrait start below it. */
.editor-dialog { --under-close: 24px; }

/* At most 650px tall, for the same reason. */
.editor-dialog::part(panel) {
  position: relative;
  height: min(calc(100% - 48px), 650px);
  max-height: none;
}

.editor-dialog::part(title) { display: none; }

.editor-dialog::part(body) {
  display: grid;
  grid-template-columns: 290px 1fr;
  gap: 32px;
  overflow: hidden;
}

.documents-dialog::part(body) { grid-template-columns: 250px 1fr; }

.editor-side {
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding-inline-end: 24px;
  /* The fields' own border colour, so every line in an editor matches. */
  border-inline-end: 1px solid var(--sl-input-border-color);
}

/* The main pane scrolls when a form is taller than the stage; the padding
   keeps pill shadows and focus rings inside the scroll box instead of
   clipped at its edge (the negative margins put the content back where a
   pane without padding would draw it). */
.editor-main {
  min-height: 0;
  overflow: auto;
  padding: 16px 12px 24px 4px;
  margin: -4px -12px 0 -4px;
}

.editor-header h2 {
  margin: 0 0 12px;
  font: 300 34px/1.1 var(--font-ui);
}

.editor-buttons {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.editor-buttons .pill { font-size: 17px; padding: 12px 22px; }

/* Tooltips on icon buttons (sl-tooltip, research/app/UI.md "Tooltips"). */
.button-tooltip::part(body) { font: 500 13px var(--font-ui); }

.tree-mount {
  flex: 1;
  min-height: 0;
  overflow: auto;
  margin-inline: -8px;
}

/* Scene form (SceneForm.mjs): fields on the left, preview and
   levels on the right, the error line under the fields. No buttons: it
   saves as you go. */

.scene-form form {
  display: grid;
  grid-template-columns: minmax(240px, 1fr) minmax(220px, 260px);
  grid-template-areas: "fields side" "error side";
  grid-template-rows: auto 1fr;
  gap: 12px 32px;
}

.scene-form.is-empty form { display: none; }
.scene-form:not(.is-empty) .form-empty { display: none; }

.scene-form .form-empty {
  margin: 48px 0;
  color: var(--on-surface-muted);
  text-align: center;
}

.scene-fields {
  grid-area: fields;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.scene-side {
  grid-area: side;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.scene-form .error { grid-area: error; align-self: start; }

.scene-form sl-input::part(form-control-label),
.scene-form sl-select::part(form-control-label),
.scene-form sl-range::part(form-control-label) {
  font-size: 13px;
  color: var(--on-surface-muted);
}

.scene-form sl-input::part(input),
.scene-form sl-select::part(display-input) { font-size: 17px; }

.option-thumb {
  width: 40px;
  height: 24px;
  object-fit: cover;
  border-radius: 3px;
  background: var(--stage-bg);
}

.scene-form .preview {
  position: relative;
  margin-top: var(--under-close);
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-menu);
  overflow: hidden;
  background: var(--stage-bg);
}

.scene-form .preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: blur(var(--bg-blur));
}

/* The darken level, as on the stage (stage.css maps data-darken). */
.scene-form .preview-shade {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / var(--bg-darken));
  pointer-events: none;
}

.scene-form .preview-upload {
  position: absolute;
  right: 10px;
  bottom: 10px;
}

.scene-form .levels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 16px;
}

.scene-form .levels sl-range:nth-child(n+3) { grid-column: 1 / -1; max-width: 55%; }

.scene-form sl-range { --track-color-active: var(--accent); --thumb-size: 16px; }

/* A picture on its way: the box keeps the old picture dimmed under a
   spinner until the new address has loaded (editors/previewimage.mjs). */

.preview-spinner,
.slot-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  font-size: 40px;
  --indicator-color: var(--accent-soft);
  --track-color: rgb(255 255 255 / .2);
}

.preview:not(.is-loading) .preview-spinner,
.slot-frame:not(.is-loading) .slot-spinner { display: none; }

.preview.is-loading img,
.slot-frame.is-loading img { opacity: .35; }

/* Library dialogs (LibraryDialog.mjs): a black box
   that is a drop target while empty and a preview once something is
   chosen. They and the confirm dialog open over the editor dialog, so they
   sit one layer above it (Shoelace gives every dialog the same layer). */

.library-dialog,
.confirm-dialog,
.import-dialog,
.youtube-dialog { --sl-z-index-dialog: 900; }

.library-dialog::part(panel) { max-width: 400px; }

.library-dialog form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.library-dialog .actions { display: flex; justify-content: center; }

.drop-zone {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-menu);
  background: #000;
  overflow: hidden;
}

.drop-zone.is-over { outline: 2px solid var(--accent-soft); outline-offset: -2px; }

.zone-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px;
}

.zone-hint { margin: 0; font-size: 13px; color: var(--on-surface-muted); }

.drop-zone .url-field {
  width: 220px;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--radius-menu);
  background: var(--surface);
  color: var(--on-surface);
  font: 17px var(--font-ui);
  text-align: center;
}

.drop-zone .url-field::placeholder { color: var(--on-surface); }
.drop-zone .url-field:focus { outline: 2px solid var(--accent-soft); }

.zone-preview { position: absolute; inset: 0; }
.zone-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }

.drop-zone:not(.is-empty) .zone-empty,
.drop-zone.is-empty .zone-preview,
.drop-zone:not(.is-video) .play-badge,
.drop-zone:not(.is-busy) .zone-spinner { display: none; }

.play-badge {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  width: 64px;
  height: 44px;
  border-radius: 12px;
  background: #ff0000;
  color: #fff;
  font-size: 36px;
  pointer-events: none;
}

.zone-clear {
  position: absolute;
  top: 10px;
  right: 10px;
  background: var(--accent);
  color: var(--on-accent);
}

.zone-clear:hover { background: var(--accent-soft); }

.zone-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  font-size: 40px;
  --indicator-color: var(--accent-soft);
  --track-color: rgb(255 255 255 / .2);
}

.drop-zone.is-busy .zone-preview img { opacity: .5; }

.library-dialog .retry-upload { align-self: flex-start; }

/* New Picture (NewPictureDialog.mjs): the background box, square. */

.picture-dialog::part(panel) { max-width: 360px; }
.picture-dialog .drop-zone { aspect-ratio: 1; width: 260px; margin-inline: auto; }
.picture-dialog .field[hidden] { display: none; }

/* New Background's wand: quiet in the empty box's corner. */

.zone-wand {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 36px;
  height: 36px;
  background: transparent;
  box-shadow: none;
  color: var(--on-surface-muted);
}

.zone-wand:hover { background: var(--surface-hover); color: var(--accent-soft); }
.zone-wand .material-symbols-outlined { font-size: 22px; }

/* Generate Background (GenerateBackgroundDialog.mjs): the
   prompt on the left, a growing grid of paintings on the right. Over New
   Background, so a layer above the library dialogs. */

.generate-dialog {
  --sl-z-index-dialog: 950;
  --width: min(calc(100% - 32px), 960px);
}

.generate-dialog::part(panel) { height: min(calc(100% - 32px), 560px); max-height: none; }
.generate-dialog::part(body) { padding: 20px 20px 20px 24px; overflow: hidden; display: flex; }

.generate-layout {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(220px, 320px) 1fr;
  gap: 28px;
}

.generate-side { display: flex; flex-direction: column; gap: 14px; min-height: 0; }

.generate-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font: 400 24px var(--font-ui);
  color: var(--on-surface);
}

.generate-title .material-symbols-outlined { font-size: 28px; color: var(--accent-soft); }

.generate-prompt {
  flex: 1;
  min-height: 0;
  resize: none;
  padding: 12px 14px;
  border: 1px solid var(--sl-input-border-color);
  border-radius: var(--radius-menu);
  background: var(--stage-bg);
  color: var(--on-surface);
  font: 17px/1.4 var(--font-ui);
}

.generate-prompt:focus { outline: 2px solid var(--accent-soft); outline-offset: -1px; }

.generate-controls { display: flex; align-items: center; gap: 12px; }
.generate-framing { flex: 1; min-width: 0; }
/* Its label is for screen readers; the options say enough. */
.generate-framing::part(form-control-label) {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.generate-go { background: var(--accent); color: var(--on-accent); }
.generate-go:hover:not(:disabled) { background: var(--accent-soft); }
.generate-go:disabled { opacity: .4; cursor: default; }

.generate-results { display: flex; flex-direction: column; min-height: 0; min-width: 0; }

.generate-top {
  display: flex;
  align-items: center;
  gap: 4px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--sl-input-border-color);
}

.generate-status {
  flex: 1;
  margin: 0;
  font-size: 13px;
  color: var(--on-surface-muted);
}

.generate-clear,
.generate-close {
  width: 36px;
  height: 36px;
  background: transparent;
  box-shadow: none;
}

.generate-clear:hover,
.generate-close:hover { background: var(--surface-hover); }
.generate-clear[hidden],
.generate-top sl-tooltip[hidden] { display: none; }

.generate-grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  scrollbar-gutter: stable;
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Rows as tall as a 16:9 tile; otherwise a full grid squashes them, the
     tiles' overflow: hidden letting them shrink. */
  grid-auto-rows: max-content;
  align-content: start;
  gap: 12px;
  padding: 12px 4px 12px 0;
  border-bottom: 1px solid var(--sl-input-border-color);
}

.generate-grid.is-empty { align-content: center; }

.generate-empty {
  grid-column: 1 / -1;
  justify-self: center;
  max-width: 30ch;
  margin: 0;
  padding: 24px;
  line-height: 1.5;
  text-align: center;
  text-wrap: balance;
  color: var(--on-surface-muted);
}

.generate-empty[hidden] { display: none; }

.generate-tile {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-menu);
  background: #000;
  overflow: hidden;
  outline: 2px solid transparent;
  outline-offset: -2px;
  transition: outline-color .15s;
}

.generate-tile.is-ghost {
  display: grid;
  place-items: center;
  background: var(--surface-hover);
  animation: generate-ghost-in .35s ease-out both;
}

@keyframes generate-ghost-in {
  from { opacity: 0; scale: .96; }
  to { opacity: 1; scale: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .generate-tile.is-ghost { animation: none; }
}

.generate-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  font-size: 32px;
  transition: opacity .5s;
  --indicator-color: var(--accent-soft);
  --track-color: rgb(255 255 255 / .15);
}

.generate-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: zoom-in;
  opacity: 0;
  transition: opacity .6s;
}

/* The ghost's grey and spinner hold until the picture has arrived; then it
   fades in over them and the spinner fades away. */
.generate-tile.is-loading { background: var(--surface-hover); }
.generate-tile.is-loaded .generate-image { opacity: 1; }
.generate-tile.is-loaded .generate-spinner { opacity: 0; }

.generate-tile.is-error,
.generate-tile.is-flagged {
  display: grid;
  place-items: center;
  padding: 12px;
  background: var(--surface-hover);
}

.generate-tile-message { margin: 0; font-size: 13px; text-align: center; color: var(--on-surface-muted); }

.generate-actions {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
}

.generate-tile.is-loaded:hover,
.generate-tile.is-loaded:focus-within { outline-color: var(--accent-soft); }

.generate-tile.is-loaded:hover .generate-actions,
.generate-tile.is-loaded:focus-within .generate-actions { opacity: 1; }

/* Only the buttons take the pointer; the strip between them leaves the
   picture's zoom click alone. */
.generate-tile.is-loaded:hover .generate-actions > *,
.generate-tile.is-loaded:focus-within .generate-actions > * { pointer-events: auto; }

.generate-actions .round-button {
  width: 34px;
  height: 34px;
  background: rgb(60 64 67 / .85);
}

.generate-actions .round-button:hover { background: var(--surface-hover); color: var(--accent-soft); }
.generate-actions .material-symbols-outlined { font-size: 20px; }
.generate-choose { margin-left: auto; }

.generate-download-link { display: none; }

/* The full-window view of one painting, above everything. */
.generate-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgb(0 0 0 / .88);
  cursor: zoom-out;
}

.generate-lightbox[hidden] { display: none; }
.generate-lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--radius-menu); }

.generate-lightbox-close {
  position: absolute;
  top: 24px;
  right: 24px;
  background: rgb(60 64 67 / .85);
}

.generate-lightbox-close:hover { background: var(--surface-hover); color: var(--accent-soft); }

.generate-lightbox-choose {
  position: absolute;
  right: 24px;
  bottom: 24px;
  font-size: 17px;
  padding: 12px 22px;
  cursor: pointer;
}

/* New Sound (NewSoundDialog.mjs): a short box; once a file is chosen, a
   note, its file name and a listen button instead of a picture. */

.sound-dialog .drop-zone { aspect-ratio: auto; height: 140px; }
.sound-dialog .zone-preview img { display: none; }
.drop-zone:not(.is-sound) .sound-preview { display: none; }

.sound-preview {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 64px 0 20px;
  color: var(--on-surface);
}

.sound-preview-icon { font-size: 36px; color: var(--accent-soft); }

.sound-file-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.drop-zone.is-busy .sound-preview { opacity: .5; }

/* Image slot (ImageSlot.mjs): a picture with upload and clear buttons,
   used for the role portrait and the Manage Game defaults. The shape
   comes from data-kind: thumbs are square, backgrounds 16:9. */

.image-slot .slot-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-menu);
  overflow: hidden;
  background: var(--stage-bg);
}

.image-slot[data-kind="thumb"] .slot-frame { aspect-ratio: 1; }

.image-slot .slot-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.image-slot .slot-upload { position: absolute; right: 10px; bottom: 10px; }
.image-slot .slot-clear { position: absolute; top: 10px; right: 10px; }
.image-slot.is-default .slot-clear { display: none; }

/* Role form (RoleForm.mjs): fields left, square portrait
   right, the error line under the fields. */

.role-form form {
  display: grid;
  grid-template-columns: minmax(240px, 1fr) minmax(200px, 260px);
  grid-template-areas: "fields picture" "error picture";
  grid-template-rows: auto 1fr;
  gap: 16px 32px;
}

.role-form.is-empty form { display: none; }
.role-form:not(.is-empty) .form-empty { display: none; }
.role-form .form-empty { margin: 48px 0; color: var(--on-surface-muted); text-align: center; }

.role-fields {
  grid-area: fields;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.role-picture { grid-area: picture; margin-top: var(--under-close); }
.role-form .error { grid-area: error; align-self: start; }

.role-form sl-input::part(form-control-label),
.role-form sl-textarea::part(form-control-label) {
  font-size: 13px;
  color: var(--on-surface-muted);
}

.role-form sl-input::part(input),
.role-form sl-textarea::part(textarea) { font-size: 17px; }

/* Effect form (EffectForm.mjs): a square picture left,
   the name (large) and one-line description right, the two fields
   exactly as tall as the picture; no visible labels, the placeholders say
   what goes where. The form fills the pane: the head stays put, its name
   12px under the X, and only the steps scroll, under a line 12px below
   the head, with the step buttons fixed in a footer under another line. */

.editor-main.effect-main {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-bottom: 0;
  /* Down through the dialog body's bottom padding, so the footer sets
     the gap to the edge itself. */
  margin-bottom: -24px;
}

.effect-main > div { flex: 1; min-height: 0; display: flex; flex-direction: column; }

.effect-form { --effect-picture: 120px; --effect-gap: 12px; }

.effect-form form {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* The X's glyph ends 8px below the pane's top; 12px under it. */
  margin-top: 20px;
}

.effect-form.is-empty form { display: none; }
.effect-form:not(.is-empty) .form-empty { display: none; }
.effect-form .form-empty { margin: 48px 0; color: var(--on-surface-muted); text-align: center; }

.effect-head {
  flex: none;
  display: grid;
  grid-template-columns: var(--effect-picture) minmax(240px, 1fr);
  gap: 24px;
}

.effect-form .error { flex: none; margin: 12px 0 0; }

.effect-fields {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: var(--effect-gap);
  height: var(--effect-picture);
}

/* The name takes what the description leaves of the picture's height. */
.effect-name { --sl-input-height-medium: calc(var(--effect-picture) - var(--effect-gap) - 40px); }
.effect-name::part(input) { font-size: 26px; }
.effect-description { --sl-input-height-medium: 40px; }
.effect-description::part(input) { font-size: 15px; }

.effect-form sl-input::part(form-control-label) {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.effect-steps {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  margin-top: 12px;
}

/* Steps (StepList.mjs): numbered summaries in a box that
   scrolls under the line; the number and the bold verb in the soft red,
   the rest grey; the pencil and trash and a light fill on the hovered row;
   drop lines as the trees'. The buttons sit in the footer. */

.step-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  border-top: 1px solid var(--sl-input-border-color);
}

.step-list {
  margin: 8px 0;
  padding: 0;
  list-style: none;
}

.step-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 4px 4px 4px 28px;
  font-size: 16px;
  color: var(--on-surface-muted);
  cursor: pointer;
}

.step-row:hover { background: rgb(255 255 255 / .04); }
.step-row.is-dragging { opacity: .35; }
.step-row.is-drop-before { box-shadow: 0 -1px 0 0 var(--accent-soft), inset 0 1px 0 0 var(--accent-soft); }
.step-row.is-drop-after { box-shadow: 0 1px 0 0 var(--accent-soft), inset 0 -1px 0 0 var(--accent-soft); }

.step-number { flex: none; min-width: calc(1.6em - 6px); font-weight: 600; color: var(--accent-soft); }
.step-text { flex: 1; min-width: 0; }
.step-text strong { font-weight: 600; color: var(--accent-soft); }
.step-detail { display: block; margin-top: 2px; font-size: 13px; color: var(--on-surface-muted); }

.step-action {
  display: grid;
  place-items: center;
  flex: none;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--on-surface-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .15s;
}

.step-action[hidden] { display: none; }
.step-action .material-symbols-outlined { font-size: 20px; }
/* The trash's hover circle touches the pencil's, and leaves room on the
   right for a scroll bar. */
.step-delete { margin-inline: -8px 12px; }
.step-row:hover > .step-action,
.step-action:focus-visible { opacity: 1; }
.step-action:hover { background: var(--surface-hover); color: var(--on-surface); }

.step-empty { margin: 12px 28px; color: var(--on-surface-muted); }

/* The footer: a line, the buttons as far under it as the head is above
   the top line (12px), their bottoms level with the side pane's divider. */
.step-buttons {
  flex: none;
  display: flex;
  gap: 12px;
  padding: 12px 0 24px;
  border-top: 1px solid var(--sl-input-border-color);
}

.step-buttons .pill,
.step-dialog .pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.step-buttons .material-symbols-outlined,
.step-dialog .pill .material-symbols-outlined { font-size: 20px; }
/* Over the inline-flex above: a pill hidden (test, for Pause) stays gone. */
.step-buttons .pill[hidden],
.step-dialog .pill[hidden] { display: none; }

/* Edit Step (StepDialog.mjs): narrow, the command first, its
   fields stacked under it, test and done centred. */

.step-dialog { --width: min(calc(100% - 48px), 420px); }
/* Tall enough to show the Command pulldown whole: its list stays inside
   the dialog's body (not hoisted), which would clip it and scroll, with no
   scroll bar on a Mac. */
.step-dialog::part(panel) { min-height: 450px; }
.step-dialog::part(body) { display: flex; flex-direction: column; gap: 14px; }
.step-dialog::part(footer) { display: flex; justify-content: center; gap: 12px; }
.step-fields { display: flex; flex-direction: column; gap: 14px; }
.step-unknown { margin: 0; color: var(--on-surface-muted); }

.step-dialog sl-input::part(form-control-label),
.step-dialog sl-select::part(form-control-label),
.step-dialog sl-range::part(form-control-label),
.sound-checks-label {
  font-size: 13px;
  color: var(--on-surface-muted);
}

/* The sound commands' fields (commands/Command.mjs): New sound… set apart
   at the end of the pulldown; Play Random Sound's checkboxes in a box that
   scrolls past six or so, "+ new sound…" under it. */

.sound-field sl-option.new-sound:not(:first-child) { border-top: 1px solid var(--sl-input-border-color); }

.sound-checks { display: flex; flex-direction: column; gap: 6px; }

.sound-checks-box {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 180px;
  overflow-y: auto;
  padding: 10px 12px;
  border: 1px solid var(--sl-input-border-color);
  border-radius: var(--sl-input-border-radius-medium);
}

.sound-checks-empty { margin: 0; color: var(--on-surface-muted); }
.sound-checks-empty[hidden] { display: none; }

.sound-checks-new {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent-soft);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}

.sound-checks-new:hover { text-decoration: underline; }

/* Settings dialog (SettingsDialog.mjs): one column, the two default
   pictures side by side under the name. */

.settings-dialog::part(panel) { max-width: 560px; }

.settings-dialog form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.settings-dialog sl-input::part(form-control-label) {
  font-size: 13px;
  color: var(--on-surface-muted);
}

.settings-dialog sl-input::part(input) { font-size: 17px; }

/* 16:9 beside 1:1 at these column widths gives both slots the same height. */
.settings-pictures {
  display: grid;
  grid-template-columns: 16fr 9fr;
  gap: 16px;
  align-items: start;
}

/* Confirm dialog (ConfirmDialog.mjs). */

.confirm-dialog::part(panel) { max-width: 420px; }
.confirm-dialog p { margin: 0; }
.confirm-dialog::part(footer),
.stats-dialog::part(footer),
.import-dialog::part(footer) { display: flex; justify-content: flex-end; gap: 8px; }

/* Import dialog (ImportDialog.mjs): a sentence, Choose File, then a summary
   or the reason the file was refused. Export has no dialog: the menu item
   opens the export route in a new tab, because the Meet frame blocks downloads. */
.import-dialog::part(panel) { max-width: 440px; }
.import-dialog p { margin: 0 0 14px; }
.import-dialog .summary { font-weight: 500; overflow-wrap: anywhere; }
.import-dialog .summary:empty { display: none; }
.import-dialog .pill + .summary,
.import-dialog .pill + .error { margin-top: 14px; }

/* Import YouTube (YouTubeImportDialog.mjs): the
   address in the library dialogs' black box, then a row per video (keep,
   preview, scene name) that scrolls, and the backgrounds box beside
   Create Scenes. data-step says which half shows. */
.youtube-dialog::part(panel) { max-width: 480px; }
.youtube-dialog::part(body) { padding-top: 0; }
.youtube-dialog[data-step="url"] .youtube-videos-step,
.youtube-dialog[data-step="videos"] .youtube-url-step,
.youtube-dialog[data-step="url"] .youtube-back { display: none; }

.youtube-url-step {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.youtube-url-step .actions { display: flex; justify-content: center; }
.zone-caption { align-self: center; width: 220px; font-size: 13px; }
.drop-zone.is-busy .zone-empty { opacity: .35; }

.youtube-back {
  align-self: flex-end; /* level with the close button, which sits at the bottom */
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--on-surface);
  cursor: pointer;
}

.youtube-back:hover { background: var(--surface-hover); }

.youtube-head,
.youtube-row {
  display: grid;
  grid-template-columns: 48px 80px 1fr;
  align-items: center;
  gap: 10px;
}

.youtube-head {
  padding-bottom: 6px;
  border-bottom: 1px solid var(--divider);
  font-size: 13px;
}

.youtube-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 240px;
  padding: 10px 0;
  overflow-y: auto;
}

.youtube-row.is-dropped .youtube-preview,
.youtube-row.is-dropped .youtube-name { opacity: .4; }

.youtube-preview {
  width: 80px;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  object-fit: cover;
  background: var(--stage-bg);
}

.youtube-name {
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--divider);
  border-radius: var(--radius-menu);
  background: var(--stage-bg);
  color: var(--on-surface);
  font: 17px var(--font-ui);
}

.youtube-name:focus { outline: 2px solid var(--accent-soft); }

.youtube-dialog .error:not([hidden]) { margin-bottom: 10px; }

.youtube-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--divider);
}

/* A dark square with a tick, the input itself hidden but still focusable. */
.check-box {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
}

.check-box input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.check-mark {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex: none;
  box-sizing: border-box;
  border: 1px solid var(--divider);
  border-radius: var(--radius-menu);
  background: var(--stage-bg);
  color: transparent;
  font-size: 28px;
}

.check-box input:checked + .check-mark { color: var(--on-surface); }
.check-box input:focus-visible + .check-mark { outline: 2px solid var(--accent-soft); }
.check-caption { max-width: 150px; font-size: 13px; line-height: 1.3; }

/* Documents (DocumentsEditor.mjs, DocumentPane.mjs): the
   Quill toolbar across the top of the main pane, then a "paper" with the
   editable title, who else is here, and the shared editor. Quill's snow
   theme is restyled to the dark surface; quill-cursors draws the other
   people's carets. */
/* The pane is exactly the main pane's height and the text scrolls inside
   the paper, so the title, the message line and who is here stay in view
   however far down someone is typing. */
.document-pane {
  display: flex;
  flex-direction: column;
  height: 100%;
  gap: 12px;
}

.document-pane.is-empty .doc-toolbar,
.document-pane.is-empty .doc-paper { display: none; }
.document-pane:not(.is-empty) .pane-empty { display: none; }

.document-pane .pane-empty {
  margin: 0;
  color: var(--on-surface-soft, var(--on-surface));
  opacity: .7;
}

/* One row of toolbar buttons is reserved (24px buttons plus 4px below),
   so the paper does not jump when the toolbar appears; narrower stages
   (Meet with its chat panel open) wrap to two rows and it grows. */
.doc-toolbar { min-height: 28px; }

.doc-toolbar .ql-toolbar.ql-snow {
  border: 0;
  padding: 0 0 4px;
  font-family: var(--font-ui);
}

.doc-toolbar .ql-toolbar.ql-snow .ql-formats { margin-right: 12px; }
.doc-toolbar .ql-stroke { stroke: var(--on-surface); }
.doc-toolbar .ql-fill { fill: var(--on-surface); }
.doc-toolbar .ql-picker { color: var(--on-surface); }
.doc-toolbar .ql-picker-options {
  background: var(--surface);
  border-color: var(--divider);
}
/* Hover, focus and what is applied: our brighter red instead of
   Quill's blue (#06c), on every part Quill paints it. */
.doc-toolbar .ql-toolbar.ql-snow button:hover,
.doc-toolbar .ql-toolbar.ql-snow button:focus,
.doc-toolbar .ql-toolbar.ql-snow button.ql-active,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label:hover,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label.ql-active,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-item:hover,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-item.ql-selected { color: var(--accent-soft); }
.doc-toolbar .ql-toolbar.ql-snow button:hover .ql-stroke,
.doc-toolbar .ql-toolbar.ql-snow button:focus .ql-stroke,
.doc-toolbar .ql-toolbar.ql-snow button.ql-active .ql-stroke,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label:hover .ql-stroke,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label.ql-active .ql-stroke,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-item:hover .ql-stroke,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-item.ql-selected .ql-stroke,
.doc-toolbar .ql-toolbar.ql-snow button:hover .ql-stroke-miter,
.doc-toolbar .ql-toolbar.ql-snow button:focus .ql-stroke-miter,
.doc-toolbar .ql-toolbar.ql-snow button.ql-active .ql-stroke-miter,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label:hover .ql-stroke-miter,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label.ql-active .ql-stroke-miter { stroke: var(--accent-soft); }
.doc-toolbar .ql-toolbar.ql-snow button:hover .ql-fill,
.doc-toolbar .ql-toolbar.ql-snow button:focus .ql-fill,
.doc-toolbar .ql-toolbar.ql-snow button.ql-active .ql-fill,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label:hover .ql-fill,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-label.ql-active .ql-fill,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-item:hover .ql-fill,
.doc-toolbar .ql-toolbar.ql-snow .ql-picker-item.ql-selected .ql-fill { fill: var(--accent-soft); }

.doc-paper {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: rgb(0 0 0 / .25);
  border-radius: 12px;
  padding: 12px 20px 16px;
}

.doc-head {
  display: flex;
  align-items: center;
  gap: 12px;
  border-bottom: 1px solid var(--divider);
  padding-bottom: 8px;
}

.doc-title {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  color: var(--on-surface);
  font: 400 24px/1.3 var(--font-ui);
  padding: 4px 0;
  outline: none;
}


/* The other people in the document, beside the title (DocumentPane
   renderPeople_): their initial in their cursor colour. Dark letters read
   on every palette colour; the ring parts neighbours. */
.doc-people {
  flex: none;
  display: flex;
  gap: 4px;
}

.presence-bubble {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--on-surface-muted);
  box-shadow: 0 0 0 2px var(--stage-bg);
  color: #202124;
  font: 700 14px/1 var(--font-ui);
  cursor: default;
  user-select: none;
}

.presence-bubble[data-color="0"] { background: var(--cursor-0); }
.presence-bubble[data-color="1"] { background: var(--cursor-1); }
.presence-bubble[data-color="2"] { background: var(--cursor-2); }
.presence-bubble[data-color="3"] { background: var(--cursor-3); }
.presence-bubble[data-color="4"] { background: var(--cursor-4); }
.presence-bubble[data-color="5"] { background: var(--cursor-5); }
.presence-bubble[data-color="6"] { background: var(--cursor-6); }
.presence-bubble[data-color="7"] { background: var(--cursor-7); }

/* DocumentPane.BUBBLE_LEAVE_MS is the pop-out's length. */
.presence-bubble.is-arriving { animation: presence-pop-in 320ms cubic-bezier(.3, .7, .3, 1) both; }
.presence-bubble.is-leaving { animation: presence-pop-out 200ms ease-in both; }

@keyframes presence-pop-in {
  0% { opacity: 0; scale: .3; }
  60% { opacity: 1; scale: 1.15; }
  100% { scale: 1; }
}

@keyframes presence-pop-out {
  0% { scale: 1; }
  100% { opacity: 0; scale: .3; }
}

.doc-editor {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Quill makes the mount itself the container, so this is the same element. */
.doc-editor.ql-container.ql-snow {
  border: 0;
  font-family: var(--font-ui);
  font-size: 17px;
}

.doc-editor .ql-editor {
  padding: 12px 8px 12px 0;
  /* The text of the editors' other fields (Shoelace's input colour), not
     the brighter stage labels: easier on the eyes for long reading. */
  color: var(--sl-input-color);
  outline: none;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.doc-editor .ql-editor.ql-blank::before { color: var(--on-surface); opacity: .5; font-style: normal; }
.doc-editor .ql-editor a { color: var(--accent-soft); }
.doc-editor .ql-editor blockquote { border-left-color: var(--divider); }
.doc-editor .ql-editor pre.ql-syntax { background: rgb(0 0 0 / .35); color: var(--sl-input-color); }
.doc-editor .ql-snow .ql-tooltip {
  background: var(--surface);
  border-color: var(--divider);
  color: var(--on-surface);
  box-shadow: var(--shadow-button);
}
.doc-editor .ql-snow .ql-tooltip input[type=text] {
  background: transparent;
  color: var(--on-surface);
  border-color: var(--divider);
}
.doc-editor .ql-cursor-name { font-family: var(--font-ui); }

/* After a lost edit (DocumentPane.onShareError_) the editor is read-only
   until a reload; dim it so the error line is the thing to read. */
.document-pane.is-lost .doc-editor { opacity: .55; }
/* A hanging indent: a wrapped message lines up under the text, not the icon. */
.document-pane .doc-paper .error {
  margin-top: 8px;
  font-weight: 500;
  border-bottom: 1px solid var(--divider);
  padding-bottom: 6px;
  padding-left: 28px;
  text-indent: -12px;
}

/* A caution, not an error: amber, with Material Symbols' alert glyph in
   front and its smiley at the end (the ":)" of "...keep writing. :)"). */
.document-pane .error.is-warning { color: var(--warn); }

/* Inline, so on a wrapped line the face follows the last word; solid
   (the font's FILL axis). */
.document-pane .error.is-warning::before,
.document-pane .error.is-warning::after {
  display: inline-block;
  font-family: 'Material Symbols Outlined';
  font-size: 18px;
  line-height: 1;
  font-weight: normal;
  vertical-align: -4px;
  font-variation-settings: 'FILL' 1;
}

.document-pane .error.is-warning::before { content: 'warning'; margin-right: 4px; }
/* The face would inherit the hanging indent too and slide back over the
   full stop; the alert keeps it, which is what sets its place. */
.document-pane .error.is-warning::after { content: 'sentiment_satisfied'; margin-left: 4px; text-indent: 0; }
.document-pane .error.is-warning.is-sad::after { content: 'sentiment_dissatisfied'; }

.document-pane .error.is-warning[hidden] { display: none; }

/* Participant Stats (ParticipantStatsDialog.mjs): one card per person,
   newest first; the extras in small print. */
.stats-dialog::part(panel) { max-width: 520px; }
.stats-dialog p { margin: 0; }
.stats-list { display: flex; flex-direction: column; gap: 10px; }
.stats-card {
  padding: 10px 12px;
  border: 1px solid var(--divider);
  border-radius: var(--radius-menu);
  overflow-wrap: anywhere;
}
.stats-card header {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
}
.stats-role { color: var(--on-surface-muted); }
.stats-when {
  margin-left: auto;
  font-size: 12px;
  color: var(--on-surface-muted);
  white-space: nowrap;
}
.stats-when.stats-here { color: var(--accent-soft); }
.stats-line { font-size: 14px; }
.stats-extra { font-size: 12px; color: var(--on-surface-muted); }
.stats-line + .stats-extra { margin-top: 4px; }
