/* --------------------------------------------------- */
/*                  SETTINGS DIALOG                    */
/* --------------------------------------------------- */
/* Split out of styles/styles.css (was its own "SETTINGS" section there) -
   the dialog is closed by default, so this doesn't need to be render-
   blocking; loaded via <link media="print" data-lazy-css> (index.html),
   flipped to media="all" by scripts/deferredStyles.js once the deferred
   script queue reaches it, same mechanism the other dialog stylesheets
   (colorPicker.css, dropdownSelect.css, backup.css, ...) already use.
   Loaded on phone too - Settings still works there (scripts/tasksWorkspace.js's
   own phone toolbars delegate straight into showSettings()). */

/* --------------------------------------------------- */
/*                       SETTINGS                      */
/* --------------------------------------------------- */
/* .report-btn shares this same wrapping-div-around-a-real-button structure
   (see index.html) as .tools-btn — .tools-btn-icon/.report-btn-icon are the
   actual <button>s inside, so neither wrapper itself matches the global
   "button:hover { color: var(--highlight) }" rule further up this file, and
   both need their own copy of it for their action-bar label to pick up the
   same hover color .settings-btn/.tasks-btn/.add-note-btn/etc. get for free
   by being real <button>s themselves. */
/* :has(:focus-visible), not :focus-within - :focus-within also matches a
   plain mouse click's own resulting focus (clicking a button focuses it in
   most browsers), which never un-matches on its own once the mouse leaves
   again - confirmed as a real bug: clicking #tasks left its action-bar
   stuck open forever afterward, only a hover's own mouseleave ever closed
   it correctly. :focus-visible only matches focus the browser itself
   judges as keyboard-driven (its own heuristic already excludes a plain
   click), so :has(:focus-visible) reveals this the same way real Tab
   navigation does (shared/scripts/focusTrap.js) without that regression. */
.tools-btn:hover,
.tools-btn:has(:focus-visible),
.report-btn:hover,
.report-btn:has(:focus-visible) {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}

.settings-dialog {
  position: fixed;
  inset: 0;
  margin: auto;
  overflow: visible;
  width: 500px;
  padding: 0;
  background-color: var(--main-theme-lighter);
  border-radius: 10px;
  border: 1px solid var(--secondary-color);
  color: #efefef;
  font-size: 12px;

  transition: opacity 500ms ease;
  opacity: 0;
}
.update-banner-label {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translate(-50%, -100px);
  opacity: 0;
  padding: 6px 30px 10px;
  white-space: nowrap;
  pointer-events: none;

  font-size: 28px;
  font-weight: bold;
  letter-spacing: 6px;
  color: var(--secondary-color);

  transition:
    transform 500ms ease,
    opacity 500ms ease;
  animation: update-banner-pulse 1.8s ease-in-out infinite alternate;
}
.update-banner-label.visible {
  transform: translate(-50%, 0);
  opacity: 1;
}
/* @keyframes update-banner-pulse moved to styles/styles.css (core) -
   styles/desktopPanels.css's own .update-link-title reuses this exact same
   pulse too, and that file is independently deferred/phone-skipped with no
   guaranteed load-order relative to this one, so the keyframe needs to
   live somewhere both can rely on being loaded first. */
.version,
.language {
  position: absolute;
  top: 100%;
  right: 10px;
  width: 80px;
  padding: 5px 15px 3px 15px;

  display: flex;
  align-items: center;
  justify-content: center;
  border-top: 1px solid var(--secondary-color);
  border-bottom: 3px solid var(--secondary-color);
  border-bottom-left-radius: 10px;
  border-bottom-right-radius: 10px;
  background-color: var(--main-theme-darker);
}
.version {
  cursor: var(--cursor-pointer);
}
.version:hover {
  color: var(--highlight);
}
.language {
  right: 100px;
  cursor: var(--cursor-pointer);
  gap: 6px;
}
.language svg {
  width: 18px;
  height: auto;
  display: block;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
}
/* color:var(--highlight) (used on .version's own hover, above) has no effect
   on an <svg> with its own hardcoded fill colors — the label span next to
   it carries the hover highlight instead. */
.language:hover .language-label {
  color: var(--highlight);
}
/* Settings > Debug: On/Off (setDebugConsoleEnabled, scripts/settings.js) -
   same corner-tab look/positioning as .version/.language just above, on
   the opposite (left) edge of the dialog's own bottom border instead - its
   own dedicated rule rather than joining their shared one, since its text
   ("Debug: Off"/"Debug: On") needs more width than their shared 80px. */
.debug {
  position: absolute;
  top: 100%;
  left: 10px;
  width: auto;
  white-space: nowrap;
  padding: 5px 15px 3px 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-top: 1px solid var(--secondary-color);
  border-bottom: 3px solid var(--secondary-color);
  border-bottom-left-radius: 10px;
  border-bottom-right-radius: 10px;
  background-color: var(--main-theme-darker);
  cursor: var(--cursor-pointer);
}
.debug:hover {
  color: var(--highlight);
}
/* A throwaway stand-in for the real .settings-dialog, spawned right before it
   closes (spawnSettingsGhostTransfer, scripts/init.js) — sized/positioned via
   inline style to exactly match where the real dialog was, then shrinks,
   blurs, and travels to wherever .emblem-sidebar-group-left actually sits,
   landing dead center on it. Reads as the dialog's own substance draining
   into the panel replacing it, rather than the dialog just vanishing while
   an unrelated panel happens to fade in nearby. Removed from the DOM once
   the animation ends (same JS).

   .reverse (closeSpinnerSidebarPanel, scripts/init.js) plays this exact same
   keyframe backwards instead of authoring a second, mirrored one — it starts
   at 100% (small/blurred/gone, sitting near the panel) and ends at 0%
   (full-size/sharp/opaque, sitting on the dialog), so the panel reads as
   pulling back into Settings rather than just Settings reappearing on its
   own nearby. Also half the duration (250ms vs 500ms) — closing plays twice
   as fast as opening throughout (see .emblem-sidebar-group-left > * above), and
   closeSpinnerSidebarPanel's own setTimeout before Settings reappears is
   halved to match. */
.settings-drain-ghost {
  position: fixed;
  z-index: 41;
  background-color: var(--main-theme-lighter);
  border: 1px solid var(--secondary-color);
  border-radius: 8px;
  pointer-events: none;
  animation: settingsDrain 500ms ease-in forwards;
}
.settings-drain-ghost.reverse {
  animation-duration: 250ms;
  animation-direction: reverse;
}
@keyframes settingsDrain {
  0% {
    opacity: 0.9;
    transform: translate(0, 0) scale(1);
    filter: blur(0);
  }
  100% {
    opacity: 0;
    transform: translate(var(--drain-dx), var(--drain-dy)) scale(0.05);
    filter: blur(14px);
  }
}
/* Docked to the viewport's own left edge (not the settings dialog — see this
   element's own placement comment, index.html) — hidden by default and only
   revealed via .spinner-sidebar-toggle (openSpinnerSidebarPanel, scripts/
   init.js), which fully closes Settings at the same time, so .emblem shows
   through completely unobstructed instead of sitting behind the dialog's own
   opaque box. left:20px/z-index:40 match .add-note-btn's own left-edge stack
   (styles.css) for a consistent inset; vertically centered instead of pinned
   to the bottom like that stack so it never overlaps it.

   Carries no OPACITY toggle of its own (unlike its children just below) —
   an ancestor's opacity change takes its whole subtree down with it
   INSTANTLY wherever that ancestor has no transition of its own to animate
   through, which would otherwise blow away each child's own carefully
   staggered transition the moment .visible comes off on the way out
   (closeSpinnerSidebarPanel, scripts/init.js) — before their own reverse
   animation ever gets a chance to actually play. Each child fully owns its
   own visibility AND interactivity (opacity/pointer-events, both below)
   instead, so there's no ancestor opacity state left to race against.

   background-color IS this element's own, though (no such race exists for
   it — nothing else here ever touches it) — a faint white tint that fades
   in/out as its own independent transition, giving the whole panel a subtle
   backing plate distinct from the page behind it. The 200ms delay is only on
   .visible's own transition (further below) — i.e. only opening waits before
   the tint starts fading in, letting the first staggered children get a
   head start; closing fades it back out immediately alongside them instead. */
.emblem-sidebar-group-left {
  position: fixed;
  top: 50%;
  left: 30px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-radius: 10px;
  padding: 10px;
  transform: translateY(-50%);
  background-color: rgba(255, 255, 255, 0);
  transition: background-color 300ms ease;
  /* The group itself carries no opacity/visibility toggle of its own (see
     this element's placement comment above for why) — without this, its
     otherwise-invisible box still sat there hit-testable at all times,
     silently eating hover/click on anything underneath it at that exact
     fixed left-edge/vertically-centered spot (e.g. the Link Stats panel's
     own title column, when docked .side-left) even while fully closed. Its
     children already toggle their OWN pointer-events (auto once .visible,
     just below) independently of this — a descendant's explicit auto still
     receives events even though this ancestor is none, so open/closed
     interactivity for the actual buttons is unaffected. */
  pointer-events: none;
}
.emblem-sidebar-group-left.visible {
  background-color: rgba(255, 255, 255, 0.04);
  transition: background-color 300ms ease 500ms;
}
/* Each LEAF item plays its own fade+slide-in rather than the whole panel
   appearing as one flat block — openSpinnerSidebarPanel (scripts/init.js)
   randomizes each leaf's transition-delay (a shuffled 0/50/100/.../ms
   permutation, different every time this opens) right before adding
   .visible on the group above, so the five reveal one at a time in a random
   order 50ms apart, using this same transition every time. Coming in from
   the right (a large positive translateX, not a subtle one) rather than the
   left is deliberate — Settings itself sits center-screen, well to the right
   of this panel's own left-edge dock, so the motion reads as Settings'
   content dissolving into this panel rather than the panel just nudging
   into place from nearby.

   "Each leaf" — not simply "each direct child" — because some of those
   descendants aren't leaves themselves, but plain layout wrappers:
   .spinner-sidebar-columns (below, a direct child of the group), plus
   .spinner-sidebar-slider-column and .spinner-sidebar-toggle-column
   (nested a level deeper still, inside it — the three sliders and four
   toggle buttons respectively). All three carry the same shared
   .spinner-sidebar-leaf-wrapper marker class (index.html), and both
   selectors below exclude it via :not() on EITHER side of the combinator —
   not just from the group's own direct children — so a wrapper nested
   inside another wrapper is never mistaken for one of its parent's actual
   leaves. getSpinnerPanelLeaves (scripts/init.js) recurses the same way,
   for the same reason, so every real control still gets its own
   independent shuffle slot no matter how deep it's nested.

   This rule's own 200ms transition is deliberately HALF of .visible's own
   400ms just below — not a typo/mismatch. Per the transition spec, the
   duration used is always read off the element's ENDING state: entering
   .visible (this element gaining it) reads .visible's 400ms, but leaving it
   again (closeSpinnerSidebarPanel, scripts/init.js) lands back on THIS rule,
   so it reads 200ms instead. That's what actually makes the exit play twice
   as fast without any JS needing to know or care which duration is "active"
   right now — closeSpinnerSidebarPanel only has to halve its OWN 25ms
   stagger step (down from the entrance's 50ms) to match. */
/* :not(.emblem-sidebar-group-right) on both sides too - phone mode
   (_restructureEmblemSidebarForPhone, scripts/init.js) moves that preset
   picker to become a descendant here (nested inside the new
   .emblem-sidebar-group-top, itself a .spinner-sidebar-leaf-wrapper), but
   it's deliberately never one of the shared leaves either selector below
   is meant to catch - it has its own completely independent hover-
   triggered visibility (its own .visible rule, further down) that these
   opacity/transform rules would otherwise fight. Harmless/unreachable on
   desktop, where it's never actually nested here at all. */
.emblem-sidebar-group-left
  > *:not(.spinner-sidebar-leaf-wrapper):not(.emblem-sidebar-group-right),
.spinner-sidebar-leaf-wrapper
  > *:not(.spinner-sidebar-leaf-wrapper):not(.emblem-sidebar-group-right) {
  opacity: 0;
  pointer-events: none;
  transform: translateX(400px);
  transition:
    opacity 200ms ease,
    transform 200ms ease;
}
.emblem-sidebar-group-left.visible
  > *:not(.spinner-sidebar-leaf-wrapper):not(.emblem-sidebar-group-right),
.emblem-sidebar-group-left.visible
  .spinner-sidebar-leaf-wrapper
  > *:not(.spinner-sidebar-leaf-wrapper):not(.emblem-sidebar-group-right) {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(0);
  transition:
    opacity 400ms ease,
    transform 400ms ease;
}
/* All three sliders in their own column on the left, all four toggle
   buttons in their own column on the right (index.html) — a plain
   two-item flex row; each column's own width is just whatever its
   widest child already is (both --sidebar-item-width, further down), so
   neither needs an explicit width of its own here. Carries no
   opacity/transform of its own, same reasoning as the two columns just
   below. */
.spinner-sidebar-columns {
  display: flex;
  gap: 8px;
}
/* Centers the four toggle buttons as a single group within whatever
   height .spinner-sidebar-slider-column ends up needing — this row's
   default align-items:stretch (flex, no override needed here) already
   gives this column that same full height with no rule of its own, and
   justify-content:center is what then distributes the four buttons
   evenly around its own middle instead of leaving them pinned to the top
   with empty space below. No explicit width either — each button's own
   explicit var(--sidebar-item-width) (further down) is already this
   column's widest content, so it sizes to exactly that on its own.

   Also marked .spinner-sidebar-leaf-wrapper (index.html) — it's a plain
   layout wrapper, not a leaf itself, same as .spinner-sidebar-columns and
   .spinner-sidebar-slider-column; see the shared leaf-selector comment
   further up for why nesting a wrapper inside another wrapper like this
   still leaves every button animating as an independent leaf. */
.spinner-sidebar-toggle-column {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
}
/* Holds the three sliders stacked above each other (index.html) — a plain
   flex column, each slider box sized to its own natural content height
   (no flex-grow needed, unlike the single shared row this used to be —
   see .spinner-sidebar-columns' own comment above for why nothing here
   needs an explicit width either). Carries no opacity/transform of its
   own (see the leaf-selector comment further up) — always fully
   visible/static, exactly like .emblem-sidebar-group-left itself, so only
   the three sliders inside it ever actually animate. */
