/* Theme for the RBTX assistant.
 *
 * The compiled frontend already carries the RBTX brand violet
 * (--primary: 269 47% 37%), which matches the wordmark, so the colour identity
 * stays. What this file brings over from the igus Solution Finder
 * (solutions.igus.de) is the craft rather than the palette: Roboto, its radius,
 * neutrals tinted towards the brand hue instead of flat grey, a ground that is
 * not quite flat white, and a deliberate treatment for source citations and
 * product imagery.
 *
 * Specificity note: Chainlit injects this file BEFORE the app's own stylesheet,
 * so a plain `:root` block loses every tie. `html:root` outranks the compiled
 * `:root` without needing !important on every line.
 *
 * Wired up via `custom_css = "/public/theme.css"` in .chainlit/config.toml.
 */

@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');

html:root {
  --font-sans: 'Roboto', ui-sans-serif, system-ui, sans-serif;

  /* Brand violet, unchanged. One saturated colour, so it always reads as
   * "this is the action" and never competes with product photography. */
  --primary: 269 47% 37%;
  --primary-foreground: 0 0% 100%;
  --ring: 269 47% 37%;

  /* The compiled build uses pure greys (0 0% 90%, 0 0% 36%). Carrying a little
   * of the brand hue into the neutrals is what stops the violet from looking
   * pasted onto a grey shell. Saturation stays low enough to read as neutral. */
  --background: 0 0% 100%;
  --foreground: 269 12% 9%;

  --card: 0 0% 100%;
  --card-foreground: 269 12% 9%;

  --popover: 0 0% 100%;
  --popover-foreground: 269 12% 9%;

  --secondary: 269 20% 96%;
  --secondary-foreground: 269 25% 18%;

  --muted: 269 14% 95%;
  --muted-foreground: 269 8% 42%;

  --accent: 269 20% 95%;
  --accent-foreground: 269 25% 18%;

  --border: 269 14% 90%;
  --input: 269 14% 90%;

  --radius: 0.5rem;
}

/* The `dark` class sits on <html> itself, so this has to be a compound
 * selector. `html:root .dark` would be a descendant selector and never match,
 * and a bare `.dark` ties with the compiled build and loses on load order,
 * which lets the light values above leak into dark mode. */
html:root.dark {
  --background: 269 10% 8%;
  --foreground: 0 0% 96%;

  --card: 269 10% 8%;
  --card-foreground: 0 0% 96%;

  --popover: 269 10% 10%;
  --popover-foreground: 0 0% 96%;

  /* Lifted for contrast: 37% lightness is legible on white but muddy on a dark
   * ground, and the send button has to stay the brightest thing on screen. */
  --primary: 269 55% 62%;
  --primary-foreground: 269 30% 10%;
  --ring: 269 55% 62%;

  --secondary: 269 12% 17%;
  --secondary-foreground: 0 0% 96%;

  --muted: 269 10% 18%;
  --muted-foreground: 269 8% 68%;

  --accent: 269 12% 20%;
  --accent-foreground: 0 0% 96%;

  --border: 269 10% 20%;
  --input: 269 10% 20%;
}

/* The Finder's ground is not flat white; it shifts very slightly across the
 * page so large empty areas do not read as a blank sheet. Same idea, tinted to
 * the brand rather than to orange. */
body {
  background-image: linear-gradient(180deg, hsl(0 0% 100%) 0%, hsl(269 30% 99%) 100%);
  background-attachment: fixed;
}

html:root.dark body {
  background-image: linear-gradient(180deg, hsl(269 10% 8%) 0%, hsl(269 14% 11%) 100%);
}

/* The conversation surface, matched to the igus Solution Finder.
 *
 * The Finder's signature is asymmetry: the question is a gradient bubble in the
 * brand colour with white text and a real shadow, the answer is plain text on
 * the page ground with a ringed avatar beside it. That contrast is what makes a
 * transcript readable at a glance. Chainlit ships both sides as flat grey pills,
 * which reads as a chat log rather than a consultation.
 *
 * Chainlit has no `user-message` class, only `.step` and `.ai-message`, so the
 * question is addressed as the step that is not an answer. */
