AmberCutie's Forum
An adult community for cam models and members to discuss all the things!

Chaturbate Eye Contact Chat Tab For Chrome – Keep Your Gaze Near the Cam

  • ** WARNING - ACF CONTAINS ADULT CONTENT **
    Only persons aged 18 or over may read or post to the forums, without regard to whether an adult actually owns the registration or parental/guardian permission. AmberCutie's Forum (ACF) is for use by adults only and contains adult content. By continuing to use this site you are confirming that you are at least 18 years of age.
Dec 15, 2024
18
7
1
Inspired by @cbhours "Eye Contact Tool", this script introduces a new "Eye Contact Chat" tab to your broadcaster page. When activated, chat messages go fullscreen and appear from the top down, keeping them closer to your webcam—helping you maintain natural eye contact with viewers!


How It Works:
  • A new "Eye Contact Chat" tab appears on your broadcaster page.
  • Clicking it expands chat to fullscreen, repositioning messages to stack from top to bottom.
  • Keeps your gaze aligned with the camera for better engagement!

Steps to Inject the Script Using Chrome Developer Tools:​


  1. Open the Chaturbate website:
    • Go to your Chaturbate broadcast page.
  2. Open Chrome Developer Tools:
    • Right-click anywhere on the page and select "Inspect" or press Ctrl + Shift + I (Windows) or Cmd + Option + I (Mac).
    • Alternatively, press F12 to open the DevTools window.
  3. Navigate to the Console Tab:
    • Inside the Developer Tools window, click on the "Console" tab. This is where you can execute JavaScript code.
  4. Paste Your Script:
    • Copy the entire attached script .
    • Paste the copied script directly into the Console tab.
  5. Press Enter:
    • After pasting the code, press Enter to execute the script. The changes should take effect immediately, and the Eye Contact tab will be added to the Chaturbate page.
    • IMPORTANT: Close the Developer Tools window.
  6. Test the New Feature:
    • Once the script is injected, you should see the new Eye Contact Chat tab on your Chaturbate broadcast page. Test it to ensure everything works as expected.

Additional Notes:​

  • The script will only stay active for the current session. If you refresh the page, you will need to inject the script again.
  • We’re not professional coders—this took many hours of trial and error with ChatGPT to figure out. If you find this helpful, we’d love your support! Please visit our CB page and drop a follow (tips are also greatly appreciated! ❤️).

    👉 https://chaturbate.com/alyxxandmsstarzz/


    The full code is pasted down below the screenshots...

Screenshots:​

Screen Shot 2025-03-19 at 1.13.51 PM.png




Screen Shot 2025-03-19 at 1.14.57 PM.png


Full Code:​

// Create Eye Contact tab
const chatTabDefault = document.querySelector("div#chat-tab-default");
if (chatTabDefault) {
const clonedTab = chatTabDefault.cloneNode(true);

// Modify cloned tab properties
clonedTab.id = "eye-contact-tab-default";
clonedTab.classList.replace("chat-tab-handle", "eye-contact-tab-handle");
clonedTab.setAttribute("data-testid", "eye-contact-tab-default");
const tabLabel = clonedTab.querySelector("span");
if (tabLabel) tabLabel.textContent = "EYE CONTACT CHAT";

// Insert cloned tab
chatTabDefault.parentElement.insertBefore(clonedTab, chatTabDefault);

// Ensure the Eye Contact tab is not active by default
clonedTab.classList.remove("active");
clonedTab.style.backgroundColor = "rgb(201, 201, 201)";
clonedTab.style.color = "rgb(76, 76, 76)";
clonedTab.style.cursor = "pointer";
}

// Tab behavior for switching
const allTabs = Array.from(
document.querySelectorAll(
"div#eye-contact-tab-default, div#chat-tab-default, div#pm-tab-default, div#users-tab-default, div#settings-tab-default"
)
).filter(Boolean);

const tsLeElement = document.querySelector('div[ts="le"]');
const messageList = document.querySelector(".msg-list-fvm.message-list");
const theaterChatDiv = document.querySelector(".ChatTabContents.TheatermodeChatDivChat");

