/* Self-hosted fonts (vendored from Google Fonts) */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/jetbrains-mono-latin-68dbce1c.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/jetbrains-mono-latin-ext-b0c48b7a.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/outfit-latin-9bbb8353.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/assets/outfit-latin-ext-6bff6d2d.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Terminal theme fonts */
@layer base {
  body {
    font-family: 'JetBrains Mono', 'Courier New', monospace;
  }

  h1, h2, h3, h4, h5, h6 {
    font-family: 'Outfit', system-ui, -apple-system, sans-serif;
  }

  /* Ensure form inputs with w-full class respect full width by overriding size attribute */
  input.w-full[type="email"],
  input.w-full[type="text"],
  textarea.w-full {
    min-width: 0;
    width: 100% !important;
  }
}

/* Terminal animations */
@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}

@keyframes scan {
  0% {
    background-position: 0 -100vh;
  }
  35%, 100% {
    background-position: 0 100vh;
  }
}

@keyframes flicker {
  0% { opacity: 0.27861; }
  5% { opacity: 0.34769; }
  10% { opacity: 0.23604; }
  15% { opacity: 0.90626; }
  20% { opacity: 0.18128; }
  25% { opacity: 0.83891; }
  30% { opacity: 0.65583; }
  35% { opacity: 0.67807; }
  40% { opacity: 0.26559; }
  45% { opacity: 0.84693; }
  50% { opacity: 0.96019; }
  55% { opacity: 0.08594; }
  60% { opacity: 0.20313; }
  65% { opacity: 0.71988; }
  70% { opacity: 0.53455; }
  75% { opacity: 0.37288; }
  80% { opacity: 0.71428; }
  85% { opacity: 0.70419; }
  90% { opacity: 0.7003; }
  95% { opacity: 0.36108; }
  100% { opacity: 0.24387; }
}

/* Signal pulses travelling along the convergence wires on the homepage
   tool-replacement diagram (home/_internal_app). The offset steps by exactly
   one dash period (10 + 158), so the loop is seamless. Pulse paths carry
   motion-reduce:hidden — the global reduced-motion rule below would otherwise
   turn an infinite offset animation into per-frame shimmer. */
@keyframes wire-flow {
  to { stroke-dashoffset: -168; }
}

/* Onboarding: the journey's current step card. A slow breathing emerald glow
   marking "this is the one to do" — pairs with the full emerald border set in
   onboarding/_step.html.erb. Declared as an animation because animated
   box-shadow beats the static Tailwind shadow utilities by cascade origin, so
   the keyframes must carry the card's resting shadow (shadow-sm) themselves.
   Gated behind no-preference (with a static fallback below) rather than
   relying on the global reduced-motion rule — a 0.01ms infinite loop over
   these keyframes would strobe instead of stopping. */
.onboarding-current-card {
  --onboarding-glow: 16 185 129; /* emerald-500 */
  box-shadow: 0 0 0 4px rgb(var(--onboarding-glow) / 0.10), 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
.dark .onboarding-current-card {
  --onboarding-glow: 52 211 153; /* emerald-400 */
}
@media (prefers-reduced-motion: no-preference) {
  .onboarding-current-card {
    animation: onboarding-current-breathe 3.2s ease-in-out infinite;
  }
}
@keyframes onboarding-current-breathe {
  0%, 100% {
    box-shadow: 0 0 0 4px rgb(var(--onboarding-glow) / 0.10),
                0 0 0 0 rgb(var(--onboarding-glow) / 0),
                0 1px 2px 0 rgb(0 0 0 / 0.05);
  }
  50% {
    box-shadow: 0 0 0 4px rgb(var(--onboarding-glow) / 0.16),
                0 0 18px 4px rgb(var(--onboarding-glow) / 0.22),
                0 1px 2px 0 rgb(0 0 0 / 0.05);
  }
}

/* Onboarding: the sidebar "Get started" cue. The sidebar's one primary
   action, so it wears ui_button's primary tokens (--brand/--brand-on,
   org-brandable and theme-aware) — plus the marketing site's moving-light
   vocabulary shrunk to nav scale: a soft breathing outer glow, and a sheen
   glint that sweeps across the fill once per cycle (same 3.2s rhythm as
   .onboarding-current-card, so the two cues breathe together). The doubled
   selector out-specifies the Tailwind text/hover utilities sidebar_link
   applies, keeping the helper untouched. The view only applies the class
   while the link is NOT active; on the onboarding page itself it rests.
   Reduced motion: a static primary-coloured button, no glow, no sheen. */
.onboarding-nav-cue.onboarding-nav-cue {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: var(--brand);
  color: var(--brand-on);
}
.onboarding-nav-cue.onboarding-nav-cue:hover {
  background: var(--brand-hover);
  color: var(--brand-on);
}
@media (prefers-reduced-motion: no-preference) {
  .onboarding-nav-cue.onboarding-nav-cue {
    animation: onboarding-nav-breathe 3.2s ease-in-out infinite;
  }
  .onboarding-nav-cue.onboarding-nav-cue::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: -60%;
    width: 45%;
    background: linear-gradient(105deg, transparent, rgb(255 255 255 / 0.35), transparent);
    transform: skewX(-18deg);
    animation: onboarding-nav-sheen 3.2s ease-in-out infinite;
    pointer-events: none;
  }
}
@keyframes onboarding-nav-breathe {
  0%, 100% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 0%, transparent);
  }
  50% {
    box-shadow: 0 0 14px 2px color-mix(in srgb, var(--brand) 40%, transparent);
  }
}
@keyframes onboarding-nav-sheen {
  0% { left: -60%; }
  38% { left: 140%; }
  100% { left: 140%; }
}

/* Smooth scroll behavior */
html {
  scroll-behavior: smooth;
}

/* Respect users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* Sign out link hover effect */
.sign-out-link:hover {
  color: rgb(252 165 165); /* red-300 */
  transition: color 0.2s;
}

/* Custom select styling with properly positioned chevron */
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
  background-position: right 0.75rem center;
  background-repeat: no-repeat;
  background-size: 1.25em 1.25em;
  padding-right: 2.5rem;
}

/* Custom checkbox styling for consistent emerald accent.
   Scoped with :not(.sr-only) so it never touches the visually-hidden
   checkboxes that back our toggle switches (shared/_toggle). An element
   selector like `input[type="checkbox"]` (specificity 0,1,1) otherwise
   beats the `.sr-only` utility (0,1,0) and re-applies position/width/
   height to the hidden checkbox, dropping it back into flow as a 16px
   phantom box — the "weird left indent" on toggle rows. */
input[type="checkbox"]:not(.sr-only) {
  appearance: none;
  -webkit-appearance: none;
  width: 1rem;
  height: 1rem;
  border-radius: 0.25rem;
  border: 2px solid rgb(156 163 175); /* gray-400 */
  background-color: transparent;
  cursor: pointer;
  position: relative;
}

@media (prefers-color-scheme: dark) {
  input[type="checkbox"]:not(.sr-only) {
    border-color: rgb(107 114 128); /* gray-500 */
  }
}

input[type="checkbox"]:not(.sr-only):checked {
  background-color: rgb(16 185 129); /* emerald-500 */
  border-color: rgb(16 185 129); /* emerald-500 */
  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
}

input[type="checkbox"]:not(.sr-only):focus {
  outline: none;
  box-shadow: 0 0 0 2px rgb(52 211 153 / 0.4); /* emerald-400/40 */
}

input[type="checkbox"].accent-red-500:focus {
  box-shadow: 0 0 0 2px rgb(248 113 113 / 0.4); /* red-400/40 */
}