.step:not(:has(.ai-message)) div[class*='ml-auto'] {
  background-image: linear-gradient(to right, hsl(var(--primary)), hsl(269 47% 48%));
  color: hsl(var(--primary-foreground));
  /* Tailwind's shadow-md, the value the Finder uses on this bubble. */
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  /* Match the composer's soft 24px corners. */
  border-radius: 1.5rem;
  padding: 0.7rem 1.1rem;
}

html:root.dark .step:not(:has(.ai-message)) div[class*='ml-auto'] {
  background-image: linear-gradient(to right, hsl(269 45% 40%), hsl(269 50% 50%));
  color: hsl(0 0% 98%);
}

/* The Finder holds the transcript at max-w-3xl. Chainlit runs to 928px, which is
 * past the comfortable measure for body copy: long lines are harder to scan and
 * the answers here are advice, not chat.
 *
 * The composer and the welcome screen take the same measure, otherwise the input
 * is visibly wider than the conversation it belongs to. */
.step,
.welcome-screen,
div[class*='rounded-3xl']:has(#chat-input) {
  width: 100%;
  max-width: 48rem;
  margin-inline: auto;
}

/* The composer.
 *
 * Chainlit ships it as a flat filled panel with no edge, so it dissolves into
 * the page and gives no feedback on focus. The Finder's input is a defined
 * surface that lifts slightly and takes a brand-coloured ring when active: the
 * one place on the screen you are meant to act, so it should look like it.
 *
 * Note the input is a contenteditable div (#chat-input), not a textarea, which
 * is why a :has(textarea) selector silently matches nothing here. */
div[class*='rounded-3xl']:has(#chat-input) {
  border: 1px solid hsl(var(--border));
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.04), 0 10px 30px -18px rgb(0 0 0 / 0.25);
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

div[class*='rounded-3xl']:has(#chat-input:focus) {
  border-color: hsl(var(--primary) / 0.5);
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.12), 0 10px 30px -18px rgb(0 0 0 / 0.3);
}

html:root.dark div[class*='rounded-3xl']:has(#chat-input) {
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.4), 0 10px 30px -18px rgb(0 0 0 / 0.8);
}

/* The send control is the primary action and was rendered in a washed-out tint,
 * which reads as permanently disabled. */
div[class*='rounded-3xl']:has(#chat-input) button[class*='rounded-full'] {
  /* Same gradient as the question bubble, so the thing you press and the thing
   * it produces are visibly the same colour. It was a washed-out flat tint,
   * which read as permanently disabled. */
  background-image: linear-gradient(135deg, hsl(269 47% 44%), hsl(269 47% 32%));
  color: hsl(var(--primary-foreground));
  box-shadow: 0 1px 2px hsl(269 40% 20% / 0.25);
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}

div[class*='rounded-3xl']:has(#chat-input) button[class*='rounded-full']:hover:not(:disabled) {
  filter: brightness(1.12);
  box-shadow: 0 2px 8px hsl(269 40% 20% / 0.3);
  transform: scale(1.06);
}

div[class*='rounded-3xl']:has(#chat-input) button[class*='rounded-full']:active:not(:disabled) {
  transform: scale(0.97);
}

div[class*='rounded-3xl']:has(#chat-input) button[class*='rounded-full']:disabled {
  background-image: linear-gradient(135deg, hsl(269 30% 70%), hsl(269 30% 62%));
  box-shadow: none;
}

html:root.dark div[class*='rounded-3xl']:has(#chat-input) button[class*='rounded-full'] {
  background-image: linear-gradient(135deg, hsl(269 55% 66%), hsl(269 55% 52%));
}

/* Provenance goes last, after the product cards.
 *
 * Chainlit renders custom elements in its own order regardless of the order the
 * Python side attaches them, so this is settled in the layout rather than in
 * agent.py. `.message-content` is already a flex column, so an order value is
 * all it takes. Sources are what you check after reading the answer and seeing
 * the components, not before. */