// Helper to reset Eye Contact–specific styles
function resetEyeContactStyles() {
if (messageList) {
messageList.style.display = "";
messageList.style.flexDirection = "";
messageList.style.width = ""; // Reset width
messageList.style.margin = ""; // Reset margin
messageList.style.transform = "";
}
const msgListWrapper = document.querySelector('.msg-list-wrapper-split');
if (msgListWrapper) {
msgListWrapper.style.transform = "";
}
const noticeList = document.querySelector('.notice-list-fvm');
if (noticeList) {
noticeList.style.transform = "";
}
if (theaterChatDiv) {
theaterChatDiv.style.flexDirection = "column";
}
toggleFullscreen(false); // Exit fullscreen if active

// Reset emoji modal (regular) to default so it displays in front
const emojiModal = document.querySelector('.emojiSelectionModal');
if (emojiModal) {
emojiModal.style.position = '';
emojiModal.style.top = '';
emojiModal.style.left = '';
emojiModal.style.zIndex = '1001';
}
// Reset emojicon autocomplete modal styles to default
const emoticonList = document.querySelector('.autocompleteModal.theatermodeEmoticonAutocompleteModalChat');
if (emoticonList) {
emoticonList.style.top = '-181.337px';
emoticonList.style.bottom = '';
emoticonList.style.right = '';
emoticonList.style.left = '8px';
emoticonList.style.overflow = '';
emoticonList.style.maxHeight = '';
emoticonList.style.maxWidth = '';
emoticonList.style.width = '100%';
emoticonList.style.height = '';
emoticonList.style.zIndex = '1001';
}
}

// Helper to apply Eye Contact–specific styles
function applyEyeContactStyles() {
if (messageList) {
const msgListWrapper = document.querySelector('.msg-list-wrapper-split');
const noticeList = document.querySelector('.notice-list-fvm');

// Flip the scroll container
if (msgListWrapper) {
msgListWrapper.style.transform = 'scaleY(-1)';
}

// Ensure the message list uses column-reverse order and flip it back to normal
messageList.style.display = 'flex';
messageList.style.flexDirection = 'column-reverse';
messageList.style.transform = 'scaleY(-1)';

// Flip notice list elements back to normal
if (noticeList) {
noticeList.style.transform = 'scaleY(-1)';
}

// Additional styling specific to Eye Contact mode
messageList.style.width = "50%"; // Narrow the message container
messageList.style.margin = "0 auto"; // Center the container horizontally

// Set scroll position to the top so messages initiate at the top
messageList.scrollTop = 0;
}
if (theaterChatDiv) {
theaterChatDiv.style.flexDirection = "column-reverse";
}
toggleFullscreen(true); // Enter fullscreen
}

