> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Streamplace Recent Announcements

> Latest community activity, Discord announcements, and GitHub updates for Streamplace.

export const streamplaceSocials = [{
  icon: 'globe',
  href: 'https://stream.place/',
  label: 'Streamplace'
}, {
  icon: 'file-code',
  href: 'https://stream.place/docs/',
  label: 'Streamplace Docs'
}, {
  icon: 'gitlab',
  href: 'https://git.stream.place/streamplace/streamplace',
  label: 'Streamplace GitLab'
}, {
  icon: 'github',
  href: 'https://github.com/streamplace/streamplace',
  label: 'Streamplace GitHub'
}, {
  icon: 'tower-broadcast',
  href: 'https://stream.place/live',
  label: 'Live Dashboard'
}, {
  icon: 'discord',
  href: 'https://discord.com/invite/EdtZv4UTMU',
  label: 'Streamplace Discord'
}];

export const streamplaceDiscordData = [{
  id: "1486304411071549541",
  content: " Huge announcement: we're launching Streamplace livestreaming on the Skylight Social app on Sun, Aug 24 in Brooklyn! Hope to see some of you there!!! <a href=\"https://bsky.app/profile/stream.place/post/3lvoq67r3jk25\" target=\"_blank\" rel=\"noopener noreferrer\">https://bsky.app/profile/stream.place/post/3lvoq67r3jk25</a><div style=\"border-left:3px solid var(--accent);padding:8px 12px;margin-top:12px;border-radius:4px\"><p>LAUNCHING LIVE\\: Join the <a href=\"https://bsky.app/profile/did:plc:rbvrr34edl5ddpuwcubjiost\" target=\"_blank\" rel=\"noopener noreferrer\">@stream.place</a> and <a href=\"https://bsky.app/profile/did:plc:4adlzwqtkv4dirxjwq4c3tlm\" target=\"_blank\" rel=\"noopener noreferrer\">@skylight.social</a> teams at <a href=\"https://bsky.app/profile/did:plc:jsbkvuuviqj4xooqwcbaftav\" target=\"_blank\" rel=\"noopener noreferrer\">@thelotradio.com</a> in Brooklyn on Sun, Aug 24 to celebrate the launch of Streamplace livestreaming in the Skylight Social app\\!<br /><br />RSVP on <a href=\"https://bsky.app/profile/did:plc:tgudj2fjm77pzkuawquqhsxm\" target=\"_blank\" rel=\"noopener noreferrer\">@smokesignal.events</a>\\: <a href=\"https://smokesignal.events/did:plc:rbvrr34edl5ddpuwcubjiost/3lvopsv64m22p\" target=\"_blank\" rel=\"noopener noreferrer\">smokesignal.events/did:plc:rbvr...</a></p><img src=\"https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:rbvrr34edl5ddpuwcubjiost/bafkreifmul54obype3nff7j2fgcbcmtpiaejvuwwm6fnzfetwdn3x3hxha@jpeg\" alt=\"\" style=\"max-width:100%;border-radius:8px;margin-top:8px\" /></div>",
  author: "_alisonwonderland",
  timestamp: "2026-03-25T10:03:05.800Z",
  url: "https://discord.com/channels/1066890817425387581/1485293317766905977/1486304411071549541"
}, {
  id: "1486304386014646324",
  content: " Streamplace 0.7 is live! The big features: mobile streaming and our React Native SDK for building on top of Streamplace. Give it a shot and let us know what you think!<br /><br /><a href=\"https://bsky.app/profile/stream.place/post/3lswncrdhqk25\" target=\"_blank\" rel=\"noopener noreferrer\">https://bsky.app/profile/stream.place/post/3lswncrdhqk25</a><div style=\"border-left:3px solid var(--accent);padding:8px 12px;margin-top:12px;border-radius:4px\"><p>STREAMPLACE 0\\.7 is OUT NOW\\!\\!\\! Two huge features\\:<br /><br />1\\. You can now stream directly from the Streamplace iOS/Android app\\! Grab the latest version from the App/Play store and try it out\\!<br /><br />2\\. Our TypeScript SDK is live\\! Add livestreaming to your own <a href=\"https://bsky.app/hashtag/atproto\" target=\"_blank\" rel=\"noopener noreferrer\">#atproto</a> apps\\! <br /><br /><a href=\"https://stream.place/docs/components/player/\" target=\"_blank\" rel=\"noopener noreferrer\">stream.place/docs/compone...</a><br /><br />> -# <a href=\"https://stream.place/docs/components/player/\" target=\"_blank\" rel=\"noopener noreferrer\">Player Component</a><br />> How to set up and use the Player component from @streamplace/components\\.</p></div>",
  author: "_alisonwonderland",
  timestamp: "2026-03-25T10:02:59.826Z",
  url: "https://discord.com/channels/1066890817425387581/1485293317766905977/1486304386014646324"
}, {
  id: "1486304360861536367",
  content: " Streamplace 0.4.5 is live! This should go out to Android users instantly; iOS users will need an App Store update. Some changes since the last update:<br /><br />* Viewer counts are live! Now you can see how many people are watching!<br /><br />* WebRTC streams will now properly play through the phone speaker instead of the headset speaker. Sorry to those of you that love watching livestreams with your phone next to your ear.<br /><br />* Your phone screen will no longer turn off while you're watching a livestream<br /><br />* Server: A memory leak has been fixed; we will no longer be crashing from time to time.<br /><br />Next on our agenda: player telemetry, chat, and transcoding.",
  author: "_alisonwonderland",
  timestamp: "2026-03-25T10:02:53.829Z",
  url: "https://discord.com/channels/1066890817425387581/1485293317766905977/1486304360861536367"
}, {
  id: "1486304338291982451",
  content: " Streamplace 0.4.1 is live in the App Store and Play Store! Download at <a href=\"https://app.stream.place\" target=\"_blank\" rel=\"noopener noreferrer\">https://app.stream.place</a><br /><br />New features:<br /><br />1. There's more than one livestream now! That's right - instead of only being able to see my stream, there are other users streaming other things! Wow!<br /><br />2. You can log in to your Bluesky/atproto account through the app now! And when you do, you'll be notified when anyone you follow goes live! Holy smokes!<br /><br />And finally - we've started onboarding our first few users to the Streamplace Beta Program! If you're a streamer or hacker interested in streaming/building on the platform, head over to <<a href=\"https://invite.stream.place>!\" target=\"_blank\" rel=\"noopener noreferrer\">https://invite.stream.place>!</a>",
  author: "_alisonwonderland",
  timestamp: "2026-03-25T10:02:48.448Z",
  url: "https://discord.com/channels/1066890817425387581/1485293317766905977/1486304338291982451"
}, {
  id: "1485294377881440298",
  content: "Streamplace #releases",
  author: "_alisonwonderland",
  timestamp: "2026-03-22T15:09:35.122Z",
  url: "https://discord.com/channels/1066890817425387581/1485293317766905977/1485294377881440298"
}];

