RomeoEnhancer

Ergänzungen für die Romeo-Website

// ==UserScript==
// @name         RomeoEnhancer
// @version      1.16.2
// @author       braveguy (Romeo: braveguy / Gruppe RomeoEnhancer)
// @description  Ergänzungen für die Romeo-Website
// @require      https://code.jquery.com/jquery-3.7.0.min.js
// @match        https://*.romeo.com/*
// @match        https://83.98.143.20/*
// @match        https://www.hunqz.com/*
// @run-at       document-body
// @grant        none
// @copyright    braveguy 12.10.2016 / 07.11.2024
// @namespace    https://greasyfork.org/users/139428
// ==/UserScript==


/**
 * Copyright(c) braveguy (Romeo: braveguy / Gruppe RomeoEnhancer)
 *
 * Änderungen oder die Wiederverwendung von Code von RomeoEnhancer
 * erfordern meine ausdrückliche Zustimmung. Es ist nicht gestattet,
 * geänderte Versionen von RomeoEnhancer zu veröffentlichen.
 *
 * Das Skript wurde mit Tampermonkey in aktuellen Versionen von Safari, Chrome
 * und Firefox getestet. Dennoch geschieht die Benutzung auf eigenes Risiko.
 *
 * ** Datenschutz **
 * RomeoEnhancer enthält keinerlei Code, um Nutzer zu identifizieren
 * oder Daten auszuspähen. Es besteht keinerlei geschäftliches Interesse.
 *
 * Die aktuelle Version von RomeoEnhancer ist verfügbar unter:
 * https://greasyfork.org/scripts/31282-romeoenhancer
 *
 *
 * ****** English version *****
 *
 * Copyright(c) by braveguy (Romeo: braveguy / group RomeoEnhancer)
 *
 * Modifications and/or reuse of RomeoEnhancer code require my explicit consent.
 * You are NOT allowed to publish any changed version of RomeoEnhancer!
 *
 * All code has been tested with Tampermonkey in a recent Safari, Chrome,
 * and Firefox browser. However, the use of this script is at your own risk.
 *
 * ** Privacy **
 * RomeoEnhancer does NOT and never will include any code to identify you
 * or spy on your data. There is no commercial interest.
 *
 * The latest version of RomeoEnhancer is available on:
 * https://greasyfork.org/scripts/31282-romeoenhancer

*/


// ***** Use RE_addStyle instead of GM_addStyle to work with both Gear Browser and Userscripts *****
if (typeof RE_addStyle == 'undefined') {
  this.RE_addStyle = (aCss) => {
    'use strict';
    let head = document.querySelector('head');
    if (head) {
      let style = document.createElement('style');
      style.setAttribute('type', 'text/css');
      style.textContent = aCss;
      head.appendChild(style);
      return style;
    }
    return null;
  };
}


// ***** CSS *****

//background colors
let color1 = '', color2 = '', color3 = '', color4 = '', color5 = '', color6 = '', color7 = '', color8 = '', color9 = '';
//localStorage.setItem('REcolorScheme', 'retroBlue')
switch (localStorage.getItem('REcolorScheme')) {
    case 'sexyDark':  // Romeo default - Sexy Dark
        break;
    case 'lighterGrey':  // Lighter Grey
        color1 = '#1f1f1f', color2 = '#1f1f1f', color3 = '#000', color4 = '#101010', color5 = '#1a1a1a', color6 = '#f0f0f0', color7 = '#cc2d2d', color8 = '#2a2a2a', color9 = '#2a2a2a';
        break;
    case 'retroBlue':  // Retro Blue
        color1 = '#102c54', color2 = '#102c54', color3 = '#000', color4 = '#101010', color5 = '#122646', color6 = '#f0f0f0', color7 = '#f0f0f0', color8 = 'rgba(255,255,255,.08)', color9 = '#30415d';
        break;
    case 'fineGrey':  // RE default - Fine Grey
    default:
        color1 = '#1a1a1a', color2 = '#171717', color3 = '#000', color4 = '#101010', color5 = '#232323', color6 = '#f0f0f0', color7 = '#cc2d2d', color8 = '#2a2a2a', color9 = '#2a2a2a';
        break;
}


RE_addStyle (

    //tweak dark design
    `html.js body, .js-main-stage, .stream__content, .re-stream-toggle-div, .layer__column--background, .layer__column--detail, #group-preview .layer__container, .profile .below-fold, .profile-section {background-color:${color1} !important}` +
    `div.js-wrapper section[class^="Section"], div.js-wrapper div[class^="slider_wrapper-"]:before, div.js-wrapper div[class^="slider_wrapper-"]:after {background-color:${color1} !important}` +
    `.Xcontent-nav, .Xcontent-nav__actions, .ui-navbar--app, .filter-container {background-color:${color2} !important}` +
    `.listitem--selected > div {background-color:${color9} !important}` +
	`li[class^="stat-bar__item--"] {background-color:${color2}}` +
    `.listresult {background-color:${color2}}` +
    `.listresult:hover, :is(.js-chat, .js-contacts) .reactView > div:hover {background-color:${color8} !important}` +
    `.js-profile-footprints section {background-color:${color8}}` +
    `.listresult .list-stat-bar__item, .icon-circular.icon-u {background-color:transparent}` +
    `.WGgGs .reactView > button {background-color:${color9} !important}` +
    `:is(.js-post-edit, .js-post-list, #post, #group-post) textarea {background-color:${color4} !important}` +
    `.js-post-edit button[class*="ActionButton"] {background-color:${color1}}` +
    `.js-post-edit div[class*="Wrapper-"] {background-color:${color5} !important}` +
    `.js-post-list div[class^="Container"], :is(#group-post, #post) :is(.js-post, .js-post div[class^="Container"], div.js-comment) {background-color:${color5} !important}` +
    `section.js-main-stage div[class^="Grid-"] div[class*="Wrapper-"], section.js-main-stage div[class^="Grid-"] + div[class^="Wrapper-"] {background-color:${color5} !important}` +
    `.messages-list__content, .messages-send {background-color:${color2}!important}` +
    `:is(.js-profile-contact, #messenger .js-detail) > div > div, :is(.js-profile-contact, .js-detail) > div > div > div > div:last-child {background-color:${color2}!important}` +
    `.messages-send__form-input {background-color:${color3}!important}` +
    // `:is(.WGgGs, .js-main .js-navigation li) svg[class^="Icon__Svg-sc-"] {color:${color7}}` +
    `:is(.search-results--list-style > div, .grouped-tiles-list .refreshable) > div:nth-child(odd) .reactView > div {background-color:${color5}}` +
    `:is(.search-results--list-style > div, .grouped-tiles-list .refreshable) > div:nth-child(even) .reactView > div {background-color:${color1}}` +

    //bigger text in messages, groups, profiles; wrap long links
    ':is(#messages-list div.reactView, :is(.js-post-list, .js-post, .js-profile-text, .js-description) div[class^="TruncateBlock__Content-"]) > p[class^="BaseText-sc-"],' +
    '.profile section.profile__stats details > div > p[class^="BaseText-sc-"],' +
    ':is(.stream__content, .js-chat, .js-correspondence, .js-contacts, .js-username, .WGgGs, .js-actions, #manage, .js-profile-reviews) [class^="BodyText-sc-"],' +
    '[class^="ResponsiveBodyText-"] {font-size:.9375rem !important; letter-spacing:normal !important; line-height:1.375 !important; word-break:break-word !important}' +

    //avatar icon
    ':is(.layer-left-navigation, header li) div[class^="OnlineStatus"] svg[viewBox="0 0 10 12"] {width:.75rem; height:.75rem}' +

    //icons, links
	'a.re-icon {font-size:.9em}' +
    ':is(div.LIST, .js-contacts, .js-username) a.re-icon {font-size:.825em}' +
    'a.re-idle, a.re-idle-no-hover, a.re-idle-no-hover:hover {color:inherit !important}' +
	':is(a.re-icon, a.re-idle):hover {color:rgba(102,215,255,.8) !important}' +
    'a.re-link, a.re-link-no-hover {color:#00bdff}' +
    '[class*="hunqz"] a.re-link {color:#fc193c}' +
	'a.re-link-idle {color:unset !important}' +
    ':is(a.re-link, a.re-link-idle, .js-report-icon svg):hover {color:#66d7ff !important}' +
    '[class*="hunqz"] :is(a.re-link, a.re-link-idle, a.re-icon, .js-report-icon svg):hover {color:#fd5871 !important}' +
    'div.js-wrapper section h1, a[class*="Tabbed-nav-link-"], #cruise nav > a {font-size:1.05rem}' +

    //visitor icons
    'a.re-icon-visitor, a.re-icon-visited {font-size:1.1em; color:#fff}' +
    'a.re-icon-visited {transform:scaleX(-1)}' +

    //discover page
    ':is(a.re-eyecandy-toggle, a.re-eyecandy-toggle:hover) span {display:none; margin: 0 .25rem 0 .5rem; font-size:.875rem; color:rgba(255,255,255,1) !important; text-transform:none; font-family:Inter; font-weight:normal}' +
    'a.re-eyecandy-toggle.is-selected span:first-child, a.re-eyecandy-toggle:not(.is-selected) span:nth-child(2) {display:inline}' +
    'a.re-eyecandy-toggle svg {margin-bottom:.125rem}' +
    'a.re-eyecandy-toggle svg path {fill:currentcolor}' +

    //filter icon, bookmark name
    //'.re-filter-bookmark-name {position:absolute; right:0}' +
    '.is-filter-collapsed .re-filter-bookmark-name {display:none!important}' +
    '.is-filter-opened .re-filter-options-text, .is-filter-opened div.js-filter-button svg + span + span:not(.re-filter-bookmark-name) {display:none!important}' +

    //hide bookmark name in hunqz filter
    '.js-main-stage:has(.js-navigation a[href^="/hunqz"]) div.js-filter-header p {display:none !important}' +

    //expand truncated location names on hover
    '.typo-small.txt-truncate:hover {white-space:normal; word-break:break-all}' +

    //tiles
    ':is(div.BIG, div.SMALL, div.LIST) > div:first-child [class^="SpecialText"] {text-shadow:rgba(0,0,0,.5) 0 1px 1px, rgba(0,0,0,.5) 1px 1px 1px}' +
    ':is(div.BIG, div.SMALL) p[class^="SpecialText"] {word-break:break-word}' +
    ':is(div.BIG, div.SMALL) p[class^="SpecialText"]:hover {white-space:normal}' +
    'div.BIG span[class^="SpecialText"] {background-color:rgb(38,38,38)}' +
    '.re-nsfw-placeholder > div {background-color: rgb(31,31,31,.75) !important; background-image:none !important; backdrop-filter: blur(1px)}' +

    //FIX jumping fade in tiles during load
    'div.BIG::before, div.SMALL::before {inset:60% 0 0 !important}' +

    //tile TEST
    // ':is(div.BIG, div.SMALL, div.LIST) > div {display:none}' +
    // ':is(div.BIG, div.SMALL, div.LIST):hover > div:first-child {display:flex}' +
    // ':is(div.BIG, div.SMALL, div.LIST):hover > div:last-child {display:block}' +

    //list view
    'div:has(> .LIST) + div {margin-top:.5rem}' +

    //messenger
    '.message:not(.message--sent) .message__content {background:hsla(0,0%,100%,.125)}' +
    '.message:not(.message--sent) .message__content:before {border-color: transparent transparent hsla(0,0%,100%,.125); left:-9px}' +
    '.message:not(.message--sent) .message__attachments {background-color:transparent}' +
    '.message__status {color:rgba(255,255,255,.5)!important}' +
    ':is(.js-chat, #manage) div[class^="OnlineStatus"] > div > svg {height:13px; width:13px}' +
    ':is(.js-chat, #manage) div[class^="OnlineStatus"] > div > svg[viewBox="0 0 10 12"] {height:15px; width:15px}' +
    'p[class*="OnlineStatus__TimeString"] {line-height:1}' +
    '.js-chat .js-scrollable {max-height:1270px !important}' +
    '.js-chat .typo-headline-small {font-size:inherit}' +
    '.fQIYNa {font-weight:550}' +
    '.online-favourites__item {margin:0 .475rem !important}' +
    '.online-favourites__itemName {width:4rem; margin:0 -.125rem}' +
    'section.emoji-mart button.emoji-mart-anchor:hover {color:rgba(255,255,255,.5)}' +
    '.js-chat p[class^="BodyText-sc-"] {opacity:.87; column-gap:.25rem}' +
    '.js-chat a[href^="/messenger"] > div > span {opacity:.87}' +
    '.js-chat a > span > span svg[viewBox="0 0 9 7"] {height:9px; width:11px; opacity:.66}' +
    '.js-chat a > span > span svg[viewBox="0 0 12 12"] {height:15px; width:15px}' +
    '#messenger .js-header-region > div > h1 {padding-top:1.2rem}' +
    '#messenger .js-header-region > div > h1 a:has(span) {padding-bottom:.85rem}' +
    '#messenger .js-header-region .reactView > div > div > :is(div, a) {position:absolute; top:-1.2rem}' +
    '#messenger .re-login-location {position:absolute; top:0; width:100%}' +
    '#messenger .re-login-location > div {position:absolute; font-size:.75rem; font-weight:500; opacity:.8; background-color:transparent}' +
    '#messenger .re-msg-location {top:0; width:100%; padding:.25rem 0 0 3rem}' +
    '#messenger .re-msg-location a {padding-top:3px}' +
    '#messenger .re-msg-login {top:2.7rem; left:4rem; line-height:.85rem; min-width:10rem; padding-left:3rem; margin-right:5rem}' +
    '#messenger .re-msg-offline {top:2.7rem; left:3rem; line-height:.85rem}' +
    '.messages-send__form textarea.input--chat {padding-right:.5rem}' +
    ':is(.js-correspondence .js-header-region, #manage) div[class*="ContextMenu__"] ul {width:15.25rem; max-width:15.25rem !important}' +
    '.js-contacts div > div > button > svg {height:21px; width:21px}' +
    'ul[class^="TagCloud__UnstyledList"] button {font-size:.875rem; line-height:1.25}' +
    ':is(.js-detail, .js-profile-contact) p[class^="ResponsiveBodyText-"] + :is(span, p) {font-size:.875rem !important; padding-bottom:.25rem}' +
    '.js-chat .listitem--selected {border-right:5px solid #fff}' +
    '.js-chat .refreshable .reactView {min-height:6rem}' +
    '.js-contacts .listitem--selected > div {border-left:5px solid #fff}' +

    //cruise

    //stream
    'div.stream__content {top:calc(5.25rem + 1px)}' +
    'div.stream__content div.tile {margin:0}' +
    'div.stream__content div.tile div[class^="OnlineStatus"] {position:absolute; left:-8px; top:-8px}' +
    'div.stream__content div.tile div[class^="OnlineStatus"] > div {display:flex; justify-content:center; align-items:center; background-color:#1f1f1f; border-radius:50%; height:20px; width:20px; margin-top:0!important}' +
    'div.stream__content div.tile div[class^="OnlineStatus"] svg {height:12px; width:12px}' +
    'div.stream__content div.tile div[class^="OnlineStatus"] svg:has(path[d^="M7.463"]) {height:14px; width:14px; margin-top:2px}' +
    '.listitem .emoji:hover {font-size:1.5rem; line-height:1.33rem; margin-left:-.4rem; position:relative; right:-.2rem; padding-bottom:.33rem}' +
    '.stream__content .listitem {min-height:4.8rem}' +
    'div.stream__content .re-group-item:has(path[d^="M8.039"]) {display:none}' +

    //stream toggle
    '.re-stream-toggle-div {position:absolute; top:3.5rem; width:100%; padding:0 1rem; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #2e2e2e}' +
    ':is(a.re-stream-toggle, a.re-stream-toggle:hover) span {display:none; margin-right:.25rem; font-size:.875rem; color:rgba(255,255,255,.875); font-family:Inter; font-weight:normal}' +
    'a.re-stream-toggle.re-selected span:first-child, a.re-stream-toggle:not(.re-selected) span:nth-child(2) {display:inline}' +
    'a.re-stream-toggle svg {margin-bottom:.125rem}' +
    'a.re-stream-toggle svg path {fill:currentcolor}' +
    '.stream__content .re-group-item {display:none}' +
    '.stream__content.re-selected .re-group-item {display:flex}' +

	//profile view
    '.profile__info p {word-break:break-word}' +
    '.profile div[aria-label] > div[role="figure"] {border-color:rgba(255,255,255,0.33)}' +
    '.profile .re-profile-stats {font-size:.825rem; color:rgba(255,255,255,0.6)}' +
    '.re-img-count {position:absolute; bottom:0; left:0; padding:.5rem}' +
    '.re-img-count div {color:rgba(255,255,255,.87); background-color:rgb(18,18,18); border-radius:.125rem; padding:0 .25rem; height:1.125rem; line-height:1rem}' +
    '.js-profile-footprints h1 {grid-template-columns:2.5rem auto 3rem}' +
    '.js-profile-footprints section:last-child {padding-top:7.5rem}' +
    '.js-profile-footprints section + section:last-child {padding-top:0}' +
    '.js-profile-footprints.re-zoom section:last-child {grid-template-columns:repeat(3,33%)}' +
    '.js-profile-footprints.re-zoom section:last-child button {background-size:72px 72px; height:77px; width:72px; border-bottom-width:5px}' +
    '.js-profile-footprints.re-zoom section:last-child label p {max-width:14ch}' +

    //media viewer
    '.ReactModal__Content ul:has(img[src^="/img/usr/original/"]) {padding-bottom:2rem}' +
    '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) {max-height:85vh; min-height:unset; padding-bottom:2rem !important; Xmargin-bottom:.5rem}' +
    '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) svg:has(path[d^="M17.939"], path[d^="m3.911"]) {transform:scale(75%)}' +
    // '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) button:has(path[d^="M17.939"], path[d^="m3.911"]) {position:relative; top:.375rem}' +
    '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) p {font-size:.875rem}' +
    '.re-img-info {position:absolute; bottom:.75rem; right:3rem; width:100%; display:flex; justify-content:end}' +
    '.re-img-single {bottom:1rem; right:0; justify-content:center}' +
    '.re-img-info a {font-size:.8125rem; color:rgba(255,255,255,.5); text-shadow:rgba(0,0,0,.32) 0 1px 1px, rgba(0,0,0,.42) 1px 1px 1px; cursor:default; z-index:2}' +
    '.ReactModal__Content li:has(img[src^="/img/usr/original/"]) > div:has(path[d^="M17.939"], path[d^="m3.911"]) {background-image:linear-gradient(rgba(0,0,0,0) 10%, rgba(0,0,0,.66) 63%, rgba(0,0,0,.88) 90%)}' +
    '.ReactModal__Content li img[src^="/img/usr/original/"] {position:relative; object-fit:contain; cursor:pointer}' +

    //group tiles
    'div[class*="Tile__BaseTile-"] > p + div {position:absolute; bottom:2rem; left:.475rem}' +
    'div[class*="Tile__BaseTile-"] > p + div > p {background-color:#121212 !important}' +
    'div[class*="Tile__BaseTile-"] > p {margin:0 0 -.125rem}' +
    '.profile div[class*="Tile__BaseTile-"] > p {margin:0; font-size:.925rem}' +
    '.re-common-group p:last-child {color:#6ddc00}' +

	//groups
	'nav.re-groups-def, nav.re-groups-enh {position:relative; padding:0 1rem 1rem; height:calc(100% - 3rem); overflow-y:auto}' +
	'nav.re-groups-def {display:none}' +
	'.js-date .reactView span {font-size:.85em}' +
    '.js-post-list button[class*="ShowMoreButton"].is-hidden {display:block!important}' +
    '.js-post-list p[class^="BaseText"] > button {margin-top:.25rem}' +
    'div[class^="CommentsArea"] div[class^="TruncateBlock__Content"] {-webkit-line-clamp:unset}' +
    'div[class^="CommentsArea"] div[class^="TruncateBlock__Container"] p button {display:none}' +
    // '.js-main .js-sidebar {max-height:inherit !important}' +
    // '.js-main .js-sidebar .re-groups-def {padding-bottom:1rem}' +
    '.js-blurred-image > div {z-index:0}' +

    //groups list
	'span.re-posts-view {font-family: "Gibson Bold"; font-weight:500; text-transform:uppercase; background-color:transparent; color:#fff; height:auto; cursor:pointer; padding-left:2rem;}' +
	'span.re-list-head, span.re-list-view, span.re-list-load {font-family:Inter,Helvetica,Arial,"Open Sans",sans-serif; text-transform:uppercase; user-select:none; color:rgb(255,255,255,.5); height:auto}' +
	'span.re-list-view, span.re-list-load {font-family: "Gibson Bold"; font-weight:500; color:rgb(250,250,250); cursor:pointer;}' +
	'span.re-list-load:before {display:inline-block; transform:scaleX(-1)}' +
    '.re-groups-listitem {font-family:Inter,Helvetica,Arial,"Open Sans",sans-serif; display:flex; align-items:center; border:0; border-radius:.25em; padding:.5em; width:100%}' +
    `.re-groups-listitem:hover {background-color:${color8}}` +
    '.re-groups-tile {display:flex; flex-shrink:0; margin-right:1em; background-size:cover; border-radius:50%; height:3em; width:3em}' +
    '.re-groups-entry {flex-grow:1; flex-shrink:1; min-width:0; display:flex; align-items:center}' +
    '.re-groups-text {font-size:1em; font-weight:500; line-height:1.375em; word-break:break-word; margin-right:.25em; display:block; overflow:hidden}' +
    '.re-groups-name {font-size:.9375rem; color:rgb(0,163,228); font-weight:500; display:inline-block; overflow:hidden; text-overflow:ellipsis; word-wrap:normal}' +
    '.re-groups-time {font-size:.825em; color:rgba(250,250,250,.625)}' +
    '.re-groups-admin {display:flex; align-items:center; color:rgb(0,0,0,.8); background-color:rgb(0,189,255); padding:0 .25rem; border-radius:.25rem; font-size:0.625rem}' +
    '.re-groups-new {display:flex; align-items:center; color:rgb(250,250,250,.8); font-size:0.8em}' +
    '.re-groups-new svg {display:inline-block; height:1em; width:1.125em; vertical-align:middle}' +
    '.re-groups-new svg path {fill:currentcolor}' +
    `.re-groups-selected {background-color:${color9}}` +
    '.re-groups-selected .re-groups-name {color:rgba(255,255,255,.925)}' +
    '.re-groups-selected .re-groups-new {color:rgba(250,250,250,.425)}' +
    '.re-groups-selected .re-groups-admin {background-color:rgba(255,255,255,.87)}' +
    '.re-groups-visited :is(.re-groups-name, .re-groups-new) {color:rgba(250,250,250,.425)}' +
    '.re-groups-visited .re-groups-time {color:rgba(250,250,250,.375)}' +
    '.re-groups-visited .re-groups-admin {background-color:rgba(250,250,250,.425)}' +

    //travel
    '.re-member-travel, .re-radar-travel, .re-edit-travel {color:#00bdff; text-transform:initial; vertical-align: middle; line-height:inherit !important; cursor:pointer}' +
    '.re-member-travel:hover, .re-radar-travel:hover, .re-edit-travel:hover {color:#66d7ff}' +
    '.re-member-travel.re-selected, .re-radar-travel.re-selected {color:#fff}' +

    //picture rating
    '.re-rating-date {position:absolute; top:2.5rem; font-size:.85em; cursor:default}' +
    // '#picture-rating button[class^="TertiaryButton__Element-"]:focus-visible {outline:none !important}' +

    //relogin
    '.re-relogin-frame {background-color:transparent !important}' +
    '.re-relogin-frame:before {border:none !important}' +

    //more big tiles per page
	'@media screen and (min-width:55rem) {' +
	'.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(25% - 1px) !important; width:calc(25% - 1px) !important}' +
	':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(33.33333% - 1px) !important; width:calc(33.33333% - 1px) !important}' +
	'.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(50% - 1px) !important; width:calc(50% - 1px) !important}' +
    '#cruise main > ul {grid-template-columns:repeat(4,1fr)}' +
    '.is-stream-opened #cruise main > ul {grid-template-columns:repeat(3,1fr)}' +
    '}' +
	'@media screen and (min-width:75rem) {' +
	'.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(20% - 1px) !important; width:calc(20% - 1px) !important}' +
	':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(25% - 1px) !important; width:calc(25% - 1px) !important}' +
	'.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(33.33333% - 1px) !important; width:calc(33.33333% - 1px) !important}' +
    '#cruise main > ul {grid-template-columns: repeat(5,1fr)}' +
    '.is-stream-opened #cruise main > ul {grid-template-columns:repeat(4,1fr)}' +
    '}' +
	'@media screen and (min-width:95rem) {' +
	'.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(16.66666% - 1px) !important; width:calc(16.66666% - 1px) !important}' +
	':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(20% - 1px) !important; width:calc(20% - 1px) !important}' +
	'.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(25%  - 1px) !important; width:calc(25% - 1px) !important}' +
    '#cruise main > ul {grid-template-columns:repeat(6,1fr)}' +
    '.is-stream-opened #cruise main > ul {grid-template-columns:repeat(5,1fr)}' +
    '}' +
	'@media screen and (min-width:120rem) {' +
	'.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(12.5% - 1px) !important; width:calc(12.5% - 1px) !important}' +
	':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(16.66666% - 1px) !important; width:calc(16.66666% - 1px) !important}' +
	'.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(20% - 1px) !important; width:calc(20% - 1px) !important}' +
    '#cruise main > ul {grid-template-columns:repeat(7,1fr)}' +
    '.is-stream-opened #cruise main > ul {grid-template-columns:repeat(6,1fr)}' +
    '}' +
	'@media screen and (min-width:140rem) {' +
	'.search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(12.5% - 1px) !important; width:calc(12.5% - 1px) !important}' +
	':is(.is-filter-opened, .is-stream-opened) .search-results--big-tiles .search-results__item:not(.search-results__banner) {padding-bottom:calc(12.5% - 1px) !important; width:calc(12.5% - 1px) !important}' +
	'.is-stream-opened.is-filter-opened #profiles div.search-results--big-tiles div.search-results__item:not(.search-results__banner) {padding-bottom:calc(16.66666% - 1px) !important; width:calc(16.66666% - 1px) !important}' +
    '#cruise main > ul {grid-template-columns:repeat(8,1fr)}' +
    '.is-stream-opened #cruise main > ul {grid-template-columns:repeat(7,1fr)}' +
    '}' +


    //*** DESKTOP ***
	'@media screen and (min-width:768px) {' +

    //general
    '.re-mobi {display:none!important}' +
    '.re-touch {visibility:hidden!important}' +

    //FIX for group member tiles and posting likes
    // '.grouped-tiles-small .tile {width:calc(33.33333% - 1px)}' +
    // '.grouped-tiles-big .tile {width:calc(50% - 1px)}' +
    'div.Container--XPRsa .tile {width: calc(25% - 1px)}' +

    '}' +


    //*** DESKTOP >= 1024px ***
    '@media screen and (min-width:1024px) {' +

    //stream
    '.stream {width:18.5rem!important}'+
    '.is-stream-opened .layer--nav-primary {right:18.5rem}' +

    '}' +


    //*** TABLET  ***
    '@media screen and (max-width:1024px) {' +

    //top right navigation
    'nav.js-top-right-navigation{display:none}' +

    //FIX for group member tiles and posting likes
    '.grouped-tiles-small .tile {width:calc(33.33333% - 1px)}' +
    '.grouped-tiles-big .tile {width:calc(50% - 1px)}' +
    'div.Container--XPRsa .tile {width: calc(33.33333% - 1px)}' +

    '}' +


	//*** MOBILE ***
	'@media screen and (max-width:767px) {' +

    //general
    '.re-desk {display:none!important}' +
    '.re-touch {visibility:hidden!important}' +

    //tweak dark design
    `.ui-navbar--app, .ui-navbar.content-nav ul {background-color:#000 !important}` +
    'div.content-nav--animated {height:3rem}' +

    //main menu
    'a[class*="Tabbed-nav-link-"], #cruise nav > a {font-size:.875rem}' +

    //stream
    '.re-stream-toggle-div {top:calc(3.25rem - 2px); padding:.5rem 1rem 0}' +
    'div.stream__content {top:calc(5.5rem + 1px)}' +

    //messages
    '#messenger .js-header-region > div > h1 {padding-top:1.7rem}' +
    '#messenger .re-msg-location {padding-top:.75rem}' +
    '#messenger .re-msg-login {top:3.2rem}' +
    '#messenger .re-msg-offline {top:3.2rem}' +
    '.messages-send__form textarea.input--chat {font-size:.9375rem; letter-spacing:normal}' +

    //group tiles
    'div[class*="Tile__BaseTile-"] > p {margin:0; font-size:.925rem}' +

    //picture rating
    '.re-rating-date {top:3rem}' +

    //hide groups list when group selected, wrap long group names
    'div[class*="withNav-"] > div[class*="ReactContainer-"]:not(:has(.re-groups-def)) {flex:0; width:0}' +
    'div[class*="withNav-"] > div[class*="ReactContainer-"]:not(:has(.re-groups-def)) > :is(div, nav) {display:none}' +
    'div[class*="withNav-"] > div[class*="ReactContainer-"]:has(.re-groups-def) + div.js-main {display:none}' +
    'div[class^="NameAndOfficialBadgeWrapper-"] {white-space:unset}' +

    //back arrow in group post
    '#group-post .js-post button.js-back {margin:1rem 1rem 0 -.5rem}' +

    //FIX for flickering top right navigation
    'nav.js-top-right-navigation {display:none}' +

    //FIX to hide floating filter button in eyecandy
    'main:has(nav a[href^="/eyecandy/"]) .js-floating-filter-button {display:none}' +

    //show distance slide in hunqz nearby filter
    '.js-main-stage:has(.js-navigation a[href^="/hunqz"]) div.js-distance-radius.filter__group {display:block !important}' +

    '}' +


    //*** TOUCH SCREEN ***
	'@media screen and (hover:none) {' +

    //general
    '.re-touch {visibility:visible!important}' +

    //icons, links
    'a.re-icon {padding:.25em 0 .25rem .5em; font-size:1em}' +
    'div.stream__content a.re-icon {padding:.25em .5em .25em 0}' +
    'a.re-eyecandy-toggle {padding:.25em .5em}' +

    '}'

);