/* `order` only affects a direct flex child, and Chainlit wraps each custom
 * element in a container of its own. Both forms are covered: the component root
 * itself, and whatever direct child of .message-content contains it. */
.message-content > .rbtx-sources,
.message-content > *:has(.rbtx-sources) {
  order: 99;
}

/* Product results keep their natural position, ahead of the sources. */
.message-content > *:has(.rbtx-products) {
  order: 10;
}

/* Horizontal scrollers inside the custom elements (sources, product results).
 *
 * The default bar sits flush against the cards and is heavy enough to compete
 * with them. Pushed below the content and reduced to a hairline that only gains
 * contrast when the reader is actually in that area. */
.rbtx-scroller {
  scrollbar-width: thin;
  scrollbar-color: hsl(var(--border)) transparent;
  /* The bar is drawn at the bottom of the padding box, so this is what keeps it
   * clear of the cards instead of cutting across them. */
  padding-bottom: 1.5rem;
}

.rbtx-scroller::-webkit-scrollbar {
  height: 4px;
}

.rbtx-scroller::-webkit-scrollbar-track {
  background: transparent;
}

.rbtx-scroller::-webkit-scrollbar-thumb {
  background: hsl(var(--border));
  border-radius: 9999px;
}

.rbtx-scroller:hover::-webkit-scrollbar-thumb {
  background: hsl(var(--muted-foreground) / 0.45);
}

/* The welcome mark.
 *
 * It sits alone above the input with nothing else competing, which is exactly
 * where a small amount of life pays off. The float is ambient; the tilt towards
 * the pointer is driven by public/theme.js. Both are suppressed under
 * prefers-reduced-motion, and the tilt is skipped entirely on touch devices,
 * where there is no pointer to follow. */
/* The chat profile icon is the RBTX sphere. Chainlit sizes it for a small avatar
 * with an `h-16` utility, which wastes the one element on an otherwise empty
 * screen. The compound selector is what outranks that utility class. */
.welcome-screen img[class] {
  width: 8rem;
  height: auto;
  max-width: 55%;
}

.welcome-screen img,
.welcome-screen img.logo {
  --logo-tilt-x: 0deg;
  --logo-tilt-y: 0deg;
  --logo-shift-x: 0px;
  --logo-shift-y: 0px;
  animation: rbtx-logo-float 5.5s ease-in-out infinite;
  /* A soft cast shadow sells the float; without it the sphere slides rather
   * than hovers. */
  filter: drop-shadow(0 14px 22px hsl(269 45% 35% / 0.28));
  transform: translate3d(var(--logo-shift-x), var(--logo-shift-y), 0)
             rotateX(var(--logo-tilt-x)) rotateY(var(--logo-tilt-y));
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
  transform-style: preserve-3d;
  will-change: transform;
}

@keyframes rbtx-logo-float {
  0%, 100% { translate: 0 0; scale: 1; }
  50%      { translate: 0 -14px; scale: 1.03; }
}

@media (prefers-reduced-motion: reduce) {
  .welcome-screen img {
    animation: none;
    transform: none;
  }
}

/* Answer avatar, the RBTX sphere beside each answer.
 *
 * Addressed by its source rather than by position in the tree: Chainlit wraps it
 * differently between the streaming and the finished message, so `:first-child`
 * matched one of them and not the other, which is what made it appear clipped in
 * some messages and not others.
 *
 * Sized up from Chainlit's 20px, and otherwise left alone. The sphere already
 * carries its own round shape, gradient and shadow; a ring and a backing plate on
 * top only made it look like a sticker pasted onto the page.
 */
.ai-message img[src*="/avatars/"] {
  width: 1.75rem !important;
  height: 1.75rem !important;
  min-width: 1.75rem;
  border-radius: 9999px;
  object-fit: contain;
}

/* The holder is sized for the smaller default and clips what does not fit.
 *
 * Chainlit wraps the image in a `h-6 w-6` span carrying `overflow: hidden` and a
 * full radius, so a 28px sphere sat inside a 24px circular mask and came out
 * with a flat edge down its left side. The previous selector aimed at
 * `.ai-message > :first-child`, but that span is a grandchild of .ai-message,
 * not a child, so the rule never matched anything. Address whatever element
 * directly holds the image instead, which is true in both the streaming and the
 * finished markup. */