.spinner-sidebar-slider-column {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* An explicit shared width, not just align-items:stretch matching
   whatever .spinner-sidebar-slider-column's own widest child needs —
   these three ARE that column's only children, so stretch alone would
   already land on the right value in practice, but an explicit width
   keeps all three trivially identical regardless of content width
   (Opacity's own label text vs. Tempo's shorter one, etc.) rather than
   depending on that coincidence. All three are just .emblem-sidebar-group-left's
   own --sidebar-item-width (styles.css), same as the four toggle buttons
   opposite them use. `flex: none` keeps each one at exactly that height
   rather than stretching to fill the column's own cross-axis width (moot
   here in practice, same reasoning, but explicit regardless). */
.emblem-opacity-sidebar,
.spinner-scale-sidebar,
.emblem-rotation-speed-sidebar {
  flex: none;
  width: var(--sidebar-item-width);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 5px;
  background-color: var(--main-theme-darker);
  border: 1px solid var(--secondary-color);
  border-radius: 8px;
  font-size: 11px;
  color: var(--font-color);
  white-space: nowrap;
}
/* Same box in all three cases — .spinner-glow-label/.spinner-direction-label/
   .spinner-pulsate-label and their own .spinner-*-icon spans are already
   styled identically further down this file, so giving all three the same
   box here is what actually makes them look/behave the same, not anything
   specific to any one of them.

   width is explicit (rather than relying on align-items:stretch to match
   whatever the slider column next to it needs) so these four keep the
   same --sidebar-item-width footprint the sliders themselves have,
   regardless of how tall .spinner-sidebar-slider-column's own column ends
   up being — both reference the exact same --sidebar-item-width
   (.emblem-sidebar-group-left, above), rather than repeating (and risking
   drifting from) that same value as their own separate literal. */
.spinner-glow-sidebar,
.spinner-direction-sidebar,
.spinner-pulsate-sidebar {
  aspect-ratio: 1;
  width: var(--sidebar-item-width);
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--main-theme-darker);
  border: 1px solid var(--secondary-color);
  border-radius: 8px;
  font-size: 11px;
  color: var(--font-color);
}
/* Drag-and-drop OR click-to-browse a replacement image for .emblem
   (setEmblemImageFile/wireEmblemUpload, scripts/init.js) — sits directly
   below .spinner-sidebar-columns now (index.html), sized to exactly match
   it: the same two-column-plus-gap width, and aspect-ratio:1 to turn that
   into a square rather than staying the same narrow width as a single
   column above it like it used to. The row's own gap (8px, hardcoded on
   .spinner-sidebar-columns itself, above) has to be repeated here too —
   there's no shared variable for it (unlike --sidebar-item-width) to derive
   this from instead. */
.emblem-upload-sidebar {
  position: relative;
  aspect-ratio: 1;
  width: calc(var(--sidebar-item-width) * 2 + 8px);
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--main-theme-darker);
  border: 1px dashed var(--secondary-color);
  border-radius: 8px;
  font-size: 30px;
  color: var(--font-color);
  cursor: var(--cursor-pointer);
  transition:
    color 150ms,
    border-color 150ms,
    background-color 150ms,
    opacity 400ms ease,
    transform 400ms ease;
}
/* Hidden the instant an override is actually active (.emblem-override-active
   — a custom upload OR any preset at all, syncEmblemUploadResetButton
   /scripts/init.js) — the thumbnail below takes over as the box's own
   visible content at that point instead. */
.emblem-upload-sidebar.emblem-override-active .emblem-upload-icon {
  display: none;
}
/* The custom image OR picked preset currently in use, filling the same box
   the fallback icon above sits in — src is kept in sync by
   syncEmblemUploadResetButton (scripts/init.js) alongside
   .emblem-override-active itself, and only actually shown once that class
   is present (no src otherwise, so there's nothing to paint even before
   this rule's own display:none applies). Unlike the changelog's own
   screenshot thumbnails (.desc-image-thumb, descriptionTypes.css) — no
   hover highlight, no click-to-zoom lightbox, and pointer-events:none so a
   click anywhere on the image still reaches .emblem-upload-sidebar's own
   click-to-browse handler underneath it, exactly like clicking the
   fallback icon already does. This is a preview, not a separate
   interactive element of its own. */
.emblem-upload-thumbnail {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 10px;
  border-radius: 8px;
  pointer-events: none;
}
.emblem-upload-sidebar.emblem-override-active .emblem-upload-thumbnail {
  display: block;
}
/* Only actually shown while an override is active (.emblem-override-active,
   syncEmblemUploadResetButton/scripts/init.js) — clicking it clears both a
   custom upload and a picked preset back to null, hiding .emblem entirely
   (resetEmblemImage, scripts/init.js), rather than reverting to
   DEFAULT_SETTINGS.emblemPreset (that's what Reset Settings does instead —
   see that function's own comment for why the two are kept apart). Same
   small circular corner-badge language as e.g.
   .icon-manager-color-disabled-badge (styles.css), just a real clickable
   button instead of a status badge. */
.emblem-upload-reset-btn {
  display: none;
  position: absolute;
  top: -6px;
  right: -6px;
  width: 16px;
  height: 16px;
  padding: 0;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--secondary-color);
  border-radius: 50%;
  background-color: var(--main-theme-darker);
  color: var(--font-color-clear);
  font-size: 9px;
}
.emblem-upload-sidebar.emblem-override-active .emblem-upload-reset-btn {
  display: flex;
}
.emblem-upload-reset-btn:hover {
  color: var(--highlight);
  border-color: var(--highlight);
  cursor: var(--cursor-pointer);
}
.emblem-upload-sidebar:hover {
  color: var(--highlight);
  border-color: var(--highlight);
}
/* Live while a file is actually being dragged over it (dragover/dragleave,
   scripts/init.js) — a stronger highlight than plain :hover so "drop it
   here" reads as a distinct, momentary state, not just mouse-over. */
.emblem-upload-sidebar.drag-over {
  color: var(--highlight);
  border-color: var(--highlight);
  border-style: solid;
  background-color: color-mix(
    in srgb,
    var(--highlight) 15%,
    var(--main-theme-lighter)
  );
}
/* A rejected file (wrong type, or over EMBLEM_UPLOAD_MAX_BYTES —
   flashEmblemUploadInvalid, scripts/init.js) — same brief shake convention as
   showUserThatInputIsNotValid's own .wrong toggle (updateLinks.js), just
   with its own rule here since that one's existing CSS is scoped to input
   contexts this element isn't. Reuses @keyframes username-gate-shake
   (styles.css) rather than declaring a new, identical animation. */
.emblem-upload-sidebar.wrong {
  border-color: #d03b3b;
  animation: username-gate-shake 300ms ease;
}
/* Same "the native file picker gives no feedback of its own" problem
   .icon-manager-grid-loading-overlay solves (showIconManagerGridLoader,
   iconManager.js) — shown the instant the OS file dialog opens
   (wireEmblemUpload's click handler, scripts/init.js) and removed the
   moment it closes either way (a file was chosen or it was cancelled), so
   the box doesn't just sit there looking unresponsive while that dialog is
   up. Absolute inset:0 against .emblem-upload-sidebar itself (already
   position:relative, above) rather than a wrapper of its own — this box
   never scrolls, unlike the grid the icon-manager version has to account
   for. */
.emblem-upload-loading-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--main-theme-darker);
  border-radius: 8px;
}
.emblem-upload-spinner {
  width: 36px;
  height: 36px;
  border: 5px solid var(--main-theme-lighter);
  border-top-color: var(--secondary-color);
  border-radius: 50%;
  animation: icon-fetch-spin 700ms linear infinite;
}
/* Docked to the viewport's own RIGHT edge, mirroring
   .emblem-sidebar-group-left's own left-edge dock (that rule's own comment,
   above) for the horizontal side, but full viewport height instead of
   matching the left panel's own content-driven height — top/bottom (not
   top:50%+translateY(-50%)) is what gives it that height, so there's as
   much room as possible to show the 15 full-size thumbnails before
   overflow-y (below) has to kick in and start scrolling. opacity/
   pointer-events toggle on .visible, same as the left panel's own base
   state further up — its background-color fade is intentionally the exact
   same rule the left panel uses (identical values), just repeated here
   since the two are otherwise-unrelated elements with no shared class to
   hang a single rule off of. */
/* overflow-y/scrollbar-gutter are permanent, never toggled — the panel is
   ALWAYS a scroll container from the very first render, so the gutter
   scrollbar-gutter:stable reserves is always there too, even before any
   content actually overflows. That matters because a thumbnail mid-flight
   (@keyframes emblemPresetSpawn, further down) is transformed way outside
   this panel's own box — most of the way to .emblem-upload-icon, off the
   left edge of the screen — and toggling overflow-y to hide that clipping
   (an earlier draft's approach) meant the container flipped between "not a
   scroll container yet" and "one, with a reserved gutter" right as the
   first animation finished, visibly shifting every thumbnail sideways by
   the gutter's own width. spawnEmblemPresetThumbs (scripts/init.js) solves
   the actual clipping problem differently now: each thumbnail is briefly
   switched to position:fixed at its own natural resting coordinates for
   the duration of ITS OWN flight (this element has no transform of its
   own, so position:fixed descendants correctly escape its overflow
   clipping to the real viewport instead), then handed back to normal flow
   once landed — this box's own overflow/gutter never has to change at all. */
.emblem-sidebar-group-right {
  position: fixed;
  top: 20px;
  bottom: 20px;
  right: 30px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-radius: 10px;
  padding: 10px;
  overflow-y: auto;
  scrollbar-gutter: stable;
  background-color: rgba(255, 255, 255, 0);
  transition: background-color 300ms ease;
  opacity: 0;
  pointer-events: none;
}
.emblem-sidebar-group-right.visible {
  opacity: 1;
  pointer-events: auto;
  background-color: rgba(255, 255, 255, 0.04);
}
/* Same dark box language as .spinner-glow-sidebar/.spinner-direction-sidebar
   (further up), sized to exactly match .emblem-upload-sidebar itself
   (that rule's own width, further up) rather than a smaller preview — this
   panel has the room a compact flyout wouldn't, and matching sizes is what
   actually sells "these are the choices for that box" at a glance. flex:
   none so the column above doesn't try to squash/stretch these to fit. */
.emblem-preset-thumb {
  position: relative;
  flex: none;
  aspect-ratio: 1;
  width: calc(var(--sidebar-item-width) * 2 + 8px);
  padding: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--main-theme-darker);
  border: 1px solid var(--secondary-color);
  border-radius: 8px;
  cursor: var(--cursor-pointer);
}
/* Only added to composite rune emblems RuneComposer (scripts/
   runeEmblemComposer.js) actually knows how to rebuild — see
   wireRuneEmblemCustomizers, scripts/init.js. Opens openRuneEmblemCustomizer's
   floating per-slot picker; stopPropagation on its own click handler keeps
   it from also triggering the thumbnail's own selectEmblemPreset underneath. */
.rune-emblem-customize-btn {
  position: absolute;
  top: 0;
  right: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  border-radius: 6px;
  background-color: rgba(0, 0, 0, 0.55);
  color: rgba(255, 255, 255, 0.75);
  cursor: var(--cursor-pointer);
}
.rune-emblem-customize-btn:hover {
  background-color: rgba(0, 0, 0, 0.8);
  color: #ffffff;
}
/* One reused floating panel (openRuneEmblemCustomizer, scripts/init.js) —
   its own top/right are set inline per-open, pinned to the top-left corner
   of .emblem-sidebar-group-right (its right edge against that panel's own
   left edge) rather than anchored under whichever thumbnail's gear button
   was clicked: that button can sit anywhere in a scrolled, cramped sidebar
   list (rune_orbit_duo's own thumb is currently the last one, off the
   bottom of a scrolled .emblem-sidebar-group-right), so anchoring it
   there risked opening the panel partly or fully off the visible
   viewport instead. The top/right set here are only a fallback for the
   (should-never-happen) case .emblem-sidebar-group-right isn't found. */
.rune-emblem-customizer {
  position: fixed;
  top: 20px;
  right: 20px;
  /* Below .custom-select-popup's z-index: 9000 (shared/styles/dropdownSelect.css)
     so each slot's own dropdown popup layers in front of this panel, not behind it. */
  z-index: 8000;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px;
  background-color: var(--main-theme-darker);
  border: 1px solid var(--secondary-color);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.rune-emblem-customizer-title {
  color: var(--font-color);
  font-size: 12px;
  font-weight: bold;
  margin-bottom: 2px;
}
.rune-emblem-customizer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  color: var(--font-color);
  font-size: 12px;
}
/* attachDropdownSelect's own trigger (shared/scripts/dropdownSelect.js) is
   deliberately unstyled beyond a bare flex layout — every caller boxes it
   to fit its own context, same as any other button here. */
.rune-emblem-customizer-select {
  min-width: 120px;
  background-color: var(--main-theme);
  border: 1px solid var(--secondary-color);
  border-radius: 4px;
  padding: 3px 6px;
  font-size: 12px;
}
.rune-emblem-customizer-reset-btn {
  margin-top: 4px;
  background-color: var(--main-theme);
  border: 1px solid var(--secondary-color);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 12px;
  color: var(--font-color);
  cursor: var(--cursor-pointer);
}
.rune-emblem-customizer-reset-btn:hover {
  background-color: var(--main-theme-lighter);
}
.emblem-preset-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}
.emblem-preset-thumb:hover {
  border-color: var(--highlight);
}
/* RS-mode-only presets (windmill/amulet/partyhat/coins/skull, index.html —
   every thumbnail marked .emblem-preset-rs-only) are normal, fully
   changeable EMBLEM_PRESETS entries like any other (scripts/settings.js),
   but only make sense to browse/pick while actually in RS mode.
   rs-mode-active is the same documentElement-level class toggleRsMode
   (easterEggs/initEasterEggs.js) already flips for every other
   RS-mode-only style, so this needs no extra JS of its own to stay in
   sync. The two rules below are deliberately mirror images of each
   other, making the picker fully mode-exclusive: outside RS mode every
   .emblem-preset-rs-only thumb is hidden (only normal presets show); the
   moment rs-mode-active is set, that first rule turns them back on AND
   the second rule hides every OTHER thumb instead — never a mix of both
   sets at once. */
