diff --git a/chat/ImagePreview.vue b/chat/ImagePreview.vue
index 3882151..3895467 100644
--- a/chat/ImagePreview.vue
+++ b/chat/ImagePreview.vue
@@ -8,11 +8,11 @@
             <a @click="toggleStickyMode()" :class="{toggled: sticky}" title="Toggle Stickyness"><i class="fa fa-thumbtack"></i></a>
         </div>
 
-        <webview src="about:blank" webpreferences="allowRunningInsecureContent, autoplayPolicy=no-user-gesture-required" id="image-preview-ext" ref="imagePreviewExt" class="image-preview-external" :style="{display: externalUrlVisible ? 'flex' : 'none'}"></webview>
+        <webview src="about:blank" webpreferences="allowRunningInsecureContent, autoplayPolicy=no-user-gesture-required" id="image-preview-ext" ref="imagePreviewExt" class="image-preview-external" :style="{display: externalPreviewHelper.isVisible() ? 'flex' : 'none'}"></webview>
 
         <div
             class="image-preview-local"
-            :style="{backgroundImage: `url(${internalUrl})`, display: internalUrlVisible ? 'block' : 'none'}"
+            :style="{backgroundImage: `url(${localPreviewHelper.getUrl()})`, display: localPreviewHelper.isVisible() ? 'block' : 'none'}"
         >
         </div>
     </div>
@@ -40,19 +40,112 @@
     }
 
 