// ***** Clean up old settings *****
localStorage.removeItem('contactsSelection');
localStorage.removeItem('REgroupPostsView');
localStorage.removeItem('reRatingMax');


// ***** Test if mobile or touch device *****
// usage: if (mobile.matches) ...; if (touch.matches) ...
let mobile = window.matchMedia("(max-width:767px)");
let touch = window.matchMedia("(hover:none)");


// **************************************************************
// ***** The following function is taken from:
// ***** https://gist.github.com/BrockA
// ***** https://gist.github.com/raw/2625891/waitForKeyElements.js


/*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.

    Usage example:

        waitForKeyElements (
            "div.comments"
            , commentCallbackFunction
        );

        //--- Page-specific function to do what we want when the node is found.
        function commentCallbackFunction (jNode) {
            jNode.text ("This comment changed by waitForKeyElements().");
        }

    IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements (
selectorTxt,    /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */
 actionFunction, /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */
 bWaitOnce,      /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */
 iframeSelector  /* Optional: If set, identifies the iframe to
                        search.
                    */
) {
	var targetNodes, btargetsFound;

	if (typeof iframeSelector == "undefined")
		targetNodes     = $(selectorTxt);
	else
		targetNodes     = $(iframeSelector).contents ()
			.find (selectorTxt);

	if (targetNodes  &&  targetNodes.length > 0) {
		btargetsFound   = true;
		/*--- Found target node(s).  Go through each and act if they
            are new.
        */
		targetNodes.each ( function () {
			var jThis        = $(this);
			var alreadyFound = jThis.data ('alreadyFound')  ||  false;

			if (!alreadyFound) {
				//--- Call the payload function.
				var cancelFound     = actionFunction (jThis);
				if (cancelFound)
					btargetsFound   = false;
				else
					jThis.data ('alreadyFound', true);
			}
		} );
	}
	else {
		btargetsFound   = false;
	}

	//--- Get the timer-control variable for this selector.
	var controlObj      = waitForKeyElements.controlObj  ||  {};
	var controlKey      = selectorTxt.replace (/[^\w]/g, "_");
	var timeControl     = controlObj [controlKey];
    var timerInterval   = (document.visibilityState === "visible" ? 750 : 1500);

	//--- Now set or clear the timer as appropriate.
	if (btargetsFound  &&  bWaitOnce  &&  timeControl) {
		//--- The only condition where we need to clear the timer.
		clearInterval (timeControl);
		delete controlObj [controlKey];
	}
    else {
        //--- Set a timer, if needed.
        if ( ! timeControl ) {
            timeControl = setInterval ( function () {
                waitForKeyElements (selectorTxt,
                                    actionFunction,
                                    bWaitOnce,
                                    iframeSelector
                                   );
            },
                                       timerInterval
                                      );
            controlObj [controlKey] = timeControl;
        }
    }
    waitForKeyElements.controlObj   = controlObj;
}

// ***** end
// **************************************************************


// ***** Set AJAX headers *****
let key0 = window.atob('WC1TZXNzaW9uLUlk');
let key1 = window.atob('WC1BcGktS2V5');
let val1 = window.atob('QVM4YnpHSExBOFk5QlhGNzNpRE51UUJIZUVPMFVLamY=');
function ajaxHead(){
	return {[key0]: JSON.parse(localStorage.getItem('PR_SETTINGS:SESSION_ID')), [key1]: val1};
}