.emblem-preset-thumb.emblem-preset-rs-only {
  display: none;
}
:root.rs-mode-active .emblem-preset-thumb.emblem-preset-rs-only {
  display: flex;
}
:root.rs-mode-active .emblem-preset-thumb:not(.emblem-preset-rs-only) {
  display: none;
}
/* Total-level-gated rune presets (RUNE_UNLOCK_LEVELS/RUNE_EMBLEM_COMPONENTS,
   scripts/rsSkills.js) — present in the markup from the start so nothing
   needs adding at runtime, just hidden until refreshEmblemPresetPickerLocks
   removes this class once the requirement is met. Layered on top of (not
   instead of) the rs-mode-active rule above: a locked rune thumb stays
   display:none regardless of mode, exactly like an rs-only thumb outside
   rs mode. */
.emblem-preset-thumb.emblem-preset-locked {
  display: none !important;
}
/* The preset actually applied to .emblem right now — kept in sync by
   updateSelectedEmblemPresetThumb (scripts/init.js), called both from
   selectEmblemPreset and once up front from wireEmblemPresetPicker so it's
   already correct the very first time this panel ever opens. */
.emblem-preset-thumb.selected {
  border-color: var(--highlight);
  box-shadow: 0 0 0 1px var(--highlight);
}
/* Each thumbnail spawns from .emblem-upload-icon's own position and arcs
   into its resting spot in .emblem-sidebar-group-right instead of just
   appearing — --spawn-dx/--spawn-dy/--spawn-arc-x/--spawn-arc-y are set
   inline by JS (wireEmblemPresetPicker, scripts/init.js) fresh every time
   this panel opens, not baked in statically — the travel distance now
   spans most of the viewport (icon on the left edge, this panel on the
   right) and shifts with window size and scroll position alike, so it has
   to be measured (getBoundingClientRect) rather than assumed fixed. Scoped
   under .visible so the animation (re)plays fresh every open — unlike a
   transition, a CSS animation restarts on its own whenever animation-name
   newly matches, no forced-reflow trick needed the way the left panel's
   own staggered transition-delay commit does (that rule's own comment,
   above). */
.emblem-sidebar-group-right.visible .emblem-preset-thumb {
  animation: emblemPresetSpawn 480ms ease-out backwards;
  animation-delay: var(--spawn-delay, 0ms);
}
@keyframes emblemPresetSpawn {
  0% {
    transform: translate(var(--spawn-dx), var(--spawn-dy)) scale(0);
    opacity: 0.5;
  }
  55% {
    transform: translate(
        calc(var(--spawn-dx) * 0.35 + var(--spawn-arc-x)),
        calc(var(--spawn-dy) * 0.35 + var(--spawn-arc-y))
      )
      scale(0.85);
    opacity: 1;
  }
  100% {
    transform: translate(0, 0) scale(1);
    opacity: 1;
  }
}
/* Exact same rule as .spinner-sidebar-alt-hint below (copy, not a shared
   class — the two live in different files' worth of surrounding context and
   nothing else ties them together), with the one deliberate difference:
   bottom instead of top. That value is never hardcoded here the way alt-
   hint's own top:50px is — it's set inline (positionEmblemUploadHint,
   scripts/init.js) to whatever .emblem-upload-sidebar's real, live
   getBoundingClientRect().bottom actually is, since this hint's whole job
   is captioning that exact box, not just sitting somewhere reasonable near
   it. Same body-level-sibling placement reasoning as alt-hint's own comment
   (index.html) — a position:fixed descendant of the group would anchor
   against ITS transform instead of the real viewport. Toggled by the same
   openSpinnerSidebarPanel/closeSpinnerSidebarPanel (scripts/init.js) that
   toggle alt-hint's own .visible, right alongside it — see .visible variant
   below alt-hint's own for the identical fade. */
.emblem-upload-hint {
  position: fixed;
  border-radius: 10px;
  padding: 10px 20px;
  left: 200px;
  z-index: 40;
  /* Unlike alt-hint's own short, one-line "Alt gedrückt halten..." text,
     this hint's combined click/drag/hover message is long enough to read as
     an unbroken wall of text at that same size on a single line - capped to
     roughly two lines' worth of width instead, at a smaller size that still
     reads as a caption rather than competing with the actual controls. */
  max-width: 300px;
  font-size: 11px;
  line-height: 1.4;
  text-align: left;
  color: color-mix(in srgb, var(--font-color) 60%, transparent);
  opacity: 0;
  pointer-events: none;
  background-color: rgba(255, 255, 255, 0);
  transition:
    opacity 200ms ease,
    background-color 300ms ease;
}
.emblem-upload-hint.visible {
  opacity: 1;
  background-color: rgba(255, 255, 255, 0.04);
  transition:
    opacity 400ms ease 250ms,
    background-color 300ms ease 500ms;
}
/* A plain aside, not another control — no box/border of its own like
   .emblem-sidebar-group-left's real children, so it reads as a caption rather
   than another toggle. A sibling of that group in the DOM (index.html), not
   a child — position:fixed is meant to pin this to the viewport's own
   top-left corner, but any TRANSFORMED ancestor becomes the containing block
   for a fixed descendant instead of the real viewport, and the group has its
   own transform: translateY(-50%) (needed for vertical centering, just
   below) — nested inside it, this would actually anchor to the GROUP's own
   box, not the screen. left:20px matches the sidebar panel/left-edge button
   stack's own inset exactly, rather than guessing a value that might
   overflow the viewport or land closer to one edge than the other.

   Toggled by its own .visible (openSpinnerSidebarPanel/
   closeSpinnerSidebarPanel, scripts/init.js) rather than joining
   .emblem-sidebar-group-left's per-child shuffle (styles.css) — it was never
   really one of the five staggered controls, just a plain fade instead, with
   its own "muted" look done via color (color-mix) rather than opacity so it
   doesn't fight that same fade. transition-delay on .visible only (not the
   base state) is what makes it appear well after the shuffled controls have
   started arriving but disappear immediately on the way out — the reverse of
   how .emblem-sidebar-group-left's own children get a slower duration entering
   and a faster one leaving (that rule's own comment, above).

   background-color fades in/out the same way .emblem-sidebar-group-left's own
   does (that rule's own comment, above) — same rgba/timing, kept as a
   separate declaration rather than a shared class since the two aren't
   related elements, just meant to read as one consistent visual language. */
.spinner-sidebar-alt-hint {
  position: fixed;
  border-radius: 10px;
  padding: 10px 20px;
  top: 50px;
  left: 200px;
  z-index: 40;
  line-height: 1.4;
  text-align: left;
  color: color-mix(in srgb, var(--font-color) 60%, transparent);
  opacity: 0;
  pointer-events: none;
  background-color: rgba(255, 255, 255, 0);
  transition:
    opacity 200ms ease,
    background-color 300ms ease;
}
.spinner-sidebar-alt-hint.visible {
  opacity: 1;
  background-color: rgba(255, 255, 255, 0.04);
  transition:
    opacity 400ms ease 250ms,
    background-color 300ms ease 500ms;
}
.spinner-sidebar-alt-hint kbd {
  display: inline-block;
  padding: 2px 6px;
  border: 1px solid var(--secondary-color);
  border-radius: 4px;
  background-color: var(--main-theme-darker);
  color: var(--font-color);
  font-family: inherit;
}
.emblem-opacity-sidebar #emblem-opacity-slider,
.spinner-scale-sidebar #spinner-scale-slider,
.emblem-rotation-speed-sidebar #emblem-rotation-speed-slider {
  writing-mode: vertical-lr;
  direction: rtl;
  cursor: var(--cursor-pointer);
  accent-color: var(--secondary-color);
  appearance: none;
  background: transparent;
  width: 12px;
  /* An explicit shared length instead of flex:1 (which needs a bounded
     parent height to actually flex against - .emblem-rotation-speed-sidebar
     and its two siblings are all plain flex-column boxes sized to their own
     content, auto, not to any fixed height) - #emblem-rotation-speed-slider's
     own override just below shortens ITS length specifically to make room
     for its now-two-line label without growing that one box taller than
     these other two. */
  height: 140px;
}
/* Shortened from the shared 140px above - "Rotation Speed"'s own label
   (emblemRotationSpeedLabel, scripts/language.js) wraps onto two lines now
   (that key's own doc comment/this box's own .emblem-rotation-speed-label
   rule further down have the full reasoning) - taking roughly one line's
   worth of extra height a single-line label like Opacity's/Size's never
   needed. Shrinking the slider by that same amount is what keeps this
   box's own overall height matching its two siblings instead of growing
   taller than them, per direct feedback. */
.emblem-rotation-speed-sidebar #emblem-rotation-speed-slider {
  height: 126px;
}
.emblem-opacity-sidebar #emblem-opacity-slider::-webkit-slider-runnable-track,
.spinner-scale-sidebar #spinner-scale-slider::-webkit-slider-runnable-track,
.emblem-rotation-speed-sidebar
  #emblem-rotation-speed-slider::-webkit-slider-runnable-track {
  width: 6px;
  background: color-mix(in srgb, var(--font-color) 40%, transparent);
  border-radius: 2px;
}
.emblem-opacity-sidebar #emblem-opacity-slider::-moz-range-track,
.spinner-scale-sidebar #spinner-scale-slider::-moz-range-track,
.emblem-rotation-speed-sidebar #emblem-rotation-speed-slider::-moz-range-track {
  width: 6px;
  height: auto;
  background: color-mix(in srgb, var(--font-color) 40%, transparent);
  border-radius: 2px;
}
.emblem-opacity-sidebar #emblem-opacity-slider::-webkit-slider-thumb,
.spinner-scale-sidebar #spinner-scale-slider::-webkit-slider-thumb,
.emblem-rotation-speed-sidebar
  #emblem-rotation-speed-slider::-webkit-slider-thumb {
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--secondary-color);
  margin-left: -4px;
  opacity: 1 !important;
}
.emblem-opacity-sidebar #emblem-opacity-slider::-moz-range-thumb,
.spinner-scale-sidebar #spinner-scale-slider::-moz-range-thumb,
.emblem-rotation-speed-sidebar #emblem-rotation-speed-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: none;
  border-radius: 50%;
  background: var(--secondary-color);
  opacity: 1 !important;
}
.emblem-opacity-sidebar #emblem-opacity-slider::-moz-range-progress,
.spinner-scale-sidebar #spinner-scale-slider::-moz-range-progress,
.emblem-rotation-speed-sidebar
  #emblem-rotation-speed-slider::-moz-range-progress {
  background: transparent;
}
.emblem-opacity-sidebar .emblem-opacity-label,
.spinner-scale-sidebar .spinner-scale-label,
.emblem-rotation-speed-sidebar .emblem-rotation-speed-label {
  font-size: 10px;
  opacity: 0.6;
  letter-spacing: 0.5px;
  transition:
    color 200ms,
    opacity 200ms;
}
/* Rotation/Speed (English), Rotations-/geschw. (German), Rotación/Velocidad
   (Spanish) - emblemRotationSpeedLabel, scripts/language.js, carries an
   actual "\n" between the two rows now rather than one flowing line -
   pre-line is what actually renders that as two lines instead of collapsing
   it to a space, overriding the shared nowrap the box itself sets
   (.emblem-opacity-sidebar/.spinner-scale-sidebar/.emblem-rotation-speed-sidebar,
   above) just for this one label. Tighter line-height than the default
   (normal, ~1.2 at this font-size) keeps the two rows compact rather than
   spreading out further than the slider's own height reduction actually
   accounts for. */
.emblem-rotation-speed-sidebar .emblem-rotation-speed-label {
  white-space: pre-line;
  line-height: 1.1;
  text-align: center;
}
.emblem-opacity-sidebar #emblem-opacity-value,
.spinner-scale-sidebar #spinner-scale-value,
.emblem-rotation-speed-sidebar #emblem-rotation-speed-value {
  transition: color 200ms;
}
/* The :not(:has(.spinner-glow-label:hover)) exclusion this used to need is
   gone along with .spinner-glow-label itself moving out into its own
   .spinner-glow-sidebar box (index.html) — it was only ever there so
   hovering that checkbox (previously nested inside THIS element) didn't
   also highlight this unrelated opacity label/value. */
.emblem-opacity-sidebar:hover .emblem-opacity-label,
.emblem-opacity-sidebar:hover #emblem-opacity-value,
.spinner-scale-sidebar:hover .spinner-scale-label,
.spinner-scale-sidebar:hover #spinner-scale-value,
.emblem-rotation-speed-sidebar:hover .emblem-rotation-speed-label,
.emblem-rotation-speed-sidebar:hover #emblem-rotation-speed-value {
  color: var(--highlight);
  opacity: 1;
}
.spinner-glow-label,
.spinner-direction-label,
.spinner-pulsate-label {
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: var(--cursor-pointer);
  font-size: 14px;
  opacity: 0.4;
  transition:
    opacity 200ms,
    color 200ms;
  color: var(--font-color);
  border-radius: inherit;
}
.spinner-glow-label input[type="checkbox"],
.spinner-direction-label input[type="checkbox"],
.spinner-pulsate-label input[type="checkbox"] {
  display: none;
}
.spinner-glow-label:has(input:checked),
.spinner-glow-label:hover,
.spinner-direction-label:has(input:checked),
.spinner-direction-label:hover,
.spinner-pulsate-label:has(input:checked),
.spinner-pulsate-label:hover {
  opacity: 1;
}
.spinner-glow-icon,
.spinner-direction-icon,
.spinner-pulsate-icon {
  display: inline-flex;
}
/* The scale-on-hover has to live on the inner <i>, not the icon span itself
   — that span already owns the pulsate keyframe animation below, and an
   animated transform always wins over a plain hover transform on the same
   element, so the scale would just get silently dropped while it plays. */
.spinner-glow-icon i,
.spinner-direction-icon i,
.spinner-pulsate-icon i {
  transition: transform 200ms ease;
}
.spinner-glow-label:hover .spinner-glow-icon i,
.spinner-direction-label:hover .spinner-direction-icon i,
.spinner-pulsate-label:hover .spinner-pulsate-icon i {
  transform: scale(1.2);
}
/* Mirrors the arrow icon horizontally once reversed is actually picked —
   a cheap, immediate visual echo of "the direction flipped" beyond just
   the opacity highlight every button here already gets on :checked. Lives
   on the icon's own <i>, same as the hover scale above, for the same
   reason (an animated transform on a PARENT would just win over this on
   the child instead of combining with it, if either ever needed to be the
   parent's own transform instead). */