// Fullscreen toggle for Eye Contact tab
function toggleFullscreen(isActive) {
const chatTabContainer = document.querySelector("#ChatTabContainer");

if (isActive) {
if (chatTabContainer?.requestFullscreen) {
chatTabContainer.requestFullscreen();
} else if (chatTabContainer?.mozRequestFullScreen) {
chatTabContainer.mozRequestFullScreen();
} else if (chatTabContainer?.webkitRequestFullscreen) {
chatTabContainer.webkitRequestFullscreen();
} else if (chatTabContainer?.msRequestFullscreen) {
chatTabContainer.msRequestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
}

// Attach tab switching behavior
allTabs.forEach(tab => {
tab.addEventListener("click", () => {
// Reset all tabs to default styles
allTabs.forEach(otherTab => {
otherTab.classList.remove("active");
otherTab.style.backgroundColor = "rgb(201, 201, 201)";
otherTab.style.color = "rgb(76, 76, 76)";
otherTab.style.cursor = "pointer";
});

// Activate the clicked tab
tab.classList.add("active");
tab.style.backgroundColor = "rgb(255, 255, 255)";
tab.style.color = "rgb(220, 85, 0)";
tab.style.cursor = "default";

// Adjust UI based on the active tab
if (tsLeElement) {
if (tab.id === "chat-tab-default") {
tsLeElement.style.display = "block";
resetEyeContactStyles();
} else if (tab.id === "eye-contact-tab-default") {
tsLeElement.style.display = "block";
applyEyeContactStyles();
} else {
tsLeElement.style.display = "none";
resetEyeContactStyles();
}
}

// Update scroll button's SVG and text based on active tab
const scrollDownButton = document.querySelector('.scrollDownButton');
if (scrollDownButton) {
const svgElement = scrollDownButton.querySelector('svg');
const spanText = scrollDownButton.querySelector('span.scrollDownText');
if (tab.id === "eye-contact-tab-default") {
// For Eye Contact: flip SVG and set text to "Scroll to top"
if (svgElement) {
svgElement.style.transform = 'rotate(180deg)';
}
if (spanText) {
spanText.textContent = 'Scroll to top';
}
} else {
// For other tabs: restore the default SVG and set text to "Scroll to bottom"
if (svgElement) {
svgElement.setAttribute("width", "9px");
svgElement.setAttribute("height", "13px");
svgElement.setAttribute("viewBox", "0 0 9 13");
svgElement.setAttribute("fill", "none");
svgElement.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svgElement.innerHTML = `<path d="M4.50848 7.97048V1.5" stroke="white" stroke-width="1.4" stroke-linecap="round"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M1.24099 4.92406C0.957097 4.66164 0.496814 4.66164 0.21292 4.92406C-0.0709734 5.18648 -0.0709734 5.61194 0.21292 5.87436L3.90125 9.28368C4.18515 9.5461 4.64543 9.5461 4.92932 9.28368L8.67227 5.82387C8.95617 5.56145 8.95617 5.13599 8.67227 4.87357C8.38838 4.61115 7.9281 4.61115 7.6442 4.87357L4.41529 7.85823L1.24099 4.92406ZM0.655497 11.2058C0.298143 11.2058 0.00844965 11.4955 0.00844965 11.8528C0.00844965 12.2102 0.298143 12.4999 0.655498 12.4999H8.3614C8.71876 12.4999 9.00845 12.2102 9.00845 11.8528C9.00845 11.4955 8.71876 11.2058 8.3614 11.2058H0.655497Z" fill="white"></path>`;
svgElement.style.transform = "";
}
if (spanText) {
spanText.textContent = 'Scroll to bottom';
}
}
}
});
});

// Observe DOM changes to adjust emoji modals when the Eye Contact tab is active
const observer = new MutationObserver(() => {
const activeTab = document.querySelector(".tab.active");
if (activeTab && activeTab.id === "eye-contact-tab-default") {
// Relocate the emoji selection modal
const emojiSelectionModal = document.querySelector(".emojiSelectionModal");
const emojiSelectionModalOverlay = document.querySelector(".emojiSelectionModalOverlay");
if (emojiSelectionModal && emojiSelectionModalOverlay) {
emojiSelectionModal.style.position = "absolute";
emojiSelectionModal.style.top = "50px"; // Lower position for better visibility
emojiSelectionModal.style.right = "10px"; // Anchored to the right with a small margin
emojiSelectionModal.style.transform = "translateY(0)";
emojiSelectionModal.style.zIndex = "1001";
}
// Apply settings for the theater mode emojicon autocomplete modal
const emojiconList = document.querySelector('.autocompleteModal.theatermodeEmoticonAutocompleteModalChat');
if (emojiconList) {
emojiconList.style.top = '100%';
emojiconList.style.bottom = 'auto';
emojiconList.style.right = 'auto';
emojiconList.style.left = '0';
emojiconList.style.overflow = 'auto';
emojiconList.style.maxHeight = '';
emojiconList.style.maxWidth = '';
emojiconList.style.width = '100%';
emojiconList.style.height = 'auto';
emojiconList.style.borderTopWidth = 'none';
emojiconList.style.borderBottomWidth = '1px';
emojiconList.style.borderBottomStyle = 'solid';
}
}
});

// Start observing the body for changes
observer.observe(document.body, { childList: true, subtree: true });

// Disconnect observer when a different tab is active
document.querySelectorAll(".tab").forEach(tab => {
tab.addEventListener("click", () => {
const activeTab = document.querySelector(".tab.active");
if (activeTab && activeTab.id !== "eye-contact-tab-default") {
observer.disconnect();
} else {
observer.observe(document.body, { childList: true, subtree: true });
}
});
});

// Ensure that when the .msg-list-wrapper-split scrolls to the bottom,
// the scroll button text remains "Scroll to top" and the SVG stays flipped.
const msgWrapper = document.querySelector('.msg-list-wrapper-split');
if (msgWrapper) {
msgWrapper.addEventListener('scroll', () => {
const activeTab = document.querySelector('.tab.active');
if (activeTab && activeTab.id === 'eye-contact-tab-default') {
const scrollDownButton = document.querySelector('.scrollDownButton');
if (scrollDownButton) {
const spanText = scrollDownButton.querySelector('span.scrollDownText');
if (spanText) {
spanText.textContent = 'Scroll to top';
}
const svgElement = scrollDownButton.querySelector('svg');
if (svgElement) {
svgElement.style.transform = 'rotate(180deg)';
}
}
}
});
}
const targetDiv = document.querySelector('div#chat-tab-default');
if (targetDiv) {
const targetSpan = targetDiv.querySelector('span');
if (targetSpan) {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'characterData' || mutation.type === 'childList') {
if (targetSpan.textContent !== "DEFAULT") {
targetSpan.textContent = "DEFAULT";
}
}
});
});
observer.observe(targetSpan, { characterData: true, childList: true, subtree: true });
if (targetSpan.textContent !== "DEFAULT") {
targetSpan.textContent = "DEFAULT";
}
}
}
const data = {
message: "MutationObserver is set up to override the text content of the span to 'DEFAULT'."
}
 