input[type="checkbox"].accent-orange-500:focus {
  box-shadow: 0 0 0 2px rgb(251 146 60 / 0.4); /* orange-400/40 */
}

input[type="checkbox"].accent-amber-500:focus {
  box-shadow: 0 0 0 2px rgb(251 191 36 / 0.4); /* amber-400/40 */
}

input[type="checkbox"].accent-blue-500:focus {
  box-shadow: 0 0 0 2px rgb(96 165 250 / 0.4); /* blue-400/40 */
}

/* Custom radio button styling for consistent accent colors.
   Scoped with :not(.sr-only) for the same reason as the checkbox rules
   above: card-style radio groups (the plan picker) hide the input with
   .sr-only, and this element selector would otherwise win, dropping the
   radio back into flow as an invisible 16px phantom box at the top of
   every card. */
input[type="radio"]:not(.sr-only) {
  appearance: none;
  -webkit-appearance: none;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  border: 2px solid rgb(156 163 175); /* gray-400 */
  background-color: transparent;
  cursor: pointer;
  position: relative;
}

@media (prefers-color-scheme: dark) {
  input[type="radio"]:not(.sr-only) {
    border-color: rgb(107 114 128); /* gray-500 */
  }
}

input[type="radio"]:not(.sr-only):checked {
  border-width: 4px;
  border-color: rgb(16 185 129); /* emerald-500 default */
}

/* Severity accent colors */
input[type="radio"].accent-red-500:checked {
  border-color: rgb(239 68 68); /* red-500 */
}

input[type="radio"].accent-orange-500:checked {
  border-color: rgb(249 115 22); /* orange-500 */
}

input[type="radio"].accent-amber-500:checked {
  border-color: rgb(245 158 11); /* amber-500 */
}

input[type="radio"].accent-blue-500:checked {
  border-color: rgb(59 130 246); /* blue-500 */
}

/* Status accent colors */
input[type="radio"].accent-purple-500:checked {
  border-color: rgb(168 85 247); /* purple-500 */
}

input[type="radio"].accent-pink-500:checked {
  border-color: rgb(236 72 153); /* pink-500 */
}

input[type="radio"].accent-emerald-500:checked {
  border-color: rgb(16 185 129); /* emerald-500 */
}

/* Checkbox accent color overrides */
input[type="checkbox"].accent-red-500:checked {
  background-color: rgb(239 68 68); /* red-500 */
  border-color: rgb(239 68 68);
}

input[type="checkbox"].accent-orange-500:checked {
  background-color: rgb(249 115 22); /* orange-500 */
  border-color: rgb(249 115 22);
}

input[type="checkbox"].accent-amber-500:checked {
  background-color: rgb(245 158 11); /* amber-500 */
  border-color: rgb(245 158 11);
}

input[type="checkbox"].accent-blue-500:checked {
  background-color: rgb(59 130 246); /* blue-500 */
  border-color: rgb(59 130 246);
}

/* Turbo Frame loading state — avoid opacity transitions which cause
   macOS compositing bugs (content in DOM but not visually rendered) */
turbo-frame[aria-busy="true"] {
  cursor: wait;
}

/* Lexxy rich text editor — dark mode overrides */
.dark {
  --lexxy-color-ink: oklch(95% 0 0);
  --lexxy-color-ink-medium: oklch(70% 0 0);
  --lexxy-color-ink-light: oklch(50% 0 0);
  --lexxy-color-ink-lighter: var(--color-gray-700);
  --lexxy-color-ink-lightest: oklch(20% 0 0);
  --lexxy-color-ink-inverted: oklch(15% 0 0);

  --lexxy-color-accent-dark: oklch(75% 0.15 260);
  --lexxy-color-accent-medium: oklch(60% 0.15 258);
  --lexxy-color-accent-light: oklch(30% 0.05 254);
  --lexxy-color-accent-lightest: oklch(25% 0.03 254);

  --lexxy-color-code-token-att: #f97583;
  --lexxy-color-code-token-comment: #6a737d;
  --lexxy-color-code-token-function: #b392f0;
  --lexxy-color-code-token-operator: #f97583;
  --lexxy-color-code-token-property: #79b8ff;
  --lexxy-color-code-token-punctuation: #e1e4e8;
  --lexxy-color-code-token-selector: #85e89d;
  --lexxy-color-code-token-variable: #ffab70;

}

/* Generic composer pattern: the send action overlays bottom-right INSIDE the
   editor frame (see /admin/design/conversation → Composer). The contenteditable
   reserves bottom padding so trailing text never renders under the button. */
.composer-editor.lexxy-content .lexxy-editor__content,
lexxy-editor.composer-editor .lexxy-editor__content {
  padding-bottom: 3rem;
}

/* Rich text highlight colors — matched to app palette.
   Overrides Lexxy defaults. Uses html selector to beat :root specificity. */

/* Light mode */
html {
  /* Text colors: muted, professional tones */
  --highlight-1: #92400e; /* amber-800 */
  --highlight-2: #c2410c; /* orange-700 */
  --highlight-3: #be123c; /* rose-700 */
  --highlight-4: #a21caf; /* fuchsia-700 */
  --highlight-5: #6d28d9; /* violet-700 */
  --highlight-6: #1d4ed8; /* blue-700 */
  --highlight-7: #047857; /* emerald-700 */
  --highlight-8: #0e7490; /* cyan-700 */
  --highlight-9: #4b5563; /* gray-600 */

  /* Background highlights: soft washes */
  --highlight-bg-1: rgba(245, 208, 97, 0.25);
  --highlight-bg-2: rgba(251, 146, 60, 0.2);
  --highlight-bg-3: rgba(251, 113, 133, 0.2);
  --highlight-bg-4: rgba(232, 121, 249, 0.18);
  --highlight-bg-5: rgba(167, 139, 250, 0.2);
  --highlight-bg-6: rgba(96, 165, 250, 0.2);
  --highlight-bg-7: rgba(52, 211, 153, 0.2);
  --highlight-bg-8: rgba(34, 211, 238, 0.18);
  --highlight-bg-9: rgba(156, 163, 175, 0.2);
}

/* Dark mode */
html.dark {
  /* Text colors: lighter pastels for readability */
  --highlight-1: #fbbf24; /* amber-400 */
  --highlight-2: #fb923c; /* orange-400 */
  --highlight-3: #fb7185; /* rose-400 */
  --highlight-4: #e879f9; /* fuchsia-400 */
  --highlight-5: #a78bfa; /* violet-400 */
  --highlight-6: #60a5fa; /* blue-400 */
  --highlight-7: #34d399; /* emerald-400 */
  --highlight-8: #22d3ee; /* cyan-400 */
  --highlight-9: #9ca3af; /* gray-400 */

  /* Background highlights: subtle tints */
  --highlight-bg-1: rgba(251, 191, 36, 0.12);
  --highlight-bg-2: rgba(251, 146, 60, 0.12);
  --highlight-bg-3: rgba(251, 113, 133, 0.12);
  --highlight-bg-4: rgba(232, 121, 249, 0.1);
  --highlight-bg-5: rgba(167, 139, 250, 0.12);
  --highlight-bg-6: rgba(96, 165, 250, 0.12);
  --highlight-bg-7: rgba(52, 211, 153, 0.12);
  --highlight-bg-8: rgba(34, 211, 238, 0.1);
  --highlight-bg-9: rgba(156, 163, 175, 0.1);
}

/* Lexxy rendered content — dark mode fixes */
.dark .lexxy-content {
  color: var(--color-gray-200);
}

.dark .lexxy-content a {
  color: var(--color-blue-400);
}