.spinner-direction-label:has(input:checked) .spinner-direction-icon i {
  transform: scaleX(-1);
}
/* Same "the icon shows what the toggle actually does" idea as the spin
   icon's own rotation above, just breathing instead of spinning — this is
   purely cosmetic on the BUTTON itself; the real effect on .emblem is
   entirely driven by tickEmblem's own sine wave (scripts/animations.js),
   not this animation. */
/* .emblem-sidebar-group-left.visible - same reasoning/fix as the spin icon's
   own identical rule above. */
.emblem-sidebar-group-left.visible
  .spinner-pulsate-label:has(input:checked)
  .spinner-pulsate-icon {
  animation: pulsateIconBreathe 1.6s ease-in-out infinite;
}
@keyframes pulsateIconBreathe {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.25);
  }
}

.emblem.glow {
  filter: drop-shadow(0 0 8px var(--secondary-color));
}
.settings-dialog .settings-content {
  position: relative;
  height: 660px;
  width: 100%;
  padding: 10px;
  border-radius: 10px;

  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.5);

  /* Dialog height is otherwise content-driven (see .settings-dialog above),
     so on short viewports this panel would grow past the screen edges with
     no way to reach the rest of the settings. Capping it here — rather than
     on .settings-dialog — keeps the absolutely-positioned .version/.language
     tab (a sibling, deliberately placed outside the dialog's own box)
     unaffected, since .settings-dialog itself stays overflow: visible.
     overflow-y: hidden, NOT auto - this box itself no longer scrolls as one
     unit (that used to drag the dialog's own h2/.settings-tabs out of view
     along with everything else on overflow, a real reported issue). Each
     actual content region below (.homepage-settings-panel/
     .tasks-settings-panel/.hotkeys-settings-panel/.settings-profile-screen,
     all flex: 1; min-height: 0; overflow-y: auto of their own) now scrolls
     independently instead, while h2/.settings-tabs (flex-shrink: 0, further
     down) keep their natural size and stay pinned above it. */
  max-height: calc(100vh - 60px);
  overflow-y: hidden;
  overflow-x: hidden;
}
/* Both this dialog's own h2 (the main title) and .settings-profile-header
   (Profile's own h2) - a plain `h2` selector reaches both, since neither
   ever needs its own separate rule here beyond this. flex-shrink: 0 keeps
   either pinned at its natural height instead of ever being squeezed by the
   flex layout, so it (and .settings-tabs/.profile-tabs below) always stay
   fully visible above whichever content region is currently scrolling. */
.settings-content > h2 {
  flex-shrink: 0;
}
/* The Profile screen replaces every other section instead of sitting
   inline among them (toggled via .settings-profile-button/
   .settings-profile-back-button, scripts/settings.js) — hidden by
   default, and every OTHER direct child of .settings-content hides in
   turn once .showing-profile is set, rather than a separate overlay
   element stacked on top of them. .settings-profile-user-data-header (the
   "User Data" h3) isn't listed here anymore - it moved inside
   .settings-profile-screen itself (alongside the new "Connection" section,
   index.html), so it now just follows THAT element's own display toggle
   like any other descendant, no separate entry needed. */
