AlteredParadox

LET-Userscript-Thing

Mar 8th, 2026
121
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         LowEndTalk Compact Full-Width Layout
  3. // @namespace    https://lowendtalk.com/
  4. // @version      3.3
  5. // @description  Full-width layout with single-line discussion rows
  6. // @match        *://lowendtalk.com/*
  7. // @grant        none
  8. // @run-at       document-start
  9. // ==/UserScript==
  10.  
  11. (function () {
  12.   "use strict";
  13.  
  14.   var IS_IFRAME = window !== window.top;
  15.  
  16.   // --- Userscript Settings (persisted via localStorage) ---
  17.   var LET_DEFAULTS = {
  18.     titleFontSize: 14,
  19.     metaFontSize: 12,
  20.     hcInterval: 30,
  21.     hcAutoOn: false,
  22.     hideSidebar: false,
  23.     pushEnabled: false,
  24.     pushoverUser: "",
  25.     pushoverToken: "",
  26.     autoRefreshOn: false,
  27.     autoRefreshSec: 120,
  28.     readingPane: false,
  29.   };
  30.   var LET_STORAGE_KEY = "let-compact-settings";
  31.  
  32.   function loadSettings() {
  33.     try {
  34.       var s = JSON.parse(localStorage.getItem(LET_STORAGE_KEY));
  35.       if (s && typeof s === "object") {
  36.         return {
  37.           titleFontSize: Number(s.titleFontSize) || LET_DEFAULTS.titleFontSize,
  38.           metaFontSize: Number(s.metaFontSize) || LET_DEFAULTS.metaFontSize,
  39.           hcInterval: Number(s.hcInterval) || LET_DEFAULTS.hcInterval,
  40.           hcAutoOn: s.hcAutoOn === true,
  41.           hideSidebar: s.hideSidebar === true,
  42.           pushEnabled: s.pushEnabled === true,
  43.           pushoverUser: s.pushoverUser || "",
  44.           pushoverToken: s.pushoverToken || "",
  45.           autoRefreshOn: s.autoRefreshOn === true,
  46.           autoRefreshSec:
  47.             Number(s.autoRefreshSec) || LET_DEFAULTS.autoRefreshSec,
  48.           readingPane: s.readingPane === true,
  49.         };
  50.       }
  51.     } catch (e) {}
  52.     return Object.assign({}, LET_DEFAULTS);
  53.   }
  54.  
  55.   function saveSettings(obj) {
  56.     localStorage.setItem(LET_STORAGE_KEY, JSON.stringify(obj));
  57.   }
  58.  
  59.   var letSettings = loadSettings();
  60.  
  61.   // Dynamic style element for user-configurable font sizes
  62.   var dynStyle = document.createElement("style");
  63.   dynStyle.id = "let-dynamic-styles";
  64.   document.documentElement.appendChild(dynStyle);
  65.  
  66.   function applyDynamicStyles() {
  67.     var titleSize = letSettings.titleFontSize;
  68.     var metaSize = letSettings.metaFontSize;
  69.     var titleLine = Math.round(titleSize * 1.55);
  70.     var metaLine = Math.round(metaSize * 1.55);
  71.     var css =
  72.       ".let-title { font-size: " +
  73.       (titleSize - 1) +
  74.       "px !important; line-height: " +
  75.       titleLine +
  76.       "px !important; }" +
  77.       ".let-title a { font-size: " +
  78.       titleSize +
  79.       "px !important; }" +
  80.       ".let-meta { font-size: " +
  81.       metaSize +
  82.       "px !important; line-height: " +
  83.       Math.max(metaLine, 20) +
  84.       "px !important; }" +
  85.       ".let-meta span { font-size: " +
  86.       metaSize +
  87.       "px !important; }" +
  88.       ".let-meta a { font-size: " +
  89.       metaSize +
  90.       "px !important; }";
  91.     var isProfilePage = /\/profile\//i.test(window.location.pathname);
  92.     var isDiscussionPage = /\/discussion\/\d+/i.test(window.location.pathname);
  93.     var isListingPage = !isProfilePage && !isDiscussionPage;
  94.     if (!IS_IFRAME && letSettings.readingPane && isListingPage) {
  95.       css += ".Column.PanelColumn { display: none !important; }";
  96.       css +=
  97.         ".Column.ContentColumn { border-right: none !important; padding-right: 0 !important; margin-right: 0 !important; flex: 0 0 50% !important; max-width: 50% !important; overflow-y: auto !important; overflow-x: hidden !important; height: calc(100vh - 50px) !important; }";
  98.       css +=
  99.         "ul.DataList.Discussions > li.ItemDiscussion { flex-wrap: wrap !important; }";
  100.       css +=
  101.         ".let-meta { flex-wrap: wrap !important; white-space: normal !important; }";
  102.       css +=
  103.         "#let-reading-pane { display: block !important; flex: 0 0 50% !important; max-width: 50% !important; }";
  104.     } else if (letSettings.hideSidebar && !isProfilePage) {
  105.       css += ".Column.PanelColumn { display: none !important; }";
  106.       css +=
  107.         ".Column.ContentColumn { border-right: none !important; padding-right: 0 !important; margin-right: 0 !important; }";
  108.     }
  109.     if (!letSettings.readingPane || !isListingPage) {
  110.       css += "#let-reading-pane { display: none !important; }";
  111.     }
  112.     dynStyle.textContent = css;
  113.   }
  114.  
  115.   applyDynamicStyles();
  116.  
  117.   // Reading pane functions
  118.   function createReadingPane() {
  119.     if (document.getElementById("let-reading-pane")) return;
  120.     var row = document.querySelector("#Body .Row");
  121.     if (!row) return;
  122.     var pane = document.createElement("div");
  123.     pane.id = "let-reading-pane";
  124.     pane.innerHTML =
  125.       '<div id="let-reading-pane-placeholder">Click a post to read it here</div>';
  126.     row.appendChild(pane);
  127.   }
  128.  
  129.   function loadInReadingPane(url) {
  130.     var pane = document.getElementById("let-reading-pane");
  131.     if (!pane) return;
  132.     pane.innerHTML = '<div id="let-reading-pane-placeholder">Loading...</div>';
  133.     fetch(url, { credentials: "same-origin" })
  134.       .then(function (r) {
  135.         return r.text();
  136.       })
  137.       .then(function (html) {
  138.         var doc = new DOMParser().parseFromString(html, "text/html");
  139.         var content =
  140.           doc.querySelector(".Column.ContentColumn") ||
  141.           doc.querySelector("main.Content.MainContent") ||
  142.           doc.querySelector("#Body");
  143.         if (!content) {
  144.           pane.innerHTML =
  145.             '<div id="let-reading-pane-placeholder">Failed to load content</div>';
  146.           return;
  147.         }
  148.         content
  149.           .querySelectorAll(".Column.PanelColumn, aside.Panel, .Panel-main")
  150.           .forEach(function (el) {
  151.             el.remove();
  152.           });
  153.         pane.innerHTML = "";
  154.         var wrapper = document.createElement("div");
  155.         wrapper.className = "let-reading-content";
  156.         wrapper.innerHTML = content.innerHTML;
  157.         pane.appendChild(wrapper);
  158.         interceptPaneLinks(pane);
  159.       })
  160.       .catch(function () {
  161.         pane.innerHTML =
  162.           '<div id="let-reading-pane-placeholder">Failed to load</div>';
  163.       });
  164.   }
  165.  
  166.   function interceptPaneLinks(pane) {
  167.     pane.addEventListener("click", function (e) {
  168.       var a = e.target.closest("a");
  169.       if (!a) return;
  170.       var href = a.getAttribute("href");
  171.       if (!href || href.startsWith("#") || href.startsWith("javascript:"))
  172.         return;
  173.       e.preventDefault();
  174.       e.stopPropagation();
  175.       try {
  176.         var linkUrl = new URL(href, location.origin);
  177.         if (linkUrl.origin !== location.origin) {
  178.           window.open(href, "_blank");
  179.         } else {
  180.           loadInReadingPane(linkUrl.href);
  181.         }
  182.       } catch (ex) {
  183.         window.open(href, "_blank");
  184.       }
  185.     });
  186.     pane.addEventListener("submit", function (e) {
  187.       var form = e.target.closest("form");
  188.       if (!form) return;
  189.       e.preventDefault();
  190.       e.stopPropagation();
  191.       var action = form.action || location.href;
  192.       var method = (form.method || "GET").toUpperCase();
  193.       var data = new FormData(form);
  194.       var opts = { credentials: "same-origin", method: method };
  195.       if (method === "POST") {
  196.         opts.body = data;
  197.       } else {
  198.         action +=
  199.           (action.indexOf("?") > -1 ? "&" : "?") +
  200.           new URLSearchParams(data).toString();
  201.       }
  202.       fetch(action, opts)
  203.         .then(function (r) {
  204.           if (r.redirected) {
  205.             loadInReadingPane(r.url);
  206.           } else {
  207.             loadInReadingPane(action);
  208.           }
  209.         })
  210.         .catch(function () {
  211.           loadInReadingPane(action);
  212.         });
  213.     });
  214.   }
  215.  
  216.   function toggleReadingPane(enabled) {
  217.     if (enabled) {
  218.       createReadingPane();
  219.     } else {
  220.       var pane = document.getElementById("let-reading-pane");
  221.       if (pane) {
  222.         pane.innerHTML =
  223.           '<div id="let-reading-pane-placeholder">Click a post to read it here</div>';
  224.       }
  225.     }
  226.   }
  227.  
  228.   // Intercept post title clicks for reading pane
  229.   if (!IS_IFRAME) {
  230.     document.addEventListener("click", function (e) {
  231.       if (!letSettings.readingPane) return;
  232.       var link = e.target.closest(".let-title a");
  233.       if (!link) return;
  234.       e.preventDefault();
  235.       e.stopPropagation();
  236.       document.querySelectorAll("li.let-reading-active").forEach(function (li) {
  237.         li.classList.remove("let-reading-active");
  238.       });
  239.       var li = link.closest("li");
  240.       if (li) li.classList.add("let-reading-active");
  241.       loadInReadingPane(link.href);
  242.     });
  243.   }
  244.  
  245.   const style = document.createElement("style");
  246.   style.textContent = `
  247.         /* Override native dark theme's green #Head and #Frame backgrounds */
  248.         #Frame {
  249.             background-color: #080810 !important;
  250.             display: flex !important;
  251.             flex-direction: column !important;
  252.             min-height: 100vh !important;
  253.         }
  254.         #Body {
  255.             flex: 1 !important;
  256.         }
  257.  
  258.         /* Dark theme base - force on everything */
  259.         html, body,
  260.         #Frame,
  261.         #Body,
  262.         #Foot,
  263.         #Body .Row,
  264.         .Column.ContentColumn,
  265.         #Content,
  266.         .Section,
  267.         .Section-Content,
  268.         .DataList,
  269.         ul.DataList,
  270.         .CategoryList,
  271.         .DataTable,
  272.         .PageBox {
  273.             background: #080810 !important;
  274.             background-color: #080810 !important;
  275.             color: #ccc !important;
  276.         }
  277.  
  278.         /* Full width - override all constraining containers */
  279.         #Frame,
  280.         #Body,
  281.         #Head,
  282.         #Foot,
  283.         #Head .Row,
  284.         #Body .Row,
  285.         #Foot .Row,
  286.         .Row,
  287.         .Column.ContentColumn,
  288.         #Content {
  289.             max-width: none !important;
  290.             width: 100% !important;
  291.             box-sizing: border-box !important;
  292.         }
  293.         #Frame {
  294.             margin: 0 !important;
  295.             box-shadow: none !important;
  296.             border: none !important;
  297.         }
  298.         html {
  299.             overflow-x: hidden !important;
  300.         }
  301.         .Row {
  302.             padding: 0 10px !important;
  303.         }
  304.         #Body .Row {
  305.             display: flex !important;
  306.         }
  307.         .Column.PanelColumn {
  308.             width: 220px !important;
  309.             min-width: 220px !important;
  310.             max-width: 220px !important;
  311.             flex-shrink: 0 !important;
  312.             order: 2 !important;
  313.         }
  314.         .Column.ContentColumn {
  315.             flex: 1 !important;
  316.             min-width: 0 !important;
  317.             order: 1 !important;
  318.             border-right: 1px solid #373741 !important;
  319.             padding-right: 14px !important;
  320.             margin-right: 10px !important;
  321.         }
  322.  
  323.         /* Dark header - single green bottom border only */
  324.         #Head {
  325.             background: #15151b !important;
  326.             border: none !important;
  327.             border-bottom: 1px solid #373741 !important;
  328.             padding: 0 !important;
  329.             margin: 0 !important;
  330.             position: sticky !important;
  331.             top: 0 !important;
  332.             z-index: 1000 !important;
  333.         }
  334.         /* Kill ALL borders inside #Head */
  335.         #Head .Row,
  336.         #Head .Row > *,
  337.         #Head .Title,
  338.         #Head .SiteMenu,
  339.         #Head .SiteMenu li,
  340.         #Head .SiteMenu ul,
  341.         #Head .MeBox,
  342.         #Head #let-head-mebox {
  343.             border: none !important;
  344.             border-top: none !important;
  345.             border-bottom: none !important;
  346.             background: transparent !important;
  347.         }
  348.         /* Kill borders/gaps on elements right below header */
  349.         #Body,
  350.         #Body > .Row,
  351.         #Body > .Row:before,
  352.         #Body > .Row:after {
  353.             border-top: none !important;
  354.         }
  355.         #Body,
  356.         #Body > .Row,
  357.         .Column.ContentColumn,
  358.         #Content,
  359.         .Section,
  360.         .Section-Content {
  361.             padding-top: 0 !important;
  362.             margin-top: 0 !important;
  363.         }
  364.         /* Kill native theme's green borders on Headings/Tabs */
  365.         div.Headings,
  366.         div.Tabs,
  367.         .DiscussionsTabs,
  368.         .Column.ContentColumn > div.Tabs,
  369.         .Column.ContentColumn > div.Headings {
  370.             border-bottom: none !important;
  371.             border-top: none !important;
  372.             margin-bottom: 0 !important;
  373.         }
  374.         #Head a {
  375.             color: #ccc !important;
  376.         }
  377.         #Head a:hover {
  378.             color: #22ad5d !important;
  379.         }
  380.         /* Nav link colors to match LES */
  381.         #Head .SiteMenu a {
  382.             color: #22ad5d !important;
  383.             font-size: 14px !important;
  384.             font-weight: 400 !important;
  385.         }
  386.         #Head .SiteMenu a:hover {
  387.             color: orange !important;
  388.         }
  389.         /* Logo styling to match LES */
  390.         #Head .Title a {
  391.             font-size: 20px !important;
  392.             font-weight: 700 !important;
  393.             letter-spacing: 2px !important;
  394.             color: #fff !important;
  395.             text-transform: uppercase !important;
  396.         }
  397.         /* Center header content vertically */
  398.         #Head .Row {
  399.             display: flex !important;
  400.             align-items: center !important;
  401.             padding: 8px 10px !important;
  402.             border: none !important;
  403.             background: #15151b !important;
  404.         }
  405.         /* Hide search in header, show in sidebar */
  406.         #Head .SiteSearch {
  407.             display: none !important;
  408.         }
  409.         .Column.PanelColumn > .SiteSearch {
  410.             padding: 4px 8px !important;
  411.             margin-bottom: 4px !important;
  412.             display: block !important;
  413.             visibility: visible !important;
  414.             opacity: 1 !important;
  415.             height: auto !important;
  416.             overflow: visible !important;
  417.         }
  418.         .SiteSearch div {
  419.             display: flex !important;
  420.         }
  421.         .SiteSearch input.InputBox {
  422.             font-size: 11px !important;
  423.             padding: 2px 6px !important;
  424.             flex: 1 !important;
  425.             box-sizing: border-box !important;
  426.             border: 1px solid #373741 !important;
  427.             background: #222225 !important;
  428.             color: #ccc !important;
  429.         }
  430.         .SiteSearch .Button {
  431.             font-size: 10px !important;
  432.             padding: 2px 6px !important;
  433.             filter: none !important;
  434.         }
  435.  
  436.         /* MeBox icons in header bar */
  437.         #let-head-mebox {
  438.             display: inline-flex !important;
  439.             align-items: center !important;
  440.             gap: 8px !important;
  441.             margin-left: auto !important;
  442.             padding: 0 !important;
  443.             margin-right: 0 !important;
  444.             position: relative !important;
  445.         }
  446.         #let-head-mebox a {
  447.             color: #ccc !important;
  448.             padding: 2px !important;
  449.         }
  450.         #let-head-mebox a:hover {
  451.             color: #22ad5d !important;
  452.         }
  453.         #let-head-mebox .PhotoWrap {
  454.             width: 20px !important;
  455.             height: 20px !important;
  456.             padding: 0 !important;
  457.             margin: 0 !important;
  458.         }
  459.         #let-head-mebox .PhotoWrap img {
  460.             width: 20px !important;
  461.             height: 20px !important;
  462.             border-radius: 3px !important;
  463.         }
  464.         #let-head-mebox .Alert {
  465.             font-size: 9px !important;
  466.             padding: 0 3px !important;
  467.         }
  468.         #let-head-mebox a[title="Account Options"],
  469.         #let-head-mebox > a.MeButton:not([title]) {
  470.             display: none !important;
  471.         }
  472.  
  473.         /* Custom flyout panels */
  474.         .let-flyout-wrap {
  475.             position: relative !important;
  476.             display: inline-flex !important;
  477.             align-items: center !important;
  478.         }
  479.         .let-flyout {
  480.             display: none;
  481.             position: absolute;
  482.             top: 100%;
  483.             left: 0;
  484.             width: 350px;
  485.             max-height: 400px;
  486.             overflow-y: auto;
  487.             background: #1a1a22 !important;
  488.             border: 1px solid #373741 !important;
  489.             border-radius: 4px !important;
  490.             box-shadow: 0 4px 12px rgba(0,0,0,0.5) !important;
  491.             z-index: 10001 !important;
  492.             font-size: 12px !important;
  493.             color: #ccc !important;
  494.             padding: 0 !important;
  495.         }
  496.         .let-flyout.let-flyout-open {
  497.             display: block !important;
  498.         }
  499.         .let-flyout a {
  500.             color: #22ad5d !important;
  501.             font-size: 12px !important;
  502.         }
  503.         .let-flyout a:hover {
  504.             color: orange !important;
  505.         }
  506.         .let-flyout .Item,
  507.         .let-flyout li {
  508.             padding: 8px 12px !important;
  509.             border-bottom: 1px solid #252530 !important;
  510.             list-style: none !important;
  511.             background: transparent !important;
  512.         }
  513.         .let-flyout .Item:hover,
  514.         .let-flyout li:hover {
  515.             background: #222230 !important;
  516.         }
  517.         .let-flyout .PhotoWrap img {
  518.             width: 32px !important;
  519.             height: 32px !important;
  520.             border-radius: 3px !important;
  521.         }
  522.         .let-flyout-footer {
  523.             padding: 8px 12px !important;
  524.             text-align: center !important;
  525.             border-top: 1px solid #373741 !important;
  526.         }
  527.         .let-flyout-loading {
  528.             padding: 16px !important;
  529.             text-align: center !important;
  530.             color: #666 !important;
  531.         }
  532.  
  533.         /* Settings flyout */
  534.         #let-hostc-banner .let-flyout-wrap {
  535.             margin-left: auto !important;
  536.         }
  537.         .let-settings-link {
  538.             color: #555 !important;
  539.             font-size: 10px !important;
  540.             cursor: pointer !important;
  541.             text-decoration: none !important;
  542.             flex-shrink: 0 !important;
  543.         }
  544.         .let-settings-link:hover {
  545.             color: #22ad5d !important;
  546.         }
  547.         .let-settings-flyout {
  548.             width: 260px !important;
  549.             padding: 12px 16px !important;
  550.             max-height: none !important;
  551.             z-index: 100000 !important;
  552.             left: auto !important;
  553.             right: 0 !important;
  554.         }
  555.         .let-settings-flyout label {
  556.             display: flex !important;
  557.             align-items: center !important;
  558.             justify-content: space-between !important;
  559.             color: #aaa !important;
  560.             font-size: 11px !important;
  561.             margin-bottom: 8px !important;
  562.         }
  563.         .let-settings-flyout input[type="range"] {
  564.             width: 100px !important;
  565.             margin: 0 8px !important;
  566.             accent-color: #22ad5d !important;
  567.         }
  568.         .let-settings-flyout .let-size-val {
  569.             color: #ccc !important;
  570.             font-size: 11px !important;
  571.             min-width: 30px !important;
  572.             text-align: right !important;
  573.         }
  574.         .let-settings-flyout .let-settings-reset {
  575.             display: block !important;
  576.             margin-top: 8px !important;
  577.             padding: 4px 10px !important;
  578.             background: #252530 !important;
  579.             border: 1px solid #373741 !important;
  580.             color: #888 !important;
  581.             font-size: 10px !important;
  582.             border-radius: 3px !important;
  583.             cursor: pointer !important;
  584.             text-align: center !important;
  585.             width: 100% !important;
  586.         }
  587.         .let-settings-flyout .let-settings-reset:hover {
  588.             background: #333340 !important;
  589.             color: #ccc !important;
  590.         }
  591.  
  592.         /* Hide original sidebar MeBox after move */
  593.         .Column.PanelColumn .MeBox.MeBox-SignedIn,
  594.         .Column.PanelColumn .MeBox,
  595.         .Column.PanelColumn .Box.MeBox,
  596.         .Panel .MeBox {
  597.             display: none !important;
  598.         }
  599.  
  600.         /* Dark navigation */
  601.         .SiteMenu,
  602.         .SiteSearch,
  603.         .MeBox,
  604.         .Breadcrumbs {
  605.             background: transparent !important;
  606.         }
  607.  
  608.         /* Dark content area */
  609.         .Content,
  610.         .DataList,
  611.         .MessageList,
  612.         .Item {
  613.             background: transparent !important;
  614.             color: #ccc !important;
  615.         }
  616.  
  617.         /* Hide sidebar ads */
  618.         .Column.PanelColumn [id^="bsap_"],
  619.         .Column.PanelColumn [class*="bsap"],
  620.         .Column.PanelColumn [class*="bsa_"],
  621.         .Column.PanelColumn a[rel="nofollow noopener"][target="_blank"] {
  622.             display: none !important;
  623.         }
  624.  
  625.         /* Dark sidebar */
  626.         .Column.PanelColumn {
  627.             background: #15151b !important;
  628.             color: #aaa !important;
  629.         }
  630.         .Column.PanelColumn h4,
  631.         .PanelInfo h4 {
  632.             color: #e0e0e0 !important;
  633.         }
  634.         .Column.PanelColumn a {
  635.             color: #22ad5d !important;
  636.         }
  637.         .Column.PanelColumn a:hover {
  638.             color: orange !important;
  639.         }
  640.  
  641.         /* Dark footer - condensed 3-column layout */
  642.         #Foot {
  643.             background: #15151b !important;
  644.             border-top: 1px solid #373741 !important;
  645.             color: #666 !important;
  646.             padding: 0 !important;
  647.             margin: 0 !important;
  648.             min-height: 0 !important;
  649.             height: auto !important;
  650.             line-height: 1 !important;
  651.         }
  652.         #Foot div {
  653.             background: #15151b !important;
  654.             color: #666 !important;
  655.             border: none !important;
  656.             margin: 0 !important;
  657.             padding: 2px 10px !important;
  658.             display: flex !important;
  659.             align-items: center !important;
  660.             height: auto !important;
  661.             min-height: 0 !important;
  662.             width: 100% !important;
  663.         }
  664.         #Foot a {
  665.             color: #888 !important;
  666.         }
  667.         .let-foot-left,
  668.         .let-foot-center,
  669.         .let-foot-right {
  670.             flex: 1 !important;
  671.             font-size: 11px !important;
  672.         }
  673.         .let-foot-left {
  674.             text-align: left !important;
  675.         }
  676.         .let-foot-center {
  677.             text-align: center !important;
  678.         }
  679.         .let-foot-center a {
  680.             color: #888 !important;
  681.             margin: 0 6px !important;
  682.         }
  683.         .let-foot-center a:hover {
  684.             color: #22ad5d !important;
  685.         }
  686.         .let-foot-right {
  687.             text-align: right !important;
  688.         }
  689.  
  690.         /* Hide breadcrumb "Home" line — wastes a full row */
  691.         .BreadcrumbsWrapper,
  692.         .Breadcrumbs,
  693.         #Body .HomeCrumb,
  694.         .Column.ContentColumn > .Breadcrumbs {
  695.             display: none !important;
  696.         }
  697.  
  698.         /* Compact sidebar user profile box */
  699.         .MeBox .PhotoWrap,
  700.         .Column.PanelColumn .PhotoWrap {
  701.             width: 32px !important;
  702.             height: 32px !important;
  703.         }
  704.         .MeBox .PhotoWrap img,
  705.         .Column.PanelColumn .PhotoWrap img {
  706.             width: 32px !important;
  707.             height: 32px !important;
  708.         }
  709.         .Column.PanelColumn .MeBox {
  710.             padding: 4px !important;
  711.             margin-bottom: 4px !important;
  712.         }
  713.         .Column.PanelColumn .MeBox h3 {
  714.             font-size: 12px !important;
  715.             margin: 0 !important;
  716.         }
  717.         .Column.PanelColumn .MeBox .WhoIs {
  718.             font-size: 11px !important;
  719.         }
  720.  
  721.         /* Compact sidebar sections */
  722.         .Column.PanelColumn .Box,
  723.         .Column.PanelColumn .PanelInfo,
  724.         .Column.PanelColumn .BoxButtons {
  725.             padding: 4px 8px !important;
  726.             margin-bottom: 2px !important;
  727.         }
  728.         .Column.PanelColumn h4 {
  729.             font-size: 11px !important;
  730.             margin: 2px 0 !important;
  731.             padding: 0 !important;
  732.         }
  733.         .Column.PanelColumn .PanelInfo li {
  734.             padding: 1px 0 !important;
  735.             font-size: 11px !important;
  736.         }
  737.         .Column.PanelColumn .PanelInfo li a {
  738.             font-size: 11px !important;
  739.         }
  740.  
  741.         /* Compact sidebar buttons (New Discussion, etc.) */
  742.         .Column.PanelColumn .BigButton,
  743.         .Column.PanelColumn .Button.Action.Primary {
  744.             padding: 4px 8px !important;
  745.             font-size: 11px !important;
  746.             margin-bottom: 2px !important;
  747.         }
  748.  
  749.         /* Compact the filter/view dropdown area */
  750.         .BoxFilter,
  751.         .FilterMenu,
  752.         .DiscussionsTabs {
  753.             padding: 2px 0 !important;
  754.             margin: 0 !important;
  755.             font-size: 11px !important;
  756.         }
  757.  
  758.         /* Tabs/breadcrumbs full width */
  759.         .Tabs,
  760.         .PageControls,
  761.         .FilterMenu,
  762.         #content {
  763.             max-width: none !important;
  764.             width: 100% !important;
  765.             background: transparent !important;
  766.         }
  767.         .Tabs a,
  768.         .PageControls a {
  769.             color: #ccc !important;
  770.             font-size: 11px !important;
  771.         }
  772.         .Tabs a:hover,
  773.         .PageControls a:hover {
  774.             color: #22ad5d !important;
  775.         }
  776.         .Tabs .Active a,
  777.         .Tabs .Active {
  778.             color: #22ad5d !important;
  779.             border-color: #22ad5d !important;
  780.         }
  781.  
  782.         /* Dark form elements */
  783.         input, textarea, select, .TextBoxWrapper {
  784.             background: #222225 !important;
  785.             color: #ccc !important;
  786.             border-color: #373741 !important;
  787.         }
  788.  
  789.         /* Dark buttons */
  790.         .Button, .btn, .Btn {
  791.             background: #222225 !important;
  792.             color: #ccc !important;
  793.             border-color: #373741 !important;
  794.         }
  795.         .Button.Primary, .btn-primary {
  796.             background: #22ad5d !important;
  797.             color: #fff !important;
  798.             border-color: #22ad5d !important;
  799.         }
  800.  
  801.         /* host_c latest post banner */
  802.         #let-hostc-banner {
  803.             display: flex !important;
  804.             align-items: center !important;
  805.             background: #15151b !important;
  806.             padding: 2px 10px !important;
  807.             font-size: 11px !important;
  808.             color: #888 !important;
  809.             border-bottom: 1px solid #222225 !important;
  810.             gap: 6px !important;
  811.             white-space: nowrap !important;
  812.             overflow: visible !important;
  813.         }
  814.         #let-hostc-banner .let-hc-label {
  815.             color: #888 !important;
  816.             flex-shrink: 0 !important;
  817.         }
  818.         #let-hostc-banner .let-hc-label a {
  819.             color: #22ad5d !important;
  820.             font-weight: 700 !important;
  821.         }
  822.         #let-hostc-banner .let-hc-title {
  823.             color: #ccc !important;
  824.             overflow: hidden !important;
  825.             text-overflow: ellipsis !important;
  826.         }
  827.         #let-hostc-banner .let-hc-title a {
  828.             color: #ccc !important;
  829.         }
  830.         #let-hostc-banner .let-hc-title a:hover {
  831.             color: #22ad5d !important;
  832.         }
  833.         #let-hostc-banner .let-hc-time {
  834.             flex-shrink: 0 !important;
  835.             color: #666 !important;
  836.         }
  837.         #let-hostc-banner .let-hc-toggle {
  838.             display: flex !important;
  839.             align-items: center !important;
  840.             gap: 4px !important;
  841.             flex-shrink: 0 !important;
  842.             cursor: pointer !important;
  843.             color: #555 !important;
  844.             font-size: 10px !important;
  845.         }
  846.         #let-hostc-banner .let-hc-toggle:hover {
  847.             color: #888 !important;
  848.         }
  849.         #let-hostc-banner .let-hc-switch {
  850.             position: relative !important;
  851.             width: 24px !important;
  852.             height: 12px !important;
  853.             background: #333 !important;
  854.             border-radius: 6px !important;
  855.             transition: background 0.2s !important;
  856.         }
  857.         #let-hostc-banner .let-hc-switch.on {
  858.             background: #22ad5d !important;
  859.         }
  860.         #let-hostc-banner .let-hc-switch::after {
  861.             content: '' !important;
  862.             position: absolute !important;
  863.             top: 1px !important;
  864.             left: 1px !important;
  865.             width: 10px !important;
  866.             height: 10px !important;
  867.             background: #ccc !important;
  868.             border-radius: 50% !important;
  869.             transition: left 0.2s !important;
  870.         }
  871.         #let-hostc-banner .let-hc-switch.on::after {
  872.             left: 13px !important;
  873.         }
  874.         #let-hostc-banner .let-hc-interval {
  875.             width: 28px !important;
  876.             background: #222 !important;
  877.             border: 1px solid #444 !important;
  878.             color: #ccc !important;
  879.             font-size: 10px !important;
  880.             padding: 0 2px !important;
  881.             text-align: center !important;
  882.             border-radius: 3px !important;
  883.             height: 14px !important;
  884.             line-height: 14px !important;
  885.         }
  886.         #let-hostc-banner .let-hc-interval:focus {
  887.             border-color: #22ad5d !important;
  888.             outline: none !important;
  889.         }
  890.         #let-hostc-banner .let-hc-unit {
  891.             color: #555 !important;
  892.             font-size: 10px !important;
  893.         }
  894.         #let-hostc-banner .let-hc-divider {
  895.             width: 1px !important;
  896.             height: 10px !important;
  897.             background: #444 !important;
  898.             margin: 0 4px !important;
  899.             flex-shrink: 0 !important;
  900.         }
  901.         #let-hostc-banner .let-hc-updated {
  902.             color: #555 !important;
  903.             font-size: 10px !important;
  904.             flex-shrink: 0 !important;
  905.         }
  906.  
  907.         /* host_c new post toast notification */
  908.         .let-hc-toast {
  909.             position: fixed !important;
  910.             top: 60px !important;
  911.             right: 20px !important;
  912.             background: #1a1a22 !important;
  913.             border: 1px solid #22ad5d !important;
  914.             border-radius: 6px !important;
  915.             padding: 12px 16px !important;
  916.             max-width: 350px !important;
  917.             z-index: 100001 !important;
  918.             box-shadow: 0 4px 16px rgba(0,0,0,0.6) !important;
  919.             font-size: 12px !important;
  920.             color: #ccc !important;
  921.             animation: let-toast-in 0.3s ease-out !important;
  922.             cursor: pointer !important;
  923.         }
  924.         .let-hc-toast:hover {
  925.             border-color: orange !important;
  926.         }
  927.         .let-hc-toast-label {
  928.             color: #22ad5d !important;
  929.             font-weight: 700 !important;
  930.             font-size: 11px !important;
  931.             margin-bottom: 4px !important;
  932.             display: block !important;
  933.         }
  934.         .let-hc-toast-title {
  935.             color: #e0e0e0 !important;
  936.             font-weight: 700 !important;
  937.             display: block !important;
  938.             margin-bottom: 2px !important;
  939.         }
  940.         .let-hc-toast-title a {
  941.             color: #e0e0e0 !important;
  942.             text-decoration: none !important;
  943.         }
  944.         .let-hc-toast-title a:hover {
  945.             color: #22ad5d !important;
  946.         }
  947.         .let-hc-toast-time {
  948.             color: #666 !important;
  949.             font-size: 10px !important;
  950.         }
  951.         .let-hc-toast-close {
  952.             position: absolute !important;
  953.             top: 4px !important;
  954.             right: 8px !important;
  955.             color: #555 !important;
  956.             cursor: pointer !important;
  957.             font-size: 14px !important;
  958.             background: none !important;
  959.             border: none !important;
  960.             padding: 2px !important;
  961.         }
  962.         .let-hc-toast-close:hover {
  963.             color: #ccc !important;
  964.         }
  965.         @keyframes let-toast-in {
  966.             from { opacity: 0; transform: translateX(50px); }
  967.             to { opacity: 1; transform: translateX(0); }
  968.         }
  969.  
  970.         /* Pager - right-aligned, compact */
  971.         .PageControls {
  972.             text-align: right !important;
  973.             padding: 2px 0 !important;
  974.             margin: 0 !important;
  975.         }
  976.         .Pager {
  977.             padding: 0 !important;
  978.             margin: 0 !important;
  979.         }
  980.         .Pager a,
  981.         .Pager span {
  982.             background: #222225 !important;
  983.             color: #ccc !important;
  984.             border-color: #373741 !important;
  985.             font-size: 10px !important;
  986.             padding: 1px 5px !important;
  987.             min-width: 0 !important;
  988.         }
  989.         .Pager .Active,
  990.         .Pager .Highlight {
  991.             background: #22ad5d !important;
  992.             color: #fff !important;
  993.         }
  994.  
  995.         /* Compact page controls row */
  996.         .PageControls {
  997.             padding: 2px 0 !important;
  998.             margin: 0 !important;
  999.             display: flex !important;
  1000.             align-items: center !important;
  1001.             justify-content: flex-end !important;
  1002.         }
  1003.         .let-sort-wrap {
  1004.             margin-right: 8px !important;
  1005.             display: flex !important;
  1006.             align-items: center !important;
  1007.             gap: 3px !important;
  1008.         }
  1009.         .let-sort-select {
  1010.             display: inline-block !important;
  1011.             visibility: visible !important;
  1012.             opacity: 1 !important;
  1013.             width: auto !important;
  1014.             height: auto !important;
  1015.             max-height: none !important;
  1016.             overflow: visible !important;
  1017.             background: #222225 !important;
  1018.             color: #ccc !important;
  1019.             border: 1px solid #373741 !important;
  1020.             font-size: 10px !important;
  1021.             padding: 1px 4px !important;
  1022.             border-radius: 3px !important;
  1023.             cursor: pointer !important;
  1024.             -webkit-appearance: menulist !important;
  1025.             appearance: menulist !important;
  1026.         }
  1027.         .let-sort-dir {
  1028.             display: inline-block !important;
  1029.             visibility: visible !important;
  1030.             opacity: 1 !important;
  1031.             width: auto !important;
  1032.             height: auto !important;
  1033.             background: #222225 !important;
  1034.             color: #ccc !important;
  1035.             border: 1px solid #373741 !important;
  1036.             font-size: 10px !important;
  1037.             padding: 1px 5px !important;
  1038.             border-radius: 3px !important;
  1039.             cursor: pointer !important;
  1040.             line-height: 1 !important;
  1041.         }
  1042.         .let-sort-select:hover, .let-sort-dir:hover {
  1043.             border-color: #22ad5d !important;
  1044.         }
  1045.  
  1046.         /* Reading pane */
  1047.         #let-reading-pane {
  1048.             display: none;
  1049.             order: 3 !important;
  1050.             height: calc(100vh - 50px) !important;
  1051.             position: sticky !important;
  1052.             top: 50px !important;
  1053.             border: none !important;
  1054.             border-left: 1px solid #373741 !important;
  1055.             background: #080810 !important;
  1056.             box-sizing: border-box !important;
  1057.             overflow-y: auto !important;
  1058.             overflow-x: hidden !important;
  1059.         }
  1060.         .let-reading-content {
  1061.             padding: 10px !important;
  1062.             color: #ccc !important;
  1063.         }
  1064.         .let-reading-content a {
  1065.             color: #22ad5d !important;
  1066.         }
  1067.         .let-reading-content img {
  1068.             max-width: 100% !important;
  1069.             height: auto !important;
  1070.         }
  1071.         #let-reading-pane-placeholder {
  1072.             display: flex !important;
  1073.             align-items: center !important;
  1074.             justify-content: center !important;
  1075.             height: 100% !important;
  1076.             color: #555 !important;
  1077.             font-size: 14px !important;
  1078.             font-style: italic !important;
  1079.         }
  1080.         li.let-reading-active {
  1081.             background: #1a1a28 !important;
  1082.             border-left: 3px solid #22ad5d !important;
  1083.         }
  1084.  
  1085.         /* Dark category headers */
  1086.         .CategoryHeading,
  1087.         h2.H {
  1088.             color: #e0e0e0 !important;
  1089.         }
  1090.  
  1091.         /* Generic link colors */
  1092.         a {
  1093.             color: #22ad5d !important;
  1094.         }
  1095.         a:hover {
  1096.             color: orange !important;
  1097.         }
  1098.  
  1099.         /* Fix green-on-green button text */
  1100.         .Button.Primary,
  1101.         .Button.Primary a,
  1102.         .BigButton,
  1103.         .BigButton a,
  1104.         .btn-primary a,
  1105.         a.Button.Primary,
  1106.         a.BigButton {
  1107.             color: #fff !important;
  1108.         }
  1109.  
  1110.         /* Kill all default borders/shadows on list items */
  1111.         .DataList li,
  1112.         .DataList .Item,
  1113.         ul.DataList > li,
  1114.         ul.DataList.Discussions > li,
  1115.         ul.DataList.Discussions > li.ItemDiscussion,
  1116.         li.ItemDiscussion {
  1117.             border: none !important;
  1118.             border-top: none !important;
  1119.             border-bottom: none !important;
  1120.             box-shadow: none !important;
  1121.             outline: none !important;
  1122.         }
  1123.  
  1124.         /* Compact discussion rows */
  1125.         ul.DataList.Discussions {
  1126.             padding: 0 !important;
  1127.             margin: 0 !important;
  1128.             overflow: visible !important;
  1129.         }
  1130.         .DataList,
  1131.         .Section,
  1132.         .Section-Content,
  1133.         #Content,
  1134.         .Column.ContentColumn {
  1135.             overflow: visible !important;
  1136.         }
  1137.         ul.DataList.Discussions > li.ItemDiscussion {
  1138.             display: flex !important;
  1139.             flex-wrap: nowrap !important;
  1140.             align-items: center !important;
  1141.             padding: 1px 6px !important;
  1142.             gap: 0 6px !important;
  1143.             min-height: 0 !important;
  1144.             border-bottom: 1px solid #151518 !important;
  1145.             border-top: none !important;
  1146.             background: transparent !important;
  1147.             font-size: 13px !important;
  1148.             line-height: 20px !important;
  1149.             overflow: visible !important;
  1150.         }
  1151.  
  1152.         /* Restructured row: .let-avatar, .let-title, .let-meta, .let-bookmark */
  1153.         .let-avatar {
  1154.             flex-shrink: 0 !important;
  1155.             width: 16px !important;
  1156.             height: 16px !important;
  1157.             order: 1 !important;
  1158.         }
  1159.         .let-avatar img {
  1160.             width: 16px !important;
  1161.             height: 16px !important;
  1162.             border-radius: 3px !important;
  1163.             vertical-align: middle !important;
  1164.         }
  1165.  
  1166.         .let-title {
  1167.             order: 2 !important;
  1168.             white-space: nowrap !important;
  1169.             overflow: hidden !important;
  1170.             text-overflow: ellipsis !important;
  1171.             flex: 1 1 0 !important;
  1172.             min-width: 100px !important;
  1173.         }
  1174.         .let-title a {
  1175.             color: inherit !important;
  1176.         }
  1177.  
  1178.         .let-meta {
  1179.             order: 3 !important;
  1180.             display: flex !important;
  1181.             align-items: center !important;
  1182.             gap: 5px !important;
  1183.             white-space: nowrap !important;
  1184.             flex-shrink: 0 !important;
  1185.             color: #aaa !important;
  1186.         }
  1187.         .let-meta .let-tag {
  1188.             font-size: 9px !important;
  1189.             padding: 0 3px !important;
  1190.             border-radius: 2px !important;
  1191.             font-weight: 700 !important;
  1192.         }
  1193.         .let-meta .Tag-Announcement {
  1194.             background: #aaa !important;
  1195.             color: #000 !important;
  1196.         }
  1197.         .let-meta .let-cat {
  1198.             border: 1px solid #22ad5d !important;
  1199.             padding: 0 3px !important;
  1200.             border-radius: 2px !important;
  1201.             font-size: 9px !important;
  1202.         }
  1203.         .let-meta .let-cat a {
  1204.             color: inherit !important;
  1205.             text-decoration: none !important;
  1206.         }
  1207.         .let-title .let-new {
  1208.             background: #dcb727 !important;
  1209.             color: #000 !important;
  1210.             padding: 0 3px !important;
  1211.             border-radius: 2px !important;
  1212.             font-size: 9px !important;
  1213.             font-weight: 700 !important;
  1214.             margin-left: 6px !important;
  1215.             vertical-align: middle !important;
  1216.             line-height: 1.2 !important;
  1217.             display: inline !important;
  1218.         }
  1219.         .let-meta .let-author a,
  1220.         .let-meta .let-recent a {
  1221.             color: #22ad5d !important;
  1222.             text-decoration: none !important;
  1223.         }
  1224.         .let-meta .let-author a:hover,
  1225.         .let-meta .let-recent a:hover {
  1226.             color: orange !important;
  1227.         }
  1228.  
  1229.         .let-bookmark {
  1230.             order: 4 !important;
  1231.             flex-shrink: 0 !important;
  1232.             display: flex !important;
  1233.             align-items: center !important;
  1234.             gap: 3px !important;
  1235.         }
  1236.         .let-bookmark a {
  1237.             display: inline-flex !important;
  1238.             align-items: center !important;
  1239.             justify-content: center !important;
  1240.             width: 16px !important;
  1241.             height: 16px !important;
  1242.             font-size: 0 !important;
  1243.             vertical-align: middle !important;
  1244.             flex-shrink: 0 !important;
  1245.         }
  1246.         .let-bookmark .OptionsMenu {
  1247.             display: none !important;
  1248.         }
  1249.         .let-bookmark .Sprite.SpBookmarks,
  1250.         .let-bookmark .Bookmark {
  1251.             filter: invert(0.8) !important;
  1252.         }
  1253.         .let-bookmark .OptionsMenu {
  1254.             filter: invert(0.8) !important;
  1255.         }
  1256.  
  1257.         /* Hide original children after restructure */
  1258.         ul.DataList.Discussions > li.ItemDiscussion.let-done > a.IndexPhoto,
  1259.         ul.DataList.Discussions > li.ItemDiscussion.let-done > .ItemContent,
  1260.         ul.DataList.Discussions > li.ItemDiscussion.let-done > .Options {
  1261.             display: none !important;
  1262.         }
  1263.  
  1264.         /* Unread vs read styling */
  1265.         ul.DataList.Discussions > li.ItemDiscussion.Read .let-title {
  1266.             opacity: 0.6 !important;
  1267.         }
  1268.         ul.DataList.Discussions > li.ItemDiscussion.Read .let-meta {
  1269.             opacity: 0.6 !important;
  1270.         }
  1271.  
  1272.         /* Remove backgrounds that add visual noise */
  1273.         ul.DataList.Discussions > li.Announcement {
  1274.             background: transparent !important;
  1275.         }
  1276.         .Discussions .DataList .Item,
  1277.         .Item {
  1278.             background: transparent !important;
  1279.         }
  1280.         .Discussions .DataList .Item.Read,
  1281.         .Item.Read {
  1282.             background: transparent !important;
  1283.         }
  1284.  
  1285.         /* Dark post/comment content in thread view */
  1286.         .ItemComment, .Message {
  1287.             background: transparent !important;
  1288.             color: #ccc !important;
  1289.         }
  1290.         .ItemComment .Header {
  1291.             background: #15151b !important;
  1292.             color: #aaa !important;
  1293.         }
  1294.         .CommentHeader,
  1295.         .Comment .Header {
  1296.             background: #15151b !important;
  1297.         }
  1298.         .AuthorWrap a,
  1299.         .Username a {
  1300.             color: #22ad5d !important;
  1301.         }
  1302.         blockquote {
  1303.             background: #1a1a20 !important;
  1304.             border-left: 3px solid #373741 !important;
  1305.             color: #aaa !important;
  1306.         }
  1307.         code, pre {
  1308.             background: #1a1a20 !important;
  1309.             color: #ccc !important;
  1310.             border-color: #373741 !important;
  1311.         }
  1312.  
  1313.         /* When narrow, allow wrapping */
  1314.         @media (max-width: 1000px) {
  1315.             #Body .Row {
  1316.                 flex-direction: column !important;
  1317.             }
  1318.             .Column.PanelColumn {
  1319.                 width: 100% !important;
  1320.                 min-width: 0 !important;
  1321.             }
  1322.         }
  1323.         @media (max-width: 800px) {
  1324.             ul.DataList.Discussions > li.ItemDiscussion {
  1325.                 flex-wrap: wrap !important;
  1326.             }
  1327.             .let-title {
  1328.                 flex: 1 1 100% !important;
  1329.                 min-width: 0 !important;
  1330.             }
  1331.             .let-meta {
  1332.                 flex-wrap: wrap !important;
  1333.             }
  1334.         }
  1335.     `;
  1336.   document.documentElement.appendChild(style);
  1337.   // Re-append dynamic style AFTER main style so it wins the cascade
  1338.   document.documentElement.appendChild(dynStyle);
  1339.   applyDynamicStyles();
  1340.  
  1341.   document.addEventListener("DOMContentLoaded", function () {
  1342.     if (!IS_IFRAME) {
  1343.       // Move MeBox icons from sidebar into header bar
  1344.       const headRow = document.querySelector("#Head .Row");
  1345.       if (headRow && !document.getElementById("let-head-mebox")) {
  1346.         // Try multiple selectors to find the sidebar MeBox
  1347.         const sidebarMeBox =
  1348.           document.querySelector(".Column.PanelColumn .MeBox") ||
  1349.           document.querySelector(".Panel .MeBox") ||
  1350.           document.querySelector(".PanelColumn .MeBox");
  1351.  
  1352.         if (sidebarMeBox) {
  1353.           const container = document.createElement("span");
  1354.           container.id = "let-head-mebox";
  1355.  
  1356.           // Grab all links and photo wraps from the MeBox
  1357.           sidebarMeBox.querySelectorAll("a, .PhotoWrap").forEach(function (el) {
  1358.             container.appendChild(el.cloneNode(true));
  1359.           });
  1360.  
  1361.           // Insert into the header
  1362.           headRow.appendChild(container);
  1363.  
  1364.           // Hide original
  1365.           sidebarMeBox.style.display = "none";
  1366.  
  1367.           // Attach custom flyout panels to notification/inbox links
  1368.           var flyoutMap = [
  1369.             {
  1370.               match: /notifications/i,
  1371.               popin: "/profile/notificationspopin",
  1372.               label: "All Notifications",
  1373.               full: "/profile/notifications",
  1374.             },
  1375.             {
  1376.               match: /messages|inbox/i,
  1377.               popin: "/messages/popin",
  1378.               label: "All Messages",
  1379.               full: "/messages/inbox",
  1380.             },
  1381.             {
  1382.               match: /bookmarks/i,
  1383.               popin: "/discussions/bookmarkedpopin",
  1384.               label: "All Bookmarks",
  1385.               full: "/discussions/bookmarked",
  1386.             },
  1387.           ];
  1388.  
  1389.           container.querySelectorAll("a").forEach(function (link) {
  1390.             var href = link.getAttribute("href") || "";
  1391.             var title = link.getAttribute("title") || "";
  1392.             var text = link.textContent || "";
  1393.             var cls = link.className || "";
  1394.             var inner = link.innerHTML || "";
  1395.             var matchStr =
  1396.               href + " " + title + " " + text + " " + cls + " " + inner;
  1397.             var config = null;
  1398.             for (var i = 0; i < flyoutMap.length; i++) {
  1399.               if (flyoutMap[i].match.test(matchStr)) {
  1400.                 config = flyoutMap[i];
  1401.                 break;
  1402.               }
  1403.             }
  1404.             if (!config) return;
  1405.  
  1406.             // Wrap link in a positioned container
  1407.             var wrap = document.createElement("span");
  1408.             wrap.className = "let-flyout-wrap";
  1409.             link.parentNode.insertBefore(wrap, link);
  1410.             wrap.appendChild(link);
  1411.  
  1412.             // Create flyout panel inside the wrapper
  1413.             var flyout = document.createElement("div");
  1414.             flyout.className = "let-flyout";
  1415.             flyout.innerHTML =
  1416.               '<div class="let-flyout-loading">Loading&hellip;</div>';
  1417.             wrap.appendChild(flyout);
  1418.  
  1419.             var loaded = false;
  1420.  
  1421.             link.addEventListener("click", function (e) {
  1422.               // Allow ctrl/cmd/middle click to navigate normally
  1423.               if (e.ctrlKey || e.metaKey || e.button === 1) return;
  1424.               e.preventDefault();
  1425.               e.stopPropagation();
  1426.  
  1427.               // Close other flyouts
  1428.               container.querySelectorAll(".let-flyout").forEach(function (f) {
  1429.                 if (f !== flyout) f.classList.remove("let-flyout-open");
  1430.               });
  1431.  
  1432.               // Toggle this flyout
  1433.               var isOpen = flyout.classList.toggle("let-flyout-open");
  1434.  
  1435.               // Fetch content on first open (and refresh each time)
  1436.               if (isOpen) {
  1437.                 flyout.innerHTML =
  1438.                   '<div class="let-flyout-loading">Loading&hellip;</div>';
  1439.                 var tk =
  1440.                   typeof gdn !== "undefined" && gdn.definition
  1441.                     ? gdn.definition("TransientKey")
  1442.                     : "";
  1443.                 var popinUrl =
  1444.                   config.popin +
  1445.                   (config.popin.indexOf("?") > -1 ? "&" : "?") +
  1446.                   "DeliveryType=VIEW" +
  1447.                   (tk ? "&TransientKey=" + encodeURIComponent(tk) : "");
  1448.                 fetch(popinUrl, {
  1449.                   credentials: "same-origin",
  1450.                 })
  1451.                   .then(function (r) {
  1452.                     return r.text();
  1453.                   })
  1454.                   .then(function (html) {
  1455.                     flyout.innerHTML =
  1456.                       html.trim() +
  1457.                       '<div class="let-flyout-footer"><a href="' +
  1458.                       config.full +
  1459.                       '">' +
  1460.                       config.label +
  1461.                       "</a></div>";
  1462.                     // Restyle the title row inline to beat theme CSS
  1463.                     var titleLi = flyout.querySelector("li.Item.Title");
  1464.                     if (titleLi) {
  1465.                       var prefLink = titleLi.querySelector("a");
  1466.                       var heading = titleLi.querySelector("strong");
  1467.                       titleLi.innerHTML = "";
  1468.                       titleLi.style.cssText =
  1469.                         "display:flex!important;align-items:baseline!important;justify-content:space-between!important;padding:6px 12px!important;border-bottom:1px solid #252530!important;";
  1470.                       if (heading) {
  1471.                         heading.style.cssText =
  1472.                           "font-size:12px!important;color:#ccc!important;font-weight:700!important;order:1!important;";
  1473.                         titleLi.appendChild(heading);
  1474.                       }
  1475.                       if (prefLink) {
  1476.                         prefLink.style.cssText =
  1477.                           "font-size:10px!important;color:#666!important;font-style:italic!important;order:2!important;text-decoration:none!important;";
  1478.                         titleLi.appendChild(prefLink);
  1479.                       }
  1480.                     }
  1481.                     // Remove duplicate "All Notifications" from popin (we add our own footer)
  1482.                     var centerItem = flyout.querySelector("li.Item.Center");
  1483.                     if (centerItem) centerItem.style.display = "none";
  1484.                   })
  1485.                   .catch(function () {
  1486.                     flyout.innerHTML =
  1487.                       '<div class="let-flyout-loading">Failed to load</div>' +
  1488.                       '<div class="let-flyout-footer"><a href="' +
  1489.                       config.full +
  1490.                       '">' +
  1491.                       config.label +
  1492.                       "</a></div>";
  1493.                   });
  1494.               }
  1495.             });
  1496.           });
  1497.  
  1498.           // Close flyouts on outside click
  1499.           document.addEventListener("click", function (e) {
  1500.             if (!container.contains(e.target)) {
  1501.               container.querySelectorAll(".let-flyout").forEach(function (f) {
  1502.                 f.classList.remove("let-flyout-open");
  1503.               });
  1504.             }
  1505.           });
  1506.         }
  1507.       }
  1508.  
  1509.       // Move search from header to top of sidebar, or ensure sidebar search is visible
  1510.       const sidebar = document.querySelector(".Column.PanelColumn");
  1511.       if (sidebar) {
  1512.         const headSearch = document.querySelector("#Head .SiteSearch");
  1513.         if (headSearch) {
  1514.           // Move header search to sidebar
  1515.           sidebar.insertBefore(headSearch, sidebar.firstChild);
  1516.         }
  1517.         // Find whichever search is now in sidebar and force visible at top
  1518.         const search = sidebar.querySelector(".SiteSearch");
  1519.         if (search) {
  1520.           search.style.cssText =
  1521.             "display: block !important; padding: 4px 8px; margin-bottom: 4px;";
  1522.           if (search !== sidebar.firstElementChild) {
  1523.             sidebar.insertBefore(search, sidebar.firstChild);
  1524.           }
  1525.         }
  1526.       }
  1527.  
  1528.       // Fetch host_c's latest comment and display in banner
  1529.       (function () {
  1530.         var banner = document.createElement("div");
  1531.         banner.id = "let-hostc-banner";
  1532.         var head = document.getElementById("Head");
  1533.         if (head) {
  1534.           head.appendChild(banner);
  1535.         }
  1536.  
  1537.         var intervalId = null;
  1538.         var tickerId = null;
  1539.         var autoOn = letSettings.hcAutoOn;
  1540.         var refreshSec = letSettings.hcInterval;
  1541.         var lastFetchTime = null;
  1542.         var lastPostHref = null;
  1543.  
  1544.         function showNewPostToast(prefix, title, href, time) {
  1545.           var existing = document.querySelector(".let-hc-toast");
  1546.           if (existing) existing.remove();
  1547.           var toast = document.createElement("div");
  1548.           toast.className = "let-hc-toast";
  1549.           toast.innerHTML =
  1550.             '<button class="let-hc-toast-close">&times;</button>' +
  1551.             '<span class="let-hc-toast-label">New post from host_c</span>' +
  1552.             '<span class="let-hc-toast-title"><a href="' +
  1553.             href +
  1554.             '">' +
  1555.             prefix +
  1556.             title +
  1557.             "</a></span>" +
  1558.             '<span class="let-hc-toast-time">' +
  1559.             time +
  1560.             "</span>";
  1561.           document.body.appendChild(toast);
  1562.           toast
  1563.             .querySelector(".let-hc-toast-close")
  1564.             .addEventListener("click", function (e) {
  1565.               e.stopPropagation();
  1566.               toast.remove();
  1567.             });
  1568.           toast.addEventListener("click", function () {
  1569.             window.location.href = href;
  1570.           });
  1571.           setTimeout(function () {
  1572.             if (toast.parentNode) toast.remove();
  1573.           }, 15000);
  1574.  
  1575.           // Send Pushover notification if enabled and configured
  1576.           if (
  1577.             letSettings.pushEnabled &&
  1578.             letSettings.pushoverUser &&
  1579.             letSettings.pushoverToken
  1580.           ) {
  1581.             var fullUrl = href.startsWith("http")
  1582.               ? href
  1583.               : window.location.origin + href;
  1584.             var formData = new FormData();
  1585.             formData.append("token", letSettings.pushoverToken);
  1586.             formData.append("user", letSettings.pushoverUser);
  1587.             formData.append("title", "host_c — " + prefix + title);
  1588.             formData.append("message", prefix + title + "\n" + time);
  1589.             formData.append("url", fullUrl);
  1590.             formData.append("url_title", "View Post");
  1591.             fetch("https://api.pushover.net/1/messages.json", {
  1592.               method: "POST",
  1593.               body: formData,
  1594.             }).catch(function () {});
  1595.           }
  1596.         }
  1597.  
  1598.         function getInterval() {
  1599.           return refreshSec * 1000;
  1600.         }
  1601.  
  1602.         function startInterval() {
  1603.           clearInterval(intervalId);
  1604.           intervalId = setInterval(fetchLatest, getInterval());
  1605.         }
  1606.  
  1607.         function startTicker() {
  1608.           clearInterval(tickerId);
  1609.           tickerId = setInterval(updateTicker, 1000);
  1610.         }
  1611.  
  1612.         function updateTicker() {
  1613.           var el = banner.querySelector(".let-hc-updated");
  1614.           if (!el || !lastFetchTime) return;
  1615.           var ago = Math.round((Date.now() - lastFetchTime) / 1000);
  1616.           el.textContent = "updated " + ago + "s ago";
  1617.         }
  1618.  
  1619.         function buildBanner(titleHtml, timeText) {
  1620.           banner.innerHTML =
  1621.             '<span class="let-hc-label">Latest from <a href="/profile/host_c">host_c</a>:</span>' +
  1622.             '<span class="let-hc-title">' +
  1623.             titleHtml +
  1624.             "</span>" +
  1625.             '<span class="let-hc-time">' +
  1626.             timeText +
  1627.             "</span>" +
  1628.             '<span class="let-hc-divider"></span>' +
  1629.             '<span class="let-hc-toggle">' +
  1630.             '<span class="let-hc-unit">host_c monitor settings: Auto Update interval (seconds):</span>' +
  1631.             '<input type="text" class="let-hc-interval" value="' +
  1632.             refreshSec +
  1633.             '" title="Refresh interval in seconds" />' +
  1634.             '<span class="let-hc-divider"></span>' +
  1635.             '<span class="let-hc-unit">Enabled:</span> ' +
  1636.             '<span class="let-hc-switch' +
  1637.             (autoOn ? " on" : "") +
  1638.             '"></span>' +
  1639.             "</span>" +
  1640.             '<span class="let-hc-divider"></span>' +
  1641.             '<span class="let-hc-updated">' +
  1642.             (lastFetchTime ? "updated 0s ago" : "") +
  1643.             "</span>" +
  1644.             ' <a href="#" class="let-hc-refresh" style="color:#22ad5d!important;font-size:10px!important;margin-left:4px!important">update now</a>';
  1645.           banner
  1646.             .querySelector(".let-hc-switch")
  1647.             .addEventListener("click", toggleAuto);
  1648.           banner
  1649.             .querySelector(".let-hc-refresh")
  1650.             .addEventListener("click", function (e) {
  1651.               e.preventDefault();
  1652.               fetchLatest();
  1653.             });
  1654.           var input = banner.querySelector(".let-hc-interval");
  1655.           input.addEventListener("click", function (e) {
  1656.             e.stopPropagation();
  1657.           });
  1658.           input.addEventListener("change", function () {
  1659.             var val = parseInt(this.value, 10);
  1660.             if (val && val >= 5) {
  1661.               refreshSec = val;
  1662.               letSettings.hcInterval = val;
  1663.               saveSettings(letSettings);
  1664.             } else {
  1665.               this.value = refreshSec;
  1666.             }
  1667.             if (autoOn) startInterval();
  1668.           });
  1669.         }
  1670.  
  1671.         function parseComment(doc) {
  1672.           var item = doc.querySelector("ul.DataList.SearchResults li.Item");
  1673.           if (!item) return null;
  1674.           var titleLink = item.querySelector(".Meta .MItem b a");
  1675.           var timeEl = item.querySelector(".Meta .MItem:last-child a");
  1676.           // Comments page: time may be a <time> tag or just text in an <a>
  1677.           var timeTag = item.querySelector(".Meta time");
  1678.           var title = titleLink ? titleLink.textContent.trim() : "";
  1679.           var href = titleLink ? titleLink.getAttribute("href") : "#";
  1680.           var time = timeEl ? timeEl.textContent.trim() : "";
  1681.           var datetime = timeTag ? timeTag.getAttribute("datetime") : null;
  1682.           // If no <time> tag, the comment ID in the li id tells us order;
  1683.           // but more reliably, extract from the comment permalink's title attr
  1684.           if (!datetime) {
  1685.             // Try the <time> inside the last MItem link
  1686.             var innerTime = timeEl ? timeEl.querySelector("time") : null;
  1687.             if (innerTime) {
  1688.               datetime = innerTime.getAttribute("datetime");
  1689.             } else {
  1690.               // Comments without <time> tag — parse displayed time text
  1691.               var timeText = time; // e.g. "12:56PM", "March 5", "March 6, 2025"
  1692.               var todayMatch = timeText.match(/^(\d{1,2}):(\d{2})(AM|PM)$/i);
  1693.               if (todayMatch) {
  1694.                 var h = parseInt(todayMatch[1], 10);
  1695.                 var m = parseInt(todayMatch[2], 10);
  1696.                 var ampm = todayMatch[3].toUpperCase();
  1697.                 if (ampm === "PM" && h !== 12) h += 12;
  1698.                 if (ampm === "AM" && h === 12) h = 0;
  1699.                 var now = new Date();
  1700.                 datetime = new Date(
  1701.                   now.getFullYear(),
  1702.                   now.getMonth(),
  1703.                   now.getDate(),
  1704.                   h,
  1705.                   m,
  1706.                   0,
  1707.                 ).toISOString();
  1708.               } else {
  1709.                 // Parse "Month Day" or "Month Day, Year" format
  1710.                 var months = {
  1711.                   January: 0,
  1712.                   February: 1,
  1713.                   March: 2,
  1714.                   April: 3,
  1715.                   May: 4,
  1716.                   June: 5,
  1717.                   July: 6,
  1718.                   August: 7,
  1719.                   September: 8,
  1720.                   October: 9,
  1721.                   November: 10,
  1722.                   December: 11,
  1723.                 };
  1724.                 var dateMatch = timeText.match(
  1725.                   /^(\w+)\s+(\d{1,2})(?:,?\s*(\d{4}))?$/,
  1726.                 );
  1727.                 if (dateMatch && months[dateMatch[1]] !== undefined) {
  1728.                   var yr = dateMatch[3]
  1729.                     ? parseInt(dateMatch[3], 10)
  1730.                     : new Date().getFullYear();
  1731.                   datetime = new Date(
  1732.                     yr,
  1733.                     months[dateMatch[1]],
  1734.                     parseInt(dateMatch[2], 10),
  1735.                   ).toISOString();
  1736.                 }
  1737.               }
  1738.             }
  1739.           }
  1740.           return { title: title, href: href, time: time, datetime: datetime };
  1741.         }
  1742.  
  1743.         function parseDiscussion(doc) {
  1744.           var item = doc.querySelector(
  1745.             "ul.DataList.Discussions li.ItemDiscussion",
  1746.           );
  1747.           if (!item) return null;
  1748.           var titleLink = item.querySelector(".Title a");
  1749.           var timeTag = item.querySelector(".LastCommentDate time");
  1750.           var title = titleLink ? titleLink.textContent.trim() : "";
  1751.           var href = titleLink ? titleLink.getAttribute("href") : "#";
  1752.           var time = timeTag ? timeTag.textContent.trim() : "";
  1753.           var datetime = timeTag ? timeTag.getAttribute("datetime") : null;
  1754.           return { title: title, href: href, time: time, datetime: datetime };
  1755.         }
  1756.  
  1757.         function fetchLatest() {
  1758.           var results = { comments: null, discussions: null };
  1759.           var done = 0;
  1760.  
  1761.           function finish() {
  1762.             done++;
  1763.             if (done < 2) return;
  1764.             lastFetchTime = Date.now();
  1765.  
  1766.             var c = results.comments;
  1767.             var d = results.discussions;
  1768.  
  1769.             if (!c && !d) {
  1770.               buildBanner("no posts found", "");
  1771.               return;
  1772.             }
  1773.  
  1774.             // Compare timestamps to find which is more recent
  1775.             var useComment = false;
  1776.             if (c && d) {
  1777.               var cDate = c.datetime ? new Date(c.datetime).getTime() : 0;
  1778.               var dDate = d.datetime ? new Date(d.datetime).getTime() : 0;
  1779.               useComment = cDate >= dDate;
  1780.             } else if (c) {
  1781.               useComment = true;
  1782.             }
  1783.  
  1784.             var latest = useComment ? c : d;
  1785.             var prefix = useComment ? "RE: " : "";
  1786.  
  1787.             // Show toast if this is a new post (not the first load)
  1788.             if (lastPostHref !== null && latest.href !== lastPostHref) {
  1789.               showNewPostToast(prefix, latest.title, latest.href, latest.time);
  1790.             }
  1791.             lastPostHref = latest.href;
  1792.  
  1793.             buildBanner(
  1794.               '<a href="' + latest.href + '">' + prefix + latest.title + "</a>",
  1795.               latest.time,
  1796.             );
  1797.           }
  1798.  
  1799.           function fetchPage(url, key, parser) {
  1800.             var xhr = new XMLHttpRequest();
  1801.             xhr.open("GET", url, true);
  1802.             xhr.onload = function () {
  1803.               if (xhr.status === 200) {
  1804.                 var doc = new DOMParser().parseFromString(
  1805.                   xhr.responseText,
  1806.                   "text/html",
  1807.                 );
  1808.                 results[key] = parser(doc);
  1809.               }
  1810.               finish();
  1811.             };
  1812.             xhr.onerror = function () {
  1813.               finish();
  1814.             };
  1815.             xhr.send();
  1816.           }
  1817.  
  1818.           fetchPage("/profile/comments/host_c", "comments", parseComment);
  1819.           fetchPage(
  1820.             "/profile/discussions/host_c",
  1821.             "discussions",
  1822.             parseDiscussion,
  1823.           );
  1824.         }
  1825.  
  1826.         function toggleAuto() {
  1827.           autoOn = !autoOn;
  1828.           letSettings.hcAutoOn = autoOn;
  1829.           saveSettings(letSettings);
  1830.           if (autoOn) {
  1831.             fetchLatest();
  1832.             startInterval();
  1833.           } else {
  1834.             clearInterval(intervalId);
  1835.             intervalId = null;
  1836.           }
  1837.           var sw = banner.querySelector(".let-hc-switch");
  1838.           if (sw) sw.classList.toggle("on", autoOn);
  1839.         }
  1840.  
  1841.         // After building banner, re-append the settings wrap if it exists
  1842.         var settingsWrap = null;
  1843.         var origBuildBanner = buildBanner;
  1844.  
  1845.         buildBanner = function (titleHtml, timeText) {
  1846.           origBuildBanner(titleHtml, timeText);
  1847.           if (settingsWrap) banner.appendChild(settingsWrap);
  1848.         };
  1849.  
  1850.         // Create settings link + flyout, appended to the banner
  1851.         (function () {
  1852.           settingsWrap = document.createElement("span");
  1853.           settingsWrap.className = "let-flyout-wrap";
  1854.           settingsWrap.style.cssText =
  1855.             "flex-shrink:0!important;display:inline-flex!important;align-items:center!important;gap:6px!important;";
  1856.  
  1857.           var divider = document.createElement("span");
  1858.           divider.className = "let-hc-divider";
  1859.           settingsWrap.insertBefore(divider, settingsWrap.firstChild);
  1860.  
  1861.           // Auto-refresh page toggle
  1862.           var arLabel = document.createElement("span");
  1863.           arLabel.className = "let-hc-unit";
  1864.           arLabel.textContent = "Auto Refresh (seconds):";
  1865.           settingsWrap.appendChild(arLabel);
  1866.  
  1867.           var arInput = document.createElement("input");
  1868.           arInput.type = "text";
  1869.           arInput.className = "let-hc-interval";
  1870.           arInput.value = letSettings.autoRefreshSec;
  1871.           arInput.title = "Page refresh interval in seconds";
  1872.           settingsWrap.appendChild(arInput);
  1873.  
  1874.           var arSwitch = document.createElement("span");
  1875.           arSwitch.className =
  1876.             "let-hc-switch" + (letSettings.autoRefreshOn ? " on" : "");
  1877.           var arTimerId = null;
  1878.  
  1879.           function startAutoRefresh() {
  1880.             if (arTimerId) clearInterval(arTimerId);
  1881.             arTimerId = setInterval(function () {
  1882.               window.location.reload();
  1883.             }, letSettings.autoRefreshSec * 1000);
  1884.           }
  1885.  
  1886.           arSwitch.addEventListener("click", function () {
  1887.             letSettings.autoRefreshOn = !letSettings.autoRefreshOn;
  1888.             arSwitch.classList.toggle("on", letSettings.autoRefreshOn);
  1889.             saveSettings(letSettings);
  1890.             if (letSettings.autoRefreshOn) {
  1891.               startAutoRefresh();
  1892.             } else {
  1893.               clearInterval(arTimerId);
  1894.               arTimerId = null;
  1895.             }
  1896.           });
  1897.           settingsWrap.appendChild(arSwitch);
  1898.  
  1899.           arInput.addEventListener("change", function () {
  1900.             var val = parseInt(this.value, 10);
  1901.             if (!val || val < 5) val = 5;
  1902.             letSettings.autoRefreshSec = val;
  1903.             this.value = val;
  1904.             saveSettings(letSettings);
  1905.             if (letSettings.autoRefreshOn) startAutoRefresh();
  1906.           });
  1907.  
  1908.           if (letSettings.autoRefreshOn) startAutoRefresh();
  1909.  
  1910.           var dividerAR = document.createElement("span");
  1911.           dividerAR.className = "let-hc-divider";
  1912.           settingsWrap.appendChild(dividerAR);
  1913.  
  1914.           // Push notifications toggle
  1915.           var pushLabel = document.createElement("span");
  1916.           pushLabel.className = "let-hc-unit";
  1917.           pushLabel.textContent = "Push:";
  1918.           settingsWrap.appendChild(pushLabel);
  1919.           var pushSwitch = document.createElement("span");
  1920.           pushSwitch.className =
  1921.             "let-hc-switch let-push-switch" +
  1922.             (letSettings.pushEnabled ? " on" : "");
  1923.           pushSwitch.addEventListener("click", function () {
  1924.             letSettings.pushEnabled = !letSettings.pushEnabled;
  1925.             pushSwitch.classList.toggle("on", letSettings.pushEnabled);
  1926.             saveSettings(letSettings);
  1927.           });
  1928.           settingsWrap.appendChild(pushSwitch);
  1929.  
  1930.           var divider2 = document.createElement("span");
  1931.           divider2.className = "let-hc-divider";
  1932.           settingsWrap.appendChild(divider2);
  1933.  
  1934.           var link = document.createElement("a");
  1935.           link.className = "let-settings-link";
  1936.           link.href = "#";
  1937.           link.textContent = "Userscript Settings";
  1938.           settingsWrap.appendChild(link);
  1939.  
  1940.           var flyout = document.createElement("div");
  1941.           flyout.className = "let-flyout let-settings-flyout";
  1942.           flyout.innerHTML =
  1943.             "<label>Post Title Size " +
  1944.             '<input type="range" min="8" max="20" value="' +
  1945.             letSettings.titleFontSize +
  1946.             '" class="let-range-title" />' +
  1947.             '<span class="let-size-val let-val-title">' +
  1948.             letSettings.titleFontSize +
  1949.             "px</span>" +
  1950.             "</label>" +
  1951.             "<label>Meta Info Size " +
  1952.             '<input type="range" min="6" max="16" value="' +
  1953.             letSettings.metaFontSize +
  1954.             '" class="let-range-meta" />' +
  1955.             '<span class="let-size-val let-val-meta">' +
  1956.             letSettings.metaFontSize +
  1957.             "px</span>" +
  1958.             "</label>" +
  1959.             "<label>Hide Sidebar " +
  1960.             '<input type="checkbox" class="let-chk-sidebar"' +
  1961.             (letSettings.hideSidebar ? " checked" : "") +
  1962.             " />" +
  1963.             "</label>" +
  1964.             "<label>Reading Pane " +
  1965.             '<input type="checkbox" class="let-chk-readpane"' +
  1966.             (letSettings.readingPane ? " checked" : "") +
  1967.             " />" +
  1968.             "</label>" +
  1969.             '<hr style="border:none;border-top:1px solid #333;margin:8px 0" />' +
  1970.             '<label style="font-size:11px;color:#888;margin-bottom:4px;display:block">Pushover User Key</label>' +
  1971.             '<input type="text" class="let-push-user" value="' +
  1972.             (letSettings.pushoverUser || "") +
  1973.             '" placeholder="User key" style="width:100%;padding:3px 6px;background:#111;color:#ccc;border:1px solid #333;border-radius:3px;font-size:11px;margin-bottom:6px;box-sizing:border-box" />' +
  1974.             '<label style="font-size:11px;color:#888;margin-bottom:4px;display:block">Pushover API Token</label>' +
  1975.             '<input type="password" class="let-push-token" value="' +
  1976.             (letSettings.pushoverToken || "") +
  1977.             '" placeholder="API token" style="width:100%;padding:3px 6px;background:#111;color:#ccc;border:1px solid #333;border-radius:3px;font-size:11px;margin-bottom:6px;box-sizing:border-box" />' +
  1978.             '<button class="let-settings-reset">Reset to Defaults</button>' +
  1979.             '<button class="let-settings-reset let-test-toast">Test Notification</button>';
  1980.           settingsWrap.appendChild(flyout);
  1981.  
  1982.           flyout
  1983.             .querySelector(".let-test-toast")
  1984.             .addEventListener("click", function () {
  1985.               showNewPostToast(
  1986.                 "RE: ",
  1987.                 "Test Post — This is a sample host_c notification!",
  1988.                 "#",
  1989.                 "just now",
  1990.               );
  1991.             });
  1992.  
  1993.           var rangeTitle = flyout.querySelector(".let-range-title");
  1994.           var rangeMeta = flyout.querySelector(".let-range-meta");
  1995.           var chkSidebar = flyout.querySelector(".let-chk-sidebar");
  1996.           var chkReadPane = flyout.querySelector(".let-chk-readpane");
  1997.           var valTitle = flyout.querySelector(".let-val-title");
  1998.           var valMeta = flyout.querySelector(".let-val-meta");
  1999.           var pushUserInput = flyout.querySelector(".let-push-user");
  2000.           var pushTokenInput = flyout.querySelector(".let-push-token");
  2001.  
  2002.           pushUserInput.addEventListener("change", function () {
  2003.             letSettings.pushoverUser = this.value.trim();
  2004.             saveSettings(letSettings);
  2005.           });
  2006.           pushTokenInput.addEventListener("change", function () {
  2007.             letSettings.pushoverToken = this.value.trim();
  2008.             saveSettings(letSettings);
  2009.           });
  2010.  
  2011.           rangeTitle.addEventListener("input", function () {
  2012.             var v = parseInt(this.value, 10);
  2013.             letSettings.titleFontSize = v;
  2014.             valTitle.textContent = v + "px";
  2015.             applyDynamicStyles();
  2016.             saveSettings(letSettings);
  2017.           });
  2018.  
  2019.           rangeMeta.addEventListener("input", function () {
  2020.             var v = parseInt(this.value, 10);
  2021.             letSettings.metaFontSize = v;
  2022.             valMeta.textContent = v + "px";
  2023.             applyDynamicStyles();
  2024.             saveSettings(letSettings);
  2025.           });
  2026.  
  2027.           chkSidebar.addEventListener("change", function () {
  2028.             letSettings.hideSidebar = this.checked;
  2029.             applyDynamicStyles();
  2030.             saveSettings(letSettings);
  2031.           });
  2032.  
  2033.           chkReadPane.addEventListener("change", function () {
  2034.             letSettings.readingPane = this.checked;
  2035.             applyDynamicStyles();
  2036.             saveSettings(letSettings);
  2037.             toggleReadingPane(this.checked);
  2038.           });
  2039.  
  2040.           flyout
  2041.             .querySelector(".let-settings-reset")
  2042.             .addEventListener("click", function () {
  2043.               letSettings.titleFontSize = LET_DEFAULTS.titleFontSize;
  2044.               letSettings.metaFontSize = LET_DEFAULTS.metaFontSize;
  2045.               letSettings.hideSidebar = LET_DEFAULTS.hideSidebar;
  2046.               letSettings.pushEnabled = LET_DEFAULTS.pushEnabled;
  2047.               letSettings.pushoverUser = LET_DEFAULTS.pushoverUser;
  2048.               letSettings.pushoverToken = LET_DEFAULTS.pushoverToken;
  2049.               rangeTitle.value = LET_DEFAULTS.titleFontSize;
  2050.               rangeMeta.value = LET_DEFAULTS.metaFontSize;
  2051.               valTitle.textContent = LET_DEFAULTS.titleFontSize + "px";
  2052.               valMeta.textContent = LET_DEFAULTS.metaFontSize + "px";
  2053.               chkSidebar.checked = LET_DEFAULTS.hideSidebar;
  2054.               letSettings.readingPane = LET_DEFAULTS.readingPane;
  2055.               chkReadPane.checked = LET_DEFAULTS.readingPane;
  2056.               toggleReadingPane(false);
  2057.               pushUserInput.value = "";
  2058.               pushTokenInput.value = "";
  2059.               pushSwitch.classList.remove("on");
  2060.               letSettings.autoRefreshOn = LET_DEFAULTS.autoRefreshOn;
  2061.               letSettings.autoRefreshSec = LET_DEFAULTS.autoRefreshSec;
  2062.               arInput.value = LET_DEFAULTS.autoRefreshSec;
  2063.               arSwitch.classList.remove("on");
  2064.               clearInterval(arTimerId);
  2065.               arTimerId = null;
  2066.               applyDynamicStyles();
  2067.               saveSettings(letSettings);
  2068.             });
  2069.  
  2070.           link.addEventListener("click", function (e) {
  2071.             e.preventDefault();
  2072.             e.stopPropagation();
  2073.             flyout.classList.toggle("let-flyout-open");
  2074.           });
  2075.  
  2076.           document.addEventListener("click", function (e) {
  2077.             if (!settingsWrap.contains(e.target)) {
  2078.               flyout.classList.remove("let-flyout-open");
  2079.             }
  2080.           });
  2081.  
  2082.           banner.appendChild(settingsWrap);
  2083.         })();
  2084.  
  2085.         buildBanner('<span style="color:#666!important">loading...</span>', "");
  2086.         fetchLatest();
  2087.         startTicker();
  2088.         if (autoOn) startInterval();
  2089.       })();
  2090.     } // end !IS_IFRAME
  2091.  
  2092.     document
  2093.       .querySelectorAll("ul.DataList.Discussions > li.ItemDiscussion")
  2094.       .forEach(function (li) {
  2095.         if (li.classList.contains("let-done")) return;
  2096.  
  2097.         // Extract pieces
  2098.         const photoLink = li.querySelector(":scope > a.IndexPhoto");
  2099.         const content = li.querySelector(":scope > .ItemContent.Discussion");
  2100.         const options = li.querySelector(":scope > .Options");
  2101.         if (!content) return;
  2102.  
  2103.         const titleDiv = content.querySelector(".Title");
  2104.         const meta = content.querySelector(".Meta.Meta-Discussion");
  2105.  
  2106.         // 1. Avatar
  2107.         const avatar = document.createElement("span");
  2108.         avatar.className = "let-avatar";
  2109.         if (photoLink) {
  2110.           const img = photoLink.querySelector("img");
  2111.           if (img) {
  2112.             const newImg = document.createElement("img");
  2113.             newImg.src = img.src;
  2114.             newImg.alt = img.alt || "";
  2115.             avatar.appendChild(newImg);
  2116.           }
  2117.         }
  2118.  
  2119.         // 2. Title
  2120.         const title = document.createElement("span");
  2121.         title.className = "let-title";
  2122.         if (titleDiv) {
  2123.           const link = titleDiv.querySelector("a");
  2124.           if (link) {
  2125.             title.appendChild(link.cloneNode(true));
  2126.           }
  2127.         }
  2128.  
  2129.         // 3. Meta - build in desired order
  2130.         const metaSpan = document.createElement("span");
  2131.         metaSpan.className = "let-meta";
  2132.  
  2133.         if (meta) {
  2134.           // Tag (Announcement)
  2135.           const tag = meta.querySelector(".Tag");
  2136.           if (tag) {
  2137.             const t = document.createElement("span");
  2138.             t.className = "let-tag " + tag.className;
  2139.             t.textContent = tag.textContent;
  2140.             metaSpan.appendChild(t);
  2141.           }
  2142.  
  2143.           // Author
  2144.           const author = meta.querySelector(".DiscussionAuthor");
  2145.           if (author) {
  2146.             const a = document.createElement("span");
  2147.             a.className = "let-author";
  2148.             a.innerHTML = author.innerHTML;
  2149.             metaSpan.appendChild(a);
  2150.           }
  2151.  
  2152.           // Views
  2153.           const views = meta.querySelector(".ViewCount");
  2154.           if (views) {
  2155.             const v = document.createElement("span");
  2156.             const num = views.querySelector(".Number");
  2157.             v.textContent = (num ? num.textContent : "") + " views";
  2158.             metaSpan.appendChild(v);
  2159.           }
  2160.  
  2161.           // Comments
  2162.           const comments = meta.querySelector(".CommentCount");
  2163.           if (comments) {
  2164.             const c = document.createElement("span");
  2165.             const num = comments.querySelector(".Number");
  2166.             c.textContent = (num ? num.textContent : "") + " comments";
  2167.             metaSpan.appendChild(c);
  2168.           }
  2169.  
  2170.           // New - append to title instead of meta
  2171.           const hasNew = meta.querySelector(".HasNew");
  2172.           if (hasNew) {
  2173.             const n = document.createElement("span");
  2174.             n.className = "let-new";
  2175.             n.textContent = hasNew.textContent.trim();
  2176.             title.appendChild(n);
  2177.           }
  2178.  
  2179.           // Most recent by / Started by
  2180.           const lastBy = meta.querySelector(".LastCommentBy");
  2181.           if (lastBy) {
  2182.             const r = document.createElement("span");
  2183.             r.className = "let-recent";
  2184.             r.innerHTML = lastBy.innerHTML;
  2185.             metaSpan.appendChild(r);
  2186.           }
  2187.  
  2188.           // Time
  2189.           const lastDate = meta.querySelector(".LastCommentDate");
  2190.           if (lastDate) {
  2191.             const d = document.createElement("span");
  2192.             d.textContent = lastDate.textContent.trim();
  2193.             metaSpan.appendChild(d);
  2194.           }
  2195.  
  2196.           // Category
  2197.           const cat = meta.querySelector(".Category");
  2198.           if (cat) {
  2199.             const g = document.createElement("span");
  2200.             g.className = "let-cat";
  2201.             g.innerHTML = cat.querySelector("a")
  2202.               ? cat.querySelector("a").outerHTML
  2203.               : cat.textContent;
  2204.             metaSpan.appendChild(g);
  2205.           }
  2206.         }
  2207.  
  2208.         // 4. Bookmark
  2209.         const bookmark = document.createElement("span");
  2210.         bookmark.className = "let-bookmark";
  2211.         if (options) {
  2212.           const bmLink = options.querySelector("a.Bookmark");
  2213.           if (bmLink) {
  2214.             bookmark.appendChild(bmLink);
  2215.           }
  2216.           // Also grab dismiss for announcements
  2217.           const optMenu = options.querySelector(".OptionsMenu");
  2218.           if (optMenu) {
  2219.             bookmark.appendChild(optMenu);
  2220.           }
  2221.         }
  2222.  
  2223.         // Store sortable data on li
  2224.         if (meta) {
  2225.           var vc = meta.querySelector(".ViewCount .Number");
  2226.           li.setAttribute(
  2227.             "data-views",
  2228.             vc ? parseInt(vc.textContent.replace(/,/g, ""), 10) || 0 : 0,
  2229.           );
  2230.           var cc = meta.querySelector(".CommentCount .Number");
  2231.           li.setAttribute(
  2232.             "data-comments",
  2233.             cc ? parseInt(cc.textContent.replace(/,/g, ""), 10) || 0 : 0,
  2234.           );
  2235.           var ldt = meta.querySelector(".LastCommentDate time");
  2236.           li.setAttribute(
  2237.             "data-lastpost",
  2238.             ldt ? ldt.getAttribute("datetime") || "" : "",
  2239.           );
  2240.         }
  2241.  
  2242.         // Append in order
  2243.         li.appendChild(avatar);
  2244.         li.appendChild(title);
  2245.         li.appendChild(metaSpan);
  2246.         li.appendChild(bookmark);
  2247.         li.classList.add("let-done");
  2248.       });
  2249.  
  2250.     // Create reading pane if enabled
  2251.     if (!IS_IFRAME && letSettings.readingPane) createReadingPane();
  2252.  
  2253.     // Sort dropdown for discussions
  2254.     (function () {
  2255.       var list = document.querySelector("ul.DataList.Discussions");
  2256.       if (!list) return;
  2257.       // Find the PageControls nearest to the discussion list (before or after)
  2258.       var pager =
  2259.         list.parentNode.querySelector(".PageControls") ||
  2260.         document.querySelector(".PageControls");
  2261.       if (!pager) {
  2262.         // Fallback: create a controls row above the list
  2263.         pager = document.createElement("div");
  2264.         pager.className = "PageControls";
  2265.         pager.style.cssText =
  2266.           "display:flex!important;align-items:center!important;justify-content:flex-end!important;padding:2px 0!important;";
  2267.         list.parentNode.insertBefore(pager, list);
  2268.       }
  2269.  
  2270.       // Store original order
  2271.       var items = list.querySelectorAll("li.ItemDiscussion.let-done");
  2272.       items.forEach(function (li, i) {
  2273.         li.setAttribute("data-sort-index", i);
  2274.       });
  2275.  
  2276.       // Build sort UI
  2277.       var wrap = document.createElement("span");
  2278.       wrap.className = "let-sort-wrap";
  2279.  
  2280.       var sel = document.createElement("select");
  2281.       sel.className = "let-sort-select";
  2282.       [
  2283.         { value: "default", text: "Default Order" },
  2284.         { value: "lastpost", text: "Last Post" },
  2285.         { value: "views", text: "Views" },
  2286.         { value: "comments", text: "Comments" },
  2287.       ].forEach(function (o) {
  2288.         var opt = document.createElement("option");
  2289.         opt.value = o.value;
  2290.         opt.textContent = o.text;
  2291.         sel.appendChild(opt);
  2292.       });
  2293.  
  2294.       var dirBtn = document.createElement("button");
  2295.       dirBtn.className = "let-sort-dir";
  2296.       dirBtn.textContent = "\u25BC";
  2297.       dirBtn.title = "Descending";
  2298.       var ascending = false;
  2299.  
  2300.       dirBtn.addEventListener("click", function () {
  2301.         ascending = !ascending;
  2302.         dirBtn.textContent = ascending ? "\u25B2" : "\u25BC";
  2303.         dirBtn.title = ascending ? "Ascending" : "Descending";
  2304.         doSort();
  2305.       });
  2306.  
  2307.       function doSort() {
  2308.         var key = sel.value;
  2309.         var rows = Array.from(
  2310.           list.querySelectorAll("li.ItemDiscussion.let-done"),
  2311.         );
  2312.         if (key === "default") {
  2313.           rows.sort(function (a, b) {
  2314.             return (
  2315.               parseInt(a.getAttribute("data-sort-index")) -
  2316.               parseInt(b.getAttribute("data-sort-index"))
  2317.             );
  2318.           });
  2319.           if (!ascending) rows.reverse();
  2320.         } else if (key === "views" || key === "comments") {
  2321.           rows.sort(function (a, b) {
  2322.             var va = parseInt(a.getAttribute("data-" + key)) || 0;
  2323.             var vb = parseInt(b.getAttribute("data-" + key)) || 0;
  2324.             return ascending ? va - vb : vb - va;
  2325.           });
  2326.         } else if (key === "lastpost") {
  2327.           rows.sort(function (a, b) {
  2328.             var da = new Date(a.getAttribute("data-lastpost") || 0).getTime();
  2329.             var db = new Date(b.getAttribute("data-lastpost") || 0).getTime();
  2330.             return ascending ? da - db : db - da;
  2331.           });
  2332.         }
  2333.         rows.forEach(function (li) {
  2334.           list.appendChild(li);
  2335.         });
  2336.       }
  2337.  
  2338.       sel.addEventListener("change", doSort);
  2339.  
  2340.       wrap.appendChild(sel);
  2341.       wrap.appendChild(dirBtn);
  2342.       pager.insertBefore(wrap, pager.firstChild);
  2343.     })();
  2344.   });
  2345.  
  2346.   // Condense footer into 3-column layout
  2347.   function condenseFooter() {
  2348.     var foot = document.getElementById("Foot");
  2349.     if (!foot) return;
  2350.     foot.innerHTML = "";
  2351.     var row = document.createElement("div");
  2352.     row.className = "Row";
  2353.     row.style.cssText =
  2354.       "display:flex!important;align-items:center!important;padding:2px 10px!important;height:auto!important;min-height:0!important;";
  2355.     var left = document.createElement("div");
  2356.     left.className = "let-foot-left";
  2357.     left.style.cssText =
  2358.       "flex:1!important;text-align:left!important;font-size:11px!important;";
  2359.     left.textContent = "2008-2025 \u00A9 LowEndBox & LowEndTalk.";
  2360.     var center = document.createElement("div");
  2361.     center.className = "let-foot-center";
  2362.     center.style.cssText =
  2363.       "flex:1!important;text-align:center!important;font-size:11px!important;";
  2364.     [
  2365.       { text: "Back to Top", href: "#top" },
  2366.       { text: "LowEndTalk", href: "https://lowendtalk.com" },
  2367.       { text: "LowEndBox", href: "https://www.lowendbox.com" },
  2368.       {
  2369.         text: "Dark Theme Config",
  2370.         href: "https://lowendtalk.com/page/dark-theme-config",
  2371.       },
  2372.       { text: "Advertise", href: "https://lowendbox.com/advertise/" },
  2373.     ].forEach(function (l, i) {
  2374.       if (i > 0) center.appendChild(document.createTextNode(" | "));
  2375.       var a = document.createElement("a");
  2376.       a.href = l.href;
  2377.       a.textContent = l.text;
  2378.       center.appendChild(a);
  2379.     });
  2380.     var right = document.createElement("div");
  2381.     right.className = "let-foot-right";
  2382.     right.style.cssText =
  2383.       "flex:1!important;text-align:right!important;font-size:11px!important;";
  2384.     row.appendChild(left);
  2385.     row.appendChild(center);
  2386.     row.appendChild(right);
  2387.     foot.appendChild(row);
  2388.   }
  2389.   // Run multiple times to survive late-running page scripts that modify the footer
  2390.   if (!IS_IFRAME) {
  2391.     document.addEventListener("DOMContentLoaded", condenseFooter);
  2392.     window.addEventListener("load", function () {
  2393.       condenseFooter();
  2394.       setTimeout(condenseFooter, 500);
  2395.       setTimeout(condenseFooter, 1500);
  2396.       setTimeout(condenseFooter, 3000);
  2397.     });
  2398.   }
  2399. })();
  2400.  
Advertisement
Add Comment
Please, Sign In to add comment