// ***** Replace URLs with links *****
// ***** Adopted from https://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links/37687#37687 *****
function linkify(text) {

	//Email addresses
	let pattern1 = /(^|\s|<br>)(([-\w\.\+])+@[-\w]+?(\.[A-Z]{2,6})+)/gim;
	text = text.replace(pattern1, '$1<a href="mailto:$2" class="plain-text-link">$2</a>');

	//URLs starting with http:// or https://
	let pattern2 = /(^|\s|<br>)(https?:\/\/[-\w+&@#\/\(\)%?=~|!:,.;]*[-\w+&@#\/\(\)%=~|])/gim;
	text = text.replace(pattern2, '$1<a target="_blank" href="$2" rel="noreferrer noopener" class="plain-text-link">$2</a>');

	//URLs without http:// or https://
	let pattern3 = /(^|\s|<br>)([-\w]+\.[a-z][-\w+&#\/\(\)%?=~|!:,.;]*[-\w+&#\/\(\)%=~|])/gm;
	text = text.replace(pattern3, '$1<a target="_blank" href="http://$2" rel="noreferrer noopener" class="plain-text-link re-link-idle">$2</a>');

	//keep internal links in same window
	let pattern4 = /(target="_blank" )(href="https?:\/\/(www\.)?((planet)?romeo|hunqz)\.com)/gim;
	text = text.replace(pattern4, '$2');

	return text;
}


// ***** Format date/time *****
function dateTime(timeStamp, dateOnly, noYesterday) {
    // 1s...59s; 1min...59min; hh:mm; gestern hh:mm; Mo...So, hh:mm; tt.mm.jjjj, hh:mm
    let timeNow = new Date();
    let timeNow1 = new Date (timeNow - 1000*60*60*24);
    let timeNow7 = new Date (timeNow - 1000*60*60*24*7);
    let hhmm = {hour: "numeric", minute: "numeric"};
    // console.log('dateTime');
    if (timeStamp) {
        if (timeStamp.toDateString() == timeNow.toDateString()) {  // today
            return timeStamp.toLocaleString('de-DE', hhmm);
        } else if (!noYesterday && timeStamp.toDateString() == timeNow1.toDateString()) {  // yesterday
            return `gestern ${timeStamp.toLocaleString('de-DE', hhmm)}`;
        } else if (timeStamp > timeNow7 && timeStamp.getDay() != timeNow.getDay()) {  // last 6 days of week
            return timeStamp.toLocaleString('de-DE', {weekday: 'short', hour: "numeric", minute: "numeric"});
        } else {
            return `${timeStamp.toLocaleDateString('de-DE')}${dateOnly ? `` : `, ${timeStamp.toLocaleString('de-DE', hhmm)}`}`;
        }
    }
}


// ***** Format weekday/time *****
function weekTime(timeStamp, dateOnly) {
    // console.log('weekTime');
    return dateTime(timeStamp, dateOnly, true);
}


// ***** Change URL without reloading the page *****
function changeUrl(url) {
	history.pushState({}, document.title, url);
	history.pushState({}, document.title, url);
	history.back();
}


// ***** Change object property in sessionStorage *****
function setSessionStorageItem(storageItem, key, value) {
    let storageValue = JSON.parse(sessionStorage.getItem(storageItem));
    (storageValue) ? storageValue[key] = value : storageValue = JSON.parse(`{"${key}":"${value}"}`);
    sessionStorage.setItem(storageItem, JSON.stringify(storageValue));
}


// ***** Change object property in localStorage *****
function setLocalStorageItem(storageItem, key, value) {
    let storageValue = JSON.parse(localStorage.getItem(storageItem));
    (storageValue) ? storageValue[key] = value : storageValue = JSON.parse(`{"${key}":"${value}"}`);
    localStorage.setItem(storageItem, JSON.stringify(storageValue));
}


// ***** Add title and aria-label *****
jQuery.fn.titleLabel = function(text) {
    return $(this).attr({'title': text, 'aria-label': text});
};


// ***** Open message thread or contact info by profile name *****
// (not working for blocked, blocking, or deactivated profiles)
function openByName(baseUrl, profileLink, profileName) {
	let profileType = 'profiles';
	if (profileLink.match(/^\/hunq\//)) {
		profileType = 'hunqz/profiles';
	/*} else if (profileLink.match(/^\/groups?\//)) {
		profileType = 'groups';*/
	}
	$.ajax({url: `/api/v4/${profileType}?pick=items.*.(id,name)&lang=de&length=1&filter[fulltext_search_mode]=EXACT&filter[fulltext]=@${profileName}`,
	// $.ajax({url: `/api/v4/${profileType}?lang=de&length=1&filter[fulltext_search_mode]=EXACT&filter[fulltext]=@${profileName}`,
			headers: ajaxHead()
		   })
	.done(function (data) {
		if (data.items[0]?.name == profileName) {
			changeUrl(baseUrl + data.items[0].id);
/*         } else {
            $.ajax({headers: ajaxHead(), url: `/api/v4/${profileType}/${profileName}/full?lang=de&lookup_type=NAME`})
            .done(function (data) {
                if (data.id) {
                    changeUrl(baseUrl + data.id);
                }
            }); */
        }
	});
}


// ***** Hide visit *****
function hideVisit(profileId) {
	if (profileId > 0) {
		$.ajax({url: `/api/v4/visits/${profileId}`,
				headers: ajaxHead(),
				method: 'DELETE'
			   })
		.done(function () {
			$('div.profile-topnav__hide').attr('style', 'visibility:hidden');
            $('.profile .top-info-header button').attr('style', 'color:rgb(168,168,168)');
		})
	}
}


// ***** Search profile id, open profile name directly *****
function searchInput(jNode) {
	$('#search a.re-add').off('click').remove();
	$(jNode).on('input click', function() {
        let inputValue = $(jNode).val().trim();
		$('#search a.re-add').off('click').remove();
		if (inputValue.match(/^[1-9]\d{2,}$/)) {
			$('#search div.js-input').after(
                `<a style="font-size:1rem" class="re-add re-link-no-hover mr-"><span class="re-desk">Profil-ID ${inputValue} anzeigen</span><span class="re-mobi">ID anzeigen</span></a>`
			).next('.re-add').click(function() {
				openById(inputValue);
				return false;
			});
        } else if (inputValue.match(/^@?[-\w\/]{3,}$/)) {
            inputValue = inputValue.replace(/^@/, '');
            $('#search div.js-input').after(
                `<a style="font-size:1rem" class="re-add re-link-no-hover mr-"><span>${inputValue}</span><span class="re-desk"> anzeigen</span></a>`
            ).next('.re-add').click(function() {
                changeUrl(((inputValue.match(/^\//)) ? '' : '/profile/') + inputValue);
                return false;
            });
        }
    });
}


// ***** Open profile by ID *****
function openById(id) {
    $.ajax({headers: ajaxHead(), url: `/api/v4/profiles/${id}?expand=partner&lang=de`})

	.done(function (data) {
		let type = (data.type) ? data.type : '';
		let name = (data.name) ? data.name : '';
        let profileType = 'profile';
        if (type == 'ESCORT') {
            profileType = 'hunq';
        } else if (type == 'CLUB') {
            profileType = 'group';
        }
        changeUrl(`/${profileType}/${name}`);
    })

   	.fail(function (data) {
		changeUrl('/profile/-');
	});
}


// ***** Picture upload month/year *****

//init
const imgTable = [
    0x00000000,0x00000000,0x000076f1,0x0000c0c2,0x000113ac,0x000193c7,0x00022adb,0x0002e59b,0x0003c274,0x0004bdda,0x0005cb8f,0x0006f792, //2003
    0x00083008,0x0009e166,0x000bc399,0x000dc70d,0x000fcad1,0x001219f5,0x0014e794,0x00180a61,0x001b7a83,0x001eeaaa,0x0022a968,0x0026aaf2, //2004
    0x002b6a0f,0x00313036,0x00367804,0x003c4ae9,0x00421a3c,0x0048a2b6,0x004f0ba9,0x00563a9c,0x005d4ff9,0x00642853,0x006bc702,0x0072dc7c, //2005
    0x007a1d79,0x0082636e,0x0089c239,0x00921763,0x009ae5e4,0x00a3f682,0x00acfdc3,0x00b72e9e,0x00c1e7a3,0x00cb8a2a,0x00d5c06a,0x00df62f9, //2006
    0x00e909cb,0x00f428f1,0x00fe1934,0x0108ec16,0x01141330,0x011ffb48,0x012b9597,0x0137a3d7,0x01436c46,0x014eba63,0x015aea4f,0x01665b6d, //2007
    0x01721df4,0x017ecdd9,0x018a872f,0x0197cf93,0x01a3fca7,0x01b1bb65,0x01bf7fca,0x01ce211f,0x01dd38f7,0x01eb50f3,0x01fa24cc,0x02086e83, //2008
    0x0216e0ea,0x0226de08,0x0234eda5,0x02552d86,0x02677f82,0x027aae57,0x028d8ba1,0x02a17765,0x02b5e176,0x02c9adb5,0x02dd8aa7,0x02f0dfe7, //2009
    0x0304f979,0x031b4142,0x032e6143,0x0342c960,0x0356cd19,0x036cb4d8,0x0381a103,0x039883d7,0x03b0829f,0x03c679e3,0x03dd820b,0x03f432a3, //2010
    0x040af127,0x0423ddbb,0x0439c102,0x045272f4,0x046b187c,0x048570e5,0x04a07ac1,0x04bcdec4,0x04d9b4fa,0x04f52906,0x05120af3,0x052df05d, //2011
    0x054a17b7,0x0568a266,0x0584c8a7,0x05a2542c,0x05c0b41b,0x05df82c6,0x05fe3605,0x061e623c,0x063f4240,0x065e2c95,0x067e22b7,0x069dbc58, //2012
    0x06c0432b,0x06e65935,0x0707d372,0x072df9eb,0x0751ca91,0x077860fb,0x079c0f3e,0x07c1e771,0x07e86366,0x080da8ce,0x0831ee79,0x08558d00, //2013
    0x087b94b3,0x08a21c54,0x08c4f55c,0x08eac6fe,0x09119caa,0x0939b66c,0x0960b60a,0x098a9925,0x09b76f87,0x09e01456,0x0a0906c0,0x0a305fc4, //2014
    0x0a80e9b2,0x0adaf553,0x0b2947ce,0x0b7d3b5d,0x0bcf0d62,0x0c24958c,0x0c755b5b,0x0ccd15d3,0x0d23c304,0x0d7376dc,0x0dc5b8fb,0x0e14171b, //2015
    0x0e6631a2,0x0ebdf2c6,0x0f0ce2ec,0x0f607bf5,0x0fb37151,0x100aaf7e,0x105e79ed,0x10b8bbbf,0x1115e3c6,0x116ae5ac,0x11be74a4,0x12120f48, //2016
    0x1267b822,0x12c42272,0x1315847b,0x136e4be0,0x13c5fdbd,0x141f2328,0x147b0f3e,0x14ddb823,0x1541cb7a,0x159e4a36,0x1601fff2,0x16657140, //2017
    0x16cb8a49,0x1734ece1,0x1792be94,0x17fa4016,0x185ec78c,0x18c964a2,0x193230d2,0x19a225bd,0x1a12bd1e,0x1a79217a,0x1ae1e760,0x1b4790fd, //2018
    0x1bb4f607,0x1c23ce64,0x1c84b186,0x1cf2f646,0x1d61d7f8,0x1ddb56d8,0x1e59a281,0x1edf03d3,0x1f66d255,0x1fe26f82,0x205ec456,0x20d2bded, //2019
    0x21484431,0x21c24ba6,0x2230c76f,0x22a6c5d6,0x2312ebbd,0x23844c83,0x23f45e75,0x246d38c1,0x24eb9338,0x255dc81a,0x25d30c39,0x264389a4, //2020
    0x26b9cb56,0x2730f2c2,0x279824a7,0x28072e59,0x2870b46c,0x28e20cee,0x294ea8da,0x29bdfadb,0x2a2f2276,0x2a9872c3,0x2b026a37,0x2b6628b7, //2021
    0x2bcd3a33,0x2c3819e9,0x2c95c80f,0x2cf66474,0x2d57afa7,0x2dc0df59,0x2e28345d,0x2e94df63,0x2f014a14,0x2f63046c,0x2fc3e3be,0x301fada4, //2022
    0x307f54f1,0x30e474f9,0x313bdc54,0x319a0106,0x31f9240f,0x3259674f,0x32b90ef5,0x3320eb2e,0x3386b446,0x33e585da,0x34460066,0x34a0e6fc, //2023
    0x34ffebb4,0x35602515,0x35b6161a,0x360fc04f,0x3669cd5f,0x36ca77e2,0x37290915,0x378e4751,0x37f576a3,0x385137a2,0x38ae3822,0x39094260, //2024
    0x39675d6b,0x39c57877,0x3a1a781f,0x3a7872c9,0x3ad384b3,0x3b319fbf,0x3b8cb1aa,0x3beaccb5,0x3c48e7c1,0x3ca3f9ac,0x3d023519,0x3d5d4703, //2025
    0xffffffff];

function uploadDate(imgName) {
    const monthYearIndex = (element) => element >= parseInt(imgName, 16);
    let index = imgTable.findIndex(monthYearIndex) - 1;
    let year = 2003 + Math.trunc(index / 12);
    let month = 1 + index % 12;
    if (imgTable[index] == 0x00000000) {
        return '';
    } else {
        return `≈ ${month}.${year}${imgTable[index + 1] == 0xffffffff ? '+' : ''}`;
    }
}


// ***** Copy message thread to clipboard *****
function copyMessageThread(profileId, profileName) {
    let loadTime = new Date().toLocaleString('de-DE').slice(0,-3);
    let ownName = 'unknown';
    let msgThread = '- keine Nachrichten -';
    let msgCount = '', header = '', msgTime = '', name = '';
    $.ajax({headers: ajaxHead(), url: `/api/v4/session?lang=de`}).done(function (data) {
        if (data.username) {
            ownName = data.username;
        }
        let jsonParam = `lang=de&length=10000&filter[folders][]=SENT&filter[folders][]=RECEIVED&filter[partner_id]=${profileId}`;
        $.ajax({headers: ajaxHead(), url: `/api/v4/messages?${jsonParam}`})

            .done(function (data) {

            //gather data
            header = 'Nachrichtenverlauf von ' + ownName + ' mit ' + profileName + ' (Profil-ID ' + profileId + ') vom ' + loadTime + '\n\n\n';
            if (data.items_total) {
                msgThread = '';
                msgCount = data.items_total;
                for (let i = msgCount-1; i >= 0; i--) {
                    msgTime = new Date(data.items[i].date.slice(0,-5)+'.000Z');
                    name = (data.items[i].folder == 'SENT') ? ownName : profileName;
                    msgThread += msgCount-i + '. ' + name + '  •  ' + msgTime.toLocaleString('de-DE').slice(0,-3);
                    msgThread += (data.items[i].unread == true) ? ' [ungelesen]' : '';
                    msgThread += (data.items[i].locked == true) ? ' [gespeichert]' : '';
                    msgThread += (data.items[i].spam == true) ? ' [Spam]' : '';
                    msgThread += '\n-------------------------------------------------------------------';
                    msgThread += '\n' + data.items[i].text.trim() + '\n\n\n';

                    //handle links
                    if (data.items[i].attachments) {
                        for (let item of data.items[i].attachments) {
                            if (item.type == 'COMMAND') {
                                msgThread += `[${item.index + 1}] ${item.params?.url?.match(/^\//) ? 'https://www.romeo.com' : ''}${item.params?.url}\n`;
                                msgThread = msgThread.replace(`[[${item.index}]]`, `${item.params?.text} [${item.index + 1}]`);
                            }
                        }
                        msgThread += '\n\n'
                    }
                }
                msgThread += '\n====================\n\n';
            }

            //confirm copy
            $('div.js-correspondence').after(
                `<div class="re-bubble layout layout--h-center">
<div class="ui-bubble ui-bubble--dark" style="width:auto; top:72px">
<div class="ui-bubble__content [ js-content ] [ js-scrollable ] scrollable">
<div class="confirm-box">
<div class="txt-right" style="margin:-.5rem -.5rem -.75rem">
<a class="re-copy-cancel re-link icon icon-larger icon-cross"></a>
</div>
<div class="confirm-box__label" style="margin-bottom:1.125rem">
<p>Nachrichtenverlauf kopieren</p>
</div>
<div class="confirm-box__actions">
<button class="re-copy-email ui-button ui-button--transparent">E-Mail-Entwurf</button>
<button class="re-copy-confirm ui-button ui-button--primary">Zwischenablage</button>
</div>
</div>
</div>
</div>
</div>
<div class="layer ui-bubble__overlay l-fancy"></div>`
            );
            $('button.re-copy-confirm').focus().click(function() {
                navigator.clipboard.writeText(header + msgThread);
                $('div.re-bubble, div.ui-bubble__overlay').hide();
            });
            $('button.re-copy-email').click(function() {
                location.href = `mailto:?subject=${encodeURIComponent(header)}&body=${encodeURIComponent(msgThread)}`;
                $('div.re-bubble, div.ui-bubble__overlay').hide();
            });
            $('a.re-copy-cancel, div.ui-bubble__overlay').click(function() {
                $('div.re-bubble, div.ui-bubble__overlay').hide();
            });
        });
    });
}


// ***** Fix scrolling on larger screens for messages list *****
function fixScroll (jNode) {
    $(jNode).parents('.js-chat .js-scrollable').attr('style', 'max-height:inherit !important');
}


// ***** Add double tap to menu items, change behavior for mobile *****
function handleMainMenu (jNode) {
    if (!mobile.matches) {
        // $(jNode).has('path[d^="M13.493"]').off().has('p[class^="SpecialText"]').click(function() { changeUrl('/messenger/chat'); return false; });
        // $(jNode).filter('a[href^="/messenger"]').attr('style', 'pointer-events:auto').filter('a[isactive="true"]').off().click(function() { $('#messenger').addClass('is-hidden'); return false; });
        // $(jNode).has('path[d^="M6.99 10"]').off().click(function() { changeUrl('/groups/discover'); return false; });
        $(jNode).has('path[d^="M6.99 10"]').find('button').off('click').click(function() {
            if (lastGroupName && sessionStorage.getItem('PR_SETTINGS:cachedNavTab')?.match('"groups":"member"')) {
                changeUrl(`/groups/member/${lastGroupName}`);
                return false;
            // } else {
            //     return true;
            }
        });
    } else {
        $(jNode).has('path[d^="M20.3943"], path[d^="m20.507"]').off('dblclick').dblclick(function() { changeUrl('/stream'); return false; });
        $(jNode).has('path[d^="M23.102"]').off('dblclick').dblclick(function() {
            changeUrl('/visitors/me');
            setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'visitors', 'me');
            return false;
        });
        $(jNode).has('path[d^="M13.493"]').off('dblclick').dblclick(function() {
            changeUrl('/messenger/contacts');
            setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'messenger', 'contacts');
            return false;
        });
        $(jNode).has('path[d^="M6.99 10"]').off('click').click(function() { changeUrl('/groups/member'); return false; });
    }
}


// ***** Add contacts etc. to tab navigation *****
function handleTabMenu (jNode) {

    //icons on radar and eyecandy
    if (!mobile.matches) {
        $('main nav.js-navigation ul').has('a[href^="/radar/"], a[href^="/eyecandy/"]').not(':has(li.re-add > span)').append(
            '<li class="Tabbed-nav-item--TCdwx scroll-cue-item re-add"><span class="re-add re-radar-travel icon icon-airplane" title="Reiseziel"><span></span></span></li>'
        );
        $('main nav.js-navigation ul').has('a[href^="/radar/"], a[href^="/eyecandy/"], a[href^="/hunqz"]').not(':has(li.re-add > a)').append(
            '<li class="Tabbed-nav-item--TCdwx scroll-cue-item re-add"><a style="cursor:default">|</a></li>' +
            '<li class="Tabbed-nav-item--TCdwx scroll-cue-item re-add"><a href="/messenger/contacts" class="js-nav-item Tabbed-nav-link--x9FLb" title="Kontakte"><span class="icon icon-save-contact"></span></a></li>' +
            `<li class="Tabbed-nav-item--TCdwx scroll-cue-item re-groups-icon re-add"><a href="" class="js-nav-item Tabbed-nav-link--x9FLb" title="Meine Gruppen (${lastGroupName})"><span class="icon icon-group-members"></span></a></li>`
        );
        $('.re-groups-icon a').off('click').click(function() {
            (async() => {
                while (!lastGroupName) await new Promise(resolve => setTimeout(resolve, 300));
                changeUrl(`${(lastGroupName) ? `/groups/member/${lastGroupName}` : `/groups/member`}`);
                setSessionStorageItem('PR_SETTINGS:cachedNavTab', 'groups', 'member');
            })();
            return false;
        });

        //fix for phantom filter in eyecandy and visitors
        $('.js-main-stage .js-navigation ul').has('a[href^="/eyecandy/"], a[href^="/visitors"]').closest('.is-filter-opened').removeClass('is-filter-opened');
    } else {
        $('main nav.js-navigation ul').has('a[href^="/radar/"], a[href^="/eyecandy/"]').not(':has(li.re-add)').prepend(
            '<li class="Tabbed-nav-item--TCdwx scroll-cue-item re-add"><span class="re-add re-radar-travel icon icon-airplane" title="Reiseziel"><span></span></span></li>'
        );
    }

    //travel
    if ($('main nav.js-navigation ul').has('a[href^="/radar/"], a[href^="/eyecandy/"]').length) {
    	let viewMode = localStorage.getItem('REradarTravelLocation');
    	let toggleTravel = 'span.re-radar-travel';
        handleTravelLocation(viewMode, toggleTravel);
    	$('span.re-radar-travel').off().click(function() {
			let viewMode = localStorage.getItem('REradarTravelLocation');
	  	    let toggleTravel = 'span.re-radar-travel';
	   	    let refreshClick = 'li.Tabbed-nav-item--TCdwx a.is-selected';
			viewMode = (viewMode == 'travel') ? 'default' : 'travel';
			localStorage.setItem('REradarTravelLocation', viewMode);
			handleTravelLocation(viewMode, toggleTravel, refreshClick);
		});
    }

    //icons on groups
    if (!mobile.matches) {
        $('div.js-navigation ul').has('a[href="/groups/discover"]').not(':has(li.re-add)').append(
            '<li class="Tabbed-nav-item--TCdwx re-add"><a style="cursor:default">|</a></li>' +
            '<li class="Tabbed-nav-item--TCdwx re-add"><a href="/messenger/contacts" class="js-nav-item Tabbed-nav-link--x9FLb" title="Kontakte"><span class="icon icon-save-contact"></span></a></li>'
        );
    }
    $('div.js-navigation ul li').not('.re-add').find('a[href="/groups/member"]').titleLabel(`${commonGroupsList.length.toLocaleString('de-DE')} Gruppen abonniert`);


    //remove GR-Tools hint
    $('li.re-grtools').remove();
}


// ***** Show versions and users online in main menu *****
function handleVersion (jNode) {
    $(jNode).addClass('re-done');
    let reVersion = `${(typeof GM_info !== 'undefined') ? `${GM_info.script.name} ${GM_info.script.version}` : 'RomeoEnhancer'}`;
    let onlineAll = '';
    $.ajax({url: `/api/services/landing/online-count`})
        .done(function (data) {
        onlineAll = `${data.online_count?.toLocaleString('de-DE')} Profile online`;
        $(jNode).find('.re-add').remove();
        $(jNode).append(`<div class="txt-preserve re-add">${reVersion}\n${onlineAll}</div>`);
        let profileType = ($('li.is-selected, li[class*="list__item--active"]').find('a[href*="/hunqz"]').length) ? '/hunqz/profiles' : '/profiles';
        let users = '', online = '';
        $.ajax({headers: ajaxHead(), url: `/api/v4${profileType}?pick=items,items_total&lang=de&length=1`}).done(function (data) {
            users = `${data.items_total?.toLocaleString('de-DE')} User`;
            $.ajax({headers: ajaxHead(), url: `/api/v4${profileType}?pick=items,items_total&lang=de&length=1&filter[online_status][]=ONLINE&filter[online_status][]=DATE&filter[online_status][]=SEX`}).done(function (data) {
                online = `${data.items_total?.toLocaleString('de-DE')} online`;
                $(jNode).off().on('dblclick', function() {
                    $(jNode).find('div').replaceWith(`<div class="txt-preserve re-add">${reVersion}\n${onlineAll}\n${users} • ${online}</div>`);
                });
            });
        });
    });
}


function handleSettingsDisplay (jNode) {
    $(jNode).addClass('re-done');
    // console.log('handleSettingsDisplay');

    //show/hide softcore
    $(jNode).closest('div.settings__key').clone().insertAfter($(jNode).closest('div.settings__key')).addClass('re-hide-nsfw-switch');
    $('span[data-cta="showHeadline"]').text('Überschrift in großen Kacheln anzeigen');
    $('.re-hide-nsfw-switch').find('span.re-done').text('Softcore ausblenden');
    $('.re-hide-nsfw-switch').find('a.ui-hint').attr('data-hint', 'Softcore-Inhalte in Kacheln werden durch neutrale Platzhalter ersetzt.');
    $('.re-hide-nsfw-switch').find('.js-plus-always, .js-plus').removeClass('js-plus-always js-plus is-disabled-clickable');
    $('.re-hide-nsfw-switch').find('.ui-toggle__label').attr('for', 'uid-re-100');
    $('.re-hide-nsfw-switch').find('.ui-toggle__input').attr('id', 'uid-re-100').prop('checked', hideNSFW).off().on('change', function() {
        hideNSFW = !hideNSFW;
        localStorage.setItem('REhideNSFW', hideNSFW);
        $('.re-hide-nsfw-switch').find('.ui-toggle__input').prop('checked', hideNSFW);
        // console.log('hideNSFW: ', hideNSFW);
        setTimeout(() => {location.reload()}, 200);
    });
    // console.log('init hideNSFW: ', hideNSFW);

    //switch color scheme
/*     $('.js-grid-style-selector').clone().insertBefore($('.js-grid-style-selector').siblings().first()).removeClass('js-grid-style-selector').addClass('re-color-scheme-switch');
    $('.re-color-scheme-switch').find('summary > div > span').text('Farbschema');
    $('.re-color-scheme-switch').find('input').each(function() {
        $(this).attr('name', 'color-scheme').attr('id', 'retroBlue').attr('value', 'retroBlue');
        $(this).siblings('label').attr('for', 'retroBlue').find('span').text('Retro Blue');
    }); */
}


// ***** Show selected bookmark in filter icon title and text *****
function handleBookmark (jNode) {
// console.log('handleBookmark');

    let selectedBookmark = $('div[class^="js-bookmarks-list"] div[class*="item__is-selected"] a').text().trim();
    if (selectedBookmark) {
        $('div.js-filter-button button').attr('style', 'color:rgb(250,250,250); background-color:transparent');
        $('div.js-filter-button button title').text(`Lesezeichen: ${selectedBookmark}`);
        $('div.js-filter-button button span').not('.re-add').addClass('re-filter-options-text');
        $('div.js-filter-button button').not(':has(.re-add)').append(`<span class="re-add re-filter-bookmark-name mr">${selectedBookmark}</span>`);
        $('div.js-filter-button button span').attr('title', `Lesezeichen: ${selectedBookmark}`);
        //$('nav.js-navigation li.js-filter-button div.js-filter-button button').attr('title', `Lesezeichen: ${selectedBookmark}`);
        $('button.ui-navbar__button--bookmarks').attr('title', 'Lesezeichen auswählen');
    } else {
        $('div.js-filter-button button span.re-filter-options-text').removeClass('re-filter-options-text');
        $('div.js-filter-button button span.re-add').remove();
    }
}


// ***** Preview unread messages in title tag, delete by clicking blue badge *****
function previewMessage (jNode) {
    $(jNode).addClass('re-done');
	let profileId = $(jNode).closest('a').attr('href').match(/\d{3,}/);
    if (profileId) {
	    $(jNode).closest('a').addClass('re-id' + profileId);
	    let msgCount = parseInt($(jNode).text());
	    let jsonParam = 'lang=de&length=' + msgCount + '&filter[folders][]=RECEIVED&filter[partner_id]=' + profileId;
	    let msgText = '';
	    let thisId = '.re-id' + profileId;
	    $.ajax({headers: ajaxHead(), url: '/api/v4/messages?' + jsonParam}).done(function (data) {

    		//preview
	    	for (let i = msgCount-1; i >= 0; i--) {
		    	msgText += '\r' + data.items[i].text + '\r';

                //handle links
                if (data.items[i].attachments) {
                    for (let item of data.items[i].attachments) {
                        if (item.type == 'COMMAND') {
                            // console.log('attachments COMMAND');
                            msgText = msgText.replace(`[[${item.index}]]`, `[${item.params?.text}]`);
                        }
                    }
                }

    		}
	    	$(thisId).attr('title', msgText);
            $(thisId).find('img').parent('div').attr('title', msgText);

            //delete unread
            $(jNode).parent('div').wrap(`<div style="margin:-.5rem; padding:.5rem; ${(touch.matches) ? 'margin:0 0 -.5rem -.5rem' : ''}"></div>`).parent().attr('title', 'Löschen').off().click(function () {
                if (confirm(msgCount + ' Nachrichten ungelesen löschen?')) {
                    for (let i = msgCount-1; i >= 0; i--) {
                        $.ajax({url: '/api/v4/messages/' + data.items[i].id + '?expand=from',
                                headers: ajaxHead(),
                                method: 'DELETE'
                               })
                        .done(function (data) {
                            $(jNode).closest('a[href^="/messenger/chat"]').attr('title', 'Liste aktualisieren').off().click(function() {
                                $('#messenger .js-navigation a.is-selected')[0].click();
                                return false;
                            }).children('div').replaceWith(
                                `<div style="font-size:.925rem;color:rgba(255,255,255,.8)">– ungelesen gelöscht –</div>`
                            );
                        })
                    }
                }
                return false;
            })
        });

        //highlight profile name
        $(jNode).closest('a').find('p[class^="BodyText"]').attr('style', 'color:rgba(255,255,255,1); opacity:1');
    }
}


// ***** Show last login and location in message thread *****
function showLoginLocation (jNode) {

    //placeholder for login and location
    $('section.js-detail > div').not(':has(.re-login-location)').append(
        `<div class="re-login-location re-add"></div>`
    );

    if ($('.js-correspondence > div').filter('.re-add').length) return;

    let profileId = location.pathname.match(/\d{3,}$/);
	let name = '', country = '', distance = '', sensor = '';
    let loginTime = '', loginLocalTime = '', timeTag = '', lastSince = '', offlineSince = '';
	$.ajax({headers: ajaxHead(), url: `/api/v4/profiles/${profileId}?expand=partner&lang=de`})

	.done(function (data) {
        $('.js-correspondence > div').addClass('re-add');

		//last login
		if (data.last_login && data.online_status) {
			loginTime = new Date(data.last_login.slice(0,-5)+'.000Z');
            loginLocalTime = dateTime(loginTime);
			timeTag = dateTime(loginTime, !mobile.matches);
            lastSince = `${data.online_status == 'OFFLINE' ? 'Zuletzt online:' : 'Online seit:'}`;

            if (($(jNode).closest('div').parent().find('[class*="OnlineStatus"]')).length == 0) {
                offlineSince = `<span class="icon icon-last-login-hours mr-- re-add" style="font-size:.75rem; color:rgba(250,250,250,.75)" title="${lastSince} ${loginLocalTime}"> ${touch.matches ? '' : timeTag}</span>`;
            };

		}

		//location, distance
		if (data.location?.name) {
			name = `<a class="re-link-idle" target="_blank" href="https://google.com/maps/place/${data.location.name}" rel="noreferrer noopener" title="Ort in Google Maps anzeigen">${data.location.name.trim()}</a>`;
		}
        if (data.location?.country && (data.location?.distance > 50000 || data.location?.distance == null)) {
            country = `, ${data.location.country}`;
        }
        if (data.location?.distance >= 0) {
            if (data.location.distance < 1000) {
                distance = ' • ' + data.location.distance + 'm';
            } else if (data.location.distance < 50000) {
                distance = ' • ' + (Math.round(data.location.distance/100)/10).toLocaleString('de-DE') + ' km';
            } else {
                distance = ' • ' + (Math.round(data.location.distance/1000)) + ' km';
            }
        }
        if (data.location?.sensor) {
            sensor = '<span class="icon icon-gps-needle icon-badge mr-" style="font-size:.85em"></span>';
		}
	})

	.always(function (data) {
        $('section.js-detail div.re-login-location').html(`
            <div class="re-msg-location">${sensor}${name}${country}${distance}</div>
            <div class="re-msg-login" title="${lastSince} ${loginLocalTime}">
            <span class="re-touch">${lastSince} ${loginLocalTime}</span></div>
            <div class="re-msg-offline">${offlineSince}</div>`
        );
    });
}


// ***** Add copy to message thread options menu *****
function threadOptionsMenu (jNode) {
	$(jNode).find('li').first().not('.re-done').addClass('re-done').clone().appendTo($(jNode)).click(function() {
        let profileId = '', profileName = '';
        if ($(this).closest('#messenger').length) {
            profileId = location.pathname.match(/\d{3,}/);
            profileName = $('#messenger div.js-correspondence').find('a > p[class^="BodyText"]').first().text().trim();
        } else {
            profileId = xhrFull.id;
            profileName = xhrFull.name;
        }
        copyMessageThread(profileId, profileName);
		return false;
	}).attr('id', 'options_copy').find('span').text('Kopieren');
}


// ***** Zoom footprints list *****

//init
let setFootprintZoom = (localStorage.getItem('REfootprintZoom') === 'true');

function handleFootprints (jNode) {
    setFootprintZoom ? $('.js-profile-footprints').addClass('re-zoom') : $('.js-profile-footprints').removeClass('re-zoom');
    $('.js-profile-footprints h1 > span').last().not(':has(.re-add)').wrapInner(
        `<a class="re-link mr re-add" style="font-size:1.2rem" title="Vergrößern" href="">- +</a>`
    ).click(function() {
        setFootprintZoom = !setFootprintZoom;
        localStorage.setItem('REfootprintZoom', setFootprintZoom);
        $('.js-profile-footprints').toggleClass('re-zoom');
        return false;
    });
}


// ***** Filter by "no entry" *****
function handleFilterNoEntry (jNode) {

    //insert button
    $(jNode).not('.re-add').addClass('re-add').after(`
        <div class="re-add layout mt mh++">
        <a class="re-button-no-entry ui-tag ui-tag--center" href="">+ keine Angabe
        <span class="icon icon-small icon-info ml-" title="Zeigt auch Profile, die zu den ausgewählten Filterkategorien keine Angabe enthalten"
        aria-label="Zeigt auch Profile, die zu den ausgewählten Filterkategorien keine Angabe enthalten"></span>
        </a>
        </div>`
    ).next('div.re-add').off().click(function() {
        filterNoEntry = !filterNoEntry;
        localStorage.setItem('REfilterNoEntry', filterNoEntry);

        //toggle button
        $('.re-button-no-entry').toggleClass('ui-tag--selected');

        //reload radar tab
        $('section.js-main-stage div.js-navigation').find('a.is-selected, div.js-nav-item').get(0).click();
        return false;
    });
    if (filterNoEntry) $('.re-button-no-entry').addClass('ui-tag--selected');
}


// ***** Link contact icons in messages list entries *****
function handleMessage (jNode) {
    // console.log('handleMessage');
	let profileId = new Array;
    profileId = $(jNode).attr('href').match(/\d{3,}/);
	if (! profileId) profileId = location.pathname.match(/\d{3,}/);
    $(jNode).not(':has(a.re-add)').find('svg').has('path[d^="M4 8a4"]').wrap(
		`<a href="/messenger/contacts/all/${profileId}" class="re-add"></a>`
	);
	$(jNode).not(':has(a.re-add)').find('svg').has('path[d^="M8.039"]').wrap(
		`<a href="/messenger/contacts/blocked/${profileId}" class="re-add"></a>`
	);
    $(jNode).find('span > div > p[class^="SpecialText"]:contains("Gelesen")').text('Gelesen ✓');
}


// ***** Open links in messages without opening flyout menu *****
function handleMessageLink (jNode) {
    $(jNode).off().click(function() {
        (this.target) ? window.open(this.href, this.target, this.rel) : changeUrl(this.href);
        return false;
    });
}


// ***** Scroll to last message in thread *****
function handleMessageScroll (jNode) {
    // console.log('scrollIntoView');
    $(jNode).addClass('re-done').closest('.message__content').addClass('re-done');
    $('#messages-list .message__content').last().not('.re-done')[0]?.scrollIntoView({block: 'start'});
    // $(jNode).addClass('re-done');
    // $('#messages-list .message__content').last()[0]?.scrollIntoView({block: 'start'});
}


// ***** Add message icons to contact list entries *****
function handleContacts (jNode) {
	let profileId = new Array;
    profileId = $(jNode).attr('href').match(/\d{3,}$/);
	if (! profileId) profileId = location.pathname.match(/\d{3,}$/);
	$(jNode).find('p[class^="BodyText-"] > span, span > span').last().not(':has(a)').prepend(
		`<a class="icon icon-chat re-icon re-idle ml-- mr-" title="Messages" href="/messenger/chat/${profileId}"></a>`
	);
}


// ***** Prompt for removing contacts tags *****
function handleTagRemove (jNode) {
    $(jNode).titleLabel('Löschen').off().click(function() {
        return confirm(`»${$(this).prev().text().trim()}« löschen? \rAlle Zuweisungen zu Kontakten gehen verloren.`);
    });
}


// ***** Link user names on discover page to messages *****
function handleContactStrip (jNode) {
	let replacePattern = /(.*\/(profile|hunq|group)\/)([-\w]*)(.*)/;
	let profileLink = $(jNode).attr('href');
    $(jNode).attr('href', profileLink.replace(/\/group\//, '/profile/'));
	let profileName = profileLink.replace(replacePattern, '$3');
	let baseUrl = '/messenger/chat/';
	$(jNode).find('div[class^="Name-"]').attr('title', profileName)/*.wrapInner(
		`<a class="re-link-idle" title="${profileName}  |  Messages" href="${baseUrl}${profileName}"></a>`
	).children()*/.off().click(function() {
		openByName(baseUrl, profileLink, profileName);
		return false;
	}).attr('style', `${profileLink.match(/^\/hunq\//) ? 'color:#fc193c' : ''}`);
}


// ***** Mark common groups *****
function handleGroupTiles (jNode) {
    $(jNode).addClass('re-done');
    (async() => {
        while (! commonGroupsLoaded) await new Promise(resolve => setTimeout(resolve, 300));
        // console.log('handleGroupTiles');
        let jNodeGroupTiles = $(jNode).closest('a');
        if (jNodeGroupTiles.attr('href')?.match(/^\/group\//)) {
            const profileName = jNodeGroupTiles.attr('href').match(/[-\w]+$/);
            const nameIndex = (item) => item.name == profileName;
            if (commonGroupsList.findIndex(nameIndex) > -1) {
                jNodeGroupTiles.addClass('re-common-group');
                jNodeGroupTiles.find('p').last().attr('title', 'Du bist Mitglied in dieser Gruppe');
            }

            //show full group name in title
            jNodeGroupTiles.find('p').first().attr('title', $(jNode).find('p').first().text().trim());
        }

        //blur nsfw placeholder
/*         if (jNodeGroupTiles.has(`div[style*="${nsfwPlaceholder}"]`).length) {
            let tilePicture = jNodeGroupTiles.children('div').attr('style');
            jNodeGroupTiles.addClass('re-nsfw-placeholder').attr('style', `display:block; ${tilePicture} background-size:cover`).attr('title', 'Softcore-Bild');
        } */
    })();
}


// ***** Sort EyeCandy on startpage by Nearby or Activity *****
function handleDiscoverEyecandy (jNode) {
    $(jNode).not('.re-done').addClass('re-done').append(`
        <a class="re-link re-eyecandy-toggle ml" title="Umschalten" href="">
        <span>${$('a[href^="/radar/distance"]').first().text().trim()}</span>
        <span>${$('a[href^="/radar/login"]').first().text().trim()}</span>
        <svg aria-hidden="true" viewBox="0 0 10 6" height="6" width="10">
        <path fill-rule="evenodd" d="M5.707 5.707a1 1 0 0 1-1.414 0l-4-4A1 1 0 0 1 1.707.293L5 3.586 8.293.293a1 1 0 0 1 1.414 1.414l-4 4Z" clip-rule="evenodd"></path>
        </svg>
        </a>`
    ).children().off().click(function() {
        eyecandyActive = !eyecandyActive;
        localStorage.setItem('REeyecandyActive', eyecandyActive);
        $('.re-eyecandy-toggle').toggleClass('is-selected');
        $('li[class^="Tabbed-nav-item-"] a.is-selected')[0].click();
        return false;
    });
    if (eyecandyActive) $('.re-eyecandy-toggle').addClass('is-selected');
}


// ***** Localize link to blogs *****
function handleBlogLinks (jNode) {
    // console.log('blog link');
    $(jNode).off().click(function(jNode) {
        let lang = $('html').attr('lang');
        let defaultUrl = $(this).attr('href');
        let localeUrl = defaultUrl.replace(/(\/)(\w\w)(\/blog\/.*)/, `$1${lang}$3`);
        if (localeUrl.match(/\/blog\//) && ! localeUrl.endsWith('/')) localeUrl += '/';
        let url = '';
        $.ajax({ headers: {Range: 'bytes=0-0'}, url: localeUrl })
            .done(function() {
            url = localeUrl;
        })
            .fail(function() {
            url = defaultUrl;
        });
        (async() => {
            while (! url) await new Promise(resolve => setTimeout(resolve, 300));
            window.open(url, '_blank');
        })();
        return false;
    });
}


// ***** Add message icons to discover, radar, visitor list, search results, and group member list, link contact icons *****
function handleTiles (jNode) {
    let jNodeTiles = $(jNode).closest('a, button');
    let profileName = $(jNode).text().trim();
    let profileLink = `/${(jNodeTiles.attr('href')?.match(/^\/hunq\//) ? 'hunq' : 'profile')}/${profileName}`;

    //add + to distance if travel
    if ((travelMode && location.pathname.match(/^\/(radar|groups|eyecandy)\//)) || location.pathname.match(/^\/explore\//) || $('.ui-navbar').has('path[d^="M6 8c-1"]').length) {
        if (jNodeTiles.find('svg + p[class^="SpecialText"]').not('.re-add').text().match(/(\d[\d\.,]*\s?(km|m|mi|ft))/)) {
            let distance = `+${jNodeTiles.find('svg + p[class^="SpecialText"]').html()}`;
            jNodeTiles.find('svg + p[class^="SpecialText"]').html(distance).addClass('re-add');
        }
    }

    //add visit icons
    let visitReceivedTime = '', visitMadeTime = '', index = -1;
    const nameIndex = (item) => item.name == profileName;
    (async() => {
        while (! (visitorsLoaded && visitsLoaded)) await new Promise(resolve => setTimeout(resolve, 300));
        // console.log('visitors await');
        index = visitorsList.findIndex(nameIndex);
        if (index >= 0) visitReceivedTime = new Date(visitorsList[index].date_visited);
        index = visitsList.findIndex(nameIndex);
        if (index >= 0) visitMadeTime = new Date(visitsList[index].date_visited);

        //visits made page
        if ($(jNode).closest('#visited-grid').length) {
            if (visitReceivedTime) {
                $(jNode).parent().parent().children().first().not(':has(.icon-visitor)').append(
                    `<a class="icon icon-visitor re-icon re-icon-visitor ml-" title="Hat mich besucht: ${weekTime(visitReceivedTime)}" href="/visitors"></a>`
                ).children().last().off().click(function() {
                    changeUrl(`/visitors`);
                    return false;
                });
            }
            if ($(jNodeTiles).find('time').length) $(jNodeTiles).find('time').text(weekTime(visitMadeTime)).attr('title', weekTime(visitMadeTime));

        //other
        } else {
            if (visitMadeTime) {
                $(jNode).parent().parent().children().first().not(':has(.icon-visitor)').append(
                    `<a class="icon icon-visitor re-icon re-icon-visited ml-" title="Von mir besucht: ${weekTime(visitMadeTime)}" href="/visitors/me"></a>`
                ).children().last().off().click(function() {
                    changeUrl(`/visitors/me`);
                    return false;
                });
            }
            if ($(jNodeTiles).find('time').length) $(jNodeTiles).find('time').text(weekTime(visitReceivedTime)).attr('title', weekTime(visitReceivedTime));
        }
    })();

    //link username in list view
    if ($(jNode).closest('button').length) {
        $(jNode).not(':has(.re-link-idle)').wrapInner(
            `<a class="re-link-idle" title="${profileName}" href="${profileLink}"></a>`
        ).off().click(function() {
            changeUrl(`${profileLink}`);
            return false;
        });
    }

	//add message icon
    if (! location.pathname.match(/\/radar\/home|\/hunqz\/home|\/visitors/)) {
        let baseUrl = '/messenger/chat/';
        $(jNode).parent().not(':has(.icon-chat)').append(
            `<a class="icon icon-chat re-icon re-idle ml-" title="Messages" href="${baseUrl}${profileName}"></a>`
        ).children().last().click(function() {
            openByName(baseUrl, profileLink, profileName);
            return false;
        });
    }

    //link contact icon
	jNodeTiles.find('svg').has('path[d^="M4 8a4"]').not('.re-contact').addClass('re-contact').wrap(
		`<a class="re-idle-no-hover re-contact" title="Kontakt bearbeiten" href="/messenger/contacts/all/${profileName}"></a>`
	);
	jNodeTiles.find('a.re-contact').off().click(function() {
		openByName('/messenger/contacts/all/', profileLink, profileName);
        // console.log(profileLink, profileName);
		return false;
	});
	jNodeTiles.find('svg').has('path[d^="M8.039"]').not('.re-contact-blocked').addClass('re-contact-blocked').wrap(
		`<a class="re-idle-no-hover re-contact-blocked" title="Kontakt bearbeiten" href="/messenger/contacts/blocked/${profileName}"></a>`
	);
	jNodeTiles.find('a.re-contact-blocked').off().click(function() {
		openByName('/messenger/contacts/blocked/', profileLink, profileName);
		return false;
	});

    //blur nsfw placeholder
    if (jNodeTiles.has(`div[style*="${nsfwPlaceholder}"]`).length) {
        let tilePicture = jNodeTiles.children('div').attr('style');
        jNodeTiles.addClass('re-nsfw-placeholder').attr('style', `display:block; ${tilePicture} background-size:cover`).attr('title', 'Softcore-Bild');
    }

    //only in radar, travel, hunqz, eyecandy
    if ($(jNode).closest('#profiles').length) {
        // console.log('radar');

        //link username to preview
        $(jNode).not(':has(.re-link-idle)').wrapInner(
            `<a class="re-link-idle" title="${profileName}  |  Vorschau" href="${profileLink}"></a>`
        ).off().click(function() {
            changeUrl(`${profileLink}/preview`);
            return false;
        });
    }

    //only for last tile in radar, travel, hunqz, eyecandy
    if ($(jNode).closest('#profiles').length && !$(jNode).closest('div.search-results__item').next('div.search-results__item').length) {
        // console.log('radar stats');

        //show count of profiles, Plus, travelling, and GPS in title tag of selected tab
        let loadTime = new Date().toLocaleTimeString('de-DE') + ' aktualisiert';
        let profiles = $('#profiles :is(div.BIG, div.SMALL, div.LIST)').length;
        let profilesPlus = '', percentPlus = '';
        if ($('#profiles .search-results--mixed-tiles').length) {
            profilesPlus = $('#profiles .search-results--mixed-tiles div.tile--plus, #profiles .search-results--big-tiles').length;
            percentPlus = (profiles > 0 ? ' (' + (Math.round(profilesPlus/profiles*1000)/10).toLocaleString('de-DE') + ' %)' : '');
            profilesPlus =  ' • ' + profilesPlus + ' Plus' + percentPlus;
        }
        let profilesTravel = '', percentTravel = '';
        profilesTravel = $('#profiles svg > path[d^="M10.0452"]').length;
        percentTravel = (profiles > 0 ? ' (' + (Math.round(profilesTravel/profiles*1000)/10).toLocaleString('de-DE') + ' %)' : '');
        profilesTravel =  `${profilesTravel} ${(profilesTravel == 1 ? 'Reisender' : 'Reisende')}${percentTravel}`;
        let profilesGPS = '', percentGPS = '';
        profilesGPS = $('#profiles svg > path[d^="M11.94"]').length;
        percentGPS = (profiles > 0 ? ' (' + (Math.round(profilesGPS/profiles*1000)/10).toLocaleString('de-DE') + ' %)' : '');
        profilesGPS =  ' • ' + profilesGPS + ' GPS' + percentGPS;
        profiles += ` ${(profiles == 1 ? 'Profil' : 'Profile')} geladen`;
        $('ul[class^="Tabbed-nav--"] li.is-selected, ul[class^="list--"] li[class*="list__item--active--"], div.js-nav-item').attr(
            'title', `${loadTime}\r${profiles}${profilesPlus}\r${profilesTravel}${profilesGPS}`
        );
    }
}


// ***** Add message icons to non-message Activity Stream entries; handle group related entries; add settings icon *****

//init
let streamShowAll = (localStorage.getItem('REstreamShowAll') === 'true');

function handleStream (jNode) {
    let jNodeStream = $(jNode).closest('a');
	let bodyLink = jNodeStream.attr('href');
    let profileLink = '', profileName = '';
    let baseUrl = '/messenger/chat/';
    let itemText = jNodeStream.find('span.listitem__text').not('.thumbnail__info');
    if (bodyLink != undefined) {
        let replacePattern = /(.*\/(profile|hunq|groups)\/)([-\w]*)(.*)/;
        profileLink = jNodeStream.parent().find('a').attr('href');
        profileName = profileLink.replace(replacePattern, '$3');

        //link contact icon
        jNodeStream.find('svg').has('path[d^="M4 8a4"]').not('.re-contact').addClass('re-contact').wrap(
            `<a class="re-idle-no-hover re-contact" title="Kontakt bearbeiten" href="/messenger/contacts/all/${profileName}"></a>`
        );
        jNodeStream.find('a.re-contact').off().click(function() {
            openByName('/messenger/contacts/all/', profileLink, profileName);
            return false;
        });
        jNodeStream.find('svg').has('path[d^="M8.039"]').not('.re-contact-blocked').addClass('re-contact-blocked').wrap(
            `<a class="re-idle-no-hover re-contact-blocked" title="Kontakt bearbeiten" href="/messenger/contacts/blocked/${profileName}"></a>`
        );
        jNodeStream.find('a.re-contact-blocked').off().click(function() {
            openByName('/messenger/contacts/blocked/', profileLink, profileName);
            return false;
        });
    }
    if (bodyLink != undefined && ! bodyLink.match('/messenger/chat/')) {

        //add message icon
        if (profileName) {
            jNodeStream.find('p[class^="BodyText-"]').not(':has(a,img)').prepend(
                `<a class="icon icon-chat re-icon mr-" title="Messages" href="${baseUrl}${profileName}"></a>`
            ).children().last().click(function() {
                openByName(baseUrl, profileLink, profileName);
                return false;
            });
        }

        //italic if system text
        itemText.attr('style', 'font-style:italic');

        //decode html entities in group names
        itemText.html(itemText.text().trim());

        //add icon if group picture, add class for toggle
        let jNodeGroupPicture = jNodeStream.has('img.thumbnail').not(':has(span.icon-heart, span.icon-camera-icon, .re-add)');
        jNodeGroupPicture.find('span.listitem__timestamp').after(
            `<span class="clr-ui-text listitem__highlight--expanded re-add"><span class="icon icon-group-members"></span></span>`
        );
        $(jNodeGroupPicture).parent().addClass('re-group-item');

        //move text to title on non-touch devices if group picture
        if (! touch.matches) {
            // jNodeGroupPicture.attr('title', jNodeGroupPicture.find('span.listitem__text').first().text().trim());
            // jNodeGroupPicture.find('span.listitem__text').first().remove();
        }

        //hide group pictures of blocked users
        // $(jNodeGroupPicture).has('path[d^="M8.039"]').parent().addClass('re-group-item').hide();
    }

    //add toggle and settings
    // $('div.stream__content div.js-list').not(':has(div.re-add)').prepend(`
    $('div.stream__content').not('.re-done').addClass('re-done').before(`
        <div class="re-stream-toggle-div">
            <a class="re-link re-stream-toggle" title="Umschalten" href="">
                <span>Mit Gruppenbildern<em></em></span>
                <span>Ohne Gruppenbilder<em></em></span>
                    <svg aria-hidden="true" viewBox="0 0 10 6" height="6" width="10">
                        <path fill-rule="evenodd" d="M5.707 5.707a1 1 0 0 1-1.414 0l-4-4A1 1 0 0 1 1.707.293L5 3.586 8.293.293a1 1 0 0 1 1.414 1.414l-4 4Z" clip-rule="evenodd"></path>
                    </svg>
                </a>
            <a href="/me/notifications" class="icon icon-settings re-icon re-link p-" title="Activity Stream anpassen"></span></a>
        </div>`
    );
    $('.re-stream-toggle').off().click(function() {
        streamShowAll = !streamShowAll;
        localStorage.setItem('REstreamShowAll', streamShowAll);
        $('.re-stream-toggle, .stream__content').toggleClass('re-selected');
        return false;
    });
    if (streamShowAll) $('.re-stream-toggle, .stream__content').addClass('re-selected');

    //insert number of group items
    let groupItems = $('.stream__content .re-group-item').not(':has(path[d^="M8.039"])').length;
    if (groupItems) {
        $('.re-stream-toggle em').html(` (${groupItems})`).parent().attr('title', `Neue Bilder in ${groupItems} Gruppe${(groupItems == 1) ? `` : `n`}`);
    } else {
        $('.re-stream-toggle em').html(` (0)`).parent().attr('title', `Keine neuen Gruppenbilder`);
    }
}


// ***** Profiles: show profile id, visits since; link URLs *****
function handleProfile (jNode) {
    let profileName = $('div.top-info-header p[class^="BodyText"]').first().text().trim();
    let profileType = ($('div.is-profile-loaded').hasClass('profile--hunqz')) ? '/hunqz/profiles' : '/profiles';
    let profilePath = ($('div.is-profile-loaded').hasClass('profile--hunqz')) ? 'hunq' : 'profile';
	let profileId = '', profileIdInfo = '', visits = '', since = '', known = '', known1st = '', known2nd = '', verified = '';
	let albums = 0, profilePictures = 0, albumPictures = 0, quickSharePictures = 0;
    let name = '', country = '', distance = '';
    let loginTime = '', loginLocalTime = '', timeTag = '', lastSince ='';
    let sinceMonthYear = $('section.profile__stats section > p').last().contents().filter(function(){ return this.TEXT_NODE; }).first().text();
    let galleryPath = `/${profilePath}/${profileName}/gallery`;

    let data = xhrFull;
    if (data.id) {
        profileId = data.id;
        profileIdInfo = `Profil-ID: ${data.id}`;
    }
    if (data.visits_count) { //  not 0 or undefined
        visits = `<br>Besucher: ${data.visits_count.toLocaleString('de-DE')}`;
    }
    if (data.creation_date) {
        since = new Date(data.creation_date.slice(0,-5)+'.000Z').toLocaleDateString('de-DE');
        since = 'Mitglied seit: ' + since;
    } else {
        since = sinceMonthYear;
    }

    //known by
    if (data.known_by?.first_degree > 0) {
        known1st = data.known_by.first_degree;
        known2nd = data.known_by.second_degree;
        known = `Bekannt bei ${known1st.toLocaleString('de-DE')} ${known1st == 1 ? 'Nutzer (dieser' : 'Nutzern (diese'} bekannt bei ${known2nd.toLocaleString('de-DE')})`;
    } else {
        known = `Noch nicht bei anderen bekannt`;
    }

    //online time
    if (data.last_login && data.online_status) {
        loginTime = new Date(data.last_login.slice(0,-5)+'.000Z');
        loginLocalTime = dateTime(loginTime);
        timeTag = dateTime(loginTime, 'dateOnly');
        lastSince = `${data.online_status == 'OFFLINE' ? 'Zuletzt online:' : 'Online seit:'}`;
    }

    //show pictures count
    if (data.albumsV2.items_total) {
        for (let item of data.albumsV2.items) {
            if (item.id == 'PROFILE') {
                if (item.pictures) {
                    profilePictures += item.pictures.items_total;
                }
            } else if (item.access_policy == 'SHARED') {
                quickSharePictures += item.items_total;
            } else {
                if (item.pictures) {
                    albumPictures += item.pictures.items_total;
                    //albums += (item.items_total) ? 1 : 0;
                }
            }
        }
        if (profilePictures > 1) {
            $('section.profile__image-strip').not(':has(.re-add)').append(
                `<div class="re-add re-img-count"><div><p>${profilePictures}</p></div></div>`
            ).children().last().click(function(){
                changeUrl(`${galleryPath}`);
            }).attr('style', 'cursor:pointer').titleLabel('Alle Alben anzeigen');
        }
        if (quickSharePictures) {
            $('section.js-profile-stats section > div > a > div > div').first().has('svg').not(':has(.re-add)').append(
                `<div class="re-add re-img-count"><div><p>${quickSharePictures}</p></div></div>`
            );
        }
        if (quickSharePictures || albumPictures) {
            $(`section.profile__stats section a[href^="${galleryPath}"] span`).not(':has(.re-add)').append(
                `<span class="re-add" style="font-size:.925em; color:rgba(255,255,255,.6)"> • ${quickSharePictures + albumPictures} Bilder</span>`
            );
        }
    }

    //link location name to maps
    if (data.location.name) {
        name = `<a target="_blank" href="https://google.com/maps/place/${data.location.name}" rel="noreferrer noopener" class="re-link-idle" title="Ort in Google Maps anzeigen">${data.location.name.trim()}</a>`;
    }
    if (data.location.country && (data.location.distance > 50000 || data.location.distance == null)) {
        country = `, ${data.location.country}`;
    }
    if (data.location.distance != null) {
        if (data.location.distance < 1000) {
            distance = ' • ' + data.location.distance + 'm';
        } else if (data.location.distance < 50000) {
            distance = ' • ' + (Math.round(data.location.distance/100)/10).toLocaleString('de-DE') + ' km';
        } else {
            distance = ' • ' + (Math.round(data.location.distance/1000)) + ' km';
        }
    }

    //location, country, distance
    $('div.profile__info svg + p[class^="BodyText"]').first().html(name + country + distance);

    //since, id, known by, visits, online
    $('section.profile__stats section > p').last().addClass('re-profile-stats mb-').html(`${since}<br>${profileIdInfo}<br>${known}${visits}<br>${lastSince} ${timeTag}`);

    //authenticity
    let title = $('.top-info-header p + div > button').attr('title');
    $('.top-info-header p + div > button').not('.re-add').addClass('re-add').find('title').text(`${title}${(known1st) ? ` (${(known1st <= 30) ? known1st : '30+'})` : ''}\n${sinceMonthYear}\n${lastSince} ${timeTag}`);

    //hide visit
    $('.profile--romeo .top-info-header p[class^="BodyText"]').first().attr('title', 'Profilbesuch verstecken').off().click(function(){
        hideVisit(profileId);
    });

    //complete headline
    if (data.headline?.length > 50) {
        $('div.profile__info p[class^="BodyText"]').last().text(data.headline);
    }

    //URLs in headline and profile text
    $('div.profile__info div.reactView > div > p[class^="BodyText"], section.profile__stats details > div > p[class^="BaseText-sc-"]').each(function() {
        let replacedText = linkify($(this).html());
        $(this).html(replacedText);
    });

    //link travel locations to maps
    $('section.profile__stats details').has('#travel-list').find('div:not([role="heading"]) > p:first-child').not(':has(.re-add)').each(function() {
        $(this).wrapInner(
            `<a target="_blank" href="https://google.com/maps/place/${$(this).text().trim()}" rel="noreferrer noopener" class="re-link-idle re-add" title="Ort in Google Maps anzeigen"></a>`
        )
    });

    //move Travel Date and Looking For to top, keeping Albums and B&B at top
    let sectionContainer = 'section.profile__stats > div > div.reactView:last-child > div';
    $('section.profile__stats details').not('.re-add').has('summary :contains("Ich suche"), summary :contains("Looking For"), summary :contains("Recherche")').prependTo(sectionContainer);
    $('section.profile__stats details').has('#travel-list').not('.re-done').addClass('re-done').prependTo(sectionContainer);
    $('section.profile__stats section').not('.re-done').has('img + div > p').addClass('re-done').prependTo(sectionContainer);
    $('section.profile__stats section').not('.re-done').has(`a[href^="${galleryPath}"]`).addClass('re-done').prependTo(sectionContainer);
}


// ***** Handle error page *****
function linksError (jNode) {
    //...
	$(jNode).append(
		`<div style="font-size:0.9em"><br/>...</div>`
	);
}


// ***** Sort groups list by active posts *****

//init
let groupsList = [];
let viewMode = '';
let counter = 0;
let groupsCount = 0;
let groupsRefreshed = false;
let groupsListloadTime = 0;
let lastGroupName = '';
let atoz = false;
let linkTextActive = 'Aktive Beiträge', linkTextDefault = 'Standard';

function recentPosts (jNode) {
    // console.log('recentPosts');

    $(jNode).not('.re-groups-def').addClass('re-groups-def').after(
        `<nav class="re-groups-enh is-hidden"></nav>`
    );

	//insert menu
	viewMode = localStorage.getItem('REgroupsListView');
	let linkText = (viewMode == 'active') ? linkTextActive : linkTextDefault;
	$('nav.re-groups-def').closest('div[class*="ReactContainer"]').not(':has(span.re-list-head)').prepend(`
<div class="mh mt- pv-" style="font-size:.85rem; border-bottom:1px solid rgba(255,255,255,.125)">
<span class="re-list-head">Sortierung</span>
<span class="re-list-view ml-" title="Umschalten" role="button" aria-label="Sortierung ändern">${linkText}</span>
<span class="re-list-load icon icon-stathistory pl- pr-" title="Aktualisieren">
<span class="re-list-head ml--"></span>
</span>
</div>`
    );

    //toggle sort mode (recent posts <-> a-z)
    $('.re-list-head').off().on('dblclick', function() {
        if (viewMode == 'active') {
            atoz = !atoz;
            linkText = linkTextActive = (atoz) ? 'A - Z' : 'Aktive Beiträge';
            $('.re-list-view').text(linkText);
            insertPostsList (groupsList);
        }
    });

    if (mobile.matches) {
		$('div.js-header').removeClass('l-hidden-sm');
		$('div.Container--xbtQn').hide();
	}
	if (viewMode !== 'active') {
		$('span.re-list-load').hide();
	}

	//register toggle menu click
	$('span.re-list-view').click(function() {
        if (counter == groupsCount) {  //not while building groupsList
            let oldViewMode = localStorage.getItem('REgroupsListView');
            if (oldViewMode == 'active') {
                linkText = linkTextDefault;
                viewMode = 'default';
                $('span.re-list-load').hide();
            } else {
                linkText = linkTextActive;
                viewMode = 'active';
                $('span.re-list-load').show();
            }
            localStorage.setItem('REgroupsListView', viewMode);
            $('span.re-list-view').text(linkText);
            handlePostsList(viewMode);
        }
    });

	//register refresh list click
	$('span.re-list-load').click(function() {
        if (counter == groupsCount) {  //not while building groupsList
            groupsList.length = 0;
            counter = 0;
            groupsRefreshed = true;
            groupsListloadTime = new Date().toLocaleTimeString('de-DE');
            $('nav.re-groups-enh').find('a.re-add, button, h4').remove();
            $('nav.re-groups-def').hide();
            handlePostsList(viewMode);
        }
    });
    handlePostsList(viewMode);
}


// ***** Handle posts list *****
function handlePostsList (viewMode) {

	if (viewMode !== 'active') {
		$('nav.re-groups-enh').addClass('is-hidden');
        $('nav.re-groups-def').show();
        let highlighted = 'nav.re-groups-def button';
        if ($(highlighted).length) {
            $(highlighted)[0].scrollIntoView({block: 'center'});
        }
    } else {
		$('nav.re-groups-def').hide();
		$('nav.re-groups-enh').removeClass('is-hidden');
		if (groupsList.length > 0) {
			insertPostsList(groupsList);
		} else {
            if (commonGroupsList[0]?.name == location.href.replace(/(.*\/groups\/member\/)([-\w]+)$/, '$2')) {
                // groupsRefreshed = true;
            }
            groupsListloadTime = new Date().toLocaleTimeString('de-DE');
			$('nav.re-groups-enh').not(':has(.re-add)').prepend(
				'<div class="spinner-container re-add"><div class="spinner"></div></div>'
			);

            $.ajax({headers: ajaxHead(), url: '/api/v4/profiles/me/groups?expand=items.*.(membership,activity)&lang=de&length=1000&pick=items.*.(id,name,display_name,membership.is_admin,is_forum_enabled,activity.posts.*,activity.photos.*,preview_pic.url_token),items_total'})

			.done(function (data) {
				groupsList = [];
				counter = 0;
				groupsCount = data.items_total;
				let ajaxUrl = '', name = '', displayName = '', isAdmin = false, picToken = '', actPosts = 0, countPosts = 0, actPhotos = 0, countPhotos = 0;

				for (let item of data.items) {

                    // loop "posts" for all groups
                    if (item) {
                        name = item.name;
                        displayName = item.display_name;
                        isAdmin = item.membership.is_admin;
                        picToken = (item.preview_pic) ? item.preview_pic.url_token : '';
                        if (item.activity) {
                            actPosts = (item.activity.posts) ? item.activity.posts.last_accessed : 0;
                            countPosts = (item.activity.posts) ? item.activity.posts.count : 0;
                            actPhotos = (item.activity.photos) ? item.activity.photos.last_accessed : 0;
                            countPhotos = (item.activity.photos) ? item.activity.photos.count : 0;
                        } else {
                            actPosts = 0;
                            countPosts = 0;
                            actPhotos = 0;
                            countPhotos = 0;
                        }
                        if (item.is_forum_enabled) {
                            ajaxUrl = `/api/v4/groups/${item.id}/posts?pick=items.*.(date_created,comments.items.*.(date_created))&expand=items.*.(comments)&lang=de&length=5&sort_criteria=COMMENTED_AT_DESC`;
                        } else {
                            ajaxUrl = `/api/v4/groups/${item.id}/posts?pick=items.*.(date_created)&lang=de&length=5&sort_criteria=COMMENTED_AT_DESC`;
                        }

                        $.ajax({headers: ajaxHead(),
                                url: ajaxUrl,
                                custom: {name: name, displayName: displayName, isAdmin: isAdmin, picToken: picToken, actPosts: actPosts, countPosts: countPosts, actPhotos: actPhotos, countPhotos: countPhotos} })

                        .done(function (data) {

                            //save name, timestamp, etc. to array (most recent post or comment)
                            let timeList = [], name = '', displayName = '', time = 0, isComment = false, picToken = '', actPosts = 0, countPosts = 0, actPhotos = 0, countPhotos = 0;
                            for (let item of data.items) {
                                if (item.comments?.items[0]) {
                                    time = item.comments.items[0].date_created;
                                    isComment = true;
                                } else {
                                    time = item.date_created;
                                    isComment = false;
                                }
                                if (time != 0) time = new Date(time.slice(0,-2) + ':00');
                                timeList.push({time: time, isComment: isComment});
                            }

                            //sort by most recent post or comment
                            timeList.sort(function (a,b) {
                                return b.time - a.time;
                            });

                            if (timeList[0]) time = timeList[0].time;
                            if (timeList[0]) isComment = timeList[0].isComment;
                            name = this.custom.name;
                            displayName = this.custom.displayName.trim();
                            isAdmin = this.custom.isAdmin;
                            picToken = this.custom.picToken;
                            actPosts = this.custom.actPosts;
                            countPosts = this.custom.countPosts;
                            actPhotos = this.custom.actPhotos;
                            countPhotos = this.custom.countPhotos;
                            if (actPosts != 0) actPosts = new Date(actPosts);
                            if (actPhotos != 0) actPhotos = new Date(actPhotos);
                            // console.log(displayName, timeList);

                            //add to array
                            groupsList.push({time: time, name: name, displayName: displayName, isAdmin: isAdmin, picToken: picToken, isComment: isComment, actPosts: actPosts, countPosts: countPosts, actPhotos: actPhotos, countPhotos: countPhotos, visited: false});
                        })

                        .always(function (data) {
                            counter++;
                            if (counter >= groupsCount) {
                                insertPostsList(groupsList);
                                return;
                            }
                        });

                    } else {
                        counter++;
                        if (counter >= groupsCount) {
                            insertPostsList(groupsList);
                            return;
                        }
                    }
                }
            });
        }
        $('span.re-list-load span').text(groupsListloadTime.slice(0,-3));
        $('ul.Container--1I9Gx button').click();
	}
}


// ***** Handle groups list after group is loaded *****
function refreshPostsList () {
    if (groupsRefreshed == false || lastGroupName != location.pathname.replace(/^(\/groups\/member\/)([-\w]+)(.*)/, '$2')) {
    // if (groupsRefreshed == false) {
        lastGroupName = location.pathname.replace(/^(\/groups\/member\/)([-\w]+)(.*)/, '$2');
        groupsRefreshed = false;
    } else {
        groupsRefreshed = true;
    }
    if (viewMode == 'active') insertPostsList (groupsList);
}


// ***** Insert posts list *****
function insertPostsList (groupsList) {

	//sort by a-z or most recent
	groupsList.sort(function (a,b) {
        if (atoz) {
            return b.isAdmin - a.isAdmin || a.displayName.localeCompare(b.displayName);
        } else {
            return b.time - a.time;
        }
    });
	// console.log(groupsList);

	//insert list elements on top of MY GROUPS
	$('nav.re-groups-enh').find('a.re-add, button, div, h4').remove();
	$('nav.re-groups-def').hide();
    let thumbnail = '';
	for (let item of groupsList) {
		thumbnail = (item.picToken) ? `/img/usr/squarish/212x212/${item.picToken}.jpg` : '/assets/76d319082f701e66be89.svg';
		$('nav.re-groups-enh').append(`
<a href="/groups/member/${item.name}" class="re-add re-groups-listitem">
<div class="re-groups-tile" style="background-image:url(${thumbnail})"></div>
<div class="re-groups-entry">
<span class="re-groups-text">
<span>
<span lang="" title="${item.displayName}" class="re-groups-name">${item.displayName}<br>
<span class="re-groups-time" title="${(item.isComment) ? 'Letzter Kommentar' : 'Letzter Beitrag'} vom ${item.time.toLocaleString('de-DE').slice(0,-3)}">${dateTime(item.time, !touch.matches)}</span>
</span>
</span>
</span>
</div>
</a>`
        );
        if (item.isComment) {
            $('nav.re-groups-enh a.re-add').last().append(`
<div class="re-groups-new ml-" style="margin-right:.1em" title="Aktueller Kommentar vom ${item.time.toLocaleString('de-DE').slice(0,-3)}">
<svg label="Aktueller Kommentar" role="img" aria-hidden="false" viewBox="0 0 1024 1024">
<path d="M943.3 408.7c-49.9-49.9-118.9-80.7-195-80.7H222.7l.1-183.5L0 367.4l222.8 222.8.1-183.5h525.4c54.4 0 103.6 22.1 139.3 57.7 35.6 35.7 57.6 84.9 57.6 139.3 0 54.4-22 103.6-57.7 139.3-35.7 35.7-84.9 57.7-139.3 57.7H616.3c-14.2 0-25.7 11.5-25.7 25.7v27.5c0 14.2 11.5 25.7 25.7 25.7h131.9c76.2 0 145.1-30.9 195-80.7 49.9-49.9 80.7-118.9 80.7-195 .1-76.3-30.8-145.3-80.6-195.2z"></path>
</svg>
</div>`
             );
        }
        if (item.actPosts) {
            $('nav.re-groups-enh a.re-add').last().append(`
<div class="re-groups-new ml-" title="${item.countPosts} ${(item.countPosts == 1) ? 'neuer Beitrag' : 'neue Beiträge'} seit ${item.actPosts.toLocaleString('de-DE').slice(0,-3)}">
<span style="margin-right:.25em">${item.countPosts}</span>
<svg label="Neue Beiträge" style="font-size:.675em" role="img" aria-hidden="false" viewBox="0 0 100 100">
<path d="M50 0a50 50 0 100 100A50 50 0 1050 0z"></path>
</svg>
</div>`
            );
        }
        if (item.actPhotos) {
            $('nav.re-groups-enh a.re-add').last().append(`
<div class="re-groups-new ml-" title="${item.countPhotos} ${(item.countPhotos == 1) ? 'neues Bild' : 'neue Bilder'} seit ${item.actPhotos.toLocaleString('de-DE').slice(0,-3)}">
<span style="margin-right:.25em">${item.countPhotos}</span>
<svg label="Neue Bilder" role="img" aria-hidden="false" viewBox="0 0 1024 1024">
<path d="M656 579c0 80-64 145-144 145s-144-65-144-145 64-145 144-145 144 65 144 145zM512 808c-63 0-120-26-161-68s-67-98-67-162c0-63 26-120 67-162s98-67 161-67 120 25 161 67 67 99 67 162c0 64-26 120-67 162s-98 68-161 68zm449-589H762L644 107c-7-7-16-11-27-11H407c-11 0-20 4-27 11L262 219H63c-35 0-63 28-63 63v583c0 35 28 63 63 63h898c35 0 63-28 63-63V282c0-35-28-63-63-63z"></path>
</svg>
</div>`
            );
        }
        if (item.isAdmin) {
            $('nav.re-groups-enh a.re-add').last().append(`
<div class="re-groups-admin ml-" title="Du verwaltest diese Gruppe" aria-label="Du verwaltest diese Gruppe"s>
<span>ADMIN</span>
</div>`
            );
        }
        if (item.visited && !groupsRefreshed) {  // dim visited groups
            $('nav.re-groups-enh a.re-add').last().addClass('re-groups-visited');
        }
	}

    //highlight selected item
    $('nav.re-groups-enh a.re-add').each(function() {
        let index = $('nav.re-groups-enh a.re-add').index(this);
        if ((!mobile.matches && location.href.match(`${this.href}($|\\/)`)) || (this.href.match(`/groups/member/${lastGroupName}($|\\/)`) && !groupsRefreshed)) {
            if (!groupsRefreshed) $(this)[0].scrollIntoView({block: 'center'});
            $(this).removeClass('re-groups-visited').addClass('re-groups-selected');
            groupsList[index].visited = true;
        }
    });

    //refresh content on re-click, remove indicators on first click
    $('nav.re-groups-enh a.re-add').click(function() {
        groupsRefreshed = false;
        if (location.href.match(`${this.href}($|\\/)`)) {
            $('ul.Container--ZCCAM button').click();
            return false;
        }
    });

    //insert separators admin/member if needed
    if (atoz && $('nav.re-groups-enh a.re-add').not(':has(.re-groups-admin)').length) {
        $('nav.re-groups-enh a.re-add').has('.re-groups-admin').first().before(
            `<h4 class="gzwUHp ml- mt- mb--">Admin</h4>`
        );
        $('nav.re-groups-enh a.re-add').has('.re-groups-admin').last().after(
            `<h4 class="gzwUHp ml- mt- mb--">Mitglied</h4>`
        );
    }
}


// ***** Toggle group manage mode *****
function groupManageMode (jNode) {
    if (resigned) $(jNode).not(':has(.re-add)').prepend('<span class="re-add">!</span>');
    $(jNode).off().on('dblclick', function() {
        $(this).find('.re-add').remove();
        resigned = !resigned;
        if (resigned) $(this).prepend('<span class="re-add">!</span>');
    });
}


// ***** Compact view for group posts *****
function groupPostsViewMode (jNode) {
	let viewMode = localStorage.getItem('REgroupPostsView');
	let linkTextCompact = 'Kompakt', linkTextDefault = 'Mit Kommentaren';
	let linkText = (viewMode == 'compact') ? linkTextCompact : linkTextDefault;
	$('div.Container--1cJVr').append(
		'<span class="re-posts-view" title="Umschalten">' + linkText + '</span>' +
		'<span class="layout-item icon icon-dropdown dropdown__arrow"></span>'
	);
	$('span.re-posts-view').click(function() {
		let oldViewMode = localStorage.getItem('REgroupPostsView');
		let linkText = linkTextCompact, viewMode = 'compact';
		if (oldViewMode == 'compact') {
			linkText = linkTextDefault;
			viewMode = 'default';
		}
		localStorage.setItem('REgroupPostsView', viewMode);
		$('span.re-posts-view').text(linkText);
		$('span.CommentCount--3uCNR').each(function() {
			handleGroupComment(this);
		});
	});
}


// ***** Show/hide group comments *****
function refreshGroupComments (jNode) {
	let thisNode = $(jNode).find('span.CommentCount--3uCNR');  // span.CommentCount--1Eef2
	handleGroupComment(thisNode);
}


// ***** Handle a single group post *****
function handleGroupComment (thisNode) {
	let viewMode = localStorage.getItem('REgroupPostsView');
	let timestamp = $(thisNode).parent().parent().nextAll().find('span.PostDate--2LVzF').last().text();
	if (viewMode == 'compact') {
		$(thisNode).parent().parent().nextAll().hide();
		if (timestamp == '') {
			$(thisNode).text('Kommentar schreiben');
		} else {
			$(thisNode).after(
				'<span class="ml--"> • ' + timestamp + '</span>'
			);
		}
		$(thisNode).not(':has(a)').wrapInner('<a></a>').off('click').click(function() {
			$(thisNode).parent().parent().nextAll().toggle();
		});
	} else {
		$('div.CommentsArea--3iSoQ, div.js-add-comment').show();
		$(thisNode).find('a').contents().unwrap('a');
		$(thisNode).off('click');
		$(thisNode).next('span').remove();
		if (timestamp == '') {
			$(thisNode).text('0 Kommentare');
		}
	}
}


// ***** Localize group post date *****
function handleGroupPostDate (jNode) {
    $(jNode).addClass('re-done');
    let date = $(jNode).text().trim();
    let localeDate = new Date(date).toLocaleDateString('de-DE');
    if (localeDate != 'Invalid Date' && date.length > 4) $(jNode).text(`${localeDate}`).attr('title', `${date}`);
}


// ***** Group posts *****
function handleGroupPosts (jNode) {
    //...
}


// ***** Sort group members by distance from travel location, save sorting permanently *****

//init
let travelName = '';
if (localStorage.getItem('PR_SETTINGS:groups:members:sorting')?.match(/GROUP_JOIN_DATE_DESC|LAST_LOGIN_DESC|NEARBY_ASC/)) {
    sessionStorage.setItem('PR_SETTINGS:groups:members:sorting', localStorage.getItem('PR_SETTINGS:groups:members:sorting'));
} else {
    sessionStorage.setItem('PR_SETTINGS:groups:members:sorting', 'LAST_LOGIN_DESC');
}

function groupTravelLocation (jNode) {
	let viewMode = localStorage.getItem('REgroupTravelLocation');
    let toggleTravel = 'span.re-member-travel';
	$('div.js-members-header div.js-dropdown button').not('.re-done').addClass('re-done').has('div:contains("Entf"), div:contains("In der"), div:contains("Nearby"), div:contains("Cercanos"), div:contains("À prox"), div:contains("Vicini"), div:contains("Por perto")').parent().append(
        `<span class="re-add re-member-travel icon icon-airplane ml- pl" title="Reiseziel" role="img" aria-label="Reiseziel"><span class="pl-"></span></span>`
	);
    handleTravelLocation(viewMode, toggleTravel);
    $('span.re-member-travel').off().click(function() {
		let viewMode = localStorage.getItem('REgroupTravelLocation');
        let toggleTravel = 'span.re-member-travel';
        let refreshClick = 'ul.Container--ZCCAM button';
		viewMode = (viewMode == 'travel') ? 'default' : 'travel';
		localStorage.setItem('REgroupTravelLocation', viewMode);
		handleTravelLocation(viewMode, toggleTravel, refreshClick);
	});

    //save sorting permanently
    setTimeout(() => {
        if (sessionStorage.getItem('PR_SETTINGS:groups:members:sorting')?.match(/GROUP_JOIN_DATE_DESC|LAST_LOGIN_DESC|NEARBY_ASC/)) {
            localStorage.setItem('PR_SETTINGS:groups:members:sorting', sessionStorage.getItem('PR_SETTINGS:groups:members:sorting'));
        }
    }, 300);
}


function handleTravelLocation (viewMode, toggleTravel, refreshClick) {
    if (viewMode == 'travel') {
        $(toggleTravel).addClass('re-selected');
        if (xhrTravelLat && xhrTravelLong) {
            $.ajax({headers: ajaxHead(), url: `/api/geocoder/private/name?lat=${xhrTravelLat}&lon=${xhrTravelLong}&lang=de`})

            .done(function (data) {
                if (data[0].name) {
                    travelName = data[0].name;
                }
                let travelEdit = '<a href="/explore/edit" class="re-edit-travel icon icon-pen pl-" style="line-height:inherit" title="Reiseziel ändern"></a>';
                $(toggleTravel).find('span').html(travelName).attr('title', travelName).addClass('pl-');
                $(toggleTravel).next('a.re-edit-travel').remove();
                $(toggleTravel).after(travelEdit);
                travelMode = true;
                if (refreshClick) $(refreshClick).get(0).click();
            });

        } else {
            $.ajax({headers: ajaxHead(), url: `/api/v4/locations/travel`})

            .done(function (data) {
                let travelEdit = '';
                if (data.length) {
                    let last = data.length -1;
                    xhrTravelLat = data[last].lat;
                    xhrTravelLong = data[last].long;
                    localStorage.setItem('REtravelLat', xhrTravelLat);
                    localStorage.setItem('REtravelLong', xhrTravelLong);
                    travelName = data[last].name;
                    travelMode = true;
                    travelEdit = '<a href="/explore/edit" class="re-edit-travel icon icon-pen pl-" title="Reiseziel ändern"></a>';
                } else {
                    travelMode = false;
                    travelEdit = '<a href="/explore/new" class="re-edit-travel">Klicken, um ein Reiseziel hinzuzufügen!</a>';
                }
                $(toggleTravel).find('span').html(travelName).attr('title', travelName).addClass('pl-');
                $(toggleTravel).next('a.re-edit-travel').remove();
                $(toggleTravel).after(travelEdit);
                if (refreshClick) $(refreshClick).get(0).click();
            });

        }

    } else {
        $(toggleTravel).removeClass('re-selected');
        $(toggleTravel).find('span').text('').removeAttr('title').removeClass('pl-');
        $(toggleTravel).next('a.re-edit-travel').remove();
        travelMode = false;
        if (refreshClick) $(refreshClick).get(0).click();
   }
}


// ***** Show upload date in photo viewer, link URLs *****
function handleImg (jNode) {
    $(jNode).addClass('re-done');
    // console.log('handleImg');

    //upload date
    let imgName = $(jNode).attr('src');
    let imgNameTxt = uploadDate(`${imgName.substr(imgName.lastIndexOf('/') + 1, 8)}`);
    let info = `<div class="re-add re-img-info"><a title="Upload-Datum" href="${imgName}">${imgNameTxt}</a></div>`;
    $(jNode).closest('li').not(':has(.re-add)').prepend(info);

    //URLs in picture caption
    setTimeout(() => {
        $('.ReactModal__Content li').has('img[src^="/img/usr/original/"]').find('p').not(':has(button)').each(function() {
            let replacedText = linkify($(this).text());
            $(this).html(replacedText);
        });
    }, 0);
}


// ***** Show upload date for single image, click to close *****
function handleSingleImg (jNode) {
    $(jNode).addClass('re-done');

    //upload date
    let imgName = $(jNode).attr('src');
    let imgNameTxt = uploadDate(`${imgName.substr(imgName.lastIndexOf('/') + 1, 8)}`);
    let info = `<div class="re-add re-img-info re-img-single"><a title="Upload-Datum" href="${imgName}">${imgNameTxt}</a></div>`;
    $(jNode).closest('div').after(info);
    // $(jNode).closest('main').children('div').last().children('div').append(info);

    //click to close
    $(jNode).closest('div').off().click(function() {
        if ($(this).attr('style').match(/scale\(1\)/)) {
            $(this).closest('main').find('button').focus()[0].click();
            return false;
        }
    });
}


// ***** Remove refresh for slide show likes list *****
function handleSlideshowLikes (jNode) {
    $(jNode).closest('div').children('button + div, button').remove();
}


// ***** Show picture info in picture rating *****
function ratingInfo (jNode) {
    $('#picture-rating .re-add').remove();
    $('#picture-rating button').blur();
	let imgName = $('#picture-rating img').attr('src');
    if (imgName) {
        let imgNameTxt = `${imgName.substr(imgName.lastIndexOf('/') + 1, 5)}...`;
        let imgDate = uploadDate(`${imgName.substr(imgName.lastIndexOf('/') + 1, 8)}`);
        let imgNameMax = sessionStorage.getItem('REratingMax');
        imgNameMax = (imgNameMax ? imgNameMax : imgNameTxt);
        if (imgNameTxt >= imgNameMax) {
            sessionStorage.setItem('REratingMax', imgNameTxt);
        }
        let color = (parseInt(imgNameTxt,16) + 1 < parseInt(imgNameMax,16)) ? 'rgba(255,0,0,.8)' : 'rgba(255,255,255,.375)';
        $('#picture-rating button').has('p').after(
            `<a class="re-rating-date re-add" style="color:${color}" title="Upload-Datum" href="${imgName}">${imgDate}</a>`
        );
    }

    //set focus on reload/skip button
    if (!touch.matches) {
        // $('#picture-rating button[class^="SecondaryButton__Element-"]').focus();
        // $('#picture-rating button[class^="TertiaryButton__Element-"]').focus();
    }
}


// ***** Relogin after timeout *****
function reLogin (jNode) {
    if ($(jNode).not('.re-add').addClass('re-add').children('span').filter(':contains("Erneut einloggen"), :contains("Log in again"), :contains("Reconnexion")').length) {
        $(jNode).closest('section').parent('div').addClass('re-relogin-frame');
        $(jNode).closest('section').find('div > button').hide();
        $(jNode).closest('section').find('h1').text('');
        $(jNode).closest('section').find('div > img').replaceWith('<div class="spinner-container"><div class="spinner"></div></div>');
        $(jNode).closest('section').children('p').text('Erneut einloggen ...');
        setTimeout(() => {
            location.reload();
            // location.href = '/';
        }, 600);
    } else {
        $(jNode).closest('div[class^="Modal__StyledModal"]').find('section p').off().on('dblclick', function() {
            $(this).closest('div[class^="Modal__StyledModal"]').remove();
            $('body').attr('class', '');
        });
    }
}


// ***** Fetch/XHR *****

//switch test mode
if (typeof GM_info !== 'undefined') {
    localStorage.setItem('REpostFromDeleted', `${(GM_info?.script?.name.match(/-DEV|-BETA/)) ? true : false}`);
    localStorage.setItem('REtestMode', `${(GM_info?.script?.name.match(/-DEV|-BETA/)) ? true : false}`);
}

//init
let xhrTravelLat = localStorage.getItem('REtravelLat');
let xhrTravelLong = localStorage.getItem('REtravelLong');
let travelMode = (localStorage.getItem('REradarTravelLocation') === 'travel');
let resigned = false;
let xhrFull = '';
// let eyecandyActive = (localStorage.getItem('REeyecandyActive') === 'true');
let eyecandyActive = false;
let filterNoEntry = (localStorage.getItem('REfilterNoEntry') === 'true');
let postFromDeleted = (localStorage.getItem('REpostFromDeleted') === 'true');
let hideNSFW = (localStorage.getItem('REhideNSFW') === 'true');
let testMode = (localStorage.getItem('REtestMode') === 'true');
const nsfwPlaceholder = '3646b0e5af396e593c723abdd3';  // '38830464d7d57357ddc67716bc'
const nsfwPlaceholderBig = '363b566fbda3af7de7f863585e';


// ***** Requests to modify *****
const modifyRequest = (url) => {
    // console.log(url);

    //profile view: 32 instead of 4 groups per scroll
    if (url.match(/\/groups\?length=4/)) {
        if (testMode) console.log('groups match');
        url = url.replace('length=4', 'length=32');
    }

    //sort eyecandy
    if (url.match(/v4\/profiles\/popular/)) {

        //radar
        if (travelMode && ! url.match(/&scrollable=false/)) {
            url = url.replace('sort_criteria=LAST_LOGIN_DESC', 'sort_criteria=NEARBY_ASC');
            url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
            url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
        }

        //discover
        else if (! eyecandyActive && url.match(/&scrollable=false/)) {
            url = url.replace('sort_criteria=NEARBY_ASC', 'sort_criteria=LAST_LOGIN_DESC');
        }
    }

    //travel mode
    if (travelMode) {
        if (location.pathname.match(/^\/radar\//)) {
            url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
            url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
        }
    }

    //more ...

    return url;
}


// ***** Responces to modify *****
const modifyResponse = (url, data) => {
    // console.log(data);

    //hide nsfw pictures in visitors/visits etc.
    if (hideNSFW) {
        if (url.match(/\/(profiles|popular|visitors|visits|conversations|contacts)\?/) && ! url.match(/v4\/hunqz/)) {  // ...|groups|...
            try {
                if (testMode) console.log('check for nsfw');
                // if (testMode) console.log(data);
                // let index = 0;
                for (let item of data.items) {
                    if (item?.preview_pic?.rating == 'EROTIC') {
                        if (testMode) console.log(`preview pic hidden`);
                        item.preview_pic.url_token = nsfwPlaceholder;
                        // if (url.match(/scrollable/)) data.splice(index, 1);
                        // item.personal.age = -item.personal.age;
                        // item.preview_pic.url_token = (item.display?.large_tile) ? nsfwPlaceholderBig : nsfwPlaceholder;
                    }
                    if (item?.profile?.preview_pic?.rating == 'EROTIC') {
                        // if (testMode) console.log(`profile preview pic hidden`);
                        item.profile.preview_pic.url_token = nsfwPlaceholder;
                    }
                    if (item?.chat_partner?.preview_pic?.rating == 'EROTIC') {
                        // if (testMode) console.log(`profile preview pic hidden`);
                        item.chat_partner.preview_pic.url_token = nsfwPlaceholder;
                    }
                // index++;
                // if (testMode) console.log(index);
                }
                data = JSON.stringify(data)
            } catch(e) {
                console.log(`fetch: nsfw filter error (profiles): ${e}`);
            }
        }
    }

    //more ...

    return
}


// ***** Handle Fetch *****

// handle response part
const realFetch = window.fetch;
window.fetch = async (url, options) => {

    let newUrl = url;

    // *** modify request ***
    // (not for now; see handle request part below)

    // *** send request & get response ***
    let response = await realFetch(newUrl, options);
    try {
        void await response.clone().json();
    } catch(e) {
        if (testMode) console.log(`not JSON or empty reply: ${e}`);
        return response;
    }

    // *** modify JSON response ***
    let content = await response.text();
    let data = JSON.parse(content);
    modifyResponse(url, data);
    content = JSON.stringify(data)

    return new Response(content, {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
    });
}

// handle request part
const firstFetch = window.fetch;
window.fetch = async (url, options) => {
    let newUrl = url;
    let response = {};
    if (typeof url === 'string') {
        newUrl = modifyRequest(url);
        // if (testMode) console.log('fetch string: ' + newUrl);
        response = await firstFetch(newUrl, options);
        // if (testMode) console.log('response string: ', JSON.parse(JSON.stringify(response)));
        // return response;
    }
    if (typeof url === 'object') {
        let request = url;
        const blob = await request.blob();
        const body = blob.size > 0 ? blob : undefined;
        options = {
            body,
            cache: request.cache,
            credentials: request.credentials,
            headers: request.headers,
            integrity: request.integrity,
            keepalive: request.keepalive,
            method: request.method,
            mode: request.mode,
            redirect: request.redirect,
            referrer: request.referrer,
            referrerPolicy: request.referrerPolicy,
            signal: request.signal,
        }
        newUrl = modifyRequest(request.url);
        // if (testMode) console.log('fetch object: ' + newUrl);
        response = await firstFetch(newUrl, options);
        // return response;
    }
    return response;
}


// ***** Handle XHR *****

(function() {
    let oldXHROpen = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) {

        if (url.match(/v4\/messages\//)) {
            //...
        }

        if (url.match(/v4\/messages\/conversations\?/)) {
            //url = url.replace('&length=15', '&length=120');
        }

        if (url.match(/v4\/contacts\?/)) {
            if ($('#contacts-custom-tags a.ui-tag--selected span.ui-tag__label').text().trim() == '[ A-Z ]') {
                url += '&sort_criteria=NAME_ASC';
                url = url.replace(/&filter%5Btags%5D%5B%5D\=\d+/, ``);
            }
            if ($('#contacts-custom-tags a.ui-tag--selected span.ui-tag__label').text().trim() == '[ Login ]') {
                url += '&sort_criteria=LAST_LOGIN_DESC';
                url = url.replace(/&filter%5Btags%5D%5B%5D\=\d+/, ``);
            }
            if ($('#contacts-custom-tags a.ui-tag--selected span.ui-tag__label').text().trim() == '[ Online ]') {
                url += '&filter[online]=true';
                url = url.replace(/&filter%5Btags%5D%5B%5D\=\d+/, ``);
            }
            //url += '&sort_criteria=LAST_LOGIN_DESC';
            //url += '&sort_criteria=NAME_ASC';
            //url += '&filter[online]=true';
            url = url.replace('/contacts?length=100&pick=items.*.profile&lang=de', '/contacts?length=999&pick=items.*.profile&lang=de');
        }

        if (location.pathname.match(/^\/messenger\/contacts\/name/)) {
            if (url.match(/filter%5Busername%5D\=/)) {
                url += '&sort_criteria=NAME_ASC';
                url = url.replace(/filter%5Busername%5D\=\*/, '');
            }
        }

        if (travelMode) {
            if (location.pathname.match(/^\/radar\//)) {
                url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
                url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
            }
            //url = url.replace(/filter%5Btravellers_filter%5D\=EXCLUDED/, '');
            //url = url.replace(/filter%5Btravellers_filter%5D\=INCLUDED/, 'filter%5Btravellers_filter%5D=EXCLUDED');
            //url = url.replace(/sort_criteria\=NEARBY_ASC/, 'sort_criteria=NEARBY_DESC');
        }

        if (location.pathname.match(/^\/(radar|hunqz)\//) && url.match(/filter%5Blocation%5D/)) {
            let radius = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
            if (radius >= 94500 && radius < 95500) {  //95
                url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${250000}`);
            }
            if (radius >= 95500 && radius < 96500) {  //96
                url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${500000}`);
            }
            if (radius >= 96500 && radius < 97500) {  //97
                url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${1000000}`);
            }
            if (radius >= 97500 && radius < 98500) {  //98
                url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${2000000}`);
            }
            if (radius >= 98500 && radius < 99500) {  //99
                url = url.replace(/filter%5Blocation%5D%5Bradius%5D\=[0-9\.]+/, `filter[location][radius]=${4000000}`);
            }
        }

        if (url.match(/v4\/groups\//)) {
            if (travelMode) {
                url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
                url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
            }
            if (resigned) {
                url = url.replace('statuses[]=REJECTED', 'statuses[]=NONE');
            }
        }

        if (url.match(/v4\/profiles\/popular/)) {
            if (travelMode && !url.match(/&scrollable=false/)) {
                url = url.replace('sort_criteria=LAST_LOGIN_DESC', 'sort_criteria=NEARBY_ASC');
                url = url.replace(/filter%5Blocation%5D%5Blat%5D\=[-0-9\.]+/, `filter[location][lat]=${xhrTravelLat}`);
                url = url.replace(/filter%5Blocation%5D%5Blong%5D\=[-0-9\.]+/, `filter[location][long]=${xhrTravelLong}`);
            }
            else if (!eyecandyActive && url.match(/&scrollable=false/)) {
                url = url.replace('sort_criteria=NEARBY_ASC', 'sort_criteria=LAST_LOGIN_DESC');
            }
            //url = url.replace('sort_criteria=LAST_LOGIN_DESC', 'sort_criteria=SIGNUP_DESC');
        }

        if (location.pathname.match(/^\/explore\//) && url.match(/v4\/profiles\?lang/)) {
            let loc = location.pathname.match(/[-0-9\.]+/g);
            xhrTravelLat = loc[0];
            xhrTravelLong = loc[1];
            localStorage.setItem('REtravelLat', xhrTravelLat);
            localStorage.setItem('REtravelLong', xhrTravelLong);

            //let radius = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
            //url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=LAST_LOGIN_DESC&filter[location][radius]=${radius}`);
            //url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=SIGNUP_DESC&filter[location][radius]=${radius}`);
        }

        //FIX for hunqz activity and new
        if (location.pathname.match(/^\/hunqz\/activity/) && url.match(/v4\/hunqz\/profiles\?lang/)) {
            let radiusActivity = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
            if (radiusActivity == '100000.0') radiusActivity = '100000000';
            url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=LAST_LOGIN_DESC&filter[location][radius]=${radiusActivity}`);
        }
        if (location.pathname.match(/^\/hunqz\/new/) && url.match(/v4\/hunqz\/profiles\?lang/)) {
            let radiusNew = $('.js-distance-radius .noUi-handle').attr('aria-valuenow');
            if (radiusNew == '100000.0') radiusNew = '100000000';
            url = url.replace('sort_criteria=NEARBY_ASC', `sort_criteria=SIGNUP_DESC&filter[location][radius]=${radiusNew}`);
        }

        //include "no entry" to filter
        if (filterNoEntry) {
            if (url.match(/\/profiles\?/)) {
                const categories = [
                    '&filter%5Bpersonal%5D%5Bgender_orientation%5D%5Bgender%5D%5B%5D=',
                    '&filter%5Bpersonal%5D%5Bgender_orientation%5D%5Borientation%5D%5B%5D=',
                    '&filter%5Bpersonal%5D%5Blooking_for%5D%5B%5D=',
                    '&filter%5Bpersonal%5D%5Bbody_type%5D%5B%5D=',
                    '&filter%5Bpersonal%5D%5Bbody_hair%5D%5B%5D=',
                    '&filter%5Bpersonal%5D%5Bhair_color%5D%5B%5D=',
                    '&filter%5Bpersonal%5D%5Bhair_length%5D%5B%5D=',
                    '&filter%5Bpersonal%5D%5Bethnicity%5D%5B%5D=',
                    '&filter%5Bsexual%5D%5Banal_position%5D%5B%5D=',
                    '&filter%5Bsexual%5D%5Bdick_size%5D%5B%5D=',
                    '&filter%5Bsexual%5D%5Bconcision%5D%5B%5D=',
                    '&filter%5Bsexual%5D%5Bsafer_sex%5D%5B%5D=',
                    '&filter%5Bsexual%5D%5Bfetish%5D%5B%5D=',
                    '&filter%5Bsexual%5D%5Bsm%5D%5B%5D=',
                    '&filter%5Bsexual%5D%5Bfisting%5D%5B%5D='
                ]
                for (let item of categories) {
                    url = url.replace(item, item + 'NO_ENTRY' + item)
                }
            }
        }

        //full profile
        if (url.match(/v4\/(hunqz\/)?profiles\/[-\w]+\/full\?/)) {
            this.addEventListener('load', function() {
                xhrFull = JSON.parse(this.response)
                // console.log('xhrFull');
            });
        }

        //show group posts of deleted users
        if (postFromDeleted) {
            if (url.match(/v4\/groups\/\d+\/posts\?/)) {
                this.addEventListener('load', function() {
                    try {
                        this.xhr = JSON.parse(this.response)
                        for (let item of this.xhr.items) {
                            if (item.deleted) {
                                if (testMode) console.log(`deleted`);
                                delete item.deleted;
                                //item.edit_status = 'NO_EDIT';
                                item.content = `[Gelöschter Beitrag]\n${item.content}`;
                            }
                            if (item.owner.deletion_date) {
                                if (testMode) console.log(`deletion_date`);
                                delete item.owner.deletion_date;
                                item.content = `[Profil gelöscht]\n\n${item.content}`;
                            }
                        }
                        Object.defineProperty(this, 'responseText', {
                            writable: true
                        });
                        this.responseText = JSON.stringify(this.xhr)
                    } catch(e) {
                    }
                });
            }
        }

        //hide nsfw pictures in radar etc.
        if (hideNSFW) {
            if (url.match(/\/(profiles|popular|conversations|contacts)\?/) && ! url.match(/v4\/hunqz/)) {
                this.addEventListener('load', function() {
                    try {
                        this.xhr = JSON.parse(this.response);
                        // console.log(this.xhr);
                        for (let item of this.xhr?.items) {
                            if (item?.preview_pic?.rating == 'EROTIC') {
                                // console.log(`preview pic hidden`);
                                item.preview_pic.url_token = nsfwPlaceholder;
                                // item.personal.age = -item.personal.age;
                                // item.preview_pic.url_token = (item.display?.large_tile) ? nsfwPlaceholderBig : nsfwPlaceholder;
                            }
                            if (item?.profile?.preview_pic?.rating == 'EROTIC') {
                                // console.log(`profile preview pic hidden`);
                                item.profile.preview_pic.url_token = nsfwPlaceholder;
                            }
                            if (item?.chat_partner?.preview_pic?.rating == 'EROTIC') {
                                // console.log(`profile preview pic hidden`);
                                item.chat_partner.preview_pic.url_token = nsfwPlaceholder;
                            }
                        }
                        Object.defineProperty(this, 'responseText', {
                            writable: true
                        });
                        this.responseText = JSON.stringify(this.xhr)
                    } catch(e) {
                        console.log(`XHR: nsfw filter error (profiles, conversations, contacts): ${e}`);
                    }
                });
            }
            if (url.match(/\/((profiles)\/\d+|activity-stream|list)\?/)) {
                this.addEventListener('load', function() {
                    try {
                        this.xhr = JSON.parse(this.response);
                        // console.log(this.xhr);
                        if (this.xhr?.id) {
                            // console.log(`1 profile item at top`);
                            if (this.xhr?.preview_pic?.rating == 'EROTIC') {
                                // console.log(`1 profile preview pic hidden`);
                                this.xhr.preview_pic.url_token = nsfwPlaceholder;
                            }
                        } else {
                            for (let item of this.xhr) {
                                if (item?.preview_pic?.rating == 'EROTIC') {
                                    // console.log(`no item preview pic hidden`);
                                    item.preview_pic.url_token = nsfwPlaceholder;
                                }
                                if (item?.partner?.preview_pic?.rating == 'EROTIC') {
                                    // console.log(`no item preview pic hidden`);
                                    item.partner.preview_pic.url_token = nsfwPlaceholder;
                                }
                            }
                        }
                        Object.defineProperty(this, 'responseText', {
                            writable: true
                        });
                        this.responseText = JSON.stringify(this.xhr)
                    } catch(e) {
                        console.log(`XHR: nsfw filter error (single profile, stream, list): ${e}`);
                    }
                });
            }
        }

        return oldXHROpen.apply(this, arguments);
    }
})();


// ***** Run at login *****

//init
let commonGroupsList = [], commonGroupsLoaded = false, visitorsList = [], visitsList = [], visitorsLoaded = false, visitsLoaded = false;

function runAtLogin (jNode) {

    //init common groups
    groupsRefreshed = true;
    commonGroupsList = [];
    $.ajax({headers: ajaxHead(), url: '/api/v4/profiles/me/groups?lang=de&length=10000&pick=items.*.(id,name,display_name)'})
    // $.ajax({headers: ajaxHead(), url: '/api/v4/profiles/me/groups?lang=de&length=10000&expand=items.*.(membership.*)'})
    .done(function (data) {
        for (let item of data.items) {
            commonGroupsList.push(item);
        }
        commonGroupsLoaded = true;
        if (testMode) console.log(commonGroupsList);

        //get initial group name
        const nameIndex = (item) => item.name == lastGroupName;
        if (commonGroupsList.findIndex(nameIndex) == -1) {
            if (commonGroupsList.length > 0) {
                lastGroupName = commonGroupsList[0].name;
            } else {
                lastGroupName = '';
            }
        }
    });

    //visitors, visits
    $.ajax({headers: ajaxHead(), url: '/api/v4/visitors?lang=de&length=10000&pick=items_limited,items.*.(name,date_visited)'})
    .done(function (data) {
        for (let item of data.items) {
            if (data.items_limited == undefined || visitorsList.length < data.items_limited) {
                visitorsList.push(item);
            }
        }
        visitorsLoaded = true;
        if (testMode) console.log(visitorsList, data.items_limited);
    });
    $.ajax({headers: ajaxHead(), url: '/api/v4/visits?lang=de&length=10000&pick=items_limited,items.*.(name,date_visited)'})
    .done(function (data) {
        for (let item of data.items) {
            if (data.items_limited == undefined || visitsList.length < data.items_limited) {
                visitsList.push(item);
            }
        }
        visitsLoaded = true;
        if (testMode) console.log(visitsList, data.items_limited);
    });
}



// ***** MutationObserver *****

/*** Functions for accessibility tweaks ***/

function makeHeading(el, level) {
    el.setAttribute("role", "heading");
    el.setAttribute("aria-level", level);
}

function makeRegion(el, label) {
    el.setAttribute("role", "main");
    el.setAttribute("aria-label", label);
}

function makeButton(el, label) {
    el.setAttribute("role", "button");
    if (label) el.setAttribute("aria-label", label);
}

function makeButtonFromText(el, label) {
    el.setAttribute("role", "button");
    if (!label) label = '';
    el.setAttribute("aria-label", `${label}${el.textContent.trim()}`);
}

function makeIcon(el, label) {
    el.setAttribute("role", "img");
    el.setAttribute("aria-label", label);
}

function makePresentational(el) {
    el.setAttribute("role", "presentation");
}

function setLabel(el, label) {
    el.setAttribute("aria-label", label);
}

function setLabelFromTitle(el, label) {
    if (!label) label = '';
    el.setAttribute("aria-label", `${label}${el.getAttribute('title')}`);
}

function setLabelFromText(el) {
    el.setAttribute("aria-label", el.textContent.trim());
}

function setLabelFromChildText(el) {
    el.parentNode.setAttribute("aria-label", el.textContent.trim());
}

function makeRadio(el) {
    el.setAttribute("role", "radio");
    el.setAttribute("aria-checked", "false");
}

function makeRadioChecked(el) {
    el.setAttribute("role", "radio");
    el.setAttribute("aria-checked", "true");
}


/*** Code to apply the tweaks when appropriate ***/

function applyTweaks(root, tweaks) {
    for (let tweak of tweaks) {
        for (let el of root.querySelectorAll(tweak.selector)) {
            if (Array.isArray(tweak.tweak)) {
                let [func, ...args] = tweak.tweak;
                func(el, ...args);
            } else {
                tweak.tweak(el);
            }
        }
    }
}

function initMutationObserver() {
    applyTweaks(document, LOAD_TWEAKS);
    applyTweaks(document, DYNAMIC_ELEMENT_ADDED_TWEAKS);
}

let observer = new MutationObserver(function(mutations) {
    for (let mutation of mutations) {
        try {
            if (mutation.type === "childList") {
                for (let node of mutation.addedNodes) {
                    if (node.nodeType != Node.ELEMENT_NODE) {
                        continue;
                    }
                    applyTweaks(node, DYNAMIC_ELEMENT_ADDED_TWEAKS);
                    // console.log('MO element node added');
                }
                /*for (let node of mutation.removedNodes) {
                    if (node.nodeType != Node.ELEMENT_NODE) {
                        continue;
                    }
                    applyTweaks(node, DYNAMIC_TWEAKS);
                }*/
/*             } else if (mutation.type === "attributes") {
                if (mutation.attributeName == "class") {
                    applyTweaks(mutation.target, DYNAMIC_ATTR_ONLY_TWEAKS);
                } */
            }
        } catch (e) {
            // Catch exceptions for individual mutations so other mutations are still handled
            console.log(`Exception while handling mutation: ${e}`);
        }
    }
});
observer.observe(document, {childList: true, subtree: true});

let observerAttr = new MutationObserver(function(mutations) {
    for (let mutation of mutations) {
        try {
            if (mutation.type === "attributes") {
                if (mutation.attributeName == "class") {
                    applyTweaks(mutation.target, DYNAMIC_ATTR_ONLY_TWEAKS);
                    // console.log('MO attr only');
                }
            }
            if (mutation.type === "childList") {
                applyTweaks(mutation.target, DYNAMIC_ANY_NODE_TWEAKS);
                // console.log('MO any node');
            }
        } catch (e) {
            // Catch exceptions for individual mutations so other mutations are still handled
            console.log(`Exception while handling mutation: ${e}`);
        }
    }
});
observerAttr.observe(document, {childList: true, subtree: true, attributes: true, attributeFilter: ["class"]});


/*** Define the actual tweaks ***/

// Tweaks to be applied on load
const LOAD_TWEAKS = [
]

// Tweaks to be applied whenever an element node is added
const DYNAMIC_ELEMENT_ADDED_TWEAKS = [

    //RomeoEnhancer (see also waitForKeyElements below)
    {selector: '#search input', tweak: searchInput},
    {selector: ':is(.layer-left-navigation, header) li', tweak: handleMainMenu},
    {selector: 'section.js-main-stage ul[class^="Tabbed-nav-"] li.is-selected', tweak: handleTabMenu},
    {selector: 'div.js-filter-button button', tweak: handleBookmark},
    {selector: '#offcanvas-nav .js-side-content span[data-cta="hidePlus"]:not(.re-done)', tweak: handleSettingsDisplay},
    {selector: '#messenger div:is(.js-correspondence, .js-header) div.reactView div > a > p', tweak: showLoginLocation},
    {selector: '.js-profile-footprints h1', tweak: handleFootprints},
    {selector: '.js-quick-filter .js-anal-position > div', tweak: handleFilterNoEntry},
    {selector: '#messenger div.js-chat .reactView a[href^="/messenger/chat"]', tweak: handleMessage},
    {selector: '#messages-list p a', tweak: handleMessageLink},
    {selector: '#messenger div.js-contacts .reactView a[href^="/messenger/contacts"]', tweak: handleContacts},
    {selector: '#contacts-custom-tags .js-remove', tweak: handleTagRemove},
    {selector: '#blog-posts .swiper-slide a:not(.re-done)', tweak: handleBlogLinks},
    {selector: ':is(div.js-wrapper, div.js-admins, #group-preview) a.js-contact', tweak: handleContactStrip},
    {selector: 'a[href^="/group/"] div[class*="Tile__BaseTile-"]:not(.re-done)', tweak: handleGroupTiles},
    // {selector: '.js-wrapper a[href^="/eyecandy"] h1', tweak: handleDiscoverEyecandy},
    {selector: ':is(a div.BIG, a div.SMALL, button div.LIST) div > span[class^="sc-"]', tweak: handleTiles},
    {selector: 'div.stream__content a.listitem__body .js-username p, div.stream__content div.js-list', tweak: handleStream},
    {selector: 'section.profile__stats section > p[class^="BaseText"], section.profile__stats ol', tweak: handleProfile},
    {selector: 'div[class*="SidebarContentContainer-"] div[class*="Main-"] > div', tweak: refreshPostsList},
    {selector: '#manage div[class^="FilterBar__Container"] > div > div', tweak: groupManageMode},
    {selector: 'div.js-members-header div.js-dropdown button', tweak: groupTravelLocation},
    {selector: '#liked-by-grid', tweak: handleSlideshowLikes},
    // {selector: '#picture-rating img, #picture-rating p[class^="Text-sc-"]', tweak: ratingInfo},
    {selector: '#picture-rating img, #picture-rating button[class^="TertiaryButton__Element-"], #picture-rating p[class^="Text-sc-"]', tweak: ratingInfo},
    {selector: 'section[class^="PopupWindow__Content"] button[class^="PrimaryButton__Element"]', tweak: reLogin},

    //accessibility: navigation items with badges
    {selector: '.icon-search', tweak: [makeIcon, "Suchen"]},
    {selector: '.icon-visitor', tweak: [makeIcon, "Besucher"]},
    {selector: '.icon-chat', tweak: [makeIcon, "Messages"]},
    {selector: '.icon-notification-bell', tweak: [makeIcon, "Activity Stream"]},
    {selector: '.ui-status--online', tweak: [makeIcon, "Online"]},
    {selector: '.ui-status--date', tweak: [makeIcon, "Date"]},
    {selector: '.ui-status--sex', tweak: [makeIcon, "Now"]},
    {selector: '.icon-airplane', tweak: [makeIcon, "Travel"]},
    {selector: '.icon-save-contact', tweak: [makeIcon, "Kontakte"]},
    {selector: '.js-nav-item .icon-group-members', tweak: [makeIcon, "Meine Gruppen"]},

    //accessibility: profile screen
    {selector: '.profile--romeo', tweak: [makeRegion, "Romeo-Profil"]},
    {selector: '.profile--hunqz', tweak: [makeRegion, "Hunqz-Profil"]},
    {selector: '.icon-add-footprint', tweak: [makeButton, "Fußtaps vergeben"]},
    {selector: '.js-remove-footprint', tweak: [setLabelFromTitle]},
    {selector: '.js-quickshare-trigger', tweak: [setLabel, "QuickShare-Album teilen"]},
    {selector: '.icon-default-contact', tweak: [makeIcon, "Nutzer speichern"]},
    {selector: '[id^="profile-"] .icon-save-contact', tweak: [makeIcon, "Nutzer speichern"]},
    {selector: '#visits > div', tweak: [makeRegion, "Besucher"]},
    {selector: '.icon-back', tweak: [makeIcon, "Zurück"]},
    {selector: '.icon-next', tweak: [makeIcon, "Weiter"]},
    {selector: '.icon-open-menu-ver', tweak: [makeIcon, "Menü öffnen"]},
    {selector: '.icon-open-stats', tweak: [makeIcon, "Profildetails"]},
    {selector: '.js-attach-pictures', tweak: [setLabel, "Bilder anhängen"]},
    {selector: '.js-submit', tweak: [setLabel, "Abschicken"]},
    {selector: 'button[class^="Close--"], button.js-close-button, .js-close-icon button', tweak: [setLabel, "Schließen"]},
    {selector: '.js-close-spotlight', tweak: [makeButton, "Schließen"]},
    {selector: '.js-hide.js-plus', tweak: [setLabel, "Profilbesuch verstecken"]},
    {selector: '.profile-section .reactView p[class^="BodyText"]', tweak: [makeHeading, "3"]},
    {selector: '.top-info-header p[class^="BodyText"]', tweak: [makeHeading, "1"]},
    {selector: 'button[class^="CollapsibleSection"] > p', tweak: [setLabelFromChildText]},

    //accessibility: other
    {selector: '.messages-send__select-button.messages-button-react-view', tweak: [setLabel, "Templates, Standort, Bilder senden"]},
    {selector: ':not(.js-nav-item) > .icon-group-members', tweak: [makeIcon, "Gruppe"]},
    {selector: '.js-settings-privacy div[class*="Radio-"]', tweak: [makeRadio]},
    {selector: '.js-settings-privacy div[class*="Selected-"] div[class*="Radio-"]', tweak: [makeRadioChecked]},
]

// Tweaks to be applied on any node changes
const DYNAMIC_ANY_NODE_TWEAKS = [

    //RomeoEnhancer
    {selector: '#offcanvas-nav main div > p[class^="MiniText"]:not(.re-done)', tweak: handleVersion},
    {selector: '.js-chat div[class^="Box"] + div > p[class^="SpecialText"]:not(.re-done)', tweak: previewMessage},
    {selector: '#messages-list p[class^="SpecialText"]:not(.re-done)', tweak: handleMessageScroll},
    {selector: 'div.js-correspondence div.js-header-region div[class*="ContextMenu__"] ul', tweak: threadOptionsMenu},
    {selector: 'nav:has(> #my-groups-list):not(.re-groups-def)', tweak: recentPosts},
    {selector: ':is(.js-post-list, .js-post) .js-date span:not(.re-done)', tweak: handleGroupPostDate},
    {selector: 'main > ul > li > img[src^="/img/usr/original/"]:not(.re-done)', tweak: handleImg},
    {selector: '.ReactModal__Content main div > img[src^="/img/usr/original/"]:not(.re-done)', tweak: handleSingleImg},
]

// Tweaks to be applied on attribute changes
const DYNAMIC_ATTR_ONLY_TWEAKS = [

    //RomeoEnhancer
    // {selector: 'div.swiper-slide.swiper-slide-active div.swiper-zoom-container, #metadata-bar p', tweak: imgInfo},
]

initMutationObserver();



// ***** waitForKeyElements (for nodes not handled by MutationObserver) *****

waitForKeyElements ('div#marionette.is-logged-in', runAtLogin, true);
waitForKeyElements ('div.js-chat .js-scrollable div[class="js-paging-spinner spinner-container l-fancy"]', fixScroll, true);