.settings-profile-header,
.settings-profile-screen {
  display: none;
}
.settings-profile-screen {
  flex-direction: column;
  gap: 10px;
  /* Its own scrolling region now (not .settings-content as a whole,
     styles/settingsDialog.css's own comment on that rule) - the
     Administration tab in particular can hold an arbitrary, unbounded
     number of rows (every allow-listed login, every pending request, every
     blocked account), so this needs to grow only up to whatever
     .settings-content actually has left after h2/.profile-tabs, then
     scroll internally past that rather than pushing the dialog's own header
     out of view. */
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
/* Reserves exactly .settings-corner-links' own height at the bottom of the
   scrollable area - that element floats over this box (position: absolute,
   anchored to .settings-content, not a normal in-flow child of this
   scrolling column), so without this the last row of whichever inner tab
   (Current User/Administration) is scrolled to the bottom would otherwise
   sit hidden underneath it. A real flex child with its own height, NOT
   padding-bottom on .settings-profile-screen itself - a flex container's
   own trailing padding is unreliably excluded from its scrollable overflow
   area once overflow-y: auto is involved (confirmed: the padding-bottom
   version left the last row still hidden behind the links exactly like
   before it was added). Keep this in sync with .settings-corner-links' own
   height if either ever changes - same value, further below. */
.settings-corner-links-spacer {
  flex-shrink: 0;
  height: 24px;
}
/* Admin-only inner tab bar (applyAdministrationTabVisibility toggles
   .admin-mode on .profile-tabs itself, scripts/settings.js — same pattern
   as showIconManager's own .admin-mode toggle, scripts/iconManager.js) - a
   non-admin session never sees this bar at all, just today's single-panel
   Profile screen underneath. A sibling of .settings-profile-screen, not
   nested inside it (index.html) - hidden by default like that element (its
   own rule just above), shown only once BOTH .showing-profile and
   .admin-mode apply, mirroring exactly how .settings-profile-screen itself
   only shows under .showing-profile below. */
.profile-tabs {
  display: none;
  flex-shrink: 0;
  gap: 6px;
  margin-top: -5px;
}
.settings-content.showing-profile .profile-tabs.admin-mode {
  display: flex;
}
/* .profile-administration-panel is hidden by default (a non-admin session
   never toggles showing-profile-admin-tab in the first place, but this
   also covers an admin's OWN default "Current User" tab on every fresh
   Profile screen open, per showSettings' own reset-on-open convention). */
.profile-current-user-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.profile-administration-panel {
  display: none;
  flex-direction: column;
  gap: 10px;
}
.settings-profile-screen.showing-profile-admin-tab .profile-current-user-panel {
  display: none;
}
.settings-profile-screen.showing-profile-admin-tab
  .profile-administration-panel {
  display: flex;
}
.settings-content.showing-profile
  > *:not(.settings-profile-header):not(.settings-profile-screen):not(
    .profile-tabs
  ):not(.change-log) {
  display: none;
}
.settings-content.showing-profile .settings-profile-header {
  /* flex, not block — .settings-profile-header is still an h2, and the
     class selector here (higher specificity than the plain `h2` rule
     above) would otherwise win and drop the flex centering that keeps
     .settings-title-text vertically centered. */
  display: flex;
}
.settings-content.showing-profile .settings-profile-screen {
  display: flex;
}
/* The profile button doesn't clear showing-tasks-tab when it adds
   showing-profile (init.js) - both classes can be present on
   .settings-content at once, so opening Profile from the Tasks tab must
   look/behave IDENTICALLY to opening it from the Homepage tab. Without an
   explicit, forced rule here, .settings-tabs/.tasks-settings-panel's own
   showing-tasks-tab-driven "stay visible"/"show" rules (below) would tie
   with this block's own generic ">*:not(...)" hide rule on plain CSS
   specificity, leaving the outcome to depend on source order rather than
   intent. !important settles it outright: whenever Profile is showing,
   these two are ALWAYS hidden, no matter which tab was active a moment
   before - and since neither showing-tasks-tab itself nor the tab buttons'
   own .active state are touched by opening/closing Profile, the exact same
   tab is still selected underneath once the back button returns to it. */
.settings-content.showing-profile .settings-tabs,
.settings-content.showing-profile .tasks-settings-panel,
.settings-content.showing-profile .hotkeys-settings-panel {
  display: none !important;
}
/* Homepage/Tasks settings tabs - the dialog's own h2 (title + reset/profile
   buttons) and .settings-tabs (the tab row itself) both stay visible
   regardless of which tab is active; every HOMEPAGE settings section hides
   while .showing-tasks-tab is set (mirroring .showing-profile's own
   ":not()" list above, just swapping between two INLINE panels instead of
   replacing everything with a full separate screen), and
   .tasks-settings-panel (hidden by default, below) shows in its place.
   :not(.showing-profile) here, NOT a ":not(.settings-profile-header)" etc.
   entry added to the list on the right - the profile button doesn't clear
   showing-tasks-tab when it adds showing-profile (init.js), so both classes
   can be on .settings-content at once, and this rule's own ">*:not(...)"
   (4 :not()s) outranks .showing-profile's much plainer ".settings-content.
   showing-profile .settings-profile-header {display:flex}" override on
   pure specificity - the profile content stayed hidden even though
   .showing-profile "won" the class fight. Excluding .showing-profile here
   entirely instead makes this whole rule step aside the moment Profile is
   showing, leaving THAT screen's own visibility fully up to its own rules
   (below) with nothing left to outrank - the explicit, forced hide of
   .settings-tabs/.tasks-settings-panel while Profile is open (below) still
   handles those two on its own regardless. */
.settings-content.showing-tasks-tab:not(.showing-profile)
  > *:not(.settings-tabs):not(.tasks-settings-panel):not(.change-log):not(h2) {
  display: none;
}
/* Same idea, same reasoning, for the Hotkeys tab - a third mutually
   exclusive INLINE panel alongside Homepage/Tasks above, not a fourth
   ":not()" entry added to either of THEIR own rules, since only one of
   showing-tasks-tab/showing-hotkeys-tab is ever set at a time (the tab-
   click handler, init.js, always clears the other) — .tasks-settings-panel
   is already hidden by default regardless (below), so it needs no explicit
   mention here either. */
.settings-content.showing-hotkeys-tab:not(.showing-profile)
  > *:not(.settings-tabs):not(.hotkeys-settings-panel):not(.change-log):not(
    h2
  ) {
  display: none;
}
.settings-tab-btn.active,
.link-stats-tab.active,
.profile-tab-btn.active {
  background: var(--secondary-color);
  /* Not --font-color-clear - that's a fixed light/dark gray per theme,
     oblivious to how light or dark the user's own picked accent color
     (--secondary-color, the background here) happens to be. A light accent
     in dark theme (or a dark one in light theme) left this unreadable
     either way. --accent-tab-active-text (above) is recomputed per-color
     instead (updateAccentTabActiveTextColor, general.js), so this always
     reads as black-on-light or white-on-dark regardless of theme or which
     accent color is picked. */
  color: var(--accent-tab-active-text, #000000);
}
/* Same dimmed + PRO badge treatment every other plan-gated control in this
   app gets (.plan-gated-locked, further down this file) - a bespoke rule
   rather than reusing that class directly, same reasoning
   .new-tab-setting.page-tabs.plan-gated-locked's own comment gives: that
   shared rule's sizing/positioning is tuned for small icon-shaped toolbar
   buttons, not this pill-shaped tab button. Toggled in
   applyPlanGatedToolbarVisibility (init.js); the actual tab-switch is
   blocked by that same function's click-handler counterpart, not by this
   rule - pointer-events stays enabled so hover/tooltip behavior is
   unaffected. */
.settings-tab-btn.plan-gated-locked {
  position: relative;
  opacity: 0.7;
}
.settings-tab-btn.plan-gated-locked::after {
  content: "PRO";
  position: absolute;
  top: -6px;
  right: -4px;
  background-color: var(--highlight);
  color: var(--main-theme-darkest);
  font-size: 8px;
  font-weight: bold;
  line-height: 1;
  letter-spacing: 0.5px;
  padding: 3px 2px 2px 3px;
  border-radius: 5px;
  pointer-events: none;
}
/* Same "can't verify right now" swap as the generic .plan-gated-locked.plan-check-offline
   badge (styles.css, near .needs-signin-locked) - see its own comment for
   the full reasoning. */
.settings-tab-btn.plan-gated-locked.plan-check-offline::after {
  content: "🔒";
  background-color: var(--font-color-weak);
  color: var(--main-theme-darkest);
  font-size: 7px;
}
.tasks-settings-panel,
.hotkeys-settings-panel {
  display: none;
  flex-direction: column;
  gap: 10px;
}
/* flex:1 + min-height:0 (not just display:flex) is what lets
   .categories-edit-settings' own inner .categories-list actually scroll on
   overflow (below) instead of growing this panel - and .settings-content
   with it - taller than the dialog every time the Tasks tab's own content
   doesn't fit in one screen (as many categories accumulate, exactly the
   problem this panel exists to help clean up). Without min-height:0, a flex
   child defaults to min-height:auto - "never smaller than my own content" -
   which silently defeats any overflow:auto set further down this same flex
   chain, since nothing above it is ever willing to actually get smaller
   than its content in the first place. Same reasoning as the Hotkeys tab's
   own identical toggle just below. */
.settings-content.showing-tasks-tab .tasks-settings-panel {
  display: flex;
  flex: 1;
  min-height: 0;
  /* Its own scrolling region now, same reasoning/pattern as
     .settings-profile-screen's own identical trio above - .settings-content
     no longer scrolls as a whole (its own comment), so each tab's content
     scrolls independently instead. Already effectively covered by
     .categories-list/.tags-list's own inner overflow-y: auto further below,
     but this is the belt-and-suspenders outer fallback every tab gets
     uniformly, not something to rely on only some tabs happening to have
     internally-scrolling lists of their own. */
  overflow-y: auto;
}
.settings-content.showing-hotkeys-tab .hotkeys-settings-panel {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
/* .settings-profile-user-data-header (the "User Data" h3) no longer has its
   own bespoke styling here - it moved inside .profile-user-data-settings
   (index.html), a normal card alongside .profile-connection-settings (the
   new "Connection" section), and now shares the exact same position/
   centering/card-background treatment every other Settings section's own h3
   gets (the combined selector groups above) instead of a one-off "plain
   sibling in normal flow" layout of its own. The class itself stays on the
   element purely as a stable hook (applyLanguage's own header-text lookup,
   language.js) - not a styling target anymore. */
/* --highlight (#d38200) is only ~25 lightness points off the light theme's
   --main-theme (#a8a8a8) background labels sit on here, which reads fine
   for borders/icons/badges but is too close in lightness for hovered LABEL
   TEXT to stay legible. Everywhere else --highlight is used as-is (already
   fine) — scoped to just this panel via a local override, so every
   label:hover/etc. rule already referencing var(--highlight) picks this up
   for free instead of having to rewrite each one individually. */
:root.light-theme .settings-content label {
  --highlight: #995e00;
}
/* ...except .change-log, which sits on its own always-near-black backdrop
   regardless of theme (see below) — the original brighter value already
   reads fine there, so it's restored rather than inheriting the override. */
:root.light-theme .settings-content .change-log {
  --highlight: #d38200;
}
.settings-content .change-log {
  border-radius: 10px;
  padding: 15px;
  left: 0;
  top: 0;
  position: absolute;
  z-index: 5;
  width: 100%;
  height: 100%;
  /* --main-theme-darkest stays dark in both themes by design (it's the
     "always dark" token), so this still reads as a near-opaque dark panel
     in light mode too — the .change-log --highlight restore just above
     still holds since the backdrop is still dark enough for it. */
  background-color: var(--main-theme-darker);
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms ease;
  overflow-y: auto;
  /* The title-group's own nowrap text (changelog.js) can now run wider than
     this panel — never wanted as a horizontal scrollbar, just harmless
     overflow past the edge (nothing here is meant to be scrolled sideways
     to read). */
  overflow-x: hidden;
}
.change-log .title {
  display: flex;
  justify-content: space-between;
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
}
.change-log .subtitle {
  font-size: 13px;
  color: var(--highlight);
  margin-top: 10px;
  font-weight: bold;
}
/* Bigger subtitle text for an important entry (.changelog-title-important,
   shared/styles/changelog.css) — homepage-only; the tasks page's own
   .tasks-cl-subtitle stays a fixed size regardless, its side panel is too
   cramped for the bump to read well. */
.change-log .subtitle:has(.changelog-title-important) {
  font-size: 15px;
}
.change-log .text {
  /* Same gap-per-bullet + background/box treatment as the tasks page's
     .tasks-cl-text, so both pages' changelog history reads the same way.
     12px (was 10px) — genuinely small for reading multi-sentence bullets,
     even with this same generous line-height. */
  font-size: 12px;
  display: block;
  margin-left: 10px;
  margin-top: 6px;
  line-height: 1.7;
  background: var(--main-theme);
  border-radius: 8px;
  padding: 5px;
  color: var(--font-color-clear);
}

/* Matches input[type="range"]'s own track (below) — same 8px height/5px
   radius pill shape, just filled with the actual accent color instead of a
   neutral track color + thumb, so the two controls read as the same kind
   of control now that they share a row (.settings-row-pair). The 3 pseudo-
   elements below strip the browser's own default swatch padding/border,
   which would otherwise keep the visible color inset inside a taller,
   square native box regardless of the height/border-radius set here. */
.settings-content input[type="color"] {
  -webkit-appearance: none;
  appearance: none;
  border: none;
  outline: none;
  height: 8px;
  width: 100%;
  border-radius: 5px;
  background-color: transparent;
  cursor: var(--cursor-pointer);
}
.settings-content input[type="color"]::-webkit-color-swatch-wrapper {
  padding: 0;
  border-radius: 5px;
}
.settings-content input[type="color"]::-webkit-color-swatch {
  border: none;
  border-radius: 5px;
}
.settings-content input[type="color"]::-moz-color-swatch {
  border: none;
  border-radius: 5px;
}
.accent-picker {
  background: var(--main-theme-darker);
  padding: 10px;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.settings-content label:not(.theme-mode-icon) {
  display: block;
  width: 100%;
  padding-top: 1px;
}
.settings-content label:hover,
.accent-picker input[type="color"]:hover {
  cursor: var(--cursor-pointer);
  color: var(--highlight);
}
/* Homepage/Tasks tab row - sits directly below the dialog's own h2, above
   every settings section. Plain 2-way segmented control - see
   .showing-tasks-tab (above) for the actual show/hide toggle this drives.
   The actual tab-BUTTON look (.settings-tab-btn/.link-stats-tab, below) is
   shared with Link Stats' own tab row - only this row-layout rule stays
   Settings-specific, since Link Stats' equivalent (.link-stats-tabs) needs
   its own fixed width/padding to match that panel's layout instead. */
.settings-tabs {
  display: flex;
  flex-shrink: 0;
  gap: 6px;
  margin-top: -5px;
}
/* Shared "segmented tab button" look - .settings-tab-btn (Settings dialog's
   Homepage/Tasks switch, above) and .link-stats-tab (Link Stats' own
   stats/insights switch, further down) are two different class names for
   the exact same design, kept in one rule set here rather than duplicated,
   so the two can never visually drift apart. Neither name drives any
   switching LOGIC on its own (each panel's own JS keys off its own class
   name/data-tab attribute already) - this is purely the shared look.
   .profile-tab-btn (Settings > Profile's own admin-only Current User/
   Administration switch, above) shares it too, same reasoning. */
.settings-tab-btn,
.link-stats-tab,
.profile-tab-btn {
  flex: 1;
  padding: 4px 0;
  border: none;
  border-radius: 6px;
  background-color: var(--main-theme-darker);
  color: var(--font-color-weak);
  font-weight: bold;
  cursor: var(--cursor-pointer, pointer);
  transition:
    background-color 150ms,
    color 150ms;
}
/* :not(.active) - the active tab isn't going anywhere on a click (it's
   already selected), so it shouldn't hint that it is via this same hover
   feedback the still-clickable, inactive tab(s) get. */
.settings-tab-btn:not(.active):hover,
.link-stats-tab:not(.active):hover,
.profile-tab-btn:not(.active):hover {
  color: var(--font-color-clear);
}
.settings-content
  > div:not(.context-menu-settings):not(.change-log):not(.settings-tabs):not(
    .tasks-settings-panel
  ):not(.hotkeys-settings-panel):not(.profile-tabs):not(
    .homepage-settings-panel
  ) {
  background-color: var(--main-theme);
  padding: 8px;
  border-radius: 6px;
  user-select: none;
}
/* The Homepage tab's own scrollable content region (Container/Anzeige/
   Kontextmenü, index.html - a plain wrapper, not a card itself, so it's
   excluded from the generic direct-child card rule above; each of ITS OWN
   children keeps its own card look via its own dedicated rule instead,
   same as every other panel's inner sections already do). Same
   flex: 1; min-height: 0; overflow-y: auto shape as .tasks-settings-panel/
   .hotkeys-settings-panel/.settings-profile-screen - see .settings-content's
   own comment on why each tab now scrolls independently instead of the
   whole dialog scrolling as one unit. Shown by default (no showing-X-tab
   class is what the Homepage tab itself actually looks like, styles.css) -
   hidden the same way the 3 sections it wraps already were, by the
   showing-tasks-tab/showing-hotkeys-tab/showing-profile direct-child hide
   rules simply not excluding it either. */
.homepage-settings-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
/* Accent color + Cursor Glow Radius (.accent-picker/.range-setting) share
   one row, light/dark theme toggle sharing another with Aurora — each pair
   an even flex-1 split rather than the full-width rows these used to be
   on their own. */
.settings-row-pair {
  display: flex;
  gap: 10px;
}
.settings-row-pair > * {
  flex: 1;
  min-width: 0;
}
.settings-content .theme-mode-setting {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: var(--main-theme-darker);
  padding: 10px;
  border-radius: 10px;
  cursor: var(--cursor-pointer);
}
/* No white-space: nowrap here anymore (unlike before) — halving this row's
   width (now paired with Aurora via .settings-row-pair) leaves less room
   than the longer of the two labels ("Aurora-Design aktivieren")
   comfortably fits on one line; wrapping keeps it readable instead of
   bleeding into Aurora's own column. */
.settings-content .theme-mode-setting label:not(.theme-mode-icon) {
  margin-top: 1px;
  padding-right: 20px;
  width: auto;
}
.settings-content .theme-mode-setting label.theme-mode-icon > i {
  font-size: 20px;
}
.theme-mode-icon {
  position: relative;
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 25px;
  height: 25px;
  font-size: 18px;
  cursor: var(--cursor-pointer);
  /* Previews the theme a click would switch to, not the current one. */
  color: var(--main-theme-lighter-inverse);
}
.theme-mode-icon input[type="checkbox"] {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: var(--cursor-pointer);
}
/* Keyed off the whole row (not just the icon, and not a `+ label` sibling
   selector — the text label actually comes BEFORE the icon in source
   order, see index.html, which made that selector a dead rule that never
   matched anything) so hovering ANYWHERE in the row — including the gap
   between the label and the icon, which the click listener wiring this
   same row up (scripts/init.js) also covers for clicks — highlights both
   pieces together. The icon needs its own explicit restatement since it
   sets its own color already (just above), which inheriting from the row
   alone wouldn't win against. */
.settings-content .theme-mode-setting:hover,
.settings-content .theme-mode-setting:has(input:focus-visible) {
  color: var(--highlight);
}
.settings-content .theme-mode-setting:hover .theme-mode-icon,
.settings-content
  .theme-mode-setting:has(input:focus-visible)
  .theme-mode-icon {
  color: var(--highlight);
}
/* The actual checkbox sits on top of the icon at opacity: 0 (just above) —
   opacity zeroes out the ENTIRE rendered element, so the project-wide
   :focus-visible outline (focusVisible.css) never shows even though it's
   still technically applied to that invisible box. Drawn on the icon
   itself instead. */
.settings-content
  .theme-mode-setting:has(input:focus-visible)
  .theme-mode-icon {
  outline: 2px solid var(--highlight);
  outline-offset: 2px;
  border-radius: 4px;
}
.settings-number-input {
  cursor: var(--cursor-text) !important;
}

.range-setting {
  position: relative;
  background: var(--main-theme-darker);
  padding: 10px;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

/* Sits right above the quick-search/web-search/page-tabs checkboxes below
   — their own labels are now just the bare feature name ("Quick-Search",
   not "Don't open the 'Quick-Search' on page load"), so this establishes
   the shared "show/hide on load" context they'd otherwise be missing. */
.on-page-load-subtitle {
  margin: 0;
  font-size: 11px;
  color: var(--font-color-clear);
}
.new-tab-setting {
  display: flex;
  align-items: center;
  gap: 10px;
  background-color: var(--main-theme-darker);
  padding: 10px;
  border-radius: 10px;
}
/* Two "keep open on [Esc]" checkboxes (index.html) sharing this one pill
   instead of each getting its own - overrides the base rule's row layout
   (above) with a column of the two rows instead, each row (a plain div, no
   .new-tab-setting of its own - one shared background/padding is the whole
   point) laid out exactly like the base rule's own input+label row. Only a
   small gap between the two rows, not another full padded box. */
.new-tab-setting.keep-open-on-escape-setting {
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
}
.new-tab-setting.keep-open-on-escape-setting > div {
  display: flex;
  align-items: center;
  gap: 10px;
}
.new-tab-setting label {
  white-space: nowrap;
}
/* Matches .context-menu-panel's own checkbox styling exactly — .theme-mode-icon's
   own checkbox is deliberately excluded (opacity:0, a hidden hit-target
   behind a custom icon toggle, not a visible checkbox at all). */
.new-tab-setting input[type="checkbox"] {
  accent-color: var(--secondary-color);
  cursor: var(--cursor-pointer);
  transform: scale(1.3);
}
/* "Default lead days for new templates" (index.html) - its label runs
   longer than a checkbox's, so the base rule's nowrap (above) would just
   clip/overflow it instead of wrapping onto a second line. */
.tasks-template-lead-days-setting label {
  white-space: normal;
}
.tasks-template-lead-days-field {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
/* Same "hide the native spin buttons, replace them with real themeable
   ones" approach as a single template's own lead-days field
   (.template-edit-lead-days, tasks/styles/taskTemplates.css) - see that
   rule's own comment for why the native ones can't just be restyled in
   place. No extra padding held open for a stepper anymore - the two step
   buttons sit beside the input now, not on top of it. */
.tasks-template-lead-days-field input {
  -moz-appearance: textfield;
  appearance: textfield;
  width: 36px;
  height: 28px;
  box-sizing: border-box;
  background: var(--grid-color);
  border: none;
  outline: none;
  border-radius: 8px;
  padding: 5px 10px;
  text-align: center;
  color: var(--font-color-clear);
  font-family: inherit;
  font-size: 13px;
}
.tasks-template-lead-days-field input::-webkit-inner-spin-button,
.tasks-template-lead-days-field input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* Two square buttons flanking the input directly (index.html's own DOM
   order: step-down, input, step-up) - rather than the stacked chevron pair
   overlapping the input a single template's own lead-days field uses
   (.template-edit-lead-days-stepper, tasks/styles/taskTemplates.css).
   .tasks-template-lead-days-field's own gap (above) already spaces all
   three evenly. Sized to match the input beside them. */
.tasks-template-lead-days-field button {
  width: 28px;
  height: 28px;
  box-sizing: border-box;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: var(--grid-color);
  color: var(--font-color-clear);
  opacity: 0.85;
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.tasks-template-lead-days-field button:hover {
  color: var(--highlight);
  opacity: 1;
  cursor: var(--cursor-pointer, pointer);
}
/* Sized to match one half of .settings-row-pair's own quick-search/web-search
   pair just above (index.html) — 50% minus half its 10px gap — rather than
   stretching this lone setting across the full row, as if a second setting
   still sat beside it in the same pair. */
.new-tab-setting.page-tabs {
  width: calc(50% - 5px);
}
/* Same PRO badge + dimmed look as .flat-design-setting's own locked state
   (below) — bespoke rather than reusing .plan-gated-locked directly here:
   that class's shared transform: scale(0.9)/border-color overrides
   (styles.css) are sized for small icon-shaped buttons, not this row's own
   fixed half-width. accent-color alone barely reads as "locked" on a native
   checkbox (same reasoning .flat-design-icon's own color override has over
   filter: grayscale — a plain color property, not a compositing filter, so
   it isn't at the mercy of how saturated the checkbox already happens to
   render), so it's paired with filter: grayscale as backup. */
.new-tab-setting.page-tabs.plan-gated-locked {
  position: relative;
  opacity: 0.7;
}
.new-tab-setting.page-tabs.plan-gated-locked input[type="checkbox"] {
  accent-color: var(--font-color-weak);
  filter: grayscale(1);
}
.new-tab-setting.page-tabs.plan-gated-locked label {
  opacity: 0.85;
}
.new-tab-setting.page-tabs.plan-gated-locked::after {
  content: "PRO";
  position: absolute;
  top: -4px;
  right: -4px;
  background-color: var(--highlight);
  color: var(--main-theme-darkest);
  font-size: 9px;
  font-weight: bold;
  line-height: 1;
  letter-spacing: 0.5px;
  padding: 4px 2px 2px 4px;
  border-radius: 6px;
  pointer-events: none;
}
/* Same "can't verify right now" swap as the generic .plan-gated-locked.plan-check-offline
   badge above - see its own comment for the full reasoning. No accent-color
   override needed here the way plan-gated-locked alone has above it (this
   row's own locked checkbox already goes neutral gray, not highlight-
   colored, regardless of why it's locked). */
.new-tab-setting.page-tabs.plan-gated-locked.plan-check-offline::after {
  content: "🔒";
  background-color: var(--font-color-weak);
  color: var(--main-theme-darkest);
  font-size: 8px;
}

input[type="range"] {
  cursor: var(--cursor-pointer);
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  /* A real, reported bug on phone (Settings > Customize Emblem's own three
     sliders, most easily triggered there since they sit inside a full-
     screen phone panel with real screen-edge back-gesture zones on either
     side) - without this, dragging a slider's thumb is indistinguishable,
     to the browser itself, from an edge-swipe "go back" gesture (Android
     Chrome) or a horizontal scroll-navigation gesture (iOS Safari), which
     could silently hijack the drag and fire a real history navigation
     instead of just moving the thumb. touch-action: none tells the browser
     up front that this element handles 100% of its own touch input, so
     neither gesture ever gets a chance to compete with a plain drag - not
     scoped to just the emblem sidebar's sliders since the exact same
     conflict applies to any input[type="range"] dragged near a screen edge
     on a touchscreen (e.g. Settings' own cursor-glow-radius slider, a
     sticky note's own opacity/rotation controls). */
  touch-action: none;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  height: 16px;
  width: 16px;
  border-radius: 50%;
  background: var(--secondary-color);
  cursor: var(--cursor-pointer);
  margin-top: -4px;
}
input[type="range"]::-moz-range-thumb {
  height: 16px;
  width: 16px;
  border: none;
  border-radius: 50%;
  background: var(--secondary-color);
  cursor: var(--cursor-pointer);
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 8px;
  background: #5f5f5f;
  border-radius: 5px;
}
input[type="range"]::-moz-range-track {
  height: 8px;
  background: #5f5f5f;
  border-radius: 5px;
}
#range-value {
  position: absolute;
  right: 8px;
  font-size: 12px;
  color: var(--secondary-color);
}

.settings-content .container-size {
  position: relative;
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.settings-content .theme-settings {
  position: relative;
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Both now sit one level deeper, inside .homepage-settings-panel (index.html)
   rather than as direct children of .settings-content - same "no longer
   reached by the generic direct-child card rule below, so its
   background/padding/border-radius/user-select gets repeated explicitly
   here instead" situation .tasks-edit-settings' own comment further down
   already explains for the exact same reason. A separate rule rather than
   folding these two into that shared one - it also sets its own `gap`,
   which would have silently overridden each of these two elements' own
   distinct gap (5px/8px above) with that rule's single shared 8px value. */
.settings-content .container-size,
.settings-content .theme-settings {
  background-color: var(--main-theme);
  padding: 8px;
  border-radius: 6px;
  user-select: none;
}
/* .tasks-edit-settings isn't a direct child of .settings-content like the
   two above (it's nested one level deeper, inside .tasks-settings-panel,
   so its own tab can hide/show as one unit) - a descendant selector still
   reaches it the same way regardless of that extra nesting, so it gets the
   identical position/margin/h3-divider treatment as Container/Anzeige.
   Its own card background/padding/border-radius (normally the direct-child
   rule above) is repeated explicitly here for the same reason.
   .hotkeys-edit-settings (inside .hotkeys-settings-panel) is the exact same
   situation for the Hotkeys tab, so it shares this rule rather than
   repeating it a third time. .tags-edit-settings sits right alongside
   .categories-edit-settings in the very same tab, so it gets the identical
   treatment too - same for .profile-connection-settings/
   .profile-user-data-settings inside the Profile screen. */
.settings-content .tasks-edit-settings,
.settings-content .hotkeys-edit-settings,
.settings-content .categories-edit-settings,
.settings-content .tags-edit-settings,
.settings-content .profile-connection-settings,
.settings-content .profile-user-data-settings,
.settings-content .admin-access-requests-settings,
.settings-content .admin-blocked-settings,
.settings-content .admin-allowlist-settings {
  position: relative;
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background-color: var(--main-theme);
  padding: 8px;
  border-radius: 6px;
  user-select: none;
}
/* Overrides just these two elements' own margin-top from the shared 14px
   above (same specificity, so it has to come AFTER that rule to actually
   win) - matches .theme-settings'/.container-size' own already-bumped 18px
   (further up this file) rather than folding .tasks-edit-settings/
   .hotkeys-edit-settings into that rule directly, which would also change
   margin-top for the other elements sharing it. */
.settings-content .tasks-edit-settings,
.settings-content .hotkeys-edit-settings {
  margin-top: 18px;
}
/* Lets this card itself shrink to whatever's actually left inside its own
   parent panel (.hotkeys-settings-panel/.tasks-settings-panel, both
   flex:1/min-height:0 above) instead of growing to fit its full content -
   .hotkeys-list/.categories-list/.tags-list (below) is what actually
   scrolls, not this card itself, so its own h3 (position:absolute, floating
   in the margin-top gap above the card, below) stays put right above the
   card's top edge instead of scrolling away with the rows - .tasks-edit-settings,
   sharing the rule above, never needed this since it only ever holds one
   short row. Categories and Tags share the tab's remaining space evenly
   between them (both flex: 1). */
.settings-content .hotkeys-edit-settings,
.settings-content .categories-edit-settings,
.settings-content .tags-edit-settings {
  flex: 1;
  min-height: 0;
}
/* :not(.plan-gated-locked) — .new-tab-setting.page-tabs is a direct child
   here and old enough to predate that class, so without this exclusion a
   locked Paging Toolbar row would carry BOTH this plain divider (border-top
   plus its own extra 8px padding-top, on top of .new-tab-setting's own
   uniform 10px) AND .plan-gated-locked's own dashed border (above) at once
   — the border conflict resolves fine on its own (!important always wins
   the top edge too), but the leftover 8px would still push this row's
   content down off-center inside its new box, looking asymmetric against
   the other three padded, dash-bordered sides. */
.settings-content
  .theme-settings
  > div:not(:first-of-type):not(.plan-gated-locked) {
  padding-top: 8px;
  border-top: 1px solid var(--main-theme-lighter);
}
.settings-content .theme-settings > div:first-of-type {
  margin-bottom: -5px;
}
/* :first-of-type above only counts DIV children, and .on-page-load-subtitle
   is a <p> — so the quick-search/web-search .settings-row-pair right after
   it is still the THIRD div child overall, matching the generic divider
   rule above despite already sitting directly under its own subtitle
   caption. That caption already provides this row's section separation, so
   the extra padding-top/border-top just pushed it away from the label it's
   supposed to sit right under. Same .on-page-load-subtitle + .settings-row-pair
   selector already used elsewhere (styles.css's own phone-mode hide-this-row
   rule) to reach this exact instance only — the div element selector here
   matches the generic rule's own specificity (both .settings-content
   .theme-settings > class, class, +element/class), so source order (this
   rule sits after it) is what actually wins, no !important needed. */
.settings-content
  .theme-settings
  > .on-page-load-subtitle
  + div.settings-row-pair {
  padding-top: 0;
  border-top: none;
}
.settings-content .container-size h3,
.settings-content .context-menu-settings h3,
.settings-content .theme-settings h3,
.settings-content .tasks-edit-settings h3,
.settings-content .hotkeys-edit-settings h3,
.settings-content .categories-edit-settings h3,
.settings-content .tags-edit-settings h3,
.settings-content .profile-connection-settings h3,
.settings-content .profile-user-data-settings h3,
.settings-content .admin-access-requests-settings h3,
.settings-content .admin-blocked-settings h3,
.settings-content .admin-allowlist-settings h3 {
  position: absolute;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  /* Centers the icon+label(s) themselves - the ::before/::after lines below
     are real flex items too (a zero-width flex:1 box each), so centering the
     GROUP between them puts the title in the true middle of the h3's full
     width rather than flush against whichever line happens to come first in
     source order. The chevron toggle (.settings-section-toggle, below) is
     ALSO a flex item here, not excluded from the flow - but its own
     `order: -1` pulls it before the ::before line regardless of DOM
     position, so it reads as pinned to the far left rather than counting
     towards the centered title group. */
  justify-content: center;
  gap: 6px;
  margin-bottom: 2px;
  bottom: 100%;
  font-weight: bold;
  font-style: italic;
  color: var(--secondary-color);
  /* The whole row toggles collapse now (wireSettingsSectionToggles,
     settings.js), not just the chevron button inside it - this cursor is
     the row-wide affordance for that. */
  cursor: var(--cursor-pointer, pointer);
}
.settings-content .container-size h3 i,
.settings-content .context-menu-settings h3 i,
.settings-content .theme-settings h3 i,
.settings-content .tasks-edit-settings h3 i,
.settings-content .hotkeys-edit-settings h3 i,
.settings-content .categories-edit-settings h3 i,
.settings-content .tags-edit-settings h3 i,
.settings-content .profile-connection-settings h3 i,
.settings-content .profile-user-data-settings h3 i,
.settings-content .admin-access-requests-settings h3 i,
.settings-content .admin-blocked-settings h3 i,
.settings-content .admin-allowlist-settings h3 i {
  font-size: 0.9em;
  font-style: normal;
}
/* Flanking lines either side of the centered icon+label(s) - each a
   zero-content flex:1 box (align-items: center, above, keeps them vertically
   centered against the text/icon regardless of its own line-height), one
   generated before all real children (::before) and one after (::after), so
   together with justify-content: center above they read as a single
   horizontal rule running the h3's full width with the title sitting in the
   middle of it, chevron aside. Context Menu's own h3 gets these too even
   though it already carries a real, separate divider of its own
   (.context-menu-h3-divider, index.html/below) BETWEEN its two label groups -
   these just add the same outer framing every other h3 here gets, one on
   each end of the whole title instead of only between its two halves. */
.settings-content .container-size h3::before,
.settings-content .context-menu-settings h3::before,
.settings-content .theme-settings h3::before,
.settings-content .tasks-edit-settings h3::before,
.settings-content .hotkeys-edit-settings h3::before,
.settings-content .categories-edit-settings h3::before,
.settings-content .tags-edit-settings h3::before,
.settings-content .profile-connection-settings h3::before,
.settings-content .profile-user-data-settings h3::before,
.settings-content .admin-access-requests-settings h3::before,
.settings-content .admin-blocked-settings h3::before,
.settings-content .admin-allowlist-settings h3::before,
.settings-content .container-size h3::after,
.settings-content .context-menu-settings h3::after,
.settings-content .theme-settings h3::after,
.settings-content .tasks-edit-settings h3::after,
.settings-content .hotkeys-edit-settings h3::after,
.settings-content .categories-edit-settings h3::after,
.settings-content .tags-edit-settings h3::after,
.settings-content .profile-connection-settings h3::after,
.settings-content .profile-user-data-settings h3::after,
.settings-content .admin-access-requests-settings h3::after,
.settings-content .admin-blocked-settings h3::after,
.settings-content .admin-allowlist-settings h3::after {
  content: "";
  flex: 1;
  height: 1px;
  background-color: color-mix(in srgb, var(--secondary-color) 40%, transparent);
}
.admin-access-requests-list,
.admin-blocked-list,
.admin-allowlist-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Shown instead of any rows at all when one of the three Administration
   tab lists (renderAdministrationTab, scripts/settings.js) comes back
   empty - same dim, unobtrusive treatment the old chat-flyout version of
   this same list used (.access-requests-empty, shared/styles/chat.css,
   since removed). */
.admin-list-empty {
  font-size: 12px;
  color: var(--font-color);
  opacity: 0.6;
}
/* One row inside any of the three Administration tab lists
   (renderAdministrationTab, scripts/settings.js) - login on the left,
   whatever's relevant on the right (a timestamp for Access Requests/
   Blocked, a plan badge for Allow-list) plus one or two action buttons. */
.admin-list-entry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  background: var(--main-theme-darker);
  color: var(--font-color-clear);
  font-size: 13px;
}
.admin-list-entry-login {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.admin-list-entry-time {
  flex-shrink: 0;
  font-size: 11px;
  color: var(--font-color);
  opacity: 0.75;
}
/* Free/Pro (Allow-list rows only) - reuses the same PRO-badge palette as
   .settings-tab-btn.plan-gated-locked::after above rather than inventing a
   second one, so "Pro" reads consistently across this whole dialog. */
.admin-list-entry-plan-badge {
  flex-shrink: 0;
  font-size: 9px;
  font-weight: bold;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 3px 6px;
  border-radius: 5px;
  background-color: var(--main-theme-lighter);
  color: var(--font-color-weak);
}
.admin-list-entry-plan-badge.paid {
  background-color: var(--highlight);
  color: var(--main-theme-darkest);
}
.admin-list-entry-actions {
  flex-shrink: 0;
  display: flex;
  gap: 6px;
}
/* Small pill buttons, distinct from .settings-button's own icon-first shape
   used everywhere else in this dialog — a plain text label reads better at
   this size than a leading icon would. */
.admin-list-entry-actions button {
  flex-shrink: 0;
  border: 1px solid var(--secondary-color);
  border-radius: 5px;
  background-color: transparent;
  color: var(--font-color-clear);
  font-size: 11px;
  font-weight: bold;
  padding: 4px 8px;
  cursor: var(--cursor-pointer, pointer);
  transition:
    background-color 150ms,
    color 150ms;
}
.admin-list-entry-actions button:hover {
  background-color: var(--secondary-color);
  color: var(--accent-tab-active-text, #000000);
}
/* Block/Deny reads as the destructive action of the two/three on any given
   row - a plain hover swap to the accent color (like Approve/Change Plan/
   Allow Access get, above) would read as equally safe, so this one gets
   --priority-two (this app's own existing "warning" token, reused the same
   way .profile-settings-guest-warning does above) instead. */
.admin-list-entry-actions button.admin-action-danger {
  border-color: var(--priority-two);
  color: var(--priority-two);
}
.admin-list-entry-actions button.admin-action-danger:hover {
  background-color: var(--priority-two);
  color: var(--main-theme-darkest);
}
/* The Free/Pro picker inside .access-approve-confirm (index.html) - same
   row shape as .icon-manager-filter-container-row (styles/iconManagerDialog.css)
   uses for its own label+custom-select-trigger pairing. */
.access-approve-confirm-plan-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 4px 0;
}
/* The collapse chevron (index.html's own .settings-section-toggle, first
   child of every .settings-section-h3) - `order: -1` pulls it before the
   ::before line above regardless of its own DOM position, so the visible
   order always reads chevron, then line, then title, then line, without
   needing this to actually be the very first generated box itself (a plain
   ::before pseudo-element, unlike this, can never also be an interactive
   <button>). A plain icon button, not .category-toolbar-btn's own pill look -
   this always sits directly on the h3's own italic/colored text, never a
   plain background, so that busier look would clash here. */
.settings-section-toggle {
  order: -1;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  cursor: var(--cursor-pointer, pointer);
}
.settings-section-toggle i {
  transition: transform 150ms;
}
/* Collapsed state (toggleSettingsSection, settings.js, toggles this exact
   class on the h3 itself - the chevron's own closest ancestor) - the
   chevron itself rotates from pointing down to pointing right instead of
   swapping fa-chevron-down for fa-chevron-right, since a transform
   transitions smoothly while a class swap would just snap. */
.settings-section-h3.collapsed .settings-section-toggle i {
  transform: rotate(-90deg);
}
/* Hides everything below a collapsed section's own h3 - the h3 is always
   that box's first child in every one of these 9 sections, so a plain
   general-sibling selector reaches exactly "everything else in this same
   box" without needing :has() per section - it's scoped to whichever
   specific box the match happened in, not a global "everything later in the
   document" reach, so .profile-connection-settings/.profile-user-data-settings
   sitting right next to each other (both inside .settings-profile-screen)
   each only ever hide their own content, never bleed into the other's.

   !important - two confirmed real bugs without it: (1) .container-size's
   own row divs stayed visible on collapse (only the Apply button, which had
   no competing rule, actually hid) because .settings-content .container-size
   > div's own `display: flex` (below) ties this rule's specificity and
   comes later in the file, so it won by source order alone; (2) the Profile
   screen's own collapse did NOTHING because
   .settings-content.showing-profile .settings-profile-screen's own
   `display: flex` (styles.css, the Profile-screen-visibility rule) outranks
   this rule on SPECIFICITY alone (three classes vs. two), regardless of
   order - fixed for good now anyway, since neither profile section is a
   direct child of .settings-content anymore (see profile-settings-connection/
   profile-user-data-settings' own comment, index.html), but kept as
   !important regardless for the same "must always win" reasoning the other
   7 sections need it for. */
.tasks-edit-settings h3.collapsed ~ *,
.categories-edit-settings h3.collapsed ~ *,
.tags-edit-settings h3.collapsed ~ *,
.hotkeys-edit-settings h3.collapsed ~ *,
.container-size h3.collapsed ~ *,
.theme-settings h3.collapsed ~ *,
.context-menu-settings h3.collapsed ~ *,
.profile-connection-settings h3.collapsed ~ *,
.profile-user-data-settings h3.collapsed ~ *,
.admin-access-requests-settings h3.collapsed ~ *,
.admin-blocked-settings h3.collapsed ~ *,
.admin-allowlist-settings h3.collapsed ~ * {
  display: none !important;
}
/* .categories-edit-settings/.tags-edit-settings/.hotkeys-edit-settings are
   all flex: 1 (above) so their own scrollable list can share whatever
   vertical room their tab has left - collapsed, that list is display:none
   (rule above) but flex-grow doesn't care whether a flex item actually HAS
   visible content, so without this the now-empty card would still stretch
   to fill that same space, leaving a big blank box under its own
   floated-above h3 instead of shrinking down to just the h3 + its own
   padding like every other collapsed section here. */
.categories-edit-settings:has(> h3.collapsed),
.tags-edit-settings:has(> h3.collapsed),
.hotkeys-edit-settings:has(> h3.collapsed) {
  flex: none;
}
/* Rows built by renderHotkeysSettingsPanel (settings.js) - one
   .hotkeys-section per surface (Homepage; Tasks+Description Decorators
   share one), each a slightly darker card than .hotkeys-edit-settings'
   own background (below) it sits inside, so the two surfaces read as
   clearly separate boxes rather than one flat, undifferentiated list.
   Inside the shared Tasks+Description Decorators section, one
   .hotkeys-group per HOTKEY_ACTIONS "group", each with its own
   sub-heading. */
.hotkeys-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* The actual scrolling element - flex:1/min-height:0 lets it shrink to
     whatever room .hotkeys-edit-settings (its own flex:1/min-height:0,
     above) has left after its own h3, and overflow-y:auto scrolls its rows
     internally from there instead of the card (or .settings-content itself)
     growing past the dialog's own height. */
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
.hotkeys-section {
  display: flex;
  flex-direction: column;
  gap: 14px;
  background-color: var(--main-theme-darker);
  border-radius: 6px;
  padding: 8px;
}
.hotkeys-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Not the same look as .container-size/.tasks-edit-settings' own h3
   (absolutely positioned above their box) - these two sit INSIDE one
   shared box (.hotkeys-edit-settings) as plain in-flow sub-headings
   instead, one per group, so a slimmer treatment (no icon) reads as a
   subsection rather than competing with that box's own h3 for the same
   "section title" look - but the same trailing divider line THAT h3 gets
   (::after below), stretching from the end of the text to the group's own
   right edge, same technique (a flex:1 zero-content line filling whatever
   width the text itself doesn't need). */
.hotkeys-group-title {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: 12px;
  font-weight: bold;
  font-style: italic;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--font-color-weak);
}
.hotkeys-group-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background-color: color-mix(in srgb, var(--font-color-weak) 40%, transparent);
}
.hotkey-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.hotkey-row-label {
  color: var(--font-color-clear);
}
/* The key-capture button itself - shows the current binding, and swaps to
   the "listening" prompt (beginHotkeyCapture, settings.js) while armed.
   Sized/aligned like a small pill rather than a full-width settings-button,
   since its own label is always short ("T", "Ctrl+Z", ...). */
/* Same raised-keycap visual language as .action-bar-hotkey above (heavier
   bottom border + downward shadow, lighter background than its surroundings)
   - the button itself already reads as "a key" here (it shows the current
   binding and doubles as the capture trigger), so this styles it directly
   rather than nesting a separate <kbd> inside a button, which would just
   double up the box-within-a-box look for no benefit. */
.hotkey-key-btn {
  flex: none;
  min-width: 70px;
  padding: 4px 10px;
  border: 1px solid var(--secondary-color);
  border-bottom: 2px solid var(--secondary-color);
  border-radius: 6px;
  background-color: var(--main-theme-lighter);
  color: var(--font-color-clear);
  font-weight: bold;
  font-size: 12px;
  text-align: center;
  cursor: var(--cursor-pointer, pointer);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  transition:
    background-color 150ms,
    color 150ms;
}
.hotkey-key-btn:hover {
  color: var(--highlight);
}
/* A "fixed" HOTKEY_ACTIONS entry (settings.js) - informational only, no
   capture click wired up at all, so it reads as inert rather than inviting
   a click that would do nothing. */
.hotkey-key-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
.hotkey-key-btn:disabled:hover {
  color: var(--font-color-clear);
}
/* Actively waiting for the next keypress (beginHotkeyCapture) - same
   --secondary-color background as .settings-tab-btn.active/.link-stats-tab.active
   above, so it needs that exact same computed (not fixed) text color for
   guaranteed contrast - a light accent in dark theme (or vice versa) would
   otherwise leave this unreadable, same reasoning as those two. */
.hotkey-key-btn.capturing {
  background-color: var(--secondary-color);
  color: var(--accent-tab-active-text, #000000);
  animation: hotkey-capture-pulse 1s ease-in-out infinite;
}
@keyframes hotkey-capture-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.6;
  }
}
/* Rows built by renderCategoriesSettingsPanel/renderTagsSettingsPanel
   (settings.js) - one .category-row/.tag-row per distinct category/tag name.
   Same scroll-on-overflow shape as .hotkeys-list above (flex:1/min-height:0/
   overflow-y:auto), since accumulating too many over time is exactly the
   problem these panels exist to help clean up. */
.categories-list,
.tags-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
.categories-list-empty,
.tags-list-empty {
  color: var(--font-color-weak);
  font-style: italic;
  padding: 8px;
}
.category-row,
.tag-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* The category/tag's own representative color (a category's is whichever
   task using it was read last, getCategorySummaries' own comment,
   settings.js; a tag's is its one persisted color, getTagColor, shared/
   scripts/taskTags.js) - a small fixed-size box, a plain circle for
   .tag-row-swatch (tags carry no icon of their own) but with an FA icon
   glyph centered inside for .category-row-swatch. That glyph lives on a
   separate child <i> (settings.js), NOT this element - FontAwesome's own
   base rule (fontawesome/all.min.css) sets `width: 1.25em` on every
   .fa-solid/etc element, which would otherwise fight this element's own
   width at equal selector specificity (a real, confirmed bug - this
   rendered as a barely-visible ~13px speck before it was split,
   FontAwesome's own em-relative width silently winning the cascade tie).
   Same wrapper/icon split _buildDescriptionCollapseSection's own chevron
   circle already uses (shared/scripts/taskDescriptionRenderer.js), for the
   identical reason. */
.category-row-swatch,
.tag-row-swatch {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 14px;
  aspect-ratio: 1;
  font-size: 11px;
}
.tag-row-swatch {
  border-radius: 50%;
  background-color: currentColor;
}
.category-row-name,
.tag-row-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--font-color-clear);
}
/* Swapped in for .category-row-name/.tag-row-name while renaming
   (beginCategoryRename/beginTagRename, settings.js) - same flex slot, so
   the row's own layout doesn't jump when the rename starts/ends. */
.category-row-name-input,
.tag-row-name-input {
  flex: 1;
  min-width: 0;
  background: var(--main-theme-darker);
  border: 1px solid var(--secondary-color);
  border-radius: 4px;
  padding: 2px 6px;
  color: var(--font-color-clear);
  font: inherit;
}
/* A name already belonging to a different existing category/tag - the
   input's own border turns into an unmistakable warning color rather than
   relying on the title-attribute tooltip alone to explain the rejection. */
.category-row-name-input-error,
.tag-row-name-input-error {
  border-color: #ff5c5c;
}
.category-row-count,
.tag-row-count {
  flex: none;
  min-width: 1.6em;
  text-align: center;
  padding: 1px 6px;
  border-radius: 10px;
  background-color: var(--main-theme-darker);
  color: var(--font-color-weak);
  font-size: 11px;
  font-weight: bold;
}
/* One shared toolbar per panel (index.html, wireCategoriesToolbar/
   wireTagsToolbar/renderCategoriesSettingsPanel/renderTagsSettingsPanel,
   settings.js) acting on whichever row is currently selected below, instead
   of every row carrying its own buttons - sits between the card's own h3
   and the row list, so it's visible/reachable regardless of which row (if
   any) is scrolled into view. */
.categories-toolbar,
.tags-toolbar {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--main-theme-darker);
  background-color: var(--main-theme-darker);
  padding: 2px;
  border-radius: 10px;
}
/* Same icon+input shape as .icon-manager-input-wrapper/.icon-manager-search-input
   (own selectors rather than joined into that shared group - same reasoning
   .header-search-* stays separate from .search-bar-wrapper's own classes,
   index.html: a generic unscoped lookup elsewhere could otherwise grab
   whichever same-named element comes first in document order). Always
   visible (no toggle button to reveal it first, unlike Icon Manager's own
   filter) since there's room for it directly in the toolbar row. */
.category-search-wrapper,
.tag-search-wrapper {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
}
.category-search-wrapper > i,
.tag-search-wrapper > i {
  position: absolute;
  left: 9px;
  color: var(--secondary-color);
  font-size: 12px;
  transform: scale(1.2);
  padding-bottom: 2px;
  pointer-events: none;
}
.category-search-input,
.tag-search-input {
  width: 100%;
  background-color: var(--input-color);
  color: var(--font-color-clear);
  border: none;
  border-bottom: 2px solid var(--secondary-color);
  border-radius: 6px;
  outline: none;
  padding: 4px 8px 4px 30px;
  font-size: 12px;
}
/* Same small-pill look the old per-row buttons used - just permanent
   markup now (index.html) instead of rebuilt per row, and disabled
   (below) until a row is actually selected. */
.category-toolbar-btn,
.tag-toolbar-btn {
  flex: none;
  padding: 4px 8px;
  border: none;
  border-radius: 6px;
  background-color: var(--main-theme-darker);
  color: var(--font-color-clear);
  font-size: 12px;
  cursor: var(--cursor-pointer, pointer);
  transition:
    background-color 150ms,
    color 150ms,
    opacity 150ms;
}
.category-toolbar-btn:hover:not(:disabled),
.tag-toolbar-btn:hover:not(:disabled) {
  color: var(--highlight);
}
.category-toolbar-btn-danger:hover:not(:disabled),
.tag-toolbar-btn-danger:hover:not(:disabled) {
  color: #ff5c5c;
}
.category-toolbar-btn:disabled,
.tag-toolbar-btn:disabled {
  opacity: 0.35;
  cursor: var(--cursor-default, default);
}
/* A row is now the whole clickable unit (selectCategoryRow/selectTagRow,
   settings.js), not just a label sitting next to separate buttons - the
   same pointer cursor/hover feedback every other clickable row in this app
   already gives (e.g. .hotkey-row's own key-capture button), just on the
   row itself here since there's no longer a dedicated button to hover. */
.category-row,
.tag-row {
  cursor: var(--cursor-pointer, pointer);
  border-radius: 6px;
  padding: 3px 4px;
  transition: background-color 150ms;
}
.category-row:hover,
.tag-row:hover {
  background-color: var(--main-theme-darker);
}
.category-row.selected,
.tag-row.selected {
  background-color: color-mix(in srgb, var(--secondary-color) 20%, transparent);
}
/* Context Menu's own h3 carries TWO labels instead of one (index.html) — an
   Emblem icon+heading on the left (for .spinner-sidebar-toggle, sitting
   right below it) and the original list icon+"Kontextmenü" on the right
   (for .context-menu-toggle) — so its own divider is a real flex child
   sitting BETWEEN them rather than a ::before/::after pseudo-element, which
   could only ever sit on one side of a single icon+text pair. */
.settings-content .context-menu-settings h3 .context-menu-h3-divider {
  flex: 1;
  height: 1px;
  background-color: color-mix(in srgb, var(--secondary-color) 40%, transparent);
}
/* > button, not a plain descendant selector - the "Anwenden" apply button is
   .container-size's own direct child, but so is its h3 (settings-section-h3,
   above), which nests its OWN button (.settings-section-toggle, the collapse
   chevron) two levels deep. A plain descendant selector here used to catch
   that chevron button too, stretching it out to this same 130px - a real,
   confirmed bug (it visibly rendered far wider than every other section's
   own identically-styled chevron). */
.settings-content .container-size > button {
  width: 130px;
  margin-left: auto;
  margin-top: 5px;
  font-size: 12px;
}
.settings-content .context-menu-settings h3 {
  padding-right: 6px;
}
.settings-content .context-menu-settings {
  position: relative;
  margin-top: 14px;
  display: flex;
  background-color: var(--main-theme);
  padding: 8px 12px;
  border-radius: 6px;
  gap: 10px;
}
.settings-profile-screen .profile-settings {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.profile-settings-account,
.profile-settings-active,
.profile-settings-available {
  font-size: 12px;
}
.profile-settings-available-row {
  min-height: 32px;
  display: flex;
  align-items: center;
  padding: 0 10px;
  gap: 8px;
}
.profile-settings-account {
  display: flex;
  align-items: center;
  gap: 6px;
}
.profile-settings-account i {
  font-size: 1.6em;
  transform: translateY(-2px);
}
/* Guest mode (isGuestMode, general.js; .guest-mode toggled by
   refreshProfileSettingsSection, settings.js) swaps which of these two
   buttons is even visible, rather than showing both at once — there's
   nothing to sign OUT of yet.
   .profile-settings-row .profile-signin-btn (not just .profile-signin-btn)
   specifically to outrank .settings-button's own `display: flex` below at
   equal specificity — as a single class this would otherwise lose to it on
   source order alone and stay visible always, alongside .profile-signout-btn
   rather than instead of it. */
.profile-settings-row .profile-signin-btn {
  display: none;
}
/* Toggled on .settings-profile-screen itself (refreshProfileSettingsSection,
   settings.js), not .profile-settings - the "Connection"/"User Data" split
   (.profile-connection-settings/.profile-user-data-settings, index.html)
   means the rows guest mode needs to reach are now spread across TWO
   separate .profile-settings row-lists (one per section) rather than one
   single shared one, so the toggle needs an ancestor common to both -
   .settings-profile-screen is the nearest one that still is. */
.settings-profile-screen.guest-mode .profile-signin-btn {
  display: flex;
}
.settings-profile-screen.guest-mode .profile-signout-btn {
  display: none;
}
/* Neither means anything without a real GitHub-backed profile to switch
   to/export/delete, or a fetched list to show — hidden entirely rather than
   just disabled (unlike most of this app's own "visible but locked" gating
   elsewhere), replaced by .profile-settings-guest-hint-row below instead of
   left as dead space. .profile-settings-export-row was missing from this
   list - a real, confirmed bug: disconnecting GitHub (or continuing without
   ever connecting) left the "Export profile data" row sitting there fully
   visible with nothing behind it to actually export. */
.settings-profile-screen.guest-mode .profile-settings-active-row,
.settings-profile-screen.guest-mode .profile-settings-export-row,
.settings-profile-screen.guest-mode .profile-settings-danger-row,
.settings-profile-screen.guest-mode .profile-settings-available-row {
  display: none;
}
/* .settings-profile-screen .profile-settings-guest-hint-row (not just the
   single class) for the same reason as .profile-signin-btn's own hide rule
   above — outranks .profile-settings-row's own `display: flex` at equal
   specificity, which would otherwise win on source order alone and leave
   this row always visible regardless of guest mode. */
.settings-profile-screen .profile-settings-guest-hint-row {
  display: none;
}
.settings-profile-screen.guest-mode .profile-settings-guest-hint-row {
  display: flex;
}
.profile-settings-guest-hint {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  color: var(--font-color);
  padding: 40px 40px 40px 20px;
}
.profile-settings-guest-hint i {
  font-size: 1.6em;
  color: var(--secondary-color);
  padding-right: 10px;
}
/* Reuses --priority-two (task-card "medium priority" amber, above) rather
   than a color of its own — the closest thing this app already has to a
   "warning" token. */
.profile-settings-guest-hint.profile-settings-guest-warning i {
  color: var(--priority-two);
}

/* Anchored to .settings-content's own position:relative (above) — the
   nearest positioned ancestor, since .settings-profile-screen/.profile-settings
   set no position of their own — landing this at the actual bottom edge of
   the whole panel regardless of how much/little content the profile screen
   itself has above it. No justify-content needed - the two children fill
   it exactly 50/50 (flex: 1 each, below), leaving no extra space to
   distribute. Relies on DOM order (index.html: privacy first, security
   second) for which one lands on which side, not a reorder here. Same
   background as the two links themselves so the whole bar reads as one
   continuous strip rather than two separate pills with a gap of the
   panel's own background showing between them. */
.settings-corner-links {
  position: absolute;
  width: 100%;
  bottom: 0;
  left: 0;
  height: 24px;
  display: flex;
  background-color: var(--main-theme-darker);
}
.settings-privacy-link,
.settings-security-link {
  /* flex: 1 (equal grow from a zero basis - shorthand for flex-grow: 1;
     flex-shrink: 1; flex-basis: 0%) splits .settings-corner-links exactly
     50/50 regardless of either label's own text length, so each link's
     entire half of the bar is clickable, not just a tight box around its
     own text - a real, reported gap (half the bar used to be dead space
     between two content-sized pills). Each link's own justify-content
     (set per-class just below, not here) then keeps its text pinned to
     the SAME edge its half already sits at within the wrapper - privacy's
     text stays at its half's left edge, security's at its half's right -
     instead of both defaulting to flex-start and reading as everything
     crowding toward the middle seam between the two halves. align-items:
     center here is for vertical centering only (a plain inline-block box
     doesn't center text against a min-height taller than the text itself,
     which is exactly why it rendered pinned to the top before this was
     added) - display: flex is what align-items needs to have any effect
     at all, not the generic `a` rule (styles.css) bleeding through; that
     one's own width: 100% is what's actually being overridden by flex: 1
     below, regardless of which display value is in play. */
  display: flex;
  align-items: center;
  flex: 1;
  font-size: 10px;
  color: var(--font-color);
  opacity: 0.6;
  text-decoration: none;
  background-color: var(--main-theme-darker);
  border-radius: 0;
  padding: 0 10px;
}
.settings-privacy-link {
  justify-content: flex-start;
}
.settings-security-link {
  justify-content: flex-end;
}
.settings-privacy-link:hover,
.settings-security-link:hover {
  opacity: 1;
  color: var(--secondary-color);
  text-decoration: underline;
}
/* Admin/SECURITY_DOC_ALLOWED_LOGINS-only (applySecurityInfoLinkVisibility,
   scripts/init.js) - same "start hidden, only ever revealed by script"
   posture as .report-btn.owner-feature-hidden (desktopChrome.css). */
.settings-security-link.owner-feature-hidden {
  display: none;
}
/* The actual value after the label's own ":" (the GitHub login / active
   profile name, settings.js) — stands out from the plain label text
   around it instead of reading as one flat line. */
.profile-settings-value {
  color: var(--highlight);
}
.profile-settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 5px 5px 10px;
  border-radius: 10px;
  background: var(--main-theme-darker);
}
.profile-settings-row .settings-button {
  width: auto;
  flex-shrink: 0;
  padding: 8px 16px;
}
.profile-settings-danger-label,
.profile-settings-danger-account-label {
  font-size: 12px;
  color: var(--highlight);
  opacity: 0.85;
}
.profile-settings-danger-row .profile-delete-btn,
.profile-settings-danger-row .profile-delete-account-btn {
  border-color: var(--chat-user-color-red);
  color: var(--chat-user-color-red);
}
.profile-settings-danger-row .profile-delete-btn:hover,
.profile-settings-danger-row .profile-delete-account-btn:hover {
  background-color: var(--chat-user-color-red);
}
/* Same shell as .confirm-dialog-status/.confirm-dialog-warning around it
   (shared/styles/backup.css) — a plain underline input rather than
   .username-gate-input's own icon-prefixed/centered look, which is tuned
   specifically for that gate's own layout, not a generic dialog body. */
.delete-profile-confirm-input,
.delete-account-confirm-input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  background-color: var(--input-color);
  border: none;
  border-bottom: 2px solid var(--secondary-color);
  border-radius: 4px;
  outline: none;
  color: var(--font-color-clear);
  font-size: 13px;
  font-family: inherit;
}
.delete-profile-confirm-input.wrong,
.delete-account-confirm-input.wrong {
  border-bottom-color: var(--highlight);
  animation: username-gate-shake 300ms ease;
}
/* Same "hide Save entirely until valid" convention as .history-open-confirm
   (shared/styles/backup.css) — "valid" here means the
   typed text exactly matches the profile's own name (settings.js), not
   fetched/dropped content. .delete-account-confirm mirrors this exactly,
   just matched against the account's GitHub login instead. */
.delete-profile-confirm.invalid .button-save,
.delete-account-confirm.invalid .button-save {
  display: none;
}
/* Matches .icon-manager-save-btn/.history-save-btn's own look (see the
   shared rule further down, styles.css) — an inline icon+label row instead
   of the plain centered label these used to be (with the icon, where one
   exists, pinned to the button's right edge via absolute positioning), so
   Settings' own actions read the same as the app's other save/action
   buttons rather than the gate panels' own Continue/Cancel style. */
.settings-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 8px 0;
}
.settings-button span {
  pointer-events: none;
}

/* Both buttons share the row (.context-menu-settings, now display:flex above)
   evenly instead of context-menu-toggle's old fixed 350px/margin-left:auto —
   that only ever worked alone, right-aligned by itself; with
   spinner-sidebar-toggle now sitting to its left (index.html) as a second
   flex item, flex:1 on both is what actually splits the row between them. */
.settings-button.context-menu-toggle,
.settings-button.spinner-sidebar-toggle {
  display: flex;
  flex: 1;
  width: auto;
}

.context-menu-panel {
  position: absolute;
  border-left: solid 1px var(--secondary-color);
  border-right: solid 1px var(--secondary-color);
  border-top-right-radius: 10px;
  border-bottom-right-radius: 10px;
  background-color: var(--main-theme-darker);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  left: 100%;
  width: 200px;

  bottom: 6px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 300ms ease;
}
.context-menu-panel h4 {
  color: var(--secondary-color);
  padding-left: 5px;
}
.context-menu-panel > div {
  position: relative;
  display: flex;
  padding: 5px;
  background-color: var(--main-theme-lighter);
  border-radius: 10px;
  cursor: var(--cursor-pointer);
}
.context-menu-panel label {
  width: 100%;
  padding-left: 4px;
  display: flex;
  gap: 12px;
}
.context-menu-panel label:hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
.context-menu-panel label:has(.hint) {
  padding-bottom: 20px;
}
.context-menu-panel input[type="checkbox"] {
  accent-color: var(--secondary-color);
  cursor: var(--cursor-pointer);
  transform: scale(1.3);
}
.context-menu-panel label:has(.hint) input[type="checkbox"] {
  margin-top: 8px;
}

.context-menu-panel .text {
  margin-top: 3px;
}
.context-menu-panel .text > span {
  color: var(--font-color);
}
.context-menu-panel label:hover .text > span {
  color: var(--highlight);
}
.context-menu-panel .text > i {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translate(5px, -50%);
  font-size: 18px;
  color: var(--secondary-color);
}
.context-menu-panel .hint {
  position: absolute;
  pointer-events: none;
  left: 34px;
  font-size: 10px;
  color: var(--font-color);
}

.draggable-item.over {
  outline: 4px solid var(--highlight);
}

.settings-content .container-size > div {
  display: flex;
  align-items: center;
}
.settings-content .container-size > div:nth-of-type(3),
.settings-content .container-size > div:nth-of-type(5) {
  padding-top: 5px;
  border-top: 1px solid var(--main-theme-lighter);
}
.settings-content .container-size label {
  height: 100%;
  display: flex;
  align-items: center;
}
/* label span, not just any span in .container-size — the Apply button
   below is also a direct child of .container-size, and its own new
   <span>Anwenden</span> (see index.html) would otherwise pick up this same
   margin-left: auto and get shoved to the button's far right, away from
   its icon. */
.settings-content .container-size label span {
  margin: 0 10px 0 auto;
  color: var(--font-color);
}
.settings-content .container-size input {
  width: 80px;
  background-color: var(--input-color);
  border: none;
  border-bottom: 2px solid var(--secondary-color);
  border-radius: 6px;
  outline: none;
  color: var(--font-color-clear);
  padding: 2px 7px;
  font-size: 12px;
  text-align: center;
}

.reset-settings-button,
.settings-profile-button,
.settings-profile-back-button {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}
.reset-settings-button {
  right: 10px;
}
/* Left corner — the opposite edge from .reset-settings-button — for both
   the main settings header's own "open Profile" button and the Profile
   screen's own back button, which sits in the exact same spot on its
   OWN h2 (see .settings-profile-screen, above) so the button a visitor
   just clicked stays under their cursor when the view swaps. */
.settings-profile-button,
.settings-profile-back-button {
  left: 10px;
}
/* Overrides the generic scale-on-hover below so the vertical-centering
   transform doesn't get clobbered by it. */
.reset-settings-button:hover,
.settings-profile-button:hover,
.settings-profile-back-button:hover {
  transform: translateY(-50%) scale(1.1);
}