export const youtubeDataStatic = [{
  title: 'Eli Mallon — Streamplace and the future of decentralised video streaming',
  href: 'https://www.youtube.com/watch?v=-xjd_iaULT0',
  author: 'By devtools-fm',
  content: '...',
  publishedDate: 'Feb 24, 2025'
}, {
  title: 'Eli Mallon | Live in Lisbon Summit 2025',
  href: 'https://www.youtube.com/watch?v=6mF1_CzY4qo',
  author: 'By Livepeer',
  content: '...',
  publishedDate: 'Dec 17, 2025'
}];

export const YouTubeVideoData = ({items = [], limit, cols = 2, className = "", style = {}, ...rest}) => {
  const displayItems = limit ? items.slice(0, limit) : items;
  if (!displayItems || displayItems.length === 0) {
    return <Note>
        <p style={{
      color: "var(--text-secondary)",
      textAlign: "center"
    }}>
          No videos at this time.
        </p>
      </Note>;
  }
  const getEmbedUrl = href => {
    if (!href) return "";
    const videoId = href.split("v=")[1]?.split("&")[0];
    return videoId ? `https://www.youtube.com/embed/${videoId}` : href;
  };
  return <Columns cols={cols} className={className} style={style} {...rest}>
      {displayItems.map((item, idx) => {
    if (!item || !item.href) return null;
    const needsSpacer = idx > 0 && idx % cols === 0;
    return <>
            {needsSpacer && <div key={`spacer-${idx}`} style={{
      height: "var(--lp-spacing-6)",
      width: "100%"
    }} />}
            {needsSpacer && <div key={`spacer2-${idx}`} style={{
      height: "var(--lp-spacing-6)",
      width: "100%"
    }} />}
            <YouTubeVideo key={item.href || idx} embedUrl={getEmbedUrl(item.href)} title={item.title || ""} caption={`${item.author || ""} • ${item.publishedDate || ""}`} />
          </>;
  })}
    </Columns>;
};

