/*
 * SWS Free Chat: "Ember Glass" skin (v1)
 * Original theme, product-art scope: this file declares its own palette,
 * independent of the SWS chrome palette. Every colour used anywhere below
 * is one of the custom properties declared in :root, zero one-off hex
 * literals in the rules that follow.
 *
 * Font: Manrope (SIL Open Font License 1.1), self-hosted as WOFF2 in
 * ./fonts/. This widget is a live, editable overlay distributed to buyers,
 * so the embedded font must be OFL, not a desktop/Adobe Fonts licence.
 * Licence text: ./fonts/OFL-Manrope.txt
 *
 * Structure and class names are untouched from the engine shipped in this
 * repo (row / p-icon / bubble / head / role-badge / name / text / emote).
 * Every visual value lives in the :root block. A future reskin edits only
 * this block, or loads a second stylesheet after this one.
 */

@font-face {
  font-family: "Manrope";
  src: url("./fonts/Manrope-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Manrope";
  src: url("./fonts/Manrope-ExtraBold.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ---- palette: "Ember Glass": warm graphite neutrals + one warm accent
     per platform. Declared here once, referenced everywhere else. ---- */
  --graphite-950: #17151a;   /* bubble base */
  --graphite-900: #201d24;   /* bubble border tint, alt surface */
  --paper-050: #f6f1e8;      /* primary text, warm ivory */
  --paper-300: #cdc6ba;      /* dim text: default name colour, icon */
  --ember-500: #ff6a45;      /* twitch accent: icon tint, broadcaster badge */
  --ember-300: #ffb59c;      /* ember tint, unused in v1 but declared for future emphasis */
  --gold-500: #e8b559;       /* kick accent: icon tint, vip badge */
  --moss-500: #93b07c;       /* mod badge */
  --orchid-500: #d99bd6;     /* sub badge */
  --twitch-identity: #9b7bff; /* platform identity accent: edge rail + name pill, twitch rows */
  --kick-identity: #4fd67a;   /* platform identity accent: edge rail + name pill, kick rows */
  --youtube-identity: #ef4444; /* platform identity accent: edge rail + name pill, youtube rows (optional 3rd platform, own API key) */
  --tiktok-identity: #22d3ee;  /* platform identity accent: edge rail + name pill, tiktok rows (optional 4th platform, own Euler Stream key) */

  --chat-font: "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;
  --chat-width: 420px;
  --chat-gap: 10px;
  --chat-padding: 14px;

  --row-gap: 10px;
  --bubble-bg: rgba(23, 21, 26, 0.84);
  --bubble-border: 1px solid rgba(246, 241, 232, 0.10);
  --bubble-radius: 14px;
  --bubble-padding: 9px 13px;
  --bubble-shadow: 0 4px 18px rgba(0, 0, 0, 0.28);
  --bubble-blur: 6px;

  --icon-size: 18px;
  --icon-color: var(--paper-300);

  --name-size: 14px;
  --name-weight: 800;
  --name-default-color: var(--paper-300);

  --text-size: 14.5px;
  --text-weight: 500;
  --text-color: var(--paper-050);
  --text-line-height: 1.42;

  --emote-size: 1.5em;

  --badge-size: 14px;
  --badge-broadcaster-color: var(--ember-500);
  --badge-mod-color: var(--moss-500);
  --badge-vip-color: var(--gold-500);
  --badge-sub-color: var(--orchid-500);

  --row-anim-duration: 0.42s;
  --row-anim-from-y: 10px;
  --arrival-glow-duration: 0.9s;

  /* Twitch chat GIFs (GIPHY), added 2026-09-26. Height is the only
     buyer-relevant knob in this free widget (no settings panel), exposed
     as a token so a future reskin/config layer can override it without
     touching chat.js. Radius is capped independently at 14px in .gif-media
     below, matching the paid engine's own cap so a pill-radius reskin can
     never stretch a GIF into an ellipse. */
  --gif-max-height: 120px;
}

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

html,
body {
  background: transparent;
  width: 100%;
  height: 100%;
}

body {
  font-family: var(--chat-font);
  overflow: hidden;
}

#feed {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--row-gap);
  /* Never wider than the browser source it lives in (2026-09-27). A flat
     420px clipped every bubble's right edge in the 340px setup-page preview
     and in any OBS source under 420 wide. */
  width: min(var(--chat-width), 100%);
  padding: var(--chat-padding);
  /* Fixed height, not min-height (2026-09-27). With min-height the feed grew
     past the viewport once rows outran it, the top edge stayed put, and the
     NEWEST row spilled off the bottom. A fixed height with flex-end pushes the
     overflow off the TOP instead, so the latest message is always on screen. */
  height: 100vh;
  overflow: hidden;
}

.row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  animation: rowIn var(--row-anim-duration) cubic-bezier(0.34, 1.56, 0.64, 1);
}

.row.platform-twitch {
  --platform-accent: var(--twitch-identity);
}

.row.platform-kick {
  --platform-accent: var(--kick-identity);
}

.row.platform-youtube {
  --platform-accent: var(--youtube-identity);
}

.row.platform-tiktok {
  --platform-accent: var(--tiktok-identity);
}