/* AI-surface body text — a hair of indigo so a Una draft reads as an AI
   surface. Needs its own rule (not just a Tailwind text-* class) because the
   generic ".dark .lexxy-content" colour above would otherwise win in dark mode.
   The doubled class + later source order keep this on top in both themes. */
.lexxy-content.lexxy-content--ai {
  color: var(--color-indigo-900);
}

.dark .lexxy-content.lexxy-content--ai {
  color: var(--color-indigo-100);
}

/* New text landing in the Next card while someone reads it (Una's redraft,
   a teammate's save): a brief indigo wash over the text that fades to
   nothing, so the reader sees it changed in place. draft_card_controller
   adds .una-landed for 1.4s. Motion only when the reader
   allows it; with reduced motion nothing animates (the ask row's summary still
   says what happened). */
@media (prefers-reduced-motion: no-preference) {
  .una-landed {
    animation: una-landed 1.4s ease-out;
    border-radius: 0.375rem;
  }
}
@keyframes una-landed {
  from { background-color: rgb(224 231 255 / 0.9); } /* indigo-100 */
  40% { background-color: rgb(224 231 255 / 0.9); }
  to { background-color: transparent; }
}
.dark .una-landed {
  animation-name: una-landed-dark;
}
@keyframes una-landed-dark {
  from { background-color: rgb(99 102 241 / 0.22); } /* indigo-500 */
  40% { background-color: rgb(99 102 241 / 0.22); }
  to { background-color: transparent; }
}

/* "View draft v2" under Una's line: the Next card it leads to rings once
   (with the una-landed wash on its text), so the eye lands on the version. */
@media (prefers-reduced-motion: no-preference) {
  .draft-card-flash {
    animation: draft-card-flash 1.6s ease-out;
  }
}
@keyframes draft-card-flash {
  from { box-shadow: 0 0 0 4px rgb(99 102 241 / 0.4); } /* indigo-500 */
  to { box-shadow: 0 0 0 4px rgb(99 102 241 / 0); }
}

/* "Show changes" (paragraph_diff.js): whole paragraphs, tinted. No accent
   rails — a tint and, for removals, a strikethrough. */
.una-diff p { margin: 0 0 0.625rem; }
.una-diff p:last-child { margin-bottom: 0; }
.una-diff ins, .una-diff del { text-decoration: none; }
.una-diff-add {
  background-color: rgb(238 242 255); /* indigo-50 */
  color: rgb(55 48 163); /* indigo-800 */
  border-radius: 0.25rem;
  padding: 0.125rem 0.375rem;
}
.una-diff-del {
  background-color: rgb(254 242 242); /* red-50 */
  color: rgb(185 28 28); /* red-700 */
  border-radius: 0.25rem;
  padding: 0.125rem 0.375rem;
}
.una-diff .una-diff-del del { text-decoration: line-through; }
.dark .una-diff-add {
  background-color: rgb(99 102 241 / 0.15);
  color: rgb(199 210 254); /* indigo-200 */
}
.dark .una-diff-del {
  background-color: rgb(239 68 68 / 0.12);
  color: rgb(252 165 165); /* red-300 */
}

/* Non-image attachment card — opt out of Lexxy's default link styling so the
   custom layout (filename + size + download button) isn't painted blue/underlined. */
.lexxy-content .attachment--file a,
.dark .lexxy-content .attachment--file a {
  color: inherit;
  text-decoration: none;
}

.dark .lexxy-content blockquote {
  border-color: var(--color-gray-600);
  color: var(--color-gray-400);
}

/* Code blocks (rendered view + editor). The gem's default is a near-white
   oklch(96%) surface that vanishes on a white card; give code a clear fill plus a
   hairline border so the block reads on every container — white document, gray
   inline editor, dark card — in both themes. Prism token colours layer on top
   client-side via the syntax-highlight controller. */
.lexxy-content pre,
.lexxy-content :not(pre) > code {
  background-color: var(--color-gray-100);
  border: 1px solid var(--color-gray-200);
}

.lexxy-content pre code {
  background-color: transparent;
  border: 0;
  padding: 0;
}

.dark .lexxy-content pre,
.dark .lexxy-content :not(pre) > code {
  /* gray-950, not gray-900: dark cards are gray-900, so code needs the darker
     inset fill to read as a distinct surface. */
  background-color: var(--color-gray-950);
  border-color: var(--color-gray-700);
  color: var(--color-gray-200);
}

.dark .lexxy-content pre code {
  background-color: transparent;
}

.dark .lexxy-content table {
  border-color: var(--color-gray-700);
}

.dark .lexxy-content th {
  background: var(--color-gray-800);
  border-color: var(--color-gray-700);
  color: var(--color-gray-300);
}

.dark .lexxy-content td {
  border-color: var(--color-gray-700);
  color: var(--color-gray-300);
  background: transparent;
}

.dark .lexxy-content hr {
  border-color: var(--color-gray-700);
}

/* Knowledge Base articles: when a table-of-contents link jumps to a heading,
   leave room above so the heading clears any sticky header. */
.lexxy-content :is(h1, h2, h3, h4, h5, h6) {
  scroll-margin-top: 5rem;
}

/* KB article editor (Lexxy): the contenteditable grows with the article (no
   max-height, so no internal scroll — the page scrolls instead) and floors at a
   tall min-height so a short draft leaves no dead space below the toolbar. */
.kb-lexxy-editor lexxy-editor .lexxy-editor__content {
  min-height: 65vh;
}

/* Changelog entry editor (Lexxy): floor the writing surface so it reads as the
   main event next to the settings sidebar — without the full-page height the KB
   editor uses, since the changelog form is shorter-form. */
.changelog-lexxy-editor lexxy-editor .lexxy-editor__content {
  min-height: 24rem;
}

/* Roadmap board: the Declined rail is a drop target. roadmap_board_controller
   adds `roadmap-decline-active` while any card is dragging (armed: a pink wash +
   a bin icon in place of the "Declined · N" label) and `roadmap-decline-over`
   while a card is held directly over it (solid red — release to decline). Scoped
   by the target attribute so the state colours beat the resting Tailwind classes. */