export const YouTubeVideo = ({embedUrl, url, title = "", author = "", hint = "", caption, className = "", style = {}, ...rest}) => {
  const toEmbedUrl = value => {
    if (!value || typeof value !== "string") return "";
    const source = value.trim();
    if (!source) return "";
    try {
      const parsed = new URL(source);
      const host = parsed.hostname.replace(/^www\./, "");
      if (host === "youtube.com" || host === "youtube-nocookie.com") {
        if (parsed.pathname.startsWith("/embed/")) return source;
        const videoId = parsed.pathname.startsWith("/shorts/") ? parsed.pathname.split("/").filter(Boolean)[1] : parsed.searchParams.get("v");
        if (!videoId) return "";
        const params = new URLSearchParams(parsed.search);
        params.delete("v");
        const query = params.toString();
        return `https://www.youtube.com/embed/${videoId}${query ? `?${query}` : ""}`;
      }
      if (host === "youtu.be") {
        const videoId = parsed.pathname.split("/").filter(Boolean)[0];
        if (!videoId) return "";
        const query = parsed.searchParams.toString();
        return `https://www.youtube.com/embed/${videoId}${query ? `?${query}` : ""}`;
      }
    } catch (_err) {
      return "";
    }
    return "";
  };
  const resolvedEmbedUrl = toEmbedUrl(embedUrl || url);
  if (!resolvedEmbedUrl) {
    return null;
  }
  const isValidYouTubeUrl = resolvedEmbedUrl.includes("youtube.com/embed/");
  if (!isValidYouTubeUrl) {
    console.warn("Invalid YouTube embed URL:", embedUrl || url);
    return null;
  }
  const buildCaption = () => {
    if (caption) return caption;
    if (!author && !title) return null;
    return <span style={{
      display: "flex",
      flexDirection: "column",
      alignItems: "center",
      textAlign: "center",
      lineHeight: 1.2
    }}>
        <span>
          {author && <>
              <Icon icon="microphone" size={16} /> <strong>{author}</strong>
            </>}
          {author && title ? `${" "} • ${title}` : title}
        </span>
      </span>;
  };
  const captionContent = buildCaption();
  return <Frame className={className} style={style} {...hint ? {
    hint
  } : {}} {...captionContent ? {
    caption: captionContent
  } : {}} {...rest}>
      <iframe className="w-full aspect-video rounded-xl" src={resolvedEmbedUrl} title={title || author || "YouTube Video"} allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />
    </Frame>;
};

