/* tokens.css */
/*
 * Runtime design tokens, emitted as CSS custom properties.
 *
 * These MUST stay custom properties (not SCSS variables): live theme switching
 * and the user-selectable accent color depend on overriding them at runtime.
 *
 * Accent tokens (--color-accent*) are intentionally theme-independent. They are
 * seeded here with the default accent and overridden at runtime by ThemeService
 * from the user's chosen color - so they are NOT redefined in the .light block.
 */

:root {
  /*
   * Make native form widgets (number spinners, select popups, scrollbars,
   * date pickers) render with the matching UA theme.
   */
  color-scheme: dark;

  --color-bg-primary:    #121212;
  --color-bg-secondary:  #1a1a1a;
  --color-bg-tertiary:   #242424;
  --color-bg-elevated:   #2a2a2a;
  --color-bg-hover:      rgba(255, 255, 255, 0.05);

  --color-border:        #333333;
  --color-border-light:  #404040;
  /*
   * Slightly dimmed accent outline marking a selected row. Derived from
   * --color-accent via color-mix so it follows the runtime accent. Resting
   * controls keep --color-border; the focused control itself uses the full
   * accent (see control-interactive).
   */
  --color-border-accent: color-mix(in srgb, var(--color-accent) 60%, var(--color-border));
  /*
   * Hairline edge of an elevated overlay surface (popup panels, toasts,
   * floating cards). Translucent so it reads as a material edge on whatever
   * it sits on instead of a drawn outline - elevation, not colour, is what
   * separates these surfaces from the page.
   */
  --color-border-overlay: rgba(255, 255, 255, 0.08);

  --color-text-primary:   #ffffff;
  --color-text-secondary: #a0a0a0;
  --color-text-muted:     #666666;

  /* User-selectable accent (default #2196F3). Overridden at runtime by ThemeService. */
  --color-accent:        #2196f3;
  --color-accent-hover:  #1565c0;
  --color-accent-muted:  rgba(33, 150, 243, 0.15);

  /*
   * Foreground on accent/danger/success/warning fills. Accent colors are strong
   * hues in both themes, so this stays white and is not overridden in .light.
   */
  --color-on-accent:     #ffffff;

  --color-danger:        #ef4444;
  --color-danger-hover:  #dc2626;
  --color-danger-muted:  rgba(239, 68, 68, 0.15);

  --color-success:       #22c55e;
  --color-success-muted: rgba(34, 197, 94, 0.15);

  --color-warning:       #f59e0b;
  --color-warning-muted: rgba(245, 158, 11, 0.15);
  --color-on-warning:    #1c1c1e;

  /*
   * Semantic tint pairs for value-type and classification badges
   * (variables, variable picker, template builder, condition builder).
   */
  --type-string-bg:      rgba(99, 102, 241, 0.15);
  --type-string-fg:      rgb(165, 180, 252);
  --type-numeric-bg:     rgba(34, 197, 94, 0.15);
  --type-numeric-fg:     rgb(134, 239, 172);
  --type-boolean-bg:     rgba(245, 158, 11, 0.15);
  --type-boolean-fg:     rgb(252, 211, 77);
  --type-resource-bg:    rgba(236, 72, 153, 0.15);
  --type-resource-fg:    rgb(244, 114, 182);
  --type-system-bg:      rgba(168, 85, 247, 0.15);
  --type-system-fg:      rgb(216, 180, 254);
  --type-integration-bg: rgba(14, 165, 233, 0.15);
  --type-integration-fg: rgb(125, 211, 252);
  --type-widget-bg:      rgba(20, 184, 166, 0.15);
  --type-widget-fg:      rgb(94, 234, 212);

  /* Variable-reference chips (param input, condition builder). */
  --chip-var-bg:     rgba(34, 197, 94, 0.12);
  --chip-var-border: rgba(34, 197, 94, 0.45);
  --chip-var-fg:     rgb(134, 239, 172);

  /* Event-payload references, tinted apart from variables so the two roots read differently. */
  --chip-event-bg:     rgba(14, 165, 233, 0.12);
  --chip-event-border: rgba(14, 165, 233, 0.45);
  --chip-event-fg:     rgb(125, 211, 252);

  --font-sans-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-sans: var(--font-sans-system);
  --font-mono: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;

  /*
   * The type scale, one token per semantic role. Sizes are the rendered pixels
   * at the default 16px root; --text-xs is the floor, nothing may go below it.
   *
   * The scale sat a step high throughout, so every role above the floor came down
   * by a pixel. The floor could not follow, which leaves --text-xs and --text-sm
   * sharing 12px: a caption and a label are now told apart by weight, color and
   * casing rather than by size. They stay two tokens because the roles are still
   * distinct - the next move of the scale separates them again.
   */
  --text-xs:   0.75rem;     /* 12px - caption / metadata: badges, chips, fine print */
  --text-sm:   0.75rem;     /* 12px - label: field labels, hints, secondary rows */
  --text-base: 0.8125rem;   /* 13px - body / control: the default for text and controls */
  --text-md:   0.875rem;    /* 14px - emphasized body: inputs, lead paragraphs */
  --text-lg:   1.0625rem;   /* 17px - section heading */
  --text-xl:   1.3125rem;   /* 21px - page title */

  --font-medium:   500;
  --font-semibold: 600;
  --font-bold:     700;

  --leading-tight:  1.25;
  --leading-normal: 1.5;

  /*
   * Spacing, radii and control height are whole pixels at the 16px root. The
   * values look unround because #248 raised the type scale without moving the
   * layout: they are the pre-#248 rendered sizes, which were authored against a
   * 13px root, carried over unchanged.
   */
  --space-1: 0.1875rem;    /* 3px */
  --space-2: 0.375rem;     /* 6px */
  --space-3: 0.625rem;     /* 10px */
  --space-4: 0.8125rem;    /* 13px */
  --space-5: 1.25rem;      /* 20px */
  --space-6: 1.625rem;     /* 26px */
  --space-8: 2.4375rem;    /* 39px */

  --radius-sm: 0.3125rem;   /* 5px */
  --radius-md: 0.375rem;    /* 6px */
  --radius-lg: 0.625rem;    /* 10px */
  --radius-xl: 0.8125rem;   /* 13px */

  /*
   * The single-line control height. Every single-line control declares it; none derives its
   * height from padding, so retuning the type scale cannot pull a control away from its
   * neighbour again (issue #346) - see the control-size mixin.
   */
  --control-height: 2.375rem;   /* 38px */

  /*
   * Height for a *standalone* icon button - toolbars, the top status bar, notification /
   * connection panels, and list rows with no adjoining input. No longer a form-control size:
   * single-line form controls all render at --control-height now (issue #554).
   */
  --control-height-compact: 1.625rem;   /* 26px */

  /*
   * Minimum height of a sidebar navigation item in both modes, and the width of a collapsed
   * one - one token drives both axes so the collapsed rail reads as square buttons.
   */
  --nav-item-height: 2.75rem;   /* 44px */

  /* Height of the global footer bar at the bottom of the shell, independent of the sidebar. */
  --footer-bar-height: 1.5rem;   /* 24px */

  --z-dropdown:     100;
  /*
   * The web client's reconnecting notice. Below the modal host and any dialog someone had open, so a
   * connection drop cannot bury either of them under a mere status panel - and below the toast an
   * offline press still fires.
   */
  --z-reconnecting: 900;
  --z-overlay:      1000;
  --z-modal:        1000;
  --z-modal-nested: 1100;
  /*
   * Floating panels (menus, select/combobox popups) must paint above any
   * modal that hosts them, but below toasts.
   */
  --z-popover:      1150;
  --z-screensaver:  1190;
  --z-toast:        1200;

  /*
   * Press feedback is asymmetric on purpose: it has to appear within a frame so even a tap that
   * lasts a few milliseconds is seen, and fades back out slowly enough to register (issue #454).
   */
  --transition-press-in:  0.02s ease;
  --transition-press-out: 0.14s ease;

  --transition-fast:   0.15s ease;
  --transition-normal: 0.2s ease;
  --transition-slow:   0.3s ease;

  --shadow-sm:      0 4px 6px -1px rgba(0, 0, 0, 0.3);
  --shadow-md:      0 10px 25px rgba(0, 0, 0, 0.3);
  --shadow-lg:      0 25px 50px -12px rgba(0, 0, 0, 0.5);
  --shadow-overlay: 0 20px 40px rgba(0, 0, 0, 0.5);
  --shadow-popover: 0 8px 24px rgba(0, 0, 0, 0.35);
  /*
   * Modal elevation. The leading 1px ring replaces the border a dialog used to
   * carry: without it a light-theme dialog (white on white) has no edge at all.
   */
  --shadow-dialog:
    0 0 0 1px rgba(255, 255, 255, 0.06),
    0 2px 4px rgba(0, 0, 0, 0.2),
    0 12px 28px -8px rgba(0, 0, 0, 0.45),
    0 32px 64px -20px rgba(0, 0, 0, 0.65);

  --focus-ring: 0 0 0 2px var(--color-bg-primary), 0 0 0 4px var(--color-accent);

  --color-scrim: rgba(0, 0, 0, 0.6);
}

html[lang|="ja"] {
  --font-sans-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic UI', Meiryo, 'Noto Sans CJK JP', 'Noto Sans JP', Roboto, sans-serif;
}

html[lang|="ko"] {
  --font-sans-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans CJK KR', 'Noto Sans KR', Roboto, sans-serif;
}

html[lang|="zh"] {
  --font-sans-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei UI', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Noto Sans SC', Roboto, sans-serif;
}

html[lang="zh-TW"],
html[lang="zh-HK"],
html[lang="zh-MO"],
html[lang|="zh-Hant"] {
  --font-sans-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang TC', 'Microsoft JhengHei UI', 'Microsoft JhengHei', 'Noto Sans CJK TC', 'Noto Sans TC', Roboto, sans-serif;
}

html[lang|="ar"] {
  --font-sans-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geeza Pro', 'Noto Sans Arabic', Tahoma, Roboto, sans-serif;
}

html[lang|="hi"] {
  --font-sans-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Kohinoor Devanagari', 'Nirmala UI', 'Noto Sans Devanagari', Mangal, Roboto, sans-serif;
}