+    abstract class ImagePreviewHelper {
+        protected visible = false;
+        protected url: string | null = 'about:blank';
+        protected parent: ImagePreview;
+
+        abstract show(url: string): void;
+        abstract hide(): void;
+        abstract match(domainName: string): boolean;
+
+        constructor(parent: ImagePreview) {
+            this.parent = parent;
+        }
+
+        isVisible(): boolean {
+            return this.visible;
+        }
+
+        getUrl(): string | null {
+            return this.url;
+        }
+    }
+
+
+    class LocalImagePreviewHelper extends ImagePreviewHelper {
+        hide(): void {
+            this.visible = false;
+            this.url = null;
+        }
+
+
+        show(url: string): void {
+            this.visible = true;
+            this.url = url;
+        }
+
+
+        match(domainName: string): boolean {
+            return ((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
+        }
+    }
+
+
+    class ExternalImagePreviewHelper extends ImagePreviewHelper {
+        protected lastExternalUrl: string | null = null;
+
+        protected allowCachedUrl = true;
+
+        hide(): void {
+            const wasVisible = this.visible;
+
+            if (this.parent.debug)
+                console.log('ImagePreview: exec hide mutator');
+
+            if (wasVisible) {
+                const webview = this.parent.getWebview();
+
+                if (this.allowCachedUrl) {
+                    webview.executeJavaScript(this.parent.jsMutator.getHideMutator());
+                } else {
+                    webview.loadURL('about:blank');
+                }
+
+                this.visible = false;
+            }
+        }
+
+
+        show(url: string): void {
+            const webview = this.parent.getWebview();
+
+            try {
+                if ((this.allowCachedUrl) && ((webview.getURL() === url) || (url === this.lastExternalUrl))) {
+                    if (this.parent.debug)
+                        console.log('ImagePreview: exec re-show mutator');
+
+                    webview.executeJavaScript(this.parent.jsMutator.getReShowMutator());
+                } else {
+                    if (this.parent.debug)
+                        console.log('ImagePreview: must load; skip re-show because urls don\'t match', this.url, webview.getURL());
+
+                    webview.loadURL(url);
+                }
+
+            } catch (err) {
+                console.error('ImagePreview: Webview reuse error', err);
+            }
+
+            this.url = url;
+            this.lastExternalUrl = url;
+            this.visible = true;
+        }
+
+        match(domainName: string): boolean {
+            return !((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
+        }
+    }
+
+
     @Component
     export default class ImagePreview extends Vue {
         private readonly MinTimePreviewVisible = 100;
 
         visible = false;
 
-        externalUrlVisible = false;
-        internalUrlVisible = false;
-
-        externalUrl: string | null = null;
-        internalUrl: string | null = null;
-
-        lastExternalUrl = '';
+        externalPreviewHelper = new ExternalImagePreviewHelper(this);
+        localPreviewHelper = new LocalImagePreviewHelper(this);
 
         url: string | null = null;
         domain: string | undefined;
@@ -61,7 +154,8 @@
         runJs = true;
         debug = false;
 
-        private jsMutator = new ImagePreviewMutator(this.debug);
+        jsMutator = new ImagePreviewMutator(this.debug);
+
         private interval: Timer | null = null;
 
         private exitInterval: Timer | null = null;
@@ -104,16 +198,31 @@
             const webview = this.getWebview();
 
             webview.addEventListener(
-                'dom-ready',
+                'update-target-url', // 'did-navigate', // 'dom-ready',
                 (event: EventBusEvent) => {
                     const url = webview.getURL();
-                    const js = this.jsMutator.getMutatorJsForSite(url);
+                    const js = this.jsMutator.getMutatorJsForSite(url, 'update-target-url');
+
+                    if (this.debug)
+                        console.log('ImagePreview update-target', event, js);
+
+                    if ((js) && (this.runJs))
+                        webview.executeJavaScript(js);
+                }
+            );
+
+
+            webview.addEventListener(
+                'dom-ready', // 'did-navigate', // 'dom-ready',
+                (event: EventBusEvent) => {
+                    const url = webview.getURL();
+                    const js = this.jsMutator.getMutatorJsForSite(url, 'dom-ready');
 
                     if (this.debug)
                         console.log('ImagePreview dom-ready', event, js);
 
                     if ((js) && (this.runJs))
-                        webview.executeJavaScript(js);
+                        webview.executeJavaScript(js, true);
                 }
             );
 
@@ -168,13 +277,13 @@
 
 
             _.each(
-                ['did-start-loading', 'load-commit', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'update-target-url'],
+                ['did-start-loading', 'load-commit', 'dom-ready', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'update-target-url'],
                 (en: string) => {
                     webview.addEventListener(
                         en,
                         (event: Event) => {
                             if (this.debug)
-                                console.log(`ImagePreview ${en}`, event);
+                                console.log(`ImagePreview ${en} ${Date.now()}`, event);
                         }
                     );
                 }
@@ -200,27 +309,15 @@
 
         hide(): void {
             if (this.debug)
-                console.log('ImagePreview: hide', this.externalUrlVisible, this.internalUrlVisible);
+                console.log('ImagePreview: hide', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible());
 
             this.cancelExitTimer();
 
             this.url = null;
             this.visible = false;
 
-            if (this.externalUrlVisible) {
-                const webview = this.getWebview();
-
-                if (this.debug)
-                    console.log('ImagePreview: exec hide mutator');
-
-                webview.executeJavaScript(this.jsMutator.getHideMutator());
-            }
-
-            this.internalUrlVisible = false;
-            this.externalUrlVisible = false;
-
-            // this.externalUrl = null; // 'about:blank';
-            this.internalUrl = null; // 'about:blank';
+            this.localPreviewHelper.hide();
+            this.externalPreviewHelper.hide();
 
             this.exitUrl = null;
             this.exitInterval = null;
@@ -262,7 +359,7 @@
                 return;
 
             if (this.debug)
-                console.log('ImagePreview: dismiss.exec', this.externalUrlVisible, this.internalUrlVisible, url);
+                console.log('ImagePreview: dismiss.exec', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(), url);
 
             // This timeout makes the preview window disappear with a slight delay, which helps UX
             // when dealing with situations such as quickly scrolling text that moves the cursor away
@@ -278,7 +375,7 @@
             const url = this.jsMutator.mutateUrl(initialUrl);
 
             if (this.debug)
-                console.log('ImagePreview: show', this.externalUrlVisible, this.internalUrlVisible,
+                console.log('ImagePreview: show', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(),
                 this.visible, this.hasMouseMovedSince(), !!this.interval, this.sticky, url);
 
             // console.log('SHOW');
@@ -325,36 +422,13 @@
                     if (this.debug)
                         console.log('ImagePreview: show.timeout', this.url);
 
-                    const isInternal = this.isInternalUrl();
+                    this.localPreviewHelper.match(this.domain as string)
+                        ? this.localPreviewHelper.show(this.url as string)
+                        : this.localPreviewHelper.hide();
 
-                    this.internalUrlVisible = isInternal;
-                    this.externalUrlVisible = !isInternal;
-
-                    if (isInternal) {
-                        this.internalUrl = this.url;
-                    } else {
-                        const webview = this.getWebview();
-
-                        try {
-                            if ((webview.getURL() === this.url) || (this.url === this.lastExternalUrl)) {
-                                if (this.debug)
-                                    console.log('ImagePreview: exec re-show mutator');
-
-                                webview.executeJavaScript(this.jsMutator.getReShowMutator());
-                            } else {
-                                if (this.debug)
-                                    console.log('ImagePreview: must load; skip re-show because urls don\'t match', this.url, webview.getURL());
-
-                                webview.loadURL(this.url as string);
-                            }
-
-                        } catch (err) {
-                            console.error('ImagePreview: Webview reuse error', err);
-                        }
-
-                        this.externalUrl = this.url;
-                        this.lastExternalUrl = this.url as string;
-                    }
+                    this.externalPreviewHelper.match(this.domain as string)
+                        ? this.externalPreviewHelper.show(this.url as string)
+                        : this.externalPreviewHelper.hide();
 
                     this.visible = true;
                     this.visibleSince = Date.now();
@@ -401,14 +475,14 @@
             return this.url;
         }
 
-        isExternalUrl(): boolean {
+        /* isExternalUrl(): boolean {
             // 'f-list.net' is tested here on purpose, because keeps the character URLs from being previewed
             return !((this.domain === 'f-list.net') || (this.domain === 'static.f-list.net'));
         }
 
         isInternalUrl(): boolean {
             return !this.isExternalUrl();
-        }
+        }*/
 
         toggleDevMode(): void {
             this.debug = !this.debug;
@@ -434,14 +508,13 @@
         }
 
         reloadUrl(): void {
-            if (this.externalUrlVisible) {
+            if (this.externalPreviewHelper.isVisible()) {
                 const webview = this.getWebview();
 
                 webview.reload();
             }
         }
 
-
         getWebview(): WebviewTag {
             return this.$refs.imagePreviewExt as WebviewTag;
         }
diff --git a/chat/image-preview-mutator.ts b/chat/image-preview-mutator.ts
index 3e75190..d80bbd8 100644
--- a/chat/image-preview-mutator.ts
+++ b/chat/image-preview-mutator.ts
@@ -9,6 +9,7 @@ import { domain as extractDomain } from '../bbcode/core';
 export interface PreviewMutator {
     match: string | RegExp;
     injectJs: string;
+    eventName: string;
 
     urlMutator?(url: string): string;
 }
@@ -45,12 +46,15 @@ export class ImagePreviewMutator {
     }
 
 
-    getMutatorJsForSite(url: string): string | undefined {
+    getMutatorJsForSite(url: string, eventName: string): string | undefined {
         let mutator = this.matchMutator(url);
 
         if (!mutator)
             mutator = this.hostMutators['default'];
 
+        if (mutator.eventName !== eventName)
+            return;
+
         return this.wrapJs(mutator.injectJs) + this.getReShowMutator();
     }
 
@@ -77,13 +81,19 @@ export class ImagePreviewMutator {
         return `(() => { try { ${mutatorJs} } catch (err) { console.error('Mutator error', err); } })();`;
     }
 
-    protected add(domain: string | RegExp, mutatorJs: string, urlMutator?: (url: string) => string): void {
+    protected add(
+        domain: string | RegExp,
+        mutatorJs: string,
+        urlMutator?: (url: string) => string,
+        eventName: string = 'update-target-url'
+    ): void {
         if (domain instanceof RegExp) {
             this.regexMutators.push(
                 {
                     match: domain,
                     injectJs: mutatorJs,
-                    urlMutator
+                    urlMutator,
+                    eventName
                 }
             );
 
@@ -93,30 +103,34 @@ export class ImagePreviewMutator {
         this.hostMutators[domain] = {
             match: domain,
             injectJs: mutatorJs,
-            urlMutator
+            urlMutator,
+            eventName
         };
     }
 
     protected init(): void {
-        this.add('default', this.getBaseJsMutatorScript('#video, #image, video, img'));
-        this.add('e621.net', this.getBaseJsMutatorScript('#image, video'));
-        this.add('e-hentai.org', this.getBaseJsMutatorScript('#img, video'));
-        this.add('gelbooru.com', this.getBaseJsMutatorScript('#image, video'));
-        this.add('chan.sankakucomplex.com', this.getBaseJsMutatorScript('#image, video'));
-        this.add('danbooru.donmai.us', this.getBaseJsMutatorScript('#image, video'));
-        this.add('gfycat.com', this.getBaseJsMutatorScript('video'));
-        this.add('gfycatporn.com', this.getBaseJsMutatorScript('video'));
-        this.add('youtube.com', this.getBaseJsMutatorScript('video'));
-        this.add('instantfap.com', this.getBaseJsMutatorScript('#post img, #post video'));
-        this.add('webmshare.com', this.getBaseJsMutatorScript('video'));
-        this.add('pornhub.com', this.getBaseJsMutatorScript('.mainPlayerDiv video, .photoImageSection img'));
-        this.add('sex.com', this.getBaseJsMutatorScript('.image_frame img, .image_frame video'));
-        this.add('redirect.media.tumblr.com', this.getBaseJsMutatorScript('picture img, picture video'));
-        this.add('i.imgur.com', this.getBaseJsMutatorScript('video, img'));
-        this.add('postimg.cc', this.getBaseJsMutatorScript('#main-image, video'));
-        this.add('gifsauce.com', this.getBaseJsMutatorScript('video'));
-        this.add('motherless.com', this.getBaseJsMutatorScript('.content video, .content img'));
-        this.add(/^media[0-9]\.giphy\.com$/, this.getBaseJsMutatorScript('video, img[alt]'));
+        this.add('default', this.getBaseJsMutatorScript(['#video, video', '#image, img']));
+        this.add('e621.net', this.getBaseJsMutatorScript(['video', '#image']));
+        this.add('e-hentai.org', this.getBaseJsMutatorScript(['video', '#img']));
+        this.add('gelbooru.com', this.getBaseJsMutatorScript(['video', '#image']));
+        this.add('chan.sankakucomplex.com', this.getBaseJsMutatorScript(['video', '#image']));
+        this.add('danbooru.donmai.us', this.getBaseJsMutatorScript(['video', '#image']));
+        this.add('gfycat.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
+        this.add('gfycatporn.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
+        this.add('youtube.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
+        this.add('instantfap.com', this.getBaseJsMutatorScript(['#post video', '#post img']));
+        this.add('webmshare.com', this.getBaseJsMutatorScript(['video']));
+        this.add('pornhub.com', this.getBaseJsMutatorScript(['.mainPlayerDiv video', '.photoImageSection img']));
+        this.add('sex.com', this.getBaseJsMutatorScript(['.image_frame video', '.image_frame img']));
+        this.add('redirect.media.tumblr.com', this.getBaseJsMutatorScript(['picture video', 'picture img']));
+        this.add('i.imgur.com', this.getBaseJsMutatorScript(['video', 'img']));
+        this.add('postimg.cc', this.getBaseJsMutatorScript(['video', '#main-image']));
+        this.add('gifsauce.com', this.getBaseJsMutatorScript(['video']));
+        this.add('motherless.com', this.getBaseJsMutatorScript(['.content video', '.content img']));
+        this.add(/^media[0-9]\.giphy\.com$/, this.getBaseJsMutatorScript(['video', 'img[alt]']));
+        this.add('giphy.com', this.getBaseJsMutatorScript(['video', 'a > div > img']));
+        this.add(/^media[0-9]\.tenor\.com$/, this.getBaseJsMutatorScript(['#view .file video', '#view .file img']));
+        this.add('tenor.com', this.getBaseJsMutatorScript(['#view video', '#view img']));
 
         // tslint:disable max-line-length
         this.add(
@@ -124,7 +138,7 @@ export class ImagePreviewMutator {
             `
                 const imageCount = $('.post-container video, .post-container img').length;
 
-                ${this.getBaseJsMutatorScript('.post-container video, .post-container img', true)}
+                ${this.getBaseJsMutatorScript(['.post-container video', '.post-container img'], true)}
 
                 if(imageCount > 1)
                     $('#flistWrapper').append('<div id="imageCount" style="position: absolute; bottom: 0; right: 0; background: green; border: 2px solid lightgreen; width: 5rem; height: 5rem; font-size: 2rem; font-weight: bold; color: white; border-radius: 5rem; margin: 0.75rem;"><div style="position: absolute; top: 50%; left: 50%; transform: translateY(-50%) translateX(-50%);">+' + (imageCount - 1) + '</div></div>');
@@ -133,16 +147,19 @@ export class ImagePreviewMutator {
 
         this.add(
             'rule34.xxx',
-            `${this.getBaseJsMutatorScript('#image, video')}
+            `${this.getBaseJsMutatorScript(['video', '#image'])}
                 const content = document.querySelector('#content');
-                content.remove();
-            `
+
+                if (content) content.remove();
+            `,
+            undefined,
+            'dom-ready'
         );
 
 
         this.add(
             'hentai-foundry.com',
-            this.getBaseJsMutatorScript('main video, main img'),
+            this.getBaseJsMutatorScript(['main video', 'main img']),
             (url: string): string => {
                 const u = urlHelper.parse(url, true);
 
@@ -159,14 +176,23 @@ export class ImagePreviewMutator {
         );
     }
 
-    getBaseJsMutatorScript(elSelector: string, skipElementRemove: boolean = false): string {
+    getBaseJsMutatorScript(elSelector: string[], skipElementRemove: boolean = false): string {
         return `const body = document.querySelector('body');
-            let selected = Array.from(document.querySelectorAll('${elSelector}'))
-                .filter((i) => ((i.width !== 1) && (i.height !== 1)));
+            const selectors = ${JSON.stringify(elSelector)};
+
+            // writing this out because sometimes .map and .reduce are overridden
+            let selected = [];
+
+            for (selector of selectors) {
+                const selectedElements = (Array.from(document.querySelectorAll(selector)).filter((i) => ((i.width !== 1) && (i.height !== 1))));
+                selected = selected.concat(selectedElements);
+            }
+
+            ${true /*this.debug*/ ? `console.log('Selector', '${elSelector}'); console.log('Selected', selected);` : ''}
 
             const img = selected.shift();
 
-            ${this.debug ? `console.log('Selector', '${elSelector}'); console.log('Selected', selected); console.log('Img', img);` : ''}
+            ${true /*this.debug*/ ? `console.log('Img', img);` : ''}
 
             if (!img) { return; }
 
@@ -179,7 +205,18 @@ export class ImagePreviewMutator {
                 + 'background-repeat: no-repeat !important; background-position: top left !important;'
                 + 'opacity: 1 !important; padding: 0 !important; border: 0 !important; margin: 0 !important;';
 
-            img.remove();
+            try {
+                img.remove();
+            } catch(err) {
+                console.error('Failed remove()', err);
+
+                try {
+                    img.parentNode.removeChild(img);
+                } catch(err2) {
+                    console.error('Failed removeChild()', err2);
+                }
+            }
+
             el.append(img);
             body.append(el);
             body.class = '';
@@ -197,7 +234,33 @@ export class ImagePreviewMutator {
 
             ${this.debug ? "console.log('Wrapper', el);" : ''}
 
-            if (img.play) { img.muted = true; img.play(); }
+            document.addEventListener('DOMContentLoaded', (event) => {
+                ${true /*this.debug*/ ? "console.log('on DOMContentLoaded');" : ''}
+
+                if (
+                    (img.play)
+                    && ((!img.paused) && (!img.ended) && (!(img.currentTime > 0)))
+                )
+                { img.muted = true; img.play(); }
+            });
+
+            document.addEventListener('load', (event) => {
+                ${true /*this.debug*/ ? "console.log('on load');" : ''}
+
+                if (
+                    (img.play)
+                    && ((!img.paused) && (!img.ended) && (!(img.currentTime > 0)))
+                )
+                { img.muted = true; img.play(); }
+            });
+
+
+            try {
+                if (img.play) { img.muted = true; img.play(); }
+            } catch (err) {
+                console.error('Failed img.play()', err);
+            }
+
 
             let removeList = [];
             const safeIds = ['flistWrapper', 'flistError', 'flistHider'];
diff --git a/site/character_page/character_page.vue b/site/character_page/character_page.vue
index 8df90c6..064a516 100644
--- a/site/character_page/character_page.vue
+++ b/site/character_page/character_page.vue
@@ -666,6 +666,10 @@
             .vs, .scores {
                 display: none;
             }
+
+            .minimize-btn {
+                opacity: 0.6;
+            }
         }
 
         h3 {
@@ -689,12 +693,19 @@
         .scores {
             float: left;
             flex: 1;
-            margin-right: 1rem;
+            margin: 0;
             max-width: 25rem;
 
+            &.you {
+                margin-right: 1rem;
+           }
+
+            &.them {
+                margin-left: 1rem;
+            }
+
             ul {
                 padding: 0;
-                padding-left: 0rem;
                 list-style: none;
             }
 
diff --git a/site/character_page/match-report.vue b/site/character_page/match-report.vue
index 645ed63..92f5728 100644
--- a/site/character_page/match-report.vue
+++ b/site/character_page/match-report.vue
@@ -1,6 +1,6 @@
 <template>
     <div id="match-report" :class="{'match-report': true, minimized: isMinimized}" v-if="(haveScores(characterMatch.you) || haveScores(characterMatch.them))">
-        <a class="minimize-btn" @click="toggleMinimize()"><i :class="{fa: true, 'fa-plus': isMinimized, 'fa-minus': !isMinimized}"></i></a>
+        <a class="minimize-btn" @click.prevent="toggleMinimize()"><i :class="{fa: true, 'fa-plus': isMinimized, 'fa-minus': !isMinimized}"></i></a>
 
         <div class="scores you">
             <h3>
@@ -54,7 +54,7 @@
 
         isMinimized = false;
 
-        @Watch('isMinimized')
+        @Watch('minimized')
         onMinimizedChange(): void {
             this.isMinimized = this.minimized;
         }