export const BorderedBox = ({children, variant = "default", padding = "var(--lp-spacing-4)", borderRadius = "var(--lp-spacing-px-8)", margin = "", accentBar = "", style = {}, className = "", ...rest}) => {
  const variants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    accent: {
      border: "1px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    muted: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "transparent"
    }
  };
  const accentBarColors = {
    accent: "var(--lp-color-accent)",
    positive: "var(--green-9)"
  };
  return <div data-docs-bordered-box="" data-accent-bar={accentBarColors[accentBar] ? "" : undefined} className={className} style={{
    ...variants[variant],
    padding: padding,
    borderRadius: borderRadius,
    ...margin ? {
      margin
    } : {},
    ...accentBarColors[accentBar] ? {
      position: "relative",
      '--accent-bar-color': accentBarColors[accentBar]
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const LazyLoad = ({children, height = "200px", offset = "200px", fadeDuration = 400, className = "", style = {}, ...rest}) => {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  const [ready, setReady] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        setVisible(true);
        observer.disconnect();
      }
    }, {
      rootMargin: offset
    });
    observer.observe(el);
    return () => observer.disconnect();
  }, []);
  useEffect(() => {
    if (!visible) return;
    const frameId = requestAnimationFrame(() => {
      setReady(true);
    });
    return () => cancelAnimationFrame(frameId);
  }, [visible]);
  const placeholder = <div ref={ref} className={className} style={{
    minHeight: height,
    ...style
  }} {...rest} />;
  if (!visible) return placeholder;
  return <div ref={ref} className={className} style={{
    opacity: ready ? 1 : 0,
    transition: `opacity ${fadeDuration}ms ease-in`,
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const ScrollBox = ({children, maxHeight = 300, showHint = true, ariaLabel = "Scrollable content", style = {}, className = "", ...rest}) => {
  const contentRef = useRef(null);
  const [isOverflowing, setIsOverflowing] = useState(false);
  useEffect(() => {
    const checkOverflow = () => {
      if (contentRef.current) {
        const maxHeightPx = typeof maxHeight === "number" ? maxHeight : parseInt(maxHeight, 10) || 300;
        setIsOverflowing(contentRef.current.scrollHeight > maxHeightPx);
      }
    };
    checkOverflow();
    window.addEventListener("resize", checkOverflow);
    return () => window.removeEventListener("resize", checkOverflow);
  }, [maxHeight, children]);
  return <div className={className} style={{
    position: "relative",
    ...style
  }} {...rest}>
      <div ref={contentRef} role="region" tabIndex={0} aria-label={ariaLabel} style={{
    maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
    overflowY: "auto",
    paddingRight: 4
  }} onScroll={e => {
    const el = e.target;
    const atBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 10;
    const hint = el.parentNode.querySelector("[data-scroll-hint]");
    if (hint) hint.style.opacity = atBottom ? "0" : "1";
  }}>
        {children}
      </div>
      {showHint && isOverflowing && <div data-scroll-hint style={{
    fontSize: 11,
    color: "var(--lp-color-text-muted)",
    textAlign: "center",
    marginTop: 8,
    transition: "opacity 0.2s"
  }}>
          Scroll for more ↓
        </div>}
    </div>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const MarkdownEmbed = ({url, className = '', style = {}, ...rest}) => {
  const [html, setHtml] = useState('');
  useEffect(() => {
    fetch(url).then(res => res.text()).then(md => {
      const converted = md.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>').replace(/`([^`]+)`/g, '<code>$1</code>').replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img alt="$1" src="$2" style="max-width:100%" />').replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>').replace(/^######\s+(.+?)(?:\s+#+)?$/gm, '<h6>$1</h6>').replace(/^#####\s+(.+?)(?:\s+#+)?$/gm, '<h5>$1</h5>').replace(/^####\s+(.+?)(?:\s+#+)?$/gm, '<h4>$1</h4>').replace(/^###\s+(.+?)(?:\s+#+)?$/gm, '<h3>$1</h3>').replace(/^##\s+(.+?)(?:\s+#+)?$/gm, '<h2>$1</h2>').replace(/^#\s+(.+?)(?:\s+#+)?$/gm, '<h1>$1</h1>').replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>').replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>').replace(/\*(.+?)\*/g, '<em>$1</em>').replace(/^---$/gm, '<hr />').replace(/^[-*]\s+(.+)$/gm, '<li>$1</li>').replace(/\n\n/g, '</p><p>').replace(/\n/g, '<br />');
      setHtml('<p>' + converted + '</p>');
    });
  }, [url]);
  if (!html) return <div className={className} style={style} {...rest}>
        <p style={{
    color: 'var(--text-secondary)'
  }}>Loading...</p>
      </div>;
  return <div className={className} {...rest} style={{
    maxWidth: '100%',
    overflowWrap: 'anywhere',
    wordBreak: 'break-word',
    ...style
  }} dangerouslySetInnerHTML={{
    __html: html
  }} />;
};

export const DiscordAnnouncements = ({serverName = 'Livepeer', items = [], limit, ScrollBox, scrollMaxHeight = 300, className = '', style = {}, ...rest}) => {
  const sanitiseHTML = html => {
    if (!html || typeof html !== 'string') return '';
    return html.replace(/<script[\s\S]*?<\/script>/gi, '').replace(/<iframe[\s\S]*?<\/iframe>/gi, '').replace(/<object[\s\S]*?<\/object>/gi, '').replace(/<embed[\s\S]*?>/gi, '').replace(/<form[\s\S]*?<\/form>/gi, '').replace(/<style[\s\S]*?<\/style>/gi, '').replace(/<link[\s\S]*?>/gi, '').replace(/\bon\w+\s*=\s*(['"])[^'"]*\1/gi, '').replace(/\bon\w+\s*=\s*[^\s>]*/gi, '').replace(/javascript\s*:/gi, '');
  };
  const displayItems = limit ? items.slice(0, limit) : items;
  if (!displayItems || displayItems.length === 0) {
    return <Note>
        <p style={{
      color: 'var(--text-secondary)',
      textAlign: 'center'
    }}>
          No announcements at this time.
        </p>
      </Note>;
  }
  const parseContent = content => {
    const withLinks = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1 ↗</a>');
    return withLinks;
  };
  return <div className={className} style={{
    display: 'flex',
    flexDirection: 'column',
    gap: "var(--lp-spacing-4)",
    border: '1px solid var(--lp-color-accent)',
    borderRadius: "var(--lp-spacing-2)",
    padding: "var(--lp-spacing-4)",
    ...style
  }} {...rest}>
      {displayItems.map((announcement, index) => <div key={announcement.id} href={announcement.url} target="_blank" rel="noopener noreferrer">
          <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: "var(--lp-spacing-2)",
    fontSize: '0.875rem',
    marginBottom: "var(--lp-spacing-3)",
    width: '100%'
  }}>
            <Icon icon="discord" color="var(--lp-color-brand-discord)" size={16} />
            <span style={{
    fontWeight: 600,
    color: 'var(--lp-color-accent)',
    fontSize: 'medium'
  }}>
              {serverName}
              {}
            </span>
            <span style={{
    color: 'var(--lp-color-text-secondary)'
  }}>•</span>
            <time dateTime={announcement.timestamp} style={{
    color: 'var(--lp-color-text-secondary)'
  }}>
              {new Date(announcement.timestamp).toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric'
  })}
            </time>
            <span style={{
    fontSize: '0.875rem',
    color: 'var(--lp-color-text-secondary)',
    marginLeft: 'auto'
  }}>
              View in Discord{' '}
              <Icon icon="arrow-up-right" size={12} color="var(--lp-color-accent)" />
            </span>
          </div>
          <ScrollBox maxHeight={scrollMaxHeight} ariaLabel={`Announcement from ${serverName}`}>
            <div style={{
    color: 'var(--lp-color-text-secondary)',
    fontSize: 'small'
  }} dangerouslySetInnerHTML={{
    __html: sanitiseHTML(parseContent(announcement.content))
  }} />
          </ScrollBox>
          {index < displayItems.length - 1 && <div style={{
    marginTop: "var(--lp-spacing-3)"
  }}>
              <hr style={{
    border: 'none',
    borderBottom: '1px solid var(--lp-color-border-default)',
    margin: '0'
  }} />
            </div>}
        </div>)}
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

export const SocialLinks = ({links, size = 20, gap = "var(--lp-spacing-3)", justify = "center", iconColor, color, className = "", style = {}, ...rest}) => {
  const resolvedIconColor = iconColor || color;
  const linkStyle = {
    border: "none",
    borderBottom: "none",
    textDecoration: "none",
    display: "inline-flex"
  };
  const colors = {
    discord: resolvedIconColor || "var(--lp-color-brand-discord)",
    twitter: resolvedIconColor || "var(--lp-color-text-primary)",
    github: resolvedIconColor || "var(--lp-color-brand-github)",
    forum: resolvedIconColor || "var(--lp-color-brand-forum)",
    website: resolvedIconColor || "var(--lp-color-accent)",
    blog: resolvedIconColor || "var(--lp-color-accent)",
    globe: resolvedIconColor || "var(--lp-color-brand-globe)",
    twitch: resolvedIconColor || "var(--lp-color-brand-twitch)",
    youtube: resolvedIconColor || "var(--lp-color-brand-youtube)",
    instagram: resolvedIconColor || "var(--lp-color-brand-instagram)",
    linkedin: resolvedIconColor || "var(--lp-color-brand-linkedin)"
  };
  const iconColorMap = {
    discord: "discord",
    "x-twitter": "twitter",
    github: "github",
    "comment-pen": "forum",
    "pen-line": "blog",
    "pencil-line": "blog",
    globe: "globe",
    "book-open": "website",
    twitch: "twitch",
    youtube: "youtube",
    instagram: "instagram",
    linkedin: "linkedin"
  };
  const defaultLinks = [{
    icon: "discord",
    href: "https://discord.com/invite/livepeer",
    label: "Livepeer Discord"
  }, {
    icon: "globe",
    href: "https://livepeer.org",
    label: "Livepeer Website"
  }, {
    icon: "github",
    href: "https://github.com/livepeer",
    label: "Livepeer GitHub"
  }, {
    icon: "comment-pen",
    href: "https://forum.livepeer.org",
    label: "Livepeer Forum"
  }, {
    icon: "pen-line",
    href: "https://livepeer.org/blog",
    label: "Livepeer Blog"
  }, {
    icon: "x-twitter",
    href: "https://x.com/livepeer",
    label: "Livepeer X"
  }];
  const items = links || defaultLinks;
  return <div className={className} style={style} {...rest}>
      <style>{`
        .social-links a {
          border: none;
          border-bottom: none;
        }
      `}</style>
      <span className="social-links" style={{
    display: "flex",
    justifyContent: justify,
    gap: gap,
    marginTop: "var(--lp-spacing-2)"
  }}>
        {items.map((item, i) => <a key={i} href={item.href} target="_blank" rel="noopener noreferrer" aria-label={item.label} style={linkStyle}>
            <Tooltip headline={item.label}>
              <Icon icon={item.icon} size={size} color={colors[iconColorMap[item.icon] || "website"] || "var(--lp-color-accent)"} aria-hidden="true" />
            </Tooltip>
          </a>)}
      </span>
    </div>;
};

{/* DATA */}

<CenteredContainer maxWidth="fit-content">
  <BorderedBox margin="0" padding="0.5rem 1rem 1rem 1rem" variant="muted">
    <SocialLinks iconColor="var(--hero-text)" links={streamplaceSocials} />
  </BorderedBox>
</CenteredContainer>

<br />

<CenteredContainer width="80%" minWidth="fit-content">
  <Tip>
    This page is an automated workflow.
  </Tip>
</CenteredContainer>

## <Icon icon="camera-movie" size={24} /> Videos

Watch the latest on the <LinkArrow label="Livepeer YouTube Channel" href="https://www.youtube.com/@livepeer" newline={false} />

<LazyLoad height="400px">
  <YouTubeVideoData items={youtubeDataStatic} limit={2} />
</LazyLoad>

<CustomDivider style={{margin: "0.5rem 0 -2.5rem 0"}} />

## <Icon icon="discord" size={24} /> Discord Announcements

Latest from the <LinkArrow label="Streamplace Discord" href="https://discord.com/invite/EdtZv4UTMU" newline={false} />

<LazyLoad height="300px">
  <DiscordAnnouncements serverName="Streamplace" items={streamplaceDiscordData} limit={4} ScrollBox={ScrollBox} scrollMaxHeight={200} />
</LazyLoad>

<CustomDivider style={{margin: "0 0 -2.5rem 0"}} />

## Featured X Post <Icon icon="x-twitter" size={24} />

{ /* <blockquote class="twitter-tweet"><p lang="en" dir="ltr">Use <a href="https://twitter.com/Livepeer?ref_src=twsrc%5Etfw">@Livepeer</a> for it 👀 Check out <a href="https://t.co/OSiBmRq60G">https://t.co/OSiBmRq60G</a></p>&mdash; madamcultleader.ETH (@cult_leader_en) <a href="https://twitter.com/cult_leader_en/status/1937914268646797643?ref_src=twsrc%5Etfw">June 25, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
https://x.com/cult_leader_en/status/1937914268646797643?s=20
*/}

<br />

<br />

<LazyLoad height="650px">
  <iframe src="https://platform.twitter.com/embed/Tweet.html?id=1937914268646797643&theme=dark" className="lp-tweet-frame-large" title="Studio on X" />
</LazyLoad>

<CustomDivider style={{margin: "0 0 -2.5rem 0"}} />

## <Icon icon="gitlab" size={24} /> GitLab  &  <Icon icon="github" size={24} /> GitHub

<LazyLoad height="600px">
  <BorderedBox>
    <ScrollBox maxHeight={600}>
      <BorderedBox variant="accent">
        Streamplace GitLab (primary)

        <LinkArrow label="View Streamplace on GitLab" href="https://git.stream.place/streamplace/streamplace" newline={true} />
      </BorderedBox>

      <BorderedBox variant="accent">
        Streamplace GitHub (mirror)

        <LinkArrow label="View Streamplace on GitHub" href="https://github.com/streamplace/streamplace" newline={true} />
      </BorderedBox>

      <MarkdownEmbed url="https://raw.githubusercontent.com/streamplace/streamplace/next/README.md" />
    </ScrollBox>
  </BorderedBox>
</LazyLoad>

<CustomDivider style={{margin: "0 0 -2.5rem 0"}} />

## Related Pages

<CardGroup cols={2}>
  <Card title="Streamplace Overview" icon="book-open" href="/solutions/streamplace/overview" />

  <Card title="Streamplace Changelog" icon="clock" href="/solutions/streamplace/changelog" />
</CardGroup>