.light {
  color-scheme: light;

  --color-bg-primary:    #ffffff;
  --color-bg-secondary:  #f5f5f5;
  --color-bg-tertiary:   #ebebeb;
  --color-bg-elevated:   #ffffff;
  --color-bg-hover:      rgba(0, 0, 0, 0.05);
  --color-border:        #e0e0e0;
  --color-border-light:  #d0d0d0;
  --color-border-overlay: rgba(0, 0, 0, 0.08);
  --color-text-primary:   #121212;
  --color-text-secondary: #666666;
  --color-text-muted:     #999999;
  --color-danger:        #dc2626;
  --color-danger-hover:  #b91c1c;
  --color-danger-muted:  rgba(220, 38, 38, 0.1);
  --color-success:       #16a34a;
  --color-success-muted: rgba(22, 163, 74, 0.12);
  --color-warning:       #d97706;
  --color-warning-muted: rgba(217, 119, 6, 0.12);
  --color-on-warning:    #ffffff;
  --type-string-fg:      rgb(79, 70, 229);
  --type-numeric-fg:     rgb(22, 163, 74);
  --type-boolean-fg:     rgb(180, 83, 9);
  --type-resource-fg:    rgb(219, 39, 119);
  --type-system-fg:      rgb(147, 51, 234);
  --type-integration-fg: rgb(2, 132, 199);
  --type-widget-fg:      rgb(13, 148, 136);
  --chip-var-bg:     rgba(22, 163, 74, 0.1);
  --chip-var-border: rgba(22, 163, 74, 0.45);
  --chip-var-fg:     rgb(22, 163, 74);
  --chip-event-bg:     rgba(2, 132, 199, 0.1);
  --chip-event-border: rgba(2, 132, 199, 0.45);
  --chip-event-fg:     rgb(2, 132, 199);
  --shadow-sm:      0 2px 4px rgba(0, 0, 0, 0.08);
  --shadow-md:      0 6px 16px rgba(0, 0, 0, 0.1);
  --shadow-lg:      0 16px 40px rgba(0, 0, 0, 0.15);
  --shadow-overlay: 0 12px 32px rgba(0, 0, 0, 0.18);
  --shadow-popover: 0 8px 24px rgba(0, 0, 0, 0.12);
  --shadow-dialog:
    0 0 0 1px rgba(0, 0, 0, 0.06),
    0 2px 4px rgba(0, 0, 0, 0.04),
    0 12px 28px -8px rgba(0, 0, 0, 0.12),
    0 32px 64px -20px rgba(0, 0, 0, 0.18);
  --color-scrim: rgba(0, 0, 0, 0.4);
}

/* reset.css */
/*
 * Base reset and element defaults, shared by every Macro Deck client.
 *
 * Plain CSS on purpose (issue #824): the framework-free web client consumes this without a Sass
 * toolchain, and both clients must render identically from the same bytes.
 */

*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/*
 * No `html { font-size }` rule on purpose: the root stays at the browser's own
 * font size, so 1rem is 16px by default and the rem tokens in _themes.scss mean
 * what they say. Setting it from --text-base made the root 13px and resolved
 * every rem token a second time against it (issue #248).
 */

html, body {
  background-color: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  /* Allow scrolling but block pinch-zoom and double-tap-zoom on touch devices. */
  touch-action: pan-x pan-y;
}

body {
  font-size: var(--text-base);
}

button, input, select, textarea {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

a {
  color: inherit;
  text-decoration: none;
}

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/*
 * Footer content projected into shared-modal via [modal-footer]. This global rule is the
 * button group's equivalent for projected content: a shared-button-group here would tie with
 * it on specificity, so which one lays the row out would depend on stylesheet order.
 * Wraps rather than overflowing: a three-answer dialog (Keep editing / Discard / Save & Close) does
 * not fit one row at phone width, and without this the leftmost button escapes the footer padding.
 */
[modal-footer] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  width: 100%;
}

/* animations.css */
/*
 * Shared keyframes, emitted once globally via main.scss. Component styles
 * reference them by name (Angular leaves animation-name references that are
 * not defined in the same stylesheet untouched, so they resolve globally).
 */

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