[data-roadmap-board-target="declineZone"].roadmap-decline-active {
  border-style: solid;
  border-color: rgb(252 165 165);
  background-color: rgb(254 242 242);
}
.dark [data-roadmap-board-target="declineZone"].roadmap-decline-active {
  border-color: rgb(248 113 113 / 0.4);
  background-color: rgb(127 29 29 / 0.2);
}
[data-roadmap-board-target="declineZone"].roadmap-decline-over {
  border-style: solid;
  border-color: rgb(220 38 38);
  background-color: rgb(239 68 68);
  box-shadow: 0 0 0 3px rgb(239 68 68 / 0.25);
}
.dark [data-roadmap-board-target="declineZone"].roadmap-decline-over {
  border-color: rgb(248 113 113);
  background-color: rgb(220 38 38);
}
/* Swap "Declined · N" for the bin prompt while dragging; whiten it when over. */
.roadmap-decline-icon { display: none; }
.roadmap-decline-active .roadmap-decline-default { display: none; }
.roadmap-decline-active .roadmap-decline-icon { display: flex; }
.roadmap-decline-over .roadmap-decline-icon { color: #fff; }

/* The table-of-contents link for the section you're reading. Driven by
   aria-current (set by the toc controller) so it themes off --brand-* — the
   brand accent on portals, emerald on internal — and isn't subject to
   utility-class source-order against the link's resting colours. */
[data-controller~="toc"] a[aria-current="true"] {
  color: var(--brand-fg);
  background-color: var(--brand-tint);
  border-inline-start-color: var(--brand);
  font-weight: 500;
}

/* Links inside Knowledge Base article bodies follow the brand accent (themed
   on portals, emerald internally) rather than the default rich-text blue. The
   .lexxy-content.kb-article-body pairing keeps this scoped to KB articles, away
   from ticket/changelog rich text. */
.lexxy-content.kb-article-body a {
  color: var(--brand-fg);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lexxy-content.kb-article-body a:hover {
  color: var(--brand-fg-hover);
}

/* Reading tables — KB articles, developer guides and blog posts. Lexxy's
   defaults are the editor's grid (a border on every cell, no header row,
   transparent cells); rendered pages get the product's table treatment
   instead: a rounded hairline card, a labelled header row, horizontal rules
   only, one type size down, and horizontal scroll when the columns can't fit.
   Kb::TableWrap puts every table inside Lexxy's own wrapper on every read
   path, so Markdown-rendered and editor-authored tables share this markup.
   Column alignment from Markdown (`|---:|`) survives as the cell's `align`
   attribute, which browsers honour natively. */
.lexxy-content.kb-article-body .lexxy-content__table-wrapper {
  margin: 0 0 var(--lexxy-content-margin);
  overflow-x: auto;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.75rem;
  background-color: var(--color-white);
}

.lexxy-content.kb-article-body table {
  border-collapse: collapse;
  inline-size: 100%;
  margin: 0;
  font-size: 0.875em;
  line-height: 1.5;
}

.lexxy-content.kb-article-body th,
.lexxy-content.kb-article-body td {
  border: 0;
  border-block-end: 1px solid var(--color-gray-200);
  min-width: 0;
  max-width: none;
  padding: 0.75rem 1rem;
  vertical-align: top;
}

.lexxy-content.kb-article-body thead th,
.lexxy-content.kb-article-body .lexxy-content__table-cell--header {
  background-color: var(--color-gray-50);
  color: var(--color-gray-500);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.lexxy-content.kb-article-body tbody tr:last-child > :is(td, th) {
  border-block-end: 0;
}

/* Lexxy's own `text-align: start` on cells would outrank the presentational
   `align` attribute, so honour it explicitly. */
.lexxy-content.kb-article-body :is(th, td)[align="right"] {
  text-align: end;
}

.lexxy-content.kb-article-body :is(th, td)[align="center"] {
  text-align: center;
}

.dark .lexxy-content.kb-article-body .lexxy-content__table-wrapper {
  border-color: var(--color-gray-800);
  background-color: var(--color-gray-900);
}

.dark .lexxy-content.kb-article-body th,
.dark .lexxy-content.kb-article-body td {
  border-color: var(--color-gray-800);
  color: var(--color-gray-300);
  background-color: transparent;
}

.dark .lexxy-content.kb-article-body thead th,
.dark .lexxy-content.kb-article-body .lexxy-content__table-cell--header {
  background-color: var(--color-gray-800);
  color: var(--color-gray-400);
}

/* Blog reading layout (blog/show). The article is one centred measure of
   50rem, about 74 characters of the monospace face. From xl the surrounding
   grid opens a 13rem gutter either side and the contents pin in the
   right-hand one. From 2xl the shell widens to 96rem, the gutters grow to
   21rem, and wide blocks — tables, code, figures — may spill past the text by
   --blog-breakout a side; the contents pad by the same amount plus a gap, so
   the two never meet. At xl the gutter is only wide enough for the contents,
   so nothing breaks out there; below xl there is no gutter at all and the
   contents sit inline above the body. */
.blog-reading {
  --blog-breakout: 0px;
}

@media (min-width: 80rem) {
  .blog-toc {
    padding-inline-start: calc(var(--blog-breakout) + 1.5rem);
    max-height: calc(100vh - 7rem);
    overflow-y: auto;
  }
}

@media (min-width: 96rem) {
  .blog-reading {
    --blog-breakout: 4rem;
  }
}

.lexxy-content.blog-body :is(.lexxy-content__table-wrapper, pre, figure, .attachment-gallery),
.lexxy-content.blog-body p:has(> img:only-child, > .attachment) {
  margin-inline: calc(-1 * var(--blog-breakout));
}

/* Blog typography: more air above section headings than the editor's uniform
   1rem, a touch more size on h2, and quotations as a pull-quote rather than
   an italic slab. */
.lexxy-content.blog-body :is(h2, h3, h4) {
  margin-block-start: 2em;
  letter-spacing: -0.01em;
}

.lexxy-content.blog-body > :is(h2, h3, h4):first-child {
  margin-block-start: 0;
}

.lexxy-content.blog-body h2 {
  font-size: 1.75rem;
  line-height: 1.2;
}

.lexxy-content.blog-body h3 {
  font-size: 1.25rem;
  line-height: 1.3;
}

.lexxy-content.blog-body blockquote {
  margin: 0 0 var(--lexxy-content-margin);
  padding: 0.25rem 0 0.25rem 1.5rem;
  border-inline-start: 3px solid var(--color-emerald-400);
  color: var(--color-gray-600);
  font-style: normal;
}

.dark .lexxy-content.blog-body blockquote {
  border-color: var(--color-emerald-500);
  color: var(--color-gray-400);
}

/* Lexxy editor — match app input styling */
lexxy-editor {
  --lexxy-color-canvas: var(--color-gray-100);
  background: var(--lexxy-color-canvas);
  border: 1px solid var(--color-gray-300);
  border-radius: 0.375rem;
  display: block;
  width: 100%;
  min-width: 0;
  /* Matches Lexxy's natural rendered height (toolbar + 8lh content + padding)
     so reserving space here avoids the layout jump when JS hydrates. */
  min-block-size: calc(8lh + 3.5rem);
  --lexxy-toolbar-button-size: 1.75lh;
}

/* Skeleton — before Lexxy hydrates, draw a faux toolbar bar matching the real
   toolbar's height + bottom border so the box doesn't read as empty/broken
   during the brief hydration window. The real toolbar renders within the same
   envelope, so there's no shift when it appears. */
lexxy-editor:not([connected])::before {
  content: "";
  display: block;
  block-size: calc(1.75lh + 4px);
  border-block-end: 1px solid var(--color-gray-300);
}

.dark lexxy-editor:not([connected])::before {
  border-block-end-color: var(--color-gray-700);
}

/* KB article editor is a full-page document surface, not an inline input. The
   generic gray-100 input canvas blends into the gray-100 page body (inputs only
   read because they sit on white cards). Give it a white "page" canvas with the
   card border tokens so it stands clear of the gray app chrome. */
.kb-lexxy-editor lexxy-editor {
  --lexxy-color-canvas: var(--color-white);
  border-color: var(--color-gray-200);
}

.dark .kb-lexxy-editor lexxy-editor {
  --lexxy-color-canvas: var(--color-gray-900);
  border-color: var(--color-gray-800);
}

/* Note composer — unified editor + submit button */
.note-composer {
  border: 1px solid var(--color-gray-300);
  border-radius: 0.375rem;
  background: var(--color-gray-100);
}

.note-composer lexxy-editor {
  border: none;
  border-radius: 0;
  border-start-start-radius: calc(0.375rem - 1px);
  border-start-end-radius: calc(0.375rem - 1px);
  --lexxy-color-canvas: white;
  --lexxy-color-ink-lighter: var(--color-gray-300); /* matches note-composer border */
}

.dark .note-composer lexxy-editor {
  --lexxy-color-canvas: var(--color-gray-900);
  --lexxy-color-ink-lighter: var(--color-gray-700); /* matches note-composer border */
}

.note-composer lexxy-editor:focus-within {
  border-color: transparent;
  box-shadow: none;
}

/* Compact variant — shorter editor for inline compose */
.note-composer--compact lexxy-editor {
  min-block-size: 0;
}

.note-composer--compact lexxy-editor:not([connected])::before {
  display: none;
}

.note-composer--compact lexxy-editor [contenteditable] {
  min-height: 2.5rem;
  padding-block-end: 0;
}

.note-composer-footer {
  background: white;
  display: flex;
  justify-content: flex-end;
  padding: 0.5rem 0.75rem;
  border-end-start-radius: calc(0.375rem - 1px);
  border-end-end-radius: calc(0.375rem - 1px);
  position: relative;
  margin-top: -1px;
}

:where(.dark, .dark *) .note-composer-footer {
  background: var(--color-gray-900);
}

.note-composer:focus-within {
  border-color: var(--color-emerald-400);
  box-shadow: 0 0 0 1px var(--color-emerald-400);
}

.dark .note-composer {
  border-color: var(--color-gray-700);
  background: var(--color-gray-800);
}

.dark .note-composer:focus-within {
  border-color: var(--color-emerald-400);
  box-shadow: 0 0 0 1px var(--color-emerald-400);
}

/* Search-result highlight. Defined unlayered so it wins over Lexxy's
   `mark { background-color: transparent }` reset, which is also unlayered
   and would otherwise beat Tailwind's @layer utilities. The translucent
   emerald reads naturally on both light and dark backgrounds. */
mark.search-highlight {
  background-color: color-mix(in srgb, var(--color-emerald-500) 30%, transparent);
  color: inherit;
  border-radius: 0.125rem;
  padding-inline: 0.125rem;
}

/* Ticket-list loading state — driven by search-loading-controller.
   Quick fade so the list visibly recedes the moment a filter changes,
   while the inline spinner in the search box confirms work is happening. */
[data-controller~="search-loading"] .search-loading-area {
  transition: opacity 120ms ease-out;
}

[data-search-loading-active="true"] .search-loading-area {
  opacity: 0.45;
  pointer-events: none;
}

.search-loading-spinner {
  display: none;
}

[data-search-loading-active="true"] .search-loading-spinner {
  display: inline-flex;
}

[data-search-loading-active="true"] .search-loading-hide {
  display: none;
}

/* Suppress the browser's native search-field clear control — we render our
   own to keep light/dark and spinner-swap behaviour consistent. */
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

/* iOS Safari auto-zooms on focus into any form control whose effective
   font-size is below 16px and never zooms back out. The `-webkit-touch-callout`
   feature query targets iOS specifically (it's a no-op on Android/Chrome on
   desktop). max() keeps any input larger than 16px at its designed size. */
@supports (-webkit-touch-callout: none) {
  input[type="text"],
  input[type="search"],
  input[type="email"],
  input[type="password"],
  input[type="tel"],
  input[type="url"],
  input[type="number"],
  input[type="date"],
  input[type="datetime-local"],
  input[type="month"],
  input[type="time"],
  input[type="week"],
  textarea,
  select {
    font-size: max(16px, 1em);
  }
}

/* Note bubble — visually frames user-authored rich text */
.note-bubble {
  background: var(--color-gray-100);
  border-radius: 1rem;
  padding: 0.625rem 0.75rem;
}

/* Chat-context corner variants: shrink to content width so short messages
   don't take the full thread width. The base .note-bubble keeps block width
   for non-chat uses (.note-bubble--on-page descriptions, etc). */
.note-bubble--agent,
.note-bubble--external {
  width: fit-content;
  max-width: 80%;
}

.note-bubble--agent {
  border-bottom-right-radius: 0;
  margin-left: auto;
}

.note-bubble--external {
  border-bottom-left-radius: 0;
}

/* Trim Lexxy's outer block margins inside a bubble, and — via
   .rich-text-flush — anywhere rich text renders without a bubble (flush
   list rows like customer notes) so surrounding metadata sits tight. */
.note-bubble .lexxy-content > :first-child,
.rich-text-flush .lexxy-content > :first-child {
  margin-top: 0;
}

.note-bubble .lexxy-content > :last-child,
.rich-text-flush .lexxy-content > :last-child {
  margin-bottom: 0;
}

/* Code blocks and inline code inside chat bubbles — let a hint of the
   bubble tint show through so they don't read as a disconnected panel. */
.note-bubble--agent .lexxy-content pre,
.note-bubble--agent .lexxy-content code,
.note-bubble--external .lexxy-content pre,
.note-bubble--external .lexxy-content code {
  background: color-mix(in srgb, var(--color-gray-200) 75%, transparent);
}

.dark .note-bubble--agent .lexxy-content pre,
.dark .note-bubble--agent .lexxy-content code,
.dark .note-bubble--external .lexxy-content pre,
.dark .note-bubble--external .lexxy-content code {
  background: color-mix(in srgb, var(--color-gray-900) 65%, transparent);
}

.dark .note-bubble {
  background: var(--color-gray-800);
}

/* Variant for bubbles on the page background (not inside a card) */
.note-bubble--on-page {
  background: white;
  border: 1px solid var(--color-gray-200);
}

.dark .note-bubble--on-page {
  background: var(--color-gray-900);
  border-color: var(--color-gray-700);
}

/* Variant for customer messages (blue tint) */
.note-bubble--customer {
  background: var(--color-blue-100);
}

.dark .note-bubble--customer {
  background: color-mix(in srgb, var(--color-blue-900) 40%, transparent);
}

/* Variant for team notes (internal replies): amber tint with a uniform 1px
   amber border — never a rail. The meta row says "Team" with a lock. */
.note-bubble--internal {
  background: var(--color-amber-50);
  border: 1px solid var(--color-amber-300);
}

.dark .note-bubble--internal {
  background: color-mix(in srgb, var(--color-amber-500) 10%, transparent);
  border-color: color-mix(in srgb, var(--color-amber-500) 35%, transparent);
}

/* A teammate's reply in the ticket timeline (_reply / _reply_message, and
   the asker's side of _una_exchange) sits on the left, or on the right for
   its own author: [data-own] on the .reply-row, set server-side on a
   request render and by own_reply_controller on a broadcast one (which has
   no Current). Mirrors .note-bubble--agent. The meta reads "(Team ·) <name>
   · <time>", and "You" takes the name's place on your own: never both. */
.reply-row__you {
  display: none;
}
.reply-row[data-own] .reply-row__you {
  display: inline-flex;
}
.reply-row[data-own] .reply-row__name {
  display: none;
}
.reply-row[data-own] .reply-row__bubble {
  margin-left: auto;
  border-bottom-left-radius: 1rem;
  border-bottom-right-radius: 0;
}
.reply-row[data-own] .reply-row__meta {
  justify-content: flex-end;
}

/* "Hide team notes" on the ticket timeline (team_notes_controller sets
   data-team-notes on the ticket column; every timeline row carries
   data-timeline-audience). Hidden: the team rows (team notes, Una exchanges
   and folds) give way to one "N team notes" row per run, injected by the
   controller; a run it opens carries data-team-run-open. A broadcast row
   lands hidden here before the controller regroups it. */
[data-team-notes="hidden"] #ticket_timeline > [data-timeline-audience="team"]:not([data-team-run-open]) {
  display: none;
}

/* The Next card (_draft_card, draft_card_controller). Its state is
   data-draft-card-state (rest | edit) on the card; the view inside it
   (#ticket_draft_view, broadcast-replaced) says what the draft is with
   data-draft-state (empty | draft | sending). Everything shown or hidden by
   state is here, so a broadcast replacing the view never fights it.
   A dashed neutral outline while it's a draft; solid while editing (and the
   ring while focused); solid emerald while it's sending. */
.draft-card {
  border-color: var(--color-gray-300);
}
.dark .draft-card {
  border-color: var(--color-gray-700);
}
.draft-card[data-draft-card-state="edit"] {
  border-style: solid;
  border-color: var(--color-gray-400);
}
.dark .draft-card[data-draft-card-state="edit"] {
  border-color: var(--color-gray-500);
}
.draft-card[data-draft-card-state="edit"]:focus-within {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-gray-900) 8%, transparent);
}
.dark .draft-card[data-draft-card-state="edit"]:focus-within {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-white) 10%, transparent);
}
.draft-card:has(#ticket_draft_view[data-draft-state="sending"]) {
  border-style: solid;
  border-color: color-mix(in srgb, var(--color-emerald-500) 50%, transparent);
}
.draft-card[data-draft-card-state="rest"] [data-draft-card-target~="editor"],
.draft-card[data-draft-card-state="rest"] [data-draft-card-edit-only],
.draft-card[data-draft-card-state="edit"] [data-draft-card-rest-only],
.draft-card[data-draft-card-state="edit"] #ticket_draft_view :is([data-draft-body], [data-draft-meta], [data-draft-empty]),
.draft-card[data-draft-card-state="rest"] #ticket_draft_view[data-draft-state="empty"]:not([data-revisions-key]) [data-draft-head],
.draft-card[data-draft-card-state="preview"] #ticket_draft_view [data-draft-empty],
#ticket_draft_view[data-draft-state="empty"] [data-draft-body]:empty,
.draft-card[data-draft-card-state="rest"]:has(#ticket_draft_view[data-draft-state="empty"]) [data-draft-card-target~="footer"],
.draft-card:has(#ticket_draft_view[data-draft-state="sending"]) [data-draft-card-target~="footer"],
/* An overdue send still freezes the draft (edits and Discard are refused
   until Undo, or a send that replaces it): no Edit / Write my own. */
.draft-card:has(#ticket_draft_view[data-send-state="overdue"]) [data-draft-card-rest-only] {
  display: none;
}
/* Previewing a version from v ▾ (data-draft-card-state="preview"): the
   footer is [Back to vN] · [Use this] alone; no sends. The preview-only
   groups are hidden in every other state, and v ▾ while editing. */
.draft-card:not([data-draft-card-state="preview"]) [data-draft-card-preview-only],
.draft-card[data-draft-card-state="preview"] :is([data-draft-card-target~="editor"], [data-draft-card-rest-only], [data-draft-card-edit-only], [data-draft-card-sends]),
.draft-card[data-draft-card-state="edit"] [data-draft-versions-trigger] {
  display: none;
}
/* ⌘J's reply row says what R does now: Reply on an empty card, Edit when
   there's a draft. The palette is broadcast-rendered (Current-free, no
   draft state), so the live view decides. */
[data-palette-label="edit"] { display: none; }
:root:has(#ticket_draft_view:not([data-draft-state="empty"])) [data-palette-label="reply"] { display: none; }
:root:has(#ticket_draft_view:not([data-draft-state="empty"])) [data-palette-label="edit"] { display: inline; }
/* A viewer who can't reply has nothing to do with an empty card (unless
   their team box is open in the band). */
[data-grants]:not([data-grants~="support:respond"]) #ticket_next:not([data-team-box="open"]):has(#ticket_draft_view[data-draft-state="empty"]):not(:has(#ticket_draft_banner > *)) {
  display: none;
}

/* The team box takes the Next band (ticket_dock_controller sets
   data-team-box="open" on #ticket_next). The draft folds to its one-line
   summary above the box ([data-draft-summary]); Show (data-draft-peek on
   the band) expands it read-only, with Hide in its header and no footer,
   unless a version is being previewed ([Back to vN] · [Use this]). With no
   draft the band is just the box; a blank draft with versions shows only
   while [View draft vN] has it peeked. */
#ticket_draft_view [data-draft-summary],
#ticket_next:not([data-team-box="open"][data-draft-peek]) [data-draft-peek-hide] {
  display: none;
}
#ticket_next[data-team-box="open"]:not([data-draft-peek]) #ticket_draft_view [data-draft-summary] {
  display: flex;
}
#ticket_next[data-team-box="open"]:not([data-draft-peek]) #ticket_draft_view > :not([data-draft-summary]),
#ticket_next[data-team-box="open"]:not([data-draft-peek]) .draft-card > :not(#ticket_draft_view),
#ticket_next[data-team-box="open"][data-draft-peek] [data-draft-versions-trigger],
#ticket_next[data-team-box="open"][data-draft-peek] .draft-card:not([data-draft-card-state="preview"]) [data-draft-card-target~="footer"],
#ticket_next[data-team-box="open"][data-draft-peek] #ticket_draft_view [data-draft-empty],
#ticket_next[data-team-box="open"]:not([data-draft-peek]) .draft-card:has(#ticket_draft_view[data-draft-state="empty"]),
#ticket_next[data-team-box="open"] .draft-card:has(#ticket_draft_view[data-draft-state="empty"]:not([data-revisions-key])) {
  display: none;
}