.ai-message :has(> img[src*="/avatars/"]) {
  width: 1.75rem;
  height: 1.75rem;
  min-width: 1.75rem;
  min-height: 1.75rem;
  flex-shrink: 0;
  overflow: visible;
}

/* Source citations, appended to answers by app/agent.py as a Markdown list.
 * They are provenance, so they sit under the answer as a quiet footnote rather
 * than competing with it. */
.message-content ul:last-child:has(a[href^='http']) {
  margin-top: 1.25rem;
  padding-top: 0.75rem;
  padding-left: 0;
  border-top: 1px solid hsl(var(--border));
  list-style: none;
  font-size: 0.8125rem;
  line-height: 1.7;
}

.message-content ul:last-child:has(a[href^='http']) a {
  color: hsl(var(--muted-foreground));
  text-decoration: none;
  border-bottom: 1px solid hsl(var(--border));
  transition: color 0.15s ease, border-color 0.15s ease;
}

.message-content ul:last-child:has(a[href^='http']) a:hover {
  color: hsl(var(--primary));
  border-bottom-color: hsl(var(--primary));
}

/* Product and solution imagery is the point of this release. Give it a frame
 * and room instead of letting it sit flush against the text. */
.message-content img {
  border-radius: var(--radius);
  border: 1px solid hsl(var(--border));
  margin-block: 0.75rem;
}

/* Except inside the custom elements, which frame their own pictures.
 *
 * The rule above reaches every image in an answer, so each gallery slide was
 * getting a border and 0.75rem of vertical margin inside a fixed 4:3 frame.
 * The margin pushed the photo down and left the empty strip under it that the
 * card could not hide. Here the card supplies the edge and the rounding, so the
 * image only has to fill its frame. */
.rbtx-products img,
.rbtx-sources img {
  margin-block: 0;
  border: 0;
  border-radius: 0;
}

/* Starters.
 *
 * Chainlit lays these out as a centred row of nowrap pills, which forces every
 * suggestion to be short and gives each one a small, hard-to-hit target. The
 * Finder treats the same thing as a two-column grid of cards: left-aligned,
 * generous padding, text allowed to wrap. That reads as four offers rather than
 * four tags, and it is the layout people actually click.
 *
 * Targeting the container through :has() rather than by class, because the
 * Tailwind utility classes on it are a build artefact and will change. */
.welcome-screen div:has(> button[id^='starter-']) {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem;
  /* No max-width: the composer above is full-bleed inside .welcome-screen, and
   * a narrower grid under a wider input reads as a mistake. */
  width: 100%;
}

@media (min-width: 640px) {
  .welcome-screen div:has(> button[id^='starter-']) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.welcome-screen button[id^='starter-'] {
  /* Chainlit ships these as auto-width inline pills, so without this they sit
   * shrink-to-fit inside their grid cell and the row looks ragged. */
  width: 100%;
  height: auto;
  min-height: 0;
  justify-content: flex-start;
  padding: 0.875rem 1.125rem;
  border: 1px solid hsl(var(--border));
  /* Relates to the composer's rounded-3xl without matching it: the input is the
   * primary surface and should stay the softest shape on screen. */
  border-radius: 1rem;
  background-color: hsl(var(--background));
  text-align: left;
  white-space: normal;
  line-height: 1.4;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.welcome-screen button[id^='starter-']:hover {
  border-color: hsl(var(--primary) / 0.4);
  background-color: hsl(var(--secondary));
}

/* Replaces part of chainlit_frontend_changes.patch: the welcome logo was being
 * cropped to a circle by `rounded-full`, which cuts the RBTX wordmark. */
#welcome-screen img,
[data-testid='welcome-screen'] img {
  border-radius: 0;
  width: auto;
  object-fit: contain;
}

:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* The header.
 *
 * Chainlit gives every header control the same ghost treatment: a transparent
 * 36px square that only appears on hover. For the theme toggle that is right,
 * it is a preference nobody needs to find. Starting a new conversation is the
 * one thing a visitor actually does up here, so it gets a visible edge and the
 * brand colour, in the same gradient as the send control. */
#new-chat-button {
  width: auto;
  height: 2.25rem;
  padding: 0 0.85rem;
  border-radius: 9999px;
  border: 1px solid hsl(var(--primary) / 0.22);
  background-color: hsl(var(--primary) / 0.07);
  color: hsl(var(--primary));
  transition: background-color 0.18s ease, border-color 0.18s ease;
}

/* Chainlit's icon carries `!size-6`, which is 24px against a 36px pill and
 * leaves no room for the label. */
#new-chat-button svg {
  width: 1.05rem !important;
  height: 1.05rem !important;
}