@keyframes ds-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes ds-fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes ds-pop-in {
  from {
    opacity: 0;
    transform: translateY(-0.1875rem) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes ds-pop-out {
  from {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  to {
    opacity: 0;
    transform: translateY(-0.1875rem) scale(0.98);
  }
}

@keyframes ds-slide-up-in {
  from {
    opacity: 0;
    transform: translateY(0.375rem) scale(0.98);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes ds-slide-up-out {
  from {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  to {
    opacity: 0;
    transform: translateY(0.375rem) scale(0.98);
  }
}

/* icons.css */
/*
 * The shared icon set: a `.icon` base paired with one `.icon-<name>` mask class per glyph.
 *
 * Colour comes from `background-color: currentColor` behind the mask, so an icon inherits `color`
 * and needs no fill plumbing. The `-webkit-` duplicates are not redundant: unprefixed `mask-*` only
 * arrived in Safari 15.4, and the compatibility floor for this package is Safari 9.
 *
 * The mask list was a Sass `@each` before issue #824; it is written out here because the framework-
 * free client consumes this file directly, with no Sass toolchain in between.
 */

.icon {
  display: inline-block;
  background-color: currentColor;
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  vertical-align: middle;
  flex-shrink: 0;
}

.icon-xs  { width: 14px; height: 14px; }
.icon-sm  { width: 16px; height: 16px; }
.icon-md  { width: 18px; height: 18px; }
.icon-lg  { width: 20px; height: 20px; }
.icon-xl  { width: 24px; height: 24px; }
.icon-2xl { width: 28px; height: 28px; }

.icon-disc {
  mask-image: url('./icons/disc.svg');
  -webkit-mask-image: url('./icons/disc.svg');
}
.icon-music-note {
  mask-image: url('./icons/music-note.svg');
  -webkit-mask-image: url('./icons/music-note.svg');
}
.icon-lock {
  mask-image: url('./icons/lock.svg');
  -webkit-mask-image: url('./icons/lock.svg');
}
.icon-unlock {
  mask-image: url('./icons/unlock.svg');
  -webkit-mask-image: url('./icons/unlock.svg');
}
.icon-x {
  mask-image: url('./icons/x.svg');
  -webkit-mask-image: url('./icons/x.svg');
}
.icon-arrow-left {
  mask-image: url('./icons/arrow-left.svg');
  -webkit-mask-image: url('./icons/arrow-left.svg');
}
.icon-arrow-right {
  mask-image: url('./icons/arrow-right.svg');
  -webkit-mask-image: url('./icons/arrow-right.svg');
}
.icon-check {
  mask-image: url('./icons/check.svg');
  -webkit-mask-image: url('./icons/check.svg');
}
.icon-device-floppy {
  mask-image: url('./icons/device-floppy.svg');
  -webkit-mask-image: url('./icons/device-floppy.svg');
}
.icon-action-button-type {
  mask-image: url('./icons/action-button-type.svg');
  -webkit-mask-image: url('./icons/action-button-type.svg');
}
.icon-music-player-type {
  mask-image: url('./icons/music-player-type.svg');
  -webkit-mask-image: url('./icons/music-player-type.svg');
}
.icon-sliders {
  mask-image: url('./icons/sliders.svg');
  -webkit-mask-image: url('./icons/sliders.svg');
}
.icon-weather-type {
  mask-image: url('./icons/weather-type.svg');
  -webkit-mask-image: url('./icons/weather-type.svg');
}
.icon-history-graph-type {
  mask-image: url('./icons/history-graph-type.svg');
  -webkit-mask-image: url('./icons/history-graph-type.svg');
}
.icon-clock-type {
  mask-image: url('./icons/clock-type.svg');
  -webkit-mask-image: url('./icons/clock-type.svg');
}
.icon-store {
  mask-image: url('./icons/store.svg');
  -webkit-mask-image: url('./icons/store.svg');
}
.icon-tag {
  mask-image: url('./icons/tag.svg');
  -webkit-mask-image: url('./icons/tag.svg');
}
.icon-sparkles {
  mask-image: url('./icons/sparkles.svg');
  -webkit-mask-image: url('./icons/sparkles.svg');
}
.icon-chevron-left {
  mask-image: url('./icons/chevron-left.svg');
  -webkit-mask-image: url('./icons/chevron-left.svg');
}
.icon-chevron-right {
  mask-image: url('./icons/chevron-right.svg');
  -webkit-mask-image: url('./icons/chevron-right.svg');
}
.icon-folder {
  mask-image: url('./icons/folder.svg');
  -webkit-mask-image: url('./icons/folder.svg');
}
.icon-folder-plus {
  mask-image: url('./icons/folder-plus.svg');
  -webkit-mask-image: url('./icons/folder-plus.svg');
}
.icon-pencil {
  mask-image: url('./icons/pencil.svg');
  -webkit-mask-image: url('./icons/pencil.svg');
}
.icon-copy {
  mask-image: url('./icons/copy.svg');
  -webkit-mask-image: url('./icons/copy.svg');
}
.icon-scissors {
  mask-image: url('./icons/scissors.svg');
  -webkit-mask-image: url('./icons/scissors.svg');
}
.icon-clipboard {
  mask-image: url('./icons/clipboard.svg');
  -webkit-mask-image: url('./icons/clipboard.svg');
}
.icon-trash {
  mask-image: url('./icons/trash.svg');
  -webkit-mask-image: url('./icons/trash.svg');
}
.icon-plus {
  mask-image: url('./icons/plus.svg');
  -webkit-mask-image: url('./icons/plus.svg');
}
.icon-sidebar {
  mask-image: url('./icons/sidebar.svg');
  -webkit-mask-image: url('./icons/sidebar.svg');
}
.icon-sun {
  mask-image: url('./icons/sun.svg');
  -webkit-mask-image: url('./icons/sun.svg');
}
.icon-moon {
  mask-image: url('./icons/moon.svg');
  -webkit-mask-image: url('./icons/moon.svg');
}
.icon-grid {
  mask-image: url('./icons/grid.svg');
  -webkit-mask-image: url('./icons/grid.svg');
}
.icon-list {
  mask-image: url('./icons/list.svg');
  -webkit-mask-image: url('./icons/list.svg');
}
.icon-layers {
  mask-image: url('./icons/layers.svg');
  -webkit-mask-image: url('./icons/layers.svg');
}
.icon-zap {
  mask-image: url('./icons/zap.svg');
  -webkit-mask-image: url('./icons/zap.svg');
}
.icon-puzzle {
  mask-image: url('./icons/puzzle.svg');
  -webkit-mask-image: url('./icons/puzzle.svg');
}
.icon-code {
  mask-image: url('./icons/code.svg');
  -webkit-mask-image: url('./icons/code.svg');
}
.icon-braces-x {
  mask-image: url('./icons/braces-x.svg');
  -webkit-mask-image: url('./icons/braces-x.svg');
}
.icon-list-play {
  mask-image: url('./icons/list-play.svg');
  -webkit-mask-image: url('./icons/list-play.svg');
}
.icon-settings {
  mask-image: url('./icons/settings.svg');
  -webkit-mask-image: url('./icons/settings.svg');
}
.icon-info {
  mask-image: url('./icons/info.svg');
  -webkit-mask-image: url('./icons/info.svg');
}
.icon-dots-vertical {
  mask-image: url('./icons/dots-vertical.svg');
  -webkit-mask-image: url('./icons/dots-vertical.svg');
}
.icon-star {
  mask-image: url('./icons/star.svg');
  -webkit-mask-image: url('./icons/star.svg');
}
.icon-minus {
  mask-image: url('./icons/minus.svg');
  -webkit-mask-image: url('./icons/minus.svg');
}
.icon-align-left {
  mask-image: url('./icons/align-left.svg');
  -webkit-mask-image: url('./icons/align-left.svg');
}
.icon-align-center {
  mask-image: url('./icons/align-center.svg');
  -webkit-mask-image: url('./icons/align-center.svg');
}
.icon-align-right {
  mask-image: url('./icons/align-right.svg');
  -webkit-mask-image: url('./icons/align-right.svg');
}
.icon-align-top {
  mask-image: url('./icons/align-top.svg');
  -webkit-mask-image: url('./icons/align-top.svg');
}
.icon-align-middle {
  mask-image: url('./icons/align-middle.svg');
  -webkit-mask-image: url('./icons/align-middle.svg');
}
.icon-align-bottom {
  mask-image: url('./icons/align-bottom.svg');
  -webkit-mask-image: url('./icons/align-bottom.svg');
}
.icon-image {
  mask-image: url('./icons/image.svg');
  -webkit-mask-image: url('./icons/image.svg');
}
.icon-type {
  mask-image: url('./icons/type.svg');
  -webkit-mask-image: url('./icons/type.svg');
}
.icon-upload {
  mask-image: url('./icons/upload.svg');
  -webkit-mask-image: url('./icons/upload.svg');
}
.icon-download {
  mask-image: url('./icons/download.svg');
  -webkit-mask-image: url('./icons/download.svg');
}
.icon-play {
  mask-image: url('./icons/play.svg');
  -webkit-mask-image: url('./icons/play.svg');
}
.icon-pause {
  mask-image: url('./icons/pause.svg');
  -webkit-mask-image: url('./icons/pause.svg');
}
.icon-chart {
  mask-image: url('./icons/chart.svg');
  -webkit-mask-image: url('./icons/chart.svg');
}
.icon-power {
  mask-image: url('./icons/power.svg');
  -webkit-mask-image: url('./icons/power.svg');
}
.icon-alert-triangle {
  mask-image: url('./icons/alert-triangle.svg');
  -webkit-mask-image: url('./icons/alert-triangle.svg');
}
.icon-pin {
  mask-image: url('./icons/pin.svg');
  -webkit-mask-image: url('./icons/pin.svg');
}
.icon-pin-off {
  mask-image: url('./icons/pin-off.svg');
  -webkit-mask-image: url('./icons/pin-off.svg');
}
.icon-crosshair {
  mask-image: url('./icons/crosshair.svg');
  -webkit-mask-image: url('./icons/crosshair.svg');
}
.icon-file-text {
  mask-image: url('./icons/file-text.svg');
  -webkit-mask-image: url('./icons/file-text.svg');
}
.icon-share {
  mask-image: url('./icons/share-2.svg');
  -webkit-mask-image: url('./icons/share-2.svg');
}
.icon-refresh {
  mask-image: url('./icons/refresh.svg');
  -webkit-mask-image: url('./icons/refresh.svg');
}
.icon-arrow-down {
  mask-image: url('./icons/arrow-down.svg');
  -webkit-mask-image: url('./icons/arrow-down.svg');
}
.icon-arrow-up {
  mask-image: url('./icons/arrow-up.svg');
  -webkit-mask-image: url('./icons/arrow-up.svg');
}
.icon-device-phone {
  mask-image: url('./icons/device-phone.svg');
  -webkit-mask-image: url('./icons/device-phone.svg');
}
.icon-device-tablet {
  mask-image: url('./icons/device-tablet.svg');
  -webkit-mask-image: url('./icons/device-tablet.svg');
}
.icon-device-desktop {
  mask-image: url('./icons/device-desktop.svg');
  -webkit-mask-image: url('./icons/device-desktop.svg');
}
.icon-device-laptop {
  mask-image: url('./icons/device-laptop.svg');
  -webkit-mask-image: url('./icons/device-laptop.svg');
}
.icon-device-watch {
  mask-image: url('./icons/device-watch.svg');
  -webkit-mask-image: url('./icons/device-watch.svg');
}
.icon-device-headphones {
  mask-image: url('./icons/device-headphones.svg');
  -webkit-mask-image: url('./icons/device-headphones.svg');
}
.icon-device-gamepad {
  mask-image: url('./icons/device-gamepad.svg');
  -webkit-mask-image: url('./icons/device-gamepad.svg');
}
.icon-device-mouse {
  mask-image: url('./icons/device-mouse.svg');
  -webkit-mask-image: url('./icons/device-mouse.svg');
}
.icon-device-keyboard {
  mask-image: url('./icons/device-keyboard.svg');
  -webkit-mask-image: url('./icons/device-keyboard.svg');
}
.icon-device-speaker {
  mask-image: url('./icons/device-speaker.svg');
  -webkit-mask-image: url('./icons/device-speaker.svg');
}
.icon-battery {
  mask-image: url('./icons/battery.svg');
  -webkit-mask-image: url('./icons/battery.svg');
}
.icon-battery-charging {
  mask-image: url('./icons/battery-charging.svg');
  -webkit-mask-image: url('./icons/battery-charging.svg');
}
.icon-external-link {
  mask-image: url('./icons/external-link.svg');
  -webkit-mask-image: url('./icons/external-link.svg');
}
.icon-bell {
  mask-image: url('./icons/bell.svg');
  -webkit-mask-image: url('./icons/bell.svg');
}
.icon-undo {
  mask-image: url('./icons/undo.svg');
  -webkit-mask-image: url('./icons/undo.svg');
}
.icon-message-square {
  mask-image: url('./icons/message-square.svg');
  -webkit-mask-image: url('./icons/message-square.svg');
}
.icon-mail {
  mask-image: url('./icons/mail.svg');
  -webkit-mask-image: url('./icons/mail.svg');
}
.icon-bug {
  mask-image: url('./icons/bug.svg');
  -webkit-mask-image: url('./icons/bug.svg');
}
.icon-heart {
  mask-image: url('./icons/heart.svg');
  -webkit-mask-image: url('./icons/heart.svg');
}
.icon-apple {
  mask-image: url('./icons/apple.svg');
  -webkit-mask-image: url('./icons/apple.svg');
}
.icon-linux {
  mask-image: url('./icons/linux.svg');
  -webkit-mask-image: url('./icons/linux.svg');
}
.icon-windows {
  mask-image: url('./icons/windows.svg');
  -webkit-mask-image: url('./icons/windows.svg');
}
.icon-discord {
  mask-image: url('./icons/discord.svg');
  -webkit-mask-image: url('./icons/discord.svg');
}
.icon-usb {
  mask-image: url('./icons/usb.svg');
  -webkit-mask-image: url('./icons/usb.svg');
}
.icon-user {
  mask-image: url('./icons/user.svg');
  -webkit-mask-image: url('./icons/user.svg');
}
.icon-log-out {
  mask-image: url('./icons/log-out.svg');
  -webkit-mask-image: url('./icons/log-out.svg');
}
.icon-globe {
  mask-image: url('./icons/globe.svg');
  -webkit-mask-image: url('./icons/globe.svg');
}
.icon-wifi {
  mask-image: url('./icons/wifi-solid-full.svg');
  -webkit-mask-image: url('./icons/wifi-solid-full.svg');
}
.icon-search {
  mask-image: url('./icons/search.svg');
  -webkit-mask-image: url('./icons/search.svg');
}
.icon-sun-dim {
  mask-image: url('./icons/sun-dim.svg');
  -webkit-mask-image: url('./icons/sun-dim.svg');
}
.icon-contrast {
  mask-image: url('./icons/contrast.svg');
  -webkit-mask-image: url('./icons/contrast.svg');
}
.icon-rotate-cw {
  mask-image: url('./icons/rotate-cw.svg');
  -webkit-mask-image: url('./icons/rotate-cw.svg');
}
.icon-droplet {
  mask-image: url('./icons/droplet.svg');
  -webkit-mask-image: url('./icons/droplet.svg');
}
.icon-droplets {
  mask-image: url('./icons/droplets.svg');
  -webkit-mask-image: url('./icons/droplets.svg');
}
.icon-droplet-off {
  mask-image: url('./icons/droplet-off.svg');
  -webkit-mask-image: url('./icons/droplet-off.svg');
}
.icon-blend {
  mask-image: url('./icons/blend.svg');
  -webkit-mask-image: url('./icons/blend.svg');
}
.icon-chevron-down {
  mask-image: url('./icons/chevron-down.svg');
  -webkit-mask-image: url('./icons/chevron-down.svg');
}
.icon-chevron-up {
  mask-image: url('./icons/chevron-up.svg');
  -webkit-mask-image: url('./icons/chevron-up.svg');
}

/* widget-metrics.css */
/*
 * Metrics for everything rendered inside a deck widget.
 *
 * Widget content is laid out in the reference coordinate space of a 120px cell and then
 * transform-scaled to the real cell size, so every fixed size inside a widget is authored in
 * reference px. `rem` is deliberately absent: the user's browser font-size preference changes the
 * root font size, and a widget must render identically regardless of it - its proportions come from
 * the deck cell alone. That rules out the rem-based tokens (--space-*, --text-*, --radius-*) inside
 * a widget too; use plain px.
 *
 * The two reference values are mirrored from `domain/widget.interface.ts`, which owns them. A spec
 * in this package reads this file and fails if the numbers drift apart.
 */

:root {
  --widget-reference-cell: 120px;
  --widget-reference-radius: 22px;
}

/* renderer.css */
/*
 * The stylesheet for `renderUiNode`.
 *
 * Ported from the Angular renderer's own sheet, with one structural difference: that renderer wraps
 * every node in a component element and styles `> shared-ui-node`, while this one appends each
 * child's element directly, so the flex children are the widget elements themselves and the rules
 * address `> *`.
 *
 * `inset` is written as four longhands and `rgba()` is used in place of hex alpha throughout,
 * because the compatibility floor for this package is Safari 9 - both are dropped wholesale there,
 * and a dropped `inset` is an overlay that does not cover anything.
 */

.widget-stack {
  position: relative;
  display: flex;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
}

/*
 * A child gives way when the stack has less room than the children ask for, rather than spilling out
 * of it. The profile's lengths are fractions of the widget basis and a text node's width is its own
 * text, so a stack can be handed more content than its box holds - and the box is inset by the safe
 * area that clears the tile's rounded corner, so anything that leaves it is drawn under the curve
 * and sliced by it. Shrinking only binds when the children over-subscribe the box, so a stack whose
 * content fits keeps exactly the geometry the profile prescribes.
 */
.widget-stack > * {
  flex: 0 1 auto;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
}

/*
 * A button is a stack that also takes presses. `position: relative` plus a negative z-index on the
 * artwork is what lets the flex children sit above it without a dedicated stacking wrapper that
 * would interfere with the flex layout; `isolation: isolate` is what makes that negative z-index
 * resolve against this element rather than against an ancestor that carries a transform. Without it
 * the artwork paints behind the whole tree and this element's own background covers it entirely.
 */
.widget-button {
  position: relative;
  isolation: isolate;
  display: flex;
  box-sizing: border-box;
  overflow: hidden;
  min-width: 0;
  min-height: 0;
  touch-action: none;
  transition: background-color var(--transition-slow);
  /*
   * A nested button's corner is painted inline by the renderer, as a fraction of its own height - see
   * BUTTON_CORNER_RATIO. Not stated here, because it is not a constant: a button half the height of
   * another is half as round.
   */
}

/*
 * A button that is the whole tree fills the tile, so its corner is the tile's - the reader's own
 * radius, in the reference pixels a tile's content is laid out in (`--widget-scale` is reset to 1
 * there). A border that does not follow the tile it sits in is what a reader notices first.
 *
 * A nested button that asked for `corner: "tile"` takes the same radius for the same reason: it is a
 * backdrop drawn to the tile's edges, and a fraction of its own height cuts an arc across the tile
 * wherever the two disagree.
 */
.widget-button.widget-node-root,
.widget-button.widget-tile-corner {
  border-radius: calc(var(--widget-radius, 22px) * var(--widget-scale, 1));
}

/* A button is a stack, and its children give way the same way - see the rule above. */
.widget-button > * {
  flex: 0 1 auto;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
}

/*
 * Text in a widget tree is a label, never a selection: dragging across a row to pick it would leave a
 * highlight behind and swallow the gesture the node wanted. `user-select` inherits, so the containers
 * carry it and a `ui.text-field` - the one element whose text is the user's - takes it back.
 */
.widget-stack,
.widget-button,
.widget-layer,
.widget-transform,
.widget-grid,
.widget-segmented,
.widget-modifier,
.widget-responsive,
.widget-first-fit,
.widget-list,
.widget-text,
.widget-image {
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  /*
   * A separate mechanism from selection, and the only thing that stops iOS from running its own
   * long-press recognizer over a node. The callout sheet an image raises there ("Copy" / "Share")
   * lands on top of the `long-press` the widget declares for itself, and no `preventDefault` on a
   * pointer event suppresses it. Inherits, so the containers carry it the same way.
   */
  -webkit-touch-callout: none;
}

/*
 * An image is draggable by default, so a press-and-move over artwork tears it out of the widget as a
 * drag ghost instead of running the node's own gesture. The renderer's `draggable="false"` is what
 * every engine honours; this is the WebKit-only extra beside it.
 */
.widget-button-artwork,
.widget-image img {
  -webkit-user-drag: none;
}

/* A node that claims a press says so under the pointer, the same way a deck tile does. */
.widget-pressable {
  cursor: pointer;
}

.widget-slider.widget-pressable,
.widget-dial.widget-pressable,
.widget-toggle.widget-pressable,
.widget-segmented.widget-pressable,
.widget-dialog-content .widget-button.widget-pressable {
  cursor: default;
}

/* A text node over artwork has to stay legible against it, at any depth of nesting. */
.widget-button .widget-text {
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
  line-height: 1.2;
}

.widget-button .widget-text.widget-text-no-shadow {
  text-shadow: none;
}

.widget-button-artwork {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  transform-origin: center;
}

.widget-press-tint {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
  background-color: rgba(255, 255, 255, 0);
  transition: background-color var(--transition-press-out);
  pointer-events: none;
}

.widget-press-tint-active {
  background-color: rgba(255, 255, 255, 0.2);
  transition: background-color var(--transition-press-in);
}

.widget-button-ring {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  pointer-events: none;
  /*
   * The overlay the ring is drawn into. It takes the button's corner and the ring's own layers take
   * this one, so the whole chain follows the reader's radius; broken anywhere along it, the ring is
   * drawn as a hard rectangle across a rounded tile.
   */
  border-radius: inherit;
}

.widget-text {
  display: block;
  box-sizing: border-box;
  overflow: hidden;
  line-height: 1;
  margin: 0;
  /*
   * A line box of exactly 1em is what makes the profile's geometry predictable - a text node is as
   * tall as its size, and every length around it is a fraction of the basis rather than of whatever
   * a font decided. But a font's own box is taller than 1em (around 1.21 for the stack this ships
   * with), so the half-leading is negative and the glyphs hang out of the content box at both ends.
   * `overflow: hidden` is here for the ellipsis, and it cut those ends off: every descender in the
   * deck lost its tail.
   *
   * Clipping happens at the padding edge, so this hands the ink the room it overhangs by, and the
   * negative margin gives the same amount back to the layout. The box the profile reasons about is
   * unchanged; only what may be painted inside it grows. Physical longhands rather than the block
   * pair, for the same reason `inset` is written out at the top of this file: the floor drops the
   * logical properties.
   */
  padding-top: 0.2em;
  padding-bottom: 0.2em;
  margin-top: -0.2em;
  margin-bottom: -0.2em;
}

/*
 * The `max-height: 100%` every stack child carries caps the border box, while the stack sized itself
 * from the margin box - and the rule above makes those two differ by exactly the ink's 2 x 0.2em. A
 * container whose height came out equal to the text's own margin box therefore capped the text 0.4em
 * short, squeezing the content box under the line box and clipping the bottom of every glyph: a
 * reading rendered "50" with flat-bottomed digits and "0 %" with a sliced percent sign, wherever a
 * row was sized by the text inside it.
 *
 * Handing the cap the same allowance the padding claims leaves the margin box at or below 100%, so
 * nothing the profile lays out moves; only the ink the box may paint outside itself survives. A text
 * that really does over-subscribe its box is still held to it.
 */
.widget-stack > .widget-text,
.widget-button > .widget-text,
.widget-modifier > .widget-text,
.widget-responsive > .widget-text,
.widget-first-fit > .widget-text {
  max-height: calc(100% + 0.4em);
}

.widget-stack.widget-stack-clip-start {
  overflow: hidden;
}

.widget-stack.widget-stack-clip-start > * {
  flex-shrink: 0;
  max-width: none;
  max-height: none;
}

.widget-stack.widget-stack-clip-start > .widget-text {
  max-width: none !important;
  max-height: none !important;
}

.widget-text-span-image {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: top;
  object-fit: contain;
  -webkit-user-drag: none;
}

.widget-transform {
  position: relative;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
}

/*
 * A layer's children share the box instead of dividing it, first one furthest back. Painted in
 * document order rather than with z-index, so a layer nested in a button still resolves against its
 * own box.
 *
 * The rule below deliberately sets no `display`: each node type declares its own, and this selector
 * is one specificity point heavier - a `display: block` here silently un-flexed every stack that
 * happened to sit inside a layer, which is how a centred readout ended up pinned to the top left.
 */
.widget-layer {
  position: relative;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
}

.widget-layer > * {
  position: absolute;
  top: 0;
  left: 0;
}

.widget-transform > * {
  position: absolute;
  top: 0;
  left: 0;
}

.widget-modifier {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
}

.widget-modifier > * {
  flex: 0 1 auto;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
}

.widget-responsive {
  position: relative;
  display: flex;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
}

.widget-responsive > * {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
}

.widget-first-fit {
  position: relative;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
}

.widget-first-fit > * {
  position: absolute;
  top: 0;
  left: 0;
}

.widget-first-fit > [data-first-fit-last] {
  position: relative;
}

.widget-first-fit > [data-first-fit-hidden] {
  visibility: hidden;
  pointer-events: none;
}

.widget-modifier-fill-width > * {
  flex: 1 1 auto;
}

.widget-modifier-fill-height {
  align-items: stretch;
}

.widget-modifier-border {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  box-sizing: border-box;
  border-radius: inherit;
  pointer-events: none;
}

.widget-layer > .widget-modifier,
.widget-transform > .widget-modifier {
  right: 0;
  bottom: 0;
  margin: auto;
}

.widget-chart {
  display: block;
}

.widget-chart-area {
  stroke: none;
  fill-opacity: 0.16;
}

.widget-chart-line {
  fill: none;
  stroke-linejoin: round;
  stroke-linecap: round;
  opacity: 0.9;
}

/* Equal advance widths for every digit, so a reserved run does not shift sideways as it counts. */
.widget-text-tabular,
.widget-dynamic-text {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

.widget-text-clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  white-space: normal;
}

.widget-text-clamp span.widget-text-clamp-overflow {
  color: transparent;
  text-shadow: none;
}

.widget-text-clamp img.widget-text-clamp-overflow {
  opacity: 0;
}

.widget-clock-dial {
  display: block;
}

.widget-clock-dial-tick,
.widget-clock-dial-hand-hour,
.widget-clock-dial-hand-minute,
.widget-clock-dial-hand-second {
  stroke-linecap: round;
}

.widget-video-stream-host {
  display: block;
  box-sizing: border-box;
}

.widget-video-stream {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.widget-video-stream-frame {
  position: absolute;
  overflow: hidden;
  background: var(--color-bg-tertiary);
}

.widget-video-stream-media {
  display: block;
  width: 100%;
  height: 100%;
}

.widget-video-stream:not([data-status="playing"]) .widget-video-stream-media {
  opacity: 0;
}

.widget-video-stream-still {
  display: block;
  width: 100%;
  height: 100%;
}

.widget-video-stream-status {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  padding: 0 8px;
  transform: translateY(-50%);
  color: var(--color-text-muted);
  line-height: 1.3;
  text-align: center;
  pointer-events: none;
}

.widget-video-stream-status[hidden] {
  display: none;
}

.widget-image {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}

.widget-image img {
  display: block;
  object-fit: contain;
}

.widget-image-tint {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  mask-size: contain;
  mask-position: center;
  mask-repeat: no-repeat;
  -webkit-mask-size: contain;
  -webkit-mask-position: center;
  -webkit-mask-repeat: no-repeat;
  pointer-events: none;
}

/*
 * A crossfade stacks the incoming artwork on the settled one and fades it in, so a source change
 * never cuts and never flashes an empty box. An animation rather than a transition: a transition off
 * a freshly inserted element needs a reflow kick to run at all.
 */
.widget-image-incoming {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  animation: widget-artwork-fade-in 220ms ease-out both;
}

.widget-artwork-incoming {
  animation: widget-artwork-fade-in 220ms ease-out both;
}

@keyframes widget-artwork-fade-in {
  from {
    opacity: 0;
    scale: 1.02;
  }

  to {
    opacity: var(--widget-artwork-opacity, 1);
    scale: 1;
  }
}

/*
 * A node that opts into a crossfade puts the whole artwork change on a curve, not just the swap: the
 * colour behind the artwork and the artwork's own opacity move with it, because a cover, the colour
 * derived from it and the dim it takes on when playback pauses are one visual change each time.
 */
.widget-artwork-eased {
  transition: background 800ms ease;
}

.widget-artwork-eased > img {
  transition: opacity 350ms ease, filter 350ms ease;
}

/*
 * A progress bar is re-evaluated about once a second, so its fill would step rather than travel.
 * Easing the width over a little under that interval turns the steps back into motion.
 */
.widget-progress-fill {
  transition: width 400ms linear, background 800ms ease;
}

.widget-range-bar {
  position: relative;
  box-sizing: border-box;
}

.widget-range-bar-track {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  overflow: hidden;
  background: var(--color-bg-tertiary);
}

.widget-range-bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
}

.widget-range-bar-marker {
  position: absolute;
  top: 50%;
  border-radius: 50%;
  box-sizing: content-box;
  transform: translate(-50%, -50%);
}

/* The whole box is the drag surface, so the browser's own touch gestures must not compete with it. */
.widget-slider {
  position: relative;
  box-sizing: border-box;
  touch-action: none;
}

.widget-slider-track {
  position: absolute;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

.widget-slider-vertical .widget-slider-track {
  left: 50%;
  right: auto;
  top: 0;
  bottom: 0;
  transform: translateX(-50%);
}

.widget-slider-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  transition: width 80ms linear;
}

.widget-slider-vertical .widget-slider-fill {
  top: auto;
  left: 0;
  right: 0;
  bottom: 0;
  transition: height 80ms linear;
}

.widget-slider-thumb {
  position: absolute;
  box-sizing: content-box;
  background: var(--color-text-primary);
  border-radius: 50%;
  pointer-events: none;
  top: 50%;
  transform: translateY(-50%);
  transition: left 80ms linear;
}

.widget-slider-vertical .widget-slider-thumb {
  top: auto;
  left: 50%;
  transform: translateX(-50%);
  transition: bottom 80ms linear;
}

/* Mid-drag the pill and the thumb track the pointer exactly, rather than easing toward it. */
.widget-slider-fill-dragging,
.widget-slider-thumb-dragging {
  transition: none;
}

.widget-shape,
.widget-gauge,
.widget-dial {
  display: block;
  overflow: visible;
}

.widget-gauge-track,
.widget-gauge-fill {
  fill: none;
  stroke-linecap: round;
}

.widget-gauge-track {
  stroke: var(--color-bg-tertiary);
}

.widget-dial {
  touch-action: none;
}

.widget-dial-thumb {
  fill: var(--color-text-primary);
  stroke: var(--color-bg-secondary);
}

.widget-grid,
.widget-icon,
.widget-toggle,
.widget-segmented {
  position: relative;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
}

.widget-grid > *,
.widget-icon-glyph,
.widget-toggle-track,
.widget-toggle-knob,
.widget-segmented-track,
.widget-segmented-face,
.widget-segmented-content > * {
  position: absolute;
}

.widget-toggle,
.widget-segmented {
  touch-action: none;
}

.widget-toggle-track {
  transition: background 120ms linear;
}

.widget-toggle-knob {
  border-radius: 50%;
  background: var(--color-text-primary);
  transition: left 120ms ease-out;
}

.widget-segmented-track {
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: var(--color-bg-tertiary);
}

.widget-segmented-face {
  transition: left 120ms ease-out;
}

.widget-segmented-content {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  pointer-events: none;
}

.widget-segmented-content > * {
  top: 0;
}

.widget-segmented-content .widget-pressable {
  cursor: inherit;
}

.widget-node-unsupported {
  background: var(--color-bg-tertiary);
  opacity: 0.35;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .widget-image-incoming,
  .widget-artwork-incoming {
    animation: none;
  }

  .widget-button,
  .widget-artwork-eased,
  .widget-artwork-eased > img,
  .widget-progress-fill,
  .widget-slider-fill,
  .widget-slider-thumb,
  .widget-toggle-track,
  .widget-toggle-knob,
  .widget-segmented-face {
    transition: none;
  }
}

/*
 * A single line of text the user types. The face is the reader's, not the producer's: the profile gives
 * a field a size and a placeholder and nothing else, for the same reason it gives a button no say in
 * how its press looks.
 */
.widget-text-field {
  display: block;
  user-select: text;
  -webkit-user-select: text;
  /* Takes the callout back with the selection: without it iOS offers no paste into the field. */
  -webkit-touch-callout: default;
  box-sizing: border-box;
  min-width: 0;
  margin: 0;
  padding: 0.4em 0.6em;
  border: 1px solid var(--color-border);
  border-radius: 0.4em;
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
  font-family: inherit;
  line-height: 1.2;
  /* One line that scrolls sideways rather than wrapping - the profile has no multi-line entry. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.widget-text-field:focus {
  outline: none;
  border-color: var(--color-accent);
}

/*
 * A field that declares no events still draws its value: that is what makes it usable as a readout of
 * something entered elsewhere, without a second element for the purpose.
 */
.widget-text-field[readonly] {
  cursor: default;
}

/*
 * A container that scrolls. Deliberately not a stack with a flag: a stack divides a bounded main axis
 * between its children and every length in this profile is a fraction of that, while a list's main
 * axis has no bound at all - so children take their natural extent along it.
 */
.widget-list {
  position: relative;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /*
   * The gutter is reserved whether or not a bar is showing, so the content does not reflow the moment
   * one appears - and, where the engine honours it, an overlay bar stops floating over the last
   * column instead of sitting beside it. Dropped silently below the compatibility floor, which leaves
   * the measured fallback in the renderer as the only thing keeping the two apart.
   */
  scrollbar-gutter: stable;
}

.widget-list > * {
  flex: 0 0 auto;
  min-width: 0;
}

.widget-list-horizontal {
  flex-direction: row;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-gutter: auto;
}

.widget-list.widget-list-anchor-end {
  overflow-anchor: none;
}

.widget-list > .widget-list-jump {
  position: sticky;
  bottom: var(--space-2);
  z-index: 1;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  box-sizing: border-box;
  margin: 0 auto;
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border-light);
  border-radius: 999px;
  background: var(--color-bg-elevated);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

.widget-list > .widget-list-jump:hover {
  background: var(--color-bg-tertiary);
}

.widget-list > .widget-list-jump:focus {
  outline: none;
}

.widget-list > .widget-list-jump:focus-visible {
  border-color: var(--color-accent);
}

.widget-list-jump-icon {
  width: 1em;
  height: 1em;
}

/* widget-border.css */
/*
 * A widget's configured border, as a fixed-width ring along the widget edge.
 *
 * The ring is the element's own paint area, cut out via a two-layer exclude mask (everything but the
 * content box), so the styles can paint anything into it: a flat colour for the static and
 * opacity-animated styles, or a rotating oversized conic gradient for the spatial ones.
 *
 * The width stays a fixed 2px at every deck scale - unlike the corner radius, a ring that grew with
 * the cell would read as wildly inconsistent between differently sized widgets. That holds wherever
 * the ring is mounted: a tile draws its own beside the scaled content, while a button node's ring
 * lives inside it and divides `--deck-scale` back out, so both paint 2px on screen.
 *
 * Mount it in a `position: relative` box carrying the widget's radius; the ring inherits it.
 */

.widget-button-ring {
  border-radius: inherit;
  pointer-events: none;
}

/*
 * Hidden entirely where mask compositing is unsupported: without the mask the background would cover
 * the whole widget face, which is far worse than no border at all.
 */
.ring {
  display: none;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: inherit;
  overflow: hidden;
  box-sizing: border-box;
  padding: var(--wb-width, 2px);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

@supports ((mask-composite: exclude) or (-webkit-mask-composite: xor)) {
  .ring {
    display: block;
  }
}

/* Every looping style below keeps running under prefers-reduced-motion. Freezing the ring here alone
   left one border animating in the configuration UI and dead in the web client (issue 682). */

/* Colour-tinted styles paint the ring directly; the animated ones vary its opacity. */
.wb-static,
.wb-heartbeat,
.wb-breathing,
.wb-blink {
  background: var(--wb-color);
}

.wb-heartbeat {
  animation: wb-heartbeat 1.4s ease-out infinite;
}

.wb-breathing {
  animation: wb-breathing 3.2s ease-in-out infinite;
}

.wb-blink {
  animation: wb-blink 1s linear infinite;
}

/* One colour at a time cycling through the spectrum, with no spatial gradient. */
.wb-hue-shift {
  background: #ff0000;
  animation: wb-hue-shift 8s linear infinite;
}

/*
 * Spatial styles: an oversized square-ish layer spins behind the ring mask. The -300% inset keeps the
 * layer covering the widget at every rotation angle even for extreme aspect ratios; the overflow clip
 * bounds what is actually painted.
 */
.wb-rgb::before,
.wb-comet::before,
.wb-ants::before {
  content: '';
  position: absolute;
  top: -300%;
  right: -300%;
  bottom: -300%;
  left: -300%;
  animation: wb-spin 4s linear infinite;
}

.wb-rgb::before {
  background: conic-gradient(from 0deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000);
}

.wb-comet::before {
  background: conic-gradient(from 0deg, transparent 0deg 210deg, var(--wb-color) 360deg);
  animation-duration: 1.6s;
}

.wb-ants::before {
  background: repeating-conic-gradient(var(--wb-color) 0deg 9deg, transparent 9deg 18deg);
  animation-duration: 12s;
}

/*
 * Phase-locks every looping style to the shared clock. Must stay after the rules above: their
 * `animation` shorthand resets the delay longhand.
 */
.ring,
.ring::before {
  animation-delay: var(--wb-phase, 0s);
}

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

@keyframes wb-heartbeat {
  0%, 45%, 100% {
    opacity: 0.35;
  }

  10%, 30% {
    opacity: 1;
  }

  20% {
    opacity: 0.45;
  }
}

@keyframes wb-breathing {
  0%, 100% {
    opacity: 0.15;
  }

  50% {
    opacity: 1;
  }
}

@keyframes wb-blink {
  0%, 49.9% {
    opacity: 1;
  }

  50%, 100% {
    opacity: 0;
  }
}

@keyframes wb-hue-shift {
  to {
    filter: hue-rotate(1turn);
  }
}

/* grid.css */
/*
 * The runtime deck grid.
 *
 * Nothing here uses `display: grid`. The compatibility floor drops it wholesale, and a deck whose
 * empty cells collapse is a deck of misplaced widgets - so every cell and tile takes explicit
 * left/top/width/height from the same solved metrics the widgets do.
 */

.deck-grid {
  position: relative;
  margin: 0 auto;
}

.deck-grid-cell {
  position: absolute;
  border-radius: calc(var(--widget-radius, 22px) * var(--widget-scale, 1));
  background: var(--color-bg-secondary);
  opacity: 0.35;
}

.deck-grid-empty-transparent .deck-grid-cell {
  opacity: 0;
}

.deck-grid-tile {
  position: absolute;
  overflow: hidden;
  border-radius: calc(var(--widget-radius, 22px) * var(--widget-scale, 1));
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
  transition: transform var(--transition-press-out);
}

.deck-grid-flat .deck-grid-tile {
  box-shadow: none;
}

/*
 * Press feedback scales the whole tile - surface, ring and shadow included. A scale applied further
 * in would leave the border ring and the shadow at full size while the face shrinks.
 */
.deck-grid-tile-pressed {
  transform: scale(0.95);
  transition: transform var(--transition-press-in);
}

/*
 * The wrapper carries the deck scale, so `--widget-scale` is reset to 1 inside it: anything below
 * that multiplies by it would otherwise scale twice, and a radius would come out squared.
 */
.deck-grid-tile-content {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: top left;
}

/*
 * The widget's own face. Opaque, because a widget tree paints on top of it and the deck behind it
 * must not show through; the same `--color-bg-secondary` an empty cell is tinted with.
 *
 * Text inside a deck is a label, never a selection: a drag across a tile that selected its label
 * would both look wrong and swallow the gesture the widget wanted.
 */
.deck-grid-tile-surface {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: calc(var(--widget-radius, 22px) * var(--widget-scale, 1));
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--color-bg-secondary);
  box-sizing: border-box;
  overflow: hidden;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  /*
   * A tile owns its own long press - `onLongPress` runs off a timer started in `pointerdown`. iOS
   * raises its callout sheet from a recognizer of its own that no `preventDefault` reaches, so
   * without this the platform menu covers the gesture the tile is in the middle of.
   */
  -webkit-touch-callout: none;
}

.deck-grid-tile-transparent {
  box-shadow: none;
}

/*
 * A widget that paints its own opaque fill keeps the dark face 1.5px inside the edge, so the
 * antialiased edge pixels of a fractional tile blend with the deck and not with the face.
 */
.deck-grid-tile-filled .deck-grid-tile-surface {
  background-color: transparent;
  isolation: isolate;
}

.deck-grid-tile-filled .deck-grid-tile-surface::before {
  content: '';
  position: absolute;
  top: 1.5px;
  right: 1.5px;
  bottom: 1.5px;
  left: 1.5px;
  z-index: -1;
  border-radius: inherit;
  background-color: var(--color-bg-secondary);
  pointer-events: none;
}

.deck-grid-tile-transparent .deck-grid-tile-surface {
  background-color: transparent;
}

/* The tint the tile paints for a press it owns itself - a tree that claims the gesture tints its own. */
.deck-grid-tile-press-overlay {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 2;
  background-color: rgba(255, 255, 255, 0);
  transition: background-color var(--transition-press-out);
  pointer-events: none;
}

.deck-grid-tile-pressed .deck-grid-tile-press-overlay {
  background-color: rgba(255, 255, 255, 0.2);
  transition: background-color var(--transition-press-in);
}

/* The widget's configured border, drawn over its face along the tile edge. */
.deck-grid-tile-border {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 3;
}

@media (prefers-reduced-motion: reduce) {
  .deck-grid-tile {
    transition: none;
  }
}

/*
 * Deck input focus (issue #727): the cursor a wheel or D-pad moves around a device target's deck.
 * Two-tone on purpose - a single accent ring disappears on an accent-coloured action button, which
 * is most of them.
 */
.deck-grid-tile-focused::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: inherit;
  box-shadow:
    inset 0 0 0 2px var(--color-on-accent),
    inset 0 0 0 5px var(--color-accent);
  pointer-events: none;
  z-index: 30;
}

/* client */
/* The client's own chrome. Everything a widget draws comes from the runtime package's sheets. */

/*
 * The deck fills the viewport, so the chain from the document down to it has to carry a height. The
 * shared reset deliberately does not set one - the desktop app scrolls - so it is set here, and
 * without it the grid measures a container of zero height and solves every cell to nothing.
 */
html,
body,
#app {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

/*
 * The notch, the home indicator and the rounded corners of the tablets this client lives on. Written
 * as a padding on the root rather than per screen: every screen fills the viewport, so anything that
 * does not respect the insets loses its edges under the hardware. `env()` is unknown to the
 * compatibility floor, where it evaluates to nothing and the padding simply stays zero.
 */
.wc-root {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding:
    env(safe-area-inset-top, 0px)
    env(safe-area-inset-right, 0px)
    env(safe-area-inset-bottom, 0px)
    env(safe-area-inset-left, 0px);
}

.wc-panel {
  text-align: center;
  padding: var(--space-6);
}

.wc-panel-logo {
  width: 72px;
  height: 72px;
  display: block;
  margin: 0 auto var(--space-5);
  /* The mark is square with its own padding, so it needs no rounding of its own. */
  object-fit: contain;
}

.wc-panel-title {
  font-size: var(--text-xl);
  font-weight: 600;
  margin-bottom: var(--space-3);
}

.wc-panel-detail {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  max-width: 22em;
}

.wc-deck {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/*
 * The modal chrome, shared by every dialog this client opens: a modal an action opened here, the
 * client settings and the device-setup wizard. The three used to be three sheets' worth of near
 * copies and had drifted into three different-looking dialogs (issue #873), so the surface, the
 * header and the close button are stated once. What genuinely differs stays with each surface - its
 * stacking order, its `[hidden]` rule and its own body.
 *
 * The three sheets are concatenated in `stylesheet.mjs` with this one first, so a surface rule left
 * behind in a later sheet would beat the full-screen block at the bottom of this one at equal
 * specificity. Redeclaring any of this per surface silently un-fixes the full-screen behaviour.
 */
.wc-modal-backdrop,
.wc-client-settings-backdrop,
.wc-setup-backdrop {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: var(--color-scrim);
}

.wc-modal-backdrop {
  z-index: var(--z-modal);
}

.wc-modal-backdrop[hidden] {
  display: none;
}

.wc-modal,
.wc-client-settings-dialog,
.wc-setup-dialog {
  width: 100%;
  max-width: 34rem;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  /*
   * The design system's dialog surface, token for token, because the same dialogs open in the desktop
   * UI and the two sitting side by side must not be two different dialogs. Anything hard-coded here
   * is a colour that stops following the theme the moment the tokens move.
   */
  background: var(--color-bg-elevated);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-dialog);
  overflow: hidden;
}

/*
 * A dialog an action opened is a box before it is a container: the widget profile derives every
 * length from the box it is given, so this one is stated rather than grown - letting it grow to its
 * content would feed the content's own height back into the basis. It is bounded by the backdrop's
 * box above so it never reaches past the viewport, which is how the last rows of a list ended up
 * off-screen.
 *
 * And it does not scroll. A tree that has more than fits says so with a `ui.list`, which scrolls
 * itself and asks its producer for more - a second scroll container around it puts two bars on one
 * dialog and makes the box the basis is derived from a claim rather than a fact.
 */
.wc-modal {
  height: 36rem;
}

.wc-modal-header,
.wc-client-settings-header,
.wc-setup-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem var(--space-5);
  border-bottom: 1px solid var(--color-border);
}

.wc-modal-title,
.wc-client-settings-title,
.wc-setup-title {
  font-size: var(--text-lg);
  font-weight: var(--font-semibold);
  color: var(--color-text-primary);
  margin: 0;
}

.wc-modal-close,
.wc-client-settings-close,
.wc-setup-close {
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-muted);
  font-size: var(--text-xl);
  line-height: 1;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.wc-modal-close:hover,
.wc-client-settings-close:hover,
.wc-setup-close:hover {
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

/* Its own rule, not grouped with the hover above: `:focus-visible` is younger than the compatibility
   floor, and an engine that does not know it drops the whole selector list it appears in. */
.wc-modal-close:focus-visible,
.wc-client-settings-close:focus-visible,
.wc-setup-close:focus-visible {
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

.wc-modal-content {
  position: relative;
  width: 100%;
  /* `0` rather than `auto`: the basis is the box the dialog gives it, never its own content. */
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

.wc-modal-unavailable {
  padding: var(--space-6);
  text-align: center;
  color: var(--color-text-secondary);
}

.wc-modal-unavailable h3 {
  margin: 0 0 var(--space-3);
  color: var(--color-text-primary);
}

.wc-modal-unavailable p {
  margin: 0 0 var(--space-3);
}

.wc-modal-unavailable-code {
  font-size: var(--text-sm);
}

/*
 * 7-inch decks and anything smaller: a dialog takes the whole display instead of floating in the
 * middle of it (issue #873). The condition is width *or* height because the panels this covers are
 * short rather than narrow - 1024x600 and 800x480 are the common 7-inch geometries, and the Car
 * Thing is 800x480 - so a width-only rule would leave the most common one windowed.
 *
 * The desktop UI does the same through its `phone-screen` mixin; the numbers differ because that one
 * is about phones and this one about the decks this client runs on.
 */
@media (max-width: 1024px), (max-height: 600px) {
  .wc-modal-backdrop,
  .wc-client-settings-backdrop,
  .wc-setup-backdrop {
    align-items: stretch;
    justify-content: stretch;
    /*
     * The dialog below asks for `height: 100%`, which needs a parent with a definite height. A fixed
     * element resolves a percentage height against the viewport, so stating it here is definite on
     * every engine; deriving one from `top: 0`/`bottom: 0` is not.
     */
    height: 100%;
    padding: 0;
  }

  .wc-modal,
  .wc-client-settings-dialog,
  .wc-setup-dialog {
    /*
     * No `box-shadow: none` here, though a full-screen sheet has nothing left to cast elevation onto
     * anyway: the shadow is themed, and the compatibility floor's sheet carries the light theme's
     * copy of it under a `.light` prefix that outranks this rule. Overriding it here would leave the
     * shadow on under the light theme on those engines. It falls outside the viewport regardless.
     */
    max-width: none;
    height: 100%;
    max-height: none;
    border-radius: 0;
    /*
     * Only the full-screen sheet reaches the display edges, so only it has to keep clear of the
     * notch, the rounded corners and the home indicator. It carries them itself because the backdrop
     * is fixed and so escapes the insets `.wc-root` already pads for. The `0px` fallbacks are load
     * bearing: an engine without `env()` drops the whole declaration.
     */
    padding-right: env(safe-area-inset-right, 0px);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    padding-left: env(safe-area-inset-left, 0px);
  }

  .wc-modal-header,
  .wc-client-settings-header,
  .wc-setup-header {
    padding-top: calc(1rem + env(safe-area-inset-top, 0px));
  }
}

/*
 * The UI kit: the controls the client's own chrome is built from. Ported from the design system's
 * Angular components, so a screen written against either reads the same.
 */

.wc-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  box-sizing: border-box;
  min-height: var(--control-height);
  padding: var(--space-2) 1.125rem;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: var(--font-medium);
  line-height: 1;
  /* A translated label runs longer than the English it was laid out for, and must end in an
     ellipsis rather than spill over the border. */
  white-space: nowrap;
  overflow: hidden;
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast),
    opacity var(--transition-fast);
}

.wc-btn-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wc-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.wc-btn-loading {
  cursor: progress;
}

.wc-btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.wc-btn-primary {
  background-color: var(--color-accent);
  color: var(--color-on-accent);
}

.wc-btn-primary:hover:not(:disabled) {
  background-color: var(--color-accent-hover);
}

.wc-btn-secondary {
  background-color: var(--color-bg-tertiary);
  border-color: var(--color-border);
  color: var(--color-text-secondary);
}

.wc-btn-secondary:hover:not(:disabled) {
  background-color: var(--color-bg-elevated);
  border-color: var(--color-border-light);
  color: var(--color-text-primary);
}

.wc-btn-ghost {
  background-color: transparent;
  color: var(--color-text-secondary);
}

.wc-btn-ghost:hover:not(:disabled) {
  background-color: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

.wc-btn-danger {
  background-color: var(--color-danger);
  color: var(--color-on-accent);
}

.wc-btn-danger:hover:not(:disabled) {
  background-color: var(--color-danger-hover);
}

.wc-btn-spinner {
  display: inline-block;
  flex-shrink: 0;
  width: 0.875em;
  height: 0.875em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: ds-spin 0.7s linear infinite;
}

.wc-input {
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  min-height: var(--control-height);
  padding: 0.3125rem var(--space-3);
  background-color: var(--color-bg-primary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: var(--text-base);
  line-height: var(--leading-tight);
  outline: none;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.wc-input:hover:not(:disabled):not(:focus) {
  border-color: var(--color-border-light);
}

/* A text control signals focus with the accent border, so the global outward ring is dropped
   rather than stacked on top of it. */
.wc-input:focus {
  border-color: var(--color-accent);
}

.wc-input:focus-visible {
  box-shadow: none;
}

.wc-input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.wc-input-invalid {
  border-color: var(--color-danger);
}

.wc-checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  cursor: default;
}

.wc-checkbox-box {
  flex-shrink: 0;
  width: 0.8125rem;
  height: 0.8125rem;
  accent-color: var(--color-accent);
}

.wc-checkbox-box:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.wc-checkbox-label {
  min-width: 0;
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
}

.wc-toggle {
  /* The invisible checkbox is stretched over this box, so it needs a positioned ancestor. */
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: default;
  user-select: none;
  /* The checkbox covers the whole switch, so iOS would otherwise flash its tap highlight across
     the switch and its label on every tap, including one the switch ignores. */
  -webkit-tap-highlight-color: transparent;
}

.wc-toggle-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.wc-toggle-busy {
  cursor: progress;
}

/*
 * Stretched over the whole switch rather than hidden in a corner, so the checkbox *is* the hit
 * target and no label-to-control forwarding has to happen. That forwarding is what old WebKit does
 * not do for a zero-sized control, which left the switch dead to every tap on iOS 9.
 */
.wc-toggle-input {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
}

.wc-toggle-track {
  position: relative;
  box-sizing: border-box;
  flex-shrink: 0;
  width: 2rem;
  height: 1.125rem;
  background: var(--color-bg-tertiary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.wc-toggle-checked .wc-toggle-track {
  background: var(--color-accent);
  border-color: var(--color-accent);
}

/* The thumb is derived from the track so its inset stays equal on all four sides at any root font
   size: fixed pixel offsets against rem-sized boxes only balance at a 16px root. */
.wc-toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(1.125rem - 6px);
  height: calc(1.125rem - 6px);
  background: #fff;
  border-radius: 50%;
  transition: transform var(--transition-fast);
}

.wc-toggle-checked .wc-toggle-thumb {
  transform: translateX(calc(2rem - 1.125rem));
}

.wc-toggle-spinner {
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  left: 1px;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: ds-spin 0.7s linear infinite;
}

.wc-toggle-label {
  color: var(--color-text-primary);
  font-size: var(--text-md);
}

.wc-seg-host {
  display: inline-flex;
  /* Without this the automatic minimum size stops the control shrinking below its content width,
     so a row holding it overflows instead of the pill scrolling itself. */
  min-width: 0;
  max-width: 100%;
}

.wc-seg {
  display: inline-flex;
  gap: var(--space-1);
  max-width: 100%;
  padding: var(--space-1);
  background-color: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

.wc-seg-option {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.3125rem var(--space-3);
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: var(--font-medium);
  white-space: nowrap;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.wc-seg-option:hover:not(.wc-seg-active) {
  background-color: var(--color-bg-hover);
  color: var(--color-text-primary);
}

.wc-seg-active {
  background-color: var(--color-accent);
  color: var(--color-on-accent);
}

.wc-seg-option .icon {
  background-color: currentColor;
}

.wc-seg-stretch {
  display: flex;
}

.wc-seg-stretch .wc-seg {
  flex: 1;
  min-width: 0;
}

/* Grow from each option's own label width and share only the surplus: a basis of 0 hands every
   option the same width whatever its label says, which only fits the shortest translation. */
.wc-seg-stretch .wc-seg-option {
  flex: 1 1 auto;
  justify-content: center;
}

.wc-settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Deliberately not `flex-wrap: wrap`. A control too wide to sit beside its label used to take a
     line of its own, and old WebKit measures a wrapping container from its first line alone - so on
     the compatibility floor that line contributed no height and the control was drawn over the next
     row (issue #829). The label column shrinks instead, which every engine gets right and which
     reads better here anyway: its description simply wraps to another line. */
  flex-wrap: nowrap;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-top: 1px solid var(--color-border);
}

.wc-settings-row:first-child {
  padding-top: 0;
  border-top: none;
}

.wc-settings-row-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  /* `min-width: 0` alone lets the text shrink on a current engine, but old WebKit still lays the
     block out at its max-content width. The explicit grow/shrink pair says it in terms every
     engine honours. */
  flex: 1 1 auto;
  min-width: 0;
}

.wc-settings-row-label {
  color: var(--color-text-primary);
  font-size: var(--text-md);
  font-weight: var(--font-medium);
}

.wc-settings-row-desc {
  max-width: 30rem;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.wc-settings-row-control {
  flex: 0 1 auto;
}

.wc-settings-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  background-color: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.wc-settings-section-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.wc-settings-section-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.wc-settings-section-title {
  margin: 0;
  color: var(--color-text-primary);
  font-size: var(--text-lg);
  font-weight: var(--font-semibold);
  line-height: var(--leading-tight);
}

.wc-settings-section-desc {
  margin: 0;
  max-width: 30rem;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.wc-settings-section-body {
  display: flex;
  flex-direction: column;
}

.wc-error-banner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.5rem var(--space-3);
  background: var(--color-danger-muted);
  border-radius: var(--radius-md);
  color: var(--color-danger);
  font-size: var(--text-base);
}

.wc-error-banner-message {
  flex: 1;
}

.wc-error-banner-dismiss {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  opacity: 0.7;
}

.wc-error-banner-dismiss:hover {
  opacity: 1;
}

.wc-error-banner-dismiss-icon {
  display: block;
  width: 0.8125rem;
  height: 0.8125rem;
}

.wc-toasts {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  left: auto;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
  /* The stack spans the corner of the viewport; only a toast itself may take a pointer. */
  pointer-events: none;
}

.wc-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  box-sizing: border-box;
  width: 21.125rem;
  max-width: calc(100vw - var(--space-6));
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border-overlay);
  border-left: 3px solid var(--color-success);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-popover);
  color: var(--color-text-primary);
  pointer-events: auto;
  animation: ds-slide-up-in 160ms ease-out;
}

.wc-toast-error {
  border-left-color: var(--color-danger);
}

.wc-toast-icon {
  flex: none;
  width: 0.9375rem;
  height: 0.9375rem;
  margin-top: 0.125rem;
  color: var(--color-success);
}

.wc-toast-error .wc-toast-icon {
  color: var(--color-danger);
}

.wc-toast-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.wc-toast-message {
  font-size: var(--text-base);
}

.wc-toast-detail {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  overflow-wrap: break-word;
}

.wc-toast-link {
  color: var(--color-accent);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.wc-toast-dismiss {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  opacity: 0.6;
}

.wc-toast-dismiss:hover {
  opacity: 1;
}

.wc-toast-dismiss-icon {
  display: block;
  width: 0.6875rem;
  height: 0.6875rem;
}

.wc-toast-info {
  border-left-color: var(--color-accent);
}

.wc-toast-info .wc-toast-icon {
  color: var(--color-accent);
}

.wc-toast-warning {
  border-left-color: var(--color-warning);
}

.wc-toast-warning .wc-toast-icon {
  color: var(--color-warning);
}

.wc-toast-source,
.wc-notification-source {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

.wc-toast-source-kind,
.wc-notification-source-kind {
  font-weight: var(--font-semibold);
}

.wc-toast-source-name,
.wc-notification-source-name {
  min-width: 0;
  overflow-wrap: break-word;
}

.wc-toast-actions,
.wc-notification-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}

.wc-toast-actions {
  margin-top: var(--space-1);
}

.wc-toast-actions .wc-btn {
  min-height: 1.75rem;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
}

.wc-progress {
  box-sizing: border-box;
  position: relative;
  width: 100%;
  height: 0.25rem;
  margin-top: var(--space-1);
  overflow: hidden;
  background: var(--color-bg-tertiary);
  border-radius: var(--radius-sm);
}

.wc-progress-fill {
  height: 100%;
  background: var(--color-accent);
  border-radius: var(--radius-sm);
  transition: width var(--transition-fast);
}

.wc-progress-indeterminate .wc-progress-fill {
  width: 40%;
  animation: wc-progress-slide 1.2s ease-in-out infinite;
}

@keyframes wc-progress-slide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

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

  .wc-progress-indeterminate .wc-progress-fill {
    width: 100%;
    animation: none;
    opacity: 0.5;
  }
}

.wc-notification-backdrop {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1195;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: var(--color-scrim);
}

.wc-notification-backdrop[hidden],
.wc-notification-dialog [hidden] {
  display: none;
}

.wc-notification-dialog {
  box-sizing: border-box;
  width: 100%;
  max-width: 28rem;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--color-bg-elevated);
  border-top: 3px solid var(--color-success);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-dialog);
  color: var(--color-text-primary);
  overflow: hidden;
  outline: none;
}

.wc-notification-info {
  border-top-color: var(--color-accent);
}

.wc-notification-warning {
  border-top-color: var(--color-warning);
}

.wc-notification-error {
  border-top-color: var(--color-danger);
}

.wc-notification-header {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5) var(--space-2);
}

.wc-notification-icon {
  flex: none;
  display: flex;
  margin-top: 0.125rem;
  color: var(--color-success);
}

.wc-notification-info .wc-notification-icon {
  color: var(--color-accent);
}

.wc-notification-warning .wc-notification-icon {
  color: var(--color-warning);
}

.wc-notification-error .wc-notification-icon {
  color: var(--color-danger);
}

.wc-notification-icon-svg {
  display: block;
  width: 1.25rem;
  height: 1.25rem;
}

.wc-notification-titles {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.wc-notification-title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--font-semibold);
  overflow-wrap: break-word;
}

.wc-notification-close {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-muted);
}

.wc-notification-close:hover {
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

.wc-notification-close:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.wc-notification-close-icon {
  display: block;
  width: 0.875rem;
  height: 0.875rem;
}

.wc-notification-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 0;
  padding: var(--space-2) var(--space-5) var(--space-4);
  overflow-y: auto;
}

.wc-notification-message {
  margin: 0;
  color: var(--color-text-secondary);
  font-size: var(--text-md);
  overflow-wrap: break-word;
  white-space: pre-line;
}

.wc-notification-content {
  position: relative;
  flex: 0 0 auto;
  width: 100%;
  height: 12rem;
  overflow: hidden;
}

.wc-notification-progress .wc-progress {
  margin-top: 0;
}

.wc-notification-problem {
  margin: 0;
  color: var(--color-danger);
  font-size: var(--text-base);
}

.wc-notification-actions {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-5) var(--space-4);
  border-top: 1px solid var(--color-border);
}

@media (max-width: 1024px), (max-height: 600px) {
  .wc-notification-backdrop {
    padding: var(--space-3);
  }
}

/* Sign-in. The one screen someone actually fills in, so it is a card rather than a bare panel. */
.wc-login {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  width: 100%;
  max-width: 22rem;
  padding: var(--space-6);
  box-sizing: border-box;
}

.wc-login-header {
  text-align: center;
  margin-bottom: var(--space-2);
}

.wc-login-logo {
  width: 64px;
  height: 64px;
  display: block;
  margin: 0 auto var(--space-4);
  object-fit: contain;
}

.wc-login-title {
  font-size: var(--text-xl);
  font-weight: 600;
  margin: 0 0 var(--space-1);
}

.wc-login-subtitle {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  margin: 0;
}

.wc-login-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.wc-login-label {
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.wc-login-hint {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  margin: 0;
  text-align: center;
}

.wc-login-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.wc-otp {
  position: relative;
}

.wc-otp-cells {
  display: flex;
  gap: var(--space-2);
}

.wc-otp-cell {
  flex: 1 1 0;
  min-width: 0;
  height: 3.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  background-color: var(--color-bg-primary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-primary);
  font-size: var(--text-xl);
  font-weight: 600;
  transition: border-color var(--transition-fast);
}

.wc-otp-cell-active {
  border-color: var(--color-accent);
}

.wc-otp-invalid .wc-otp-cell {
  border-color: var(--color-danger);
}

.wc-otp-invalid .wc-otp-cell-active {
  border-color: var(--color-accent);
}

.wc-otp-disabled .wc-otp-cell {
  opacity: 0.55;
}

/* The real input covers the boxes and stays invisible; 16px keeps iOS from zooming on focus. */
.wc-otp-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  font-size: 16px;
  opacity: 0.01;
}

/*
 * The host's lock screen sits over the deck rather than replacing it (issues #462, #257): the deck
 * keeps its sessions and its state and comes straight back when the machine is unlocked.
 */
.wc-lock-screen {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: var(--z-toast);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-primary);
}

/*
 * The reconnecting notice, over the deck rather than instead of it for the same reason as the lock
 * screen above. Its own z-index, below `--z-modal`: at the lock screen's height it would bury a
 * dialog someone had open and hide the "action failed" toast an offline press still fires.
 */
.wc-reconnecting {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: var(--z-reconnecting);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-primary);
}