/* The composer header's draft attribution ("Alex Kim started · Admin User
   edited 2m ago") only says something when someone other than the viewer
   last touched the draft: own_reply_controller marks the viewer's own edit
   [data-own], and a draft only Una has written has no author. The live
   "Updated by X just now" flash shows whatever the attribution says. */
.composer-attribution {
  display: none;
}
.composer-attribution:has([data-draft-present][data-author-user-id]:not([data-own])),
.composer-attribution:has([data-inline-composer-target~="remoteFlash"]:not(.hidden)) {
  display: flex;
}

/* The reply / team note editor inside its card: no box of its own (the
   card is the box), so it reads as a plain text area under Lexxy's
   formatting toolbar, which stays at the TOP of the editor, always shown
   (a quiet band with a hairline under it). The ⌘ shortcuts (⌘B, ⌘K…) are
   Lexical's. */
lexxy-editor.inline-reply-editor,
.dark lexxy-editor.inline-reply-editor {
  --lexxy-color-canvas: transparent;
  --lexxy-editor-padding: 0.25rem 0;
  --lexxy-toolbar-button-size: 1.625rem;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  min-block-size: 0;
}
lexxy-editor.inline-reply-editor:focus-within,
.dark lexxy-editor.inline-reply-editor:focus-within {
  border: 0;
  box-shadow: none;
}
lexxy-editor.inline-reply-editor lexxy-toolbar {
  border-block-end: 1px solid var(--color-gray-200);
  padding-inline: 0;
  margin-block-end: 0.25rem;
}
.dark lexxy-editor.inline-reply-editor lexxy-toolbar {
  border-block-end-color: var(--color-gray-800);
}
/* The transparent canvas above is for the editor's own surface only. Lexxy
   paints its floating layers — the @mention / + prompt menu (rendered as a
   child of the editor), the toolbar dropdown panels, the code-language
   picker — with the same --lexxy-color-canvas, so they'd inherit
   "transparent" and float bare over the page. Give them the app's popup
   surface back (bg-white / dark:bg-gray-900, as the popover_panel menus). */