/* The label is the point: an icon alone left people guessing what the pencil
 * did. `::after` rather than markup, because the button is Chainlit's. */
#new-chat-button::after {
  content: "Neuer Chat";
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  white-space: nowrap;
}

/* Hover deepens the tint and nothing else. Filling it with the gradient,
 * swapping the text to white, lifting it and adding a shadow all at once made
 * the whole control jump on the way past it. */
#new-chat-button:hover {
  background-color: hsl(var(--primary) / 0.13);
  border-color: hsl(var(--primary) / 0.34);
}

#new-chat-button:active {
  background-color: hsl(var(--primary) / 0.18);
}

html:root.dark #new-chat-button {
  border-color: hsl(269 55% 66% / 0.3);
  background-color: hsl(269 55% 66% / 0.1);
  color: hsl(269 55% 78%);
}

html:root.dark #new-chat-button:hover {
  background-color: hsl(269 55% 66% / 0.18);
  border-color: hsl(269 55% 66% / 0.42);
}

/* The Anleitung entry is retired. chainlit.md stays in the image and Chainlit
 * still serves it, so this hides the way in rather than deleting the page. */
#readme-button {
  display: none !important;
}

/* On a narrow screen the label costs more than it explains. */
@media (max-width: 30rem) {
  #new-chat-button {
    width: 2.25rem;
    padding: 0;
  }
  #new-chat-button::after { content: none; }
  #new-chat-button svg { width: 1.15rem !important; height: 1.15rem !important; }
}

/* Keep the welcome mark secondary to the composer in the floating chat. */
@media (max-width: 640px), (max-height: 700px) {
  .welcome-screen img[class] {
    width: 4.5rem;
    max-height: 4.5rem;
    object-fit: contain;
    animation: none;
    transform: none;
    filter: drop-shadow(0 4px 8px hsl(269 45% 35% / 0.18));
  }
}

/* The compact embedded chat starts with the composer, without topic cards.
 * Expanding the same iframe restores the suggestions at desktop widths. */
@media (max-width: 640px) {
  html.rbtx-embedded .welcome-screen div:has(> button[id^='starter-']) {
    display: none;
  }
}

/* Anchor the initial composer to the bottom of the compact chat as well. */
@media (max-width: 640px) {
  html.rbtx-embedded .welcome-screen {
    margin-top: 0;
    justify-content: flex-end;
  }
  html.rbtx-embedded .welcome-screen > div:first-child:not(:has(#chat-input)) {
    margin-block: 0;
  }
}

/* In the compact chat, the greeting reads like a message with a small avatar. */
@media (max-width: 640px) {
  html.rbtx-embedded .welcome-screen > div:first-child:not(:has(#chat-input)) {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 12px;
    width: 100%;
  }
  html.rbtx-embedded .welcome-screen img[class] {
    width: 32px;
    height: 32px;
    max-height: 32px;
    flex: 0 0 32px;
    margin-top: 2px;
  }
  html.rbtx-embedded .welcome-screen > div:first-child:not(:has(#chat-input)) > div {
    min-width: 0;
    flex: 1;
    text-align: left;
    font-size: 14px;
    line-height: 1.5;
    overflow-wrap: anywhere;
  }
  html.rbtx-embedded .welcome-screen > div:first-child:not(:has(#chat-input)) p {
    margin-block: 0;
  }
}