Last edited:
if you use a [ code ] tag and close it wit [/ code ] without the spaces it will give you a code block for your posts.
Here's an example using a date picker component I wrote the other day.
Code:
class DatePicker(flet.UserControl):
    def __init__(self,button_text='Select Date' ,first_date=datetime(2000, 10, 1), time=True, callback=None):
        super().__init__()
        self.time = time
        self.button_text=button_text
        self.callback = callback
        self.first_date = first_date
        self.last_date = datetime.now().date()
        self.date = None
    def on_change(self, e):
        date_str = self.date_overlay.value.strftime('%d/%m/%Y')
        print(f'Selected Date: {date_str}')
        if self.callback:
            self.callback(date_str)
    def on_dismiss(self, e):
        print("DatePicker dismissed")
        no_date = 'None: Please Select A Date'
    def build(self,icon=flet.icons.CALENDAR_MONTH):
        try:
            self.date_overlay=flet.DatePicker(
                        on_change=self.on_change,
                        on_dismiss=self.on_dismiss,
                        first_date=self.first_date,
                        last_date=self.last_date,
                    )
            self.date =flet.ElevatedButton(self.button_text,
                icon=icon,
                on_click=lambda e:self.page.open(
                    self.date_overlay
                )
            )
            return self.date
        except Exception:
            traceback.print_exc()

Might be useful if you want to preserve formatting, it also make's it a bit easier to read imo.
 
Last edited:
  • Like
Reactions: alyxxandmsstarzz
if you use a [ code ] tag and close it wit [/ clode ] without the spaces it will give you a code block for your posts.
Here's an example using a date picker component I wrote the other day.
Code:
class DatePicker(flet.UserControl):
    def __init__(self,button_text='Select Date' ,first_date=datetime(2000, 10, 1), time=True, callback=None):
        super().__init__()
        self.time = time
        self.button_text=button_text
        self.callback = callback
        self.first_date = first_date
        self.last_date = datetime.now().date()
        self.date = None
    def on_change(self, e):
        date_str = self.date_overlay.value.strftime('%d/%m/%Y')
        print(f'Selected Date: {date_str}')
        if self.callback:
            self.callback(date_str)
    def on_dismiss(self, e):
        print("DatePicker dismissed")
        no_date = 'None: Please Select A Date'
    def build(self,icon=flet.icons.CALENDAR_MONTH):
        try:
            self.date_overlay=flet.DatePicker(
                        on_change=self.on_change,
                        on_dismiss=self.on_dismiss,
                        first_date=self.first_date,
                        last_date=self.last_date,
                    )
            self.date =flet.ElevatedButton(self.button_text,
                icon=icon,
                on_click=lambda e:self.page.open(
                    self.date_overlay
                )
            )
            return self.date
        except Exception:
            traceback.print_exc()

Might be useful if you want to preserve formatting, it also make's it a bit easier to read imo.
Thank you.
 
  • Like
Reactions: KingMarti