lexxy-editor.inline-reply-editor :is(.lexxy-prompt-menu, [data-dropdown-panel], lexxy-code-language-picker) {
  --lexxy-color-canvas: var(--color-white);
}
.dark lexxy-editor.inline-reply-editor :is(.lexxy-prompt-menu, [data-dropdown-panel], lexxy-code-language-picker) {
  --lexxy-color-canvas: var(--color-gray-900);
}
lexxy-editor.inline-reply-editor:not([connected])::before {
  border-block-end-color: var(--color-gray-200);
  margin-block-end: 0.25rem;
}
.dark lexxy-editor.inline-reply-editor:not([connected])::before {
  border-block-end-color: var(--color-gray-800);
}
lexxy-editor.inline-reply-editor .lexxy-editor__content {
  font-size: 0.875rem;
  line-height: 1.5rem;
  min-block-size: 6rem;
  max-block-size: 60vh;
  overflow-y: auto;
}
/* The team box in the Next band (_team_box) starts small: a line or two,
   growing with the text. */
lexxy-editor.inline-reply-editor.team-box-editor .lexxy-editor__content {
  min-block-size: 2.75rem;
  max-block-size: 15rem;
}
/* Its send: [Post] for a team note, [Ask Una] while the @Una chip is on
   (una_chip_controller sets data-una-chip-on on the box). */