.wc-screensaver {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: var(--z-screensaver);
  background: #000;
  --color-text-primary: #ffffff;
  --color-text-secondary: #a0a0a0;
  --color-text-muted: #666666;
  color: var(--color-text-primary);
}

.wc-screensaver-deaf {
  background: transparent;
}

/*
 * A folder a provider draws. The chrome is Macro Deck's and stays outside the provider's tree, so a
 * view cannot draw over its own way out.
 */
.wc-folder-view {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
}

.wc-folder-view-chrome {
  flex: 0 0 auto;
  padding: var(--space-2);
}

.wc-folder-view-back {
  background: none;
  border: 0;
  color: var(--color-text-primary);
  font: inherit;
  padding: var(--space-2) var(--space-3);
}

.wc-folder-view-content {
  flex: 1 1 auto;
  position: relative;
  overflow: hidden;
}

.wc-folder-view-placeholder,
.wc-folder-view-loading {
  padding: var(--space-6);
  text-align: center;
  color: var(--color-text-secondary);
}

/* setup */
/* The device-setup wizard and the banner that leads into it. */

/* Everything this dialog looks like comes from the shared modal chrome in `styles.css`. Only its
   place in the stack is its own: it sits above the deck and below the settings dialog. */
.wc-setup-backdrop {
  z-index: 110;
}