@keyframes rowIn {
  from {
    opacity: 0;
    transform: translateY(var(--row-anim-from-y)) scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes arrivalFade {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .row {
    animation: none;
  }
  .bubble::after {
    animation: none;
    opacity: 0;
  }
}

.p-icon {
  width: var(--icon-size);
  height: var(--icon-size);
  flex: 0 0 auto;
  color: var(--icon-color);
  margin-top: 3px;
}

.p-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.row.platform-twitch .p-icon {
  color: var(--ember-500);
}

.row.platform-kick .p-icon {
  color: var(--gold-500);
}

.row.platform-youtube .p-icon {
  color: var(--youtube-identity);
}

.row.platform-tiktok .p-icon {
  color: var(--tiktok-identity);
}

.bubble {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 3px;
  background: var(--bubble-bg);
  border-top: var(--bubble-border);
  border-right: var(--bubble-border);
  border-bottom: var(--bubble-border);
  border-left: 3px solid var(--platform-accent, var(--paper-300));
  border-radius: var(--bubble-radius);
  box-shadow: var(--bubble-shadow);
  padding: var(--bubble-padding);
  min-width: 0;
}

/* signature detail: a brief accent-tinted arrival glow on the platform's
   own identity colour, fading over --arrival-glow-duration. Purely
   decorative, ::after so it never affects layout or bubble contents. */
.bubble::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: calc(var(--bubble-radius) + 3px);
  box-shadow: 0 0 0 2px rgb(from var(--platform-accent, var(--paper-300)) r g b / 55%);
  opacity: 1;
  pointer-events: none;
  animation: arrivalFade var(--arrival-glow-duration) ease-out forwards;
}

/* "alert" beat: a marketing-capture-only hook (`?demo=ad`, chat.js) adds
   this class to one row's arrival to stand in for a highlight moment,
   since this product has no separate alert box. Same --platform-accent
   token, just a stronger ring and a wider soft glow, held longer. No new
   colour. */
.row.alert-beat .bubble::after {
  box-shadow:
    0 0 0 3px rgb(from var(--platform-accent, var(--paper-300)) r g b / 85%),
    0 0 26px 6px rgb(from var(--platform-accent, var(--paper-300)) r g b / 50%);
  animation: arrivalFade calc(var(--arrival-glow-duration) * 1.8) ease-out forwards;
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .bubble {
    -webkit-backdrop-filter: blur(var(--bubble-blur)) saturate(1.15);
    backdrop-filter: blur(var(--bubble-blur)) saturate(1.15);
  }
}

.head {
  display: flex;
  align-items: center;
  gap: 5px;
  width: fit-content;
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  background: rgb(from var(--platform-accent, var(--graphite-900)) r g b / 16%);
  border: 1px solid rgb(from var(--platform-accent, var(--graphite-900)) r g b / 38%);
}

.role-badge {
  width: var(--badge-size);
  height: var(--badge-size);
  flex: 0 0 auto;
  display: inline-flex;
}

.role-badge svg {
  width: 100%;
  height: 100%;
}

.role-badge.role-broadcaster { color: var(--badge-broadcaster-color); }
.role-badge.role-mod { color: var(--badge-mod-color); }
.role-badge.role-vip { color: var(--badge-vip-color); }
.role-badge.role-sub { color: var(--badge-sub-color); }

.name {
  font-size: var(--name-size);
  font-weight: var(--name-weight);
  color: var(--name-default-color);
  letter-spacing: 0.005em;
}

.text {
  font-size: var(--text-size);
  font-weight: var(--text-weight);
  color: var(--text-color);
  line-height: var(--text-line-height);
  word-break: break-word;
  overflow-wrap: anywhere;
}

.emote {
  height: var(--emote-size);
  vertical-align: middle;
  margin: 0 1px;
}

/* ---- Twitch chat GIFs (GIPHY), added 2026-09-26 ----
   The GIF is its own block-level row, stacked directly below the message
   text (or alone if the message is only a GIF), left-aligned to the same
   edge as the text: display:block puts it on its own line regardless of
   any inline text around it, and a block box's left edge is the bubble's
   content edge, same as the text, so no extra alignment rule is needed.
   Two GIFs in one message stack because each non-first .gif-wrap carries
   the same margin-top. */
.gif-wrap {
  display: block;
  margin-top: 6px;
}

.gif-wrap:first-child {
  margin-top: 0;
}

.gif-media {
  display: inline-block;
  width: auto;
  max-height: var(--gif-max-height, 120px);
  max-width: 100%;
  /* Capped at 14px regardless of the bubble's own radius, so a future
     pill/capsule reskin never renders a tall GIF as a near-ellipse. */
  border-radius: 14px;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.gif-media.gif-loaded {
  opacity: 1;
}

/* Set by JS (buildGifEl in chat.js), not a `:has(.gif-wrap)` selector: OBS
   Studio's bundled CEF runtime is Chromium 103, and `:has()` did not ship
   until Chromium 105, so a CSS-only version would silently no-op on a very
   common buyer setup. Text-only messages never get the class. Current
   --bubble-radius is already 14px, so this is a no-op today; it only
   matters once a reskin pushes --bubble-radius past 28px. */
.bubble.has-gif {
  border-radius: min(var(--bubble-radius), 28px);
}