#ticket_team_box[data-una-chip-on="true"] [data-team-box-send="note"],
#ticket_team_box:not([data-una-chip-on="true"]) [data-team-box-send="una"] {
  display: none;
}

/* Variant for Una's (AI) replies — indigo tint so an AI-authored message
   reads as its own actor, distinct from a human staff reply (default gray).
   Applied only where Una is presented as Una: never when an org has chosen to
   attribute her replies to a person or a custom label, or the tint would leak
   the AI-ness that setting is meant to hide. */
.note-bubble--ai {
  background: var(--color-indigo-100);
}

.dark .note-bubble--ai {
  background: color-mix(in srgb, var(--color-indigo-900) 40%, transparent);
}

lexxy-editor:focus-within {
  border-color: var(--color-emerald-400);
  outline: none;
  box-shadow: 0 0 0 1px var(--color-emerald-400);
}

.dark lexxy-editor {
  --lexxy-color-canvas: var(--color-gray-800);
  --lexxy-color-ink-lighter: var(--color-gray-700);
  border-color: var(--color-gray-700);
}

.dark lexxy-editor:focus-within {
  border-color: var(--color-emerald-400);
  box-shadow: 0 0 0 1px var(--color-emerald-400);
}

/* Lexxy's @mention / + prompt menu: the app's popup edge (border + shadow-lg)
   so it stands clear of whatever it overlaps — Lexxy's own faint 8px shadow
   all but disappears in dark mode. Lexxy's rule is :where(), so this wins. */
.lexxy-prompt-menu {
  border: 1px solid var(--color-gray-200);
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
.dark .lexxy-prompt-menu {
  border-color: var(--color-gray-700);
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.5);
}

/* Field-level error highlighting for lexxy editor */
.field-error lexxy-editor {
  border-color: var(--color-red-500);
}

.field-error lexxy-editor:focus-within {
  border-color: var(--color-red-500);
  box-shadow: 0 0 0 1px var(--color-red-500);
}

.dark .field-error lexxy-editor {
  border-color: var(--color-red-500);
}

.dark .field-error lexxy-editor:focus-within {
  border-color: var(--color-red-500);
  box-shadow: 0 0 0 1px var(--color-red-500);
}

/* Override Lexxy's image constraints on attachments so they span full width */
.attachment--preview {
  inline-size: 100%;
  padding: 0;
}

.attachment--preview img {
  inline-size: 100%;
  max-block-size: none;
}

/* ── Collapsible sidebar ─────────────────────────────────────────────── */

/* Prevent text wrapping during width animation */
aside[data-sidebar-state] {
  white-space: nowrap;
}

/* Smooth width + margin transition for toggle click */
aside[data-sidebar-state].sidebar-animating {
  transition: width 200ms ease-in-out;
}

aside[data-sidebar-state] ~ main.sidebar-animating {
  transition: margin-left 200ms ease-in-out;
}

/* Collapsed: narrow icon-only sidebar */
aside[data-sidebar-state="collapsed"] {
  width: 4rem;
}

/* Hide text/badges/chevrons when collapsed */
aside[data-sidebar-state="collapsed"] .sidebar-label,
aside[data-sidebar-state="collapsed"] .sidebar-section-header,
aside[data-sidebar-state="collapsed"] .sidebar-badge,
aside[data-sidebar-state="collapsed"] .sidebar-chevron {
  display: none;
}

/* Collapsed nav: center icons in the narrow strip */
aside[data-sidebar-state="collapsed"] nav {
  padding-left: 0;
  padding-right: 0;
}

aside[data-sidebar-state="collapsed"] nav a,
aside[data-sidebar-state="collapsed"] nav button {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
  margin-left: auto;
  margin-right: auto;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.5rem;
}

/* Collapsed org selector: show only logo centered */
aside[data-sidebar-state="collapsed"] .sidebar-org-selector {
  padding: 0.75rem 0;
  display: flex;
  justify-content: center;
}

aside[data-sidebar-state="collapsed"] .sidebar-org-selector > div {
  margin: 0;
}

aside[data-sidebar-state="collapsed"] .sidebar-org-selector button {
  justify-content: center;
  padding: 0.25rem;
  width: auto;
}

/* In collapsed mode the landscape logo doesn't fit the narrow rail —
   hide the wide-brand slot and reveal the square-avatar fallback. */
aside[data-sidebar-state="collapsed"] .sidebar-brand-wide {
  display: none;
}

aside[data-sidebar-state="collapsed"] .sidebar-brand-square {
  display: inline-flex;
}

/* Collapsed user menu: show only avatar centered */
aside[data-sidebar-state="collapsed"] .sidebar-user-section {
  padding: 0.75rem 0;
  display: flex;
  justify-content: center;
}

aside[data-sidebar-state="collapsed"] .sidebar-user-section > div {
  margin: 0;
}

aside[data-sidebar-state="collapsed"] .sidebar-user-section button {
  justify-content: center;
  padding: 0.25rem;
  width: auto;
}

/* Collapsed section dividers: thin line, no extra spacing */
aside[data-sidebar-state="collapsed"] .sidebar-section-divider {
  margin-top: 0.25rem;
  margin-bottom: 0.25rem;
  padding-top: 0.25rem;
  padding-bottom: 0;
}

/* The first divider sits flush against the nav's own padding — no need for
   the small top margin/padding the rule above adds for inter-section breaks,
   which otherwise reads as a phantom slot for the (hidden) section header. */
aside[data-sidebar-state="collapsed"] nav > .sidebar-section-divider:first-child {
  margin-top: 0;
  padding-top: 0;
}

/* Ensure dropdown menus are wide enough when sidebar is collapsed */
aside[data-sidebar-state="collapsed"] [data-dropdown-target="menu"] {
  min-width: 16rem;
}

/* Collapse toggle icon swap */
aside[data-sidebar-state="collapsed"] .sidebar-collapse-icon-close {
  display: none;
}

aside[data-sidebar-state="collapsed"] .sidebar-collapse-icon-open {
  display: inline;
}

/* Collapse toggle: center when collapsed */
aside[data-sidebar-state="collapsed"] .sidebar-collapse-toggle {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

/* On-call rotation drag-and-drop affordances */
.slot-ghost {
  opacity: 0.35;
  background-color: rgb(243 244 246);
}
.dark .slot-ghost {
  background-color: rgb(31 41 55);
}
.slot-chosen {
  background-color: rgb(255 255 255);
  box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.15), 0 8px 10px -6px rgb(0 0 0 / 0.08);
}
.dark .slot-chosen {
  background-color: rgb(17 24 39);
}
.slot-drag {
  cursor: grabbing;
}

.pill-ghost {
  opacity: 0.35;
  border: 1px dashed rgb(52 211 153);
  background-color: rgb(236 253 245);
}
.dark .pill-ghost {
  background-color: rgb(6 78 59 / 0.3);
  border-color: rgb(16 185 129);
}
.pill-chosen {
  box-shadow: 0 6px 16px -4px rgb(16 185 129 / 0.45);
  transform: scale(1.04);
  z-index: 20;
}
.pill-drag {
  transform: rotate(-2deg);
}