.wc-setup-backdrop[hidden] {
  display: none;
}

.wc-setup-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--space-4);
}

.wc-setup-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.wc-setup-download {
  color: var(--color-accent);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
}

.wc-setup-download[hidden] {
  display: none;
}

.wc-setup-download:hover,
.wc-setup-download:focus-visible {
  text-decoration: underline;
}

.wc-setup-hint,
.wc-setup-notice,
.wc-setup-platform-body {
  margin: var(--space-2) 0 0;
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

[dir="rtl"] .wc-setup-platform-steps ol {
  padding-left: 0;
  padding-right: 1.25em;
}

.wc-setup-hint[hidden],
.wc-setup-fingerprint-block[hidden],
.wc-setup-open-secure[hidden] {
  display: none;
}

.wc-setup-fingerprint {
  margin: var(--space-1) 0 0;
  color: var(--color-text-primary);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  /* The hex string carries no break opportunity of its own, so without this it widens the dialog
     until the whole page scrolls sideways. */
  word-break: break-all;
}

.wc-setup-platform-steps {
  margin-top: var(--space-4);
}

.wc-setup-platform-steps h3 {
  margin: 0 0 var(--space-2);
  color: var(--color-text-primary);
  font-size: var(--text-md);
  font-weight: 600;
}

.wc-setup-platform-steps ol {
  margin: 0;
  padding-left: 1.25em;
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.wc-setup-platform-steps li + li {
  margin-top: var(--space-1);
}

.wc-setup-open-secure {
  margin-top: var(--space-4);
}

.wc-setup-why {
  margin-top: var(--space-4);
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
}

.wc-setup-why summary {
  color: var(--color-text-primary);
  font-weight: 600;
  cursor: default;
}

.wc-setup-why-body {
  margin: var(--space-2) 0 0;
  line-height: var(--leading-normal);
}

.wc-setup-banner {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  background: var(--color-bg-secondary);
  border-top: 1px solid var(--color-border);
}

.wc-setup-banner[hidden] {
  display: none;
}

.wc-setup-banner-message {
  color: var(--color-text-primary);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.wc-setup-banner-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* settings */
/* The client settings surface: the gear button and the modal behind it. */

.wc-client-settings-trigger {
  position: fixed;
  top: calc(var(--space-3) + env(safe-area-inset-top));
  right: calc(var(--space-3) + env(safe-area-inset-right));
  /* Above the setup banner, which is fixed in the same corner and would otherwise paint over this
     button; still below the settings backdrop, so the dialog covers it. */
  z-index: 105;
  width: 44px;
  height: 44px;
  /* Centred by padding rather than by flex: the only child is an <svg>, and old WebKit does not
     size an SVG flex item, so `justify-content` had nothing to centre and the gear sat against the
     left edge (issue #829). 44 - 2*11 leaves exactly the icon's box. */
  box-sizing: border-box;
  padding: 11px;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: var(--color-text-secondary);
  opacity: 0.55;
  transition: opacity 120ms ease, color 120ms ease;
}

.wc-client-settings-trigger[hidden] {
  display: none;
}

.wc-client-settings-trigger:hover,
.wc-client-settings-trigger:focus-visible {
  opacity: 1;
  color: var(--color-text-primary);
}

.wc-client-settings-icon {
  /* Block, so it does not sit on a text baseline and gain the descender space below it. */
  display: block;
  width: 22px;
  height: 22px;
}

.wc-edge-sidebar-backdrop {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 105;
  background: var(--color-scrim);
}

.wc-edge-sidebar {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  box-sizing: border-box;
  width: calc(68px + env(safe-area-inset-left, 0px));
  padding: calc(var(--space-3) + env(safe-area-inset-top, 0px)) 12px var(--space-3)
    calc(12px + env(safe-area-inset-left, 0px));
  background: var(--color-bg-elevated);
  box-shadow: var(--shadow-dialog);
}

.wc-edge-sidebar-button {
  display: block;
  width: 44px;
  height: 44px;
  box-sizing: border-box;
  padding: 11px;
  border: 0;
  border-radius: 50%;
  background: var(--color-bg-secondary);
  color: var(--color-text-primary);
}

/* Everything this dialog looks like comes from the shared modal chrome in `styles.css`. Only its
   place in the stack is its own: it covers the setup wizard and the gear button below it. */
.wc-client-settings-backdrop {
  z-index: 120;
}

.wc-client-settings-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
}

.wc-client-settings-status {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.wc-client-settings-status[hidden] {
  display: none;
}

.wc-client-settings-body[hidden] {
  display: none;
}

.wc-client-settings-licenses-entry {
  display: flex;
  justify-content: center;
}

.wc-client-settings-licenses {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
}

.wc-client-settings-licenses[hidden] {
  display: none;
}

.wc-client-settings-licenses-toolbar {
  flex: 0 0 auto;
  display: flex;
}

.wc-client-settings-licenses-failure {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

.wc-client-settings-licenses-failure[hidden] {
  display: none;
}

.wc-client-settings-licenses-text {
  flex: 1 1 auto;
  min-height: 0;
  margin: 0;
  padding: var(--space-3);
  overflow: auto;
  border-radius: var(--radius-md);
  background: var(--color-bg-secondary);
  color: var(--color-text-secondary);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.wc-client-settings-licenses-text[hidden] {
  display: none;
}