/* While a member pill is being dragged, hint that other slot rows are
   valid drop targets. */
.dragging-member [data-slot-rotation-target="memberRow"] {
  outline: 2px dashed transparent;
  outline-offset: 4px;
  border-radius: 10px;
  transition: outline-color 0.15s ease, background-color 0.15s ease;
}
.dragging-member [data-slot-row]:hover [data-slot-rotation-target="memberRow"] {
  outline-color: rgb(52 211 153 / 0.6);
  background-color: rgb(236 253 245 / 0.4);
}
.dark .dragging-member [data-slot-row]:hover [data-slot-rotation-target="memberRow"] {
  background-color: rgb(6 78 59 / 0.2);
}

/* Component grouping drag-and-drop (organisations/components index).
   Two affordances make the drop destination unambiguous:
   1. the ghost marks the exact slot the row will land in (faded row + dashed
      emerald outline), and
   2. while a component is held over a group, component_layout_controller adds
      `component-group-drop-active` to that group block so it lights up — making
      it clear the drop lands INSIDE the group rather than at the top level
      between groups. Mirrors the roadmap/on-call DnD affordances above. */
.component-drop-ghost {
  opacity: 0.6;
  outline: 2px dashed rgb(52 211 153); /* emerald-400 */
  outline-offset: -2px;
  background-color: rgb(236 253 245); /* emerald-50 */
}
.dark .component-drop-ghost {
  outline-color: rgb(16 185 129); /* emerald-500 */
  background-color: rgb(6 78 59 / 0.3);
}
/* Sortable applies chosenClass on plain mousedown, but rows are also
   click-to-edit — scope the lift shadow to .layout-dragging (added in onStart,
   i.e. once a drag really begins) so a click doesn't flash it. */
.layout-dragging .component-drop-chosen {
  box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.15), 0 8px 10px -6px rgb(0 0 0 / 0.08);
}
.component-group-drop-active {
  outline: 2px solid rgb(52 211 153); /* emerald-400 */
  outline-offset: -2px;
  background-color: rgb(236 253 245 / 0.6); /* emerald-50 */
}
.dark .component-group-drop-active {
  outline-color: rgb(16 185 129); /* emerald-500 */
  background-color: rgb(6 78 59 / 0.25);
}
/* While dragging, open up the gaps between top-level rows so there's always a
   clear "drop here at the top level" target between (and after) groups — the
   "spaces to drag into". Without this, a group's child list hugs its rows and the
   only top-level gap is a hairline, which is what made dropping a row below the
   last group so hard to hit. */
.layout-dragging [data-component-layout-target="top"] {
  padding-bottom: 3.5rem;
}
.layout-dragging [data-component-layout-target="top"] > [data-layout-item] {
  margin-bottom: 1.25rem;
}

/* Mention prompt popover overrides. Lexxy's defaults cap width at 20ch
   (~16rem) and force flex on each <li>, which clips names + emails in our
   custom menu rows. The :where() defaults have zero specificity, so plain
   selectors win. */
lexxy-editor .lexxy-prompt-menu {
  min-inline-size: 18rem;
  max-inline-size: min(22rem, calc(100vw - 2rem));
}
lexxy-editor .lexxy-prompt-menu__item {
  display: block;
  padding: 0;
}
/* Make our menu-item content fill the row so flex-shrink + truncate work. */
lexxy-editor .lexxy-prompt-menu__item > * {
  inline-size: 100%;
}
lexxy-editor .lexxy-prompt-menu__item img {
  inline-size: 2rem;
  block-size: 2rem;
}

/* highlight.js token theme (GitHub-style) for OpenAPI code samples + console
   responses. The <pre> already supplies the background + base text colour, so
   .hljs only sets token colours. Light by default; dark via .dark. */
.hljs { color: inherit; background: transparent; }
.hljs-comment, .hljs-quote { color: #6a737d; font-style: italic; }
.hljs-keyword, .hljs-selector-tag, .hljs-built_in, .hljs-name, .hljs-tag { color: #d73a49; }
.hljs-string, .hljs-doctag, .hljs-regexp, .hljs-addition { color: #032f62; }
.hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-type { color: #005cc5; }
.hljs-title, .hljs-section, .hljs-title.function_, .hljs-title.class_ { color: #6f42c1; }
.hljs-attr, .hljs-attribute, .hljs-meta, .hljs-symbol, .hljs-bullet { color: #e36209; }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: 600; }

.dark .hljs-comment, .dark .hljs-quote { color: #8b949e; }
.dark .hljs-keyword, .dark .hljs-selector-tag, .dark .hljs-built_in, .dark .hljs-name, .dark .hljs-tag { color: #ff7b72; }
.dark .hljs-string, .dark .hljs-doctag, .dark .hljs-regexp, .dark .hljs-addition { color: #a5d6ff; }
.dark .hljs-number, .dark .hljs-literal, .dark .hljs-variable, .dark .hljs-template-variable, .dark .hljs-type { color: #79c0ff; }
.dark .hljs-title, .dark .hljs-section, .dark .hljs-title.function_, .dark .hljs-title.class_ { color: #d2a8ff; }
.dark .hljs-attr, .dark .hljs-attribute, .dark .hljs-meta, .dark .hljs-symbol, .dark .hljs-bullet { color: #ffa657; }

/* Stack cost calculator sliders (marketing /tools page). A thin track with the
   travelled part filled emerald (the controller sets --pct on every render),
   and a round white thumb with an emerald ring that grows slightly on hover /
   focus. Styled here rather than with arbitrary Tailwind variants because the
   thumb and track pseudo-elements need identical rules for WebKit and Gecko. */
.sc-range {
  --pct: 0%;
  --sc-track: var(--color-gray-200);
  --sc-fill: var(--color-emerald-500);
  --sc-thumb: #ffffff;
  --sc-ring: var(--color-emerald-500);
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  height: 1.5rem;
  margin: 0;
  background: transparent;
  cursor: pointer;
  touch-action: pan-y;
}
.dark .sc-range { --sc-track: var(--color-gray-700); --sc-thumb: var(--color-gray-950); }
.sc-range:focus { outline: none; }
.sc-range::-webkit-slider-runnable-track {
  height: 0.375rem;
  border-radius: 9999px;
  background: linear-gradient(to right, var(--sc-fill) 0 var(--pct), var(--sc-track) var(--pct) 100%);
}
.sc-range::-moz-range-track { height: 0.375rem; border-radius: 9999px; background: var(--sc-track); }
.sc-range::-moz-range-progress { height: 0.375rem; border-radius: 9999px; background: var(--sc-fill); }
.sc-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 1.125rem;
  height: 1.125rem;
  margin-top: -0.375rem; /* centre the thumb on the 6px track */
  border-radius: 9999px;
  background: var(--sc-thumb);
  border: 2px solid var(--sc-ring);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
  transition: transform 120ms ease, box-shadow 120ms ease;
}
.sc-range::-moz-range-thumb {
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 9999px;
  background: var(--sc-thumb);
  border: 2px solid var(--sc-ring);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
  transition: transform 120ms ease, box-shadow 120ms ease;
}
.sc-range:hover::-webkit-slider-thumb, .sc-range:active::-webkit-slider-thumb { transform: scale(1.15); }
.sc-range:hover::-moz-range-thumb, .sc-range:active::-moz-range-thumb { transform: scale(1.15); }
.sc-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-emerald-400) 35%, transparent); }
.sc-range:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-emerald-400) 35%, transparent); }
@media (prefers-reduced-motion: reduce) {
  .sc-range::-webkit-slider-thumb, .sc-range::-moz-range-thumb { transition: none; }
}
