Go actual full screen on Simplepractice.com video calls

When I’m on calls with my therapist it has bugged me that I can’t actually go fullscreen with the video so I gave CGPT the code for the page and wrote up a script that expands the fullscreen video to actually be fullscreen.

Use either the console script or Tampermonkey script and try it out for yourself!

Console script

(() => {
  const TOGGLE = 'tm-full-bleed-active';
  const ROOT_SEL = 'div.bbwWj.video';
  const PANEL_SEL = `${ROOT_SEL} > div.mljTO`;
  const STREAM_CONTAINER_SEL = `${ROOT_SEL} .stream-container`;
  const PARTICIPANT_SEL = `${ROOT_SEL} .participant-container`;
  const VIDEO_SEL = `${ROOT_SEL} video`;

  // --- style injection ---
  const css = `
    html.${TOGGLE}, body.${TOGGLE}{margin:0!important;padding:0!important;overflow:hidden!important}
    html.${TOGGLE} ${PANEL_SEL}{
      position:fixed!important;inset:0!important;width:100vw!important;height:100vh!important;
      margin:0!important;padding:0!important;border:0!important;background:#000!important;
      z-index:2147483647!important;box-shadow:none!important
    }
    html.${TOGGLE} ${STREAM_CONTAINER_SEL}, html.${TOGGLE} ${PARTICIPANT_SEL}{
      position:absolute!important;inset:0!important;width:100%!important;height:100%!important;
      margin:0!important;padding:0!important;border:0!important;max-width:none!important;max-height:none!important;overflow:hidden!important;background:transparent!important
    }
    html.${TOGGLE} ${VIDEO_SEL}{
      position:absolute!important;inset:0!important;width:100%!important;height:100%!important;
      object-fit:cover!important;object-position:center center!important;display:block!important;background:#000!important;border:0!important;box-shadow:none!important;transform:none!important
    }
    html.${TOGGLE} ${ROOT_SEL} .name, html.${TOGGLE} ${ROOT_SEL} .pin-button, html.${TOGGLE} ${ROOT_SEL} .more-button{
      display:none!important;visibility:hidden!important;pointer-events:none!important
    }
    .tm-fb-btn{
      position:fixed!important;right:14px;bottom:14px;z-index:2147483647!important;
      font:600 12px/1 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
      padding:10px 12px;border-radius:999px;background:rgba(0,0,0,.6);color:#fff;
      border:1px solid rgba(255,255,255,.2);cursor:pointer;user-select:none;backdrop-filter:blur(4px)
    }
    .tm-fb-btn:hover{background:rgba(0,0,0,.75)}
  `;
  let style = document.getElementById('tm-fb-style');
  if (!style) {
    style = document.createElement('style');
    style.id = 'tm-fb-style';
    style.textContent = css;
    document.head.appendChild(style);
  }

  // --- helpers ---
  const $ = (s, r = document) => r.querySelector(s);
  const nap = (ms) => new Promise(r => setTimeout(r, ms));
  const waitFor = async (sel, tries = 200, step = 100) => {
    while (tries-- > 0) { const el = $(sel); if (el) return el; await nap(step); }
    return null;
  };

  // --- state ---
  let btn;

  const isActive = () =>
    document.documentElement.classList.contains(TOGGLE) ||
    document.body.classList.contains(TOGGLE);

  const setActive = (on) => {
    [document.documentElement, document.body].forEach(el => el && el.classList.toggle(TOGGLE, on));
    if (btn) btn.textContent = on ? 'Exit Full-Bleed' : 'Full-Bleed';
  };

  const toggle = () => setActive(!isActive());

  // --- UI button ---
  const mountButton = () => {
    if (btn && document.body.contains(btn)) return btn;
    btn = document.createElement('button');
    btn.className = 'tm-fb-btn';
    btn.type = 'button';
    btn.textContent = isActive() ? 'Exit Full-Bleed' : 'Full-Bleed';
    btn.addEventListener('click', toggle);
    document.body.appendChild(btn);
    return btn;
  };

  // --- key bindings ---
  const bindKeys = () => {
    window.addEventListener('keydown', (e) => {
      if (e.key === 'f' || e.key === 'F') { e.preventDefault(); toggle(); }
      else if (e.key === 'Escape' && isActive()) { e.preventDefault(); setActive(false); }
    }, { capture: true });
  };

  // --- dblclick on video ---
  const bindVideoDblClick = (v) => {
    if (!v || v.dataset.tmFbBound) return;
    v.addEventListener('dblclick', (e) => { e.preventDefault(); toggle(); }, { capture: true });
    v.dataset.tmFbBound = '1';
  };

  // --- observe dynamic DOM ---
  const observe = () => {
    const mo = new MutationObserver(() => {
      const v = $(VIDEO_SEL);
      if (v) bindVideoDblClick(v);
      if (!btn && document.body) mountButton();
    });
    mo.observe(document.documentElement, { childList: true, subtree: true });
  };

  // --- bootstrap ---
  (async () => {
    await waitFor('body');
    bindKeys();
    observe();
    const v = await waitFor(VIDEO_SEL, 120, 150);
    if (v) bindVideoDblClick(v);
    mountButton();

    // Expose controls for manual use
    window.tmFullBleed = { toggle, on: () => setActive(true), off: () => setActive(false) };
    console.log('[tmFullBleed] Ready. Use tmFullBleed.toggle(), press F, double-click video, or use the button.');
  })();
})();

Tampermonkey script

// ==UserScript==
// @name         Full-Bleed Video (SimplePractice only)
// @namespace    nic.tools.video
// @version      1.0.1
// @description  Force main video to go true full-bleed (edge-to-edge) on SimplePractice video
// @author       you
// @match        https://video.simplepractice.com/*
// @run-at       document-idle
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  // ---------- small utils ----------
  const nap = (ms) => new Promise(r => setTimeout(r, ms));
  const q = (sel, root = document) => root.querySelector(sel);

  // Wait for an element or return null after tries*interval
  const waitFor = async (fnOrSel, tries = 200, interval = 100) => {
    while (tries-- > 0) {
      let el = null;
      try { el = typeof fnOrSel === 'function' ? fnOrSel() : q(fnOrSel); } catch {}
      if (el) return el;
      await nap(interval);
    }
    return null;
  };

  // ---------- selectors from provided markup ----------
  const ROOT_SEL = 'div.bbwWj.video';
  const PANEL_SEL = `${ROOT_SEL} > div.mljTO`;
  const STREAM_CONTAINER_SEL = `${ROOT_SEL} .stream-container`;
  const PARTICIPANT_SEL = `${ROOT_SEL} .participant-container`;
  const VIDEO_SEL = `${ROOT_SEL} video`;

  const TOGGLE_CLASS = 'tm-full-bleed-active';

  // ---------- inject styles ----------
  GM_addStyle(`
    /* Full-bleed mode root locks */
    html.${TOGGLE_CLASS}, body.${TOGGLE_CLASS} {
      margin: 0 !important;
      padding: 0 !important;
      overflow: hidden !important;
    }

    /* Pin the main panel to the viewport */
    html.${TOGGLE_CLASS} ${PANEL_SEL} {
      position: fixed !important;
      inset: 0 !important;
      width: 100vw !important;
      height: 100vh !important;
      margin: 0 !important;
      padding: 0 !important;
      border: 0 !important;
      background: #000 !important;
      z-index: 2147483647 !important;
      box-shadow: none !important;
    }

    /* Ensure no intermediate wrapper constrains size */
    html.${TOGGLE_CLASS} ${STREAM_CONTAINER_SEL},
    html.${TOGGLE_CLASS} ${PARTICIPANT_SEL} {
      position: absolute !important;
      inset: 0 !important;
      width: 100% !important;
      height: 100% !important;
      margin: 0 !important;
      padding: 0 !important;
      border: 0 !important;
      max-width: none !important;
      max-height: none !important;
      overflow: hidden !important;
      background: transparent !important;
    }

    /* Make the video fill and crop (letterboxing avoidance) */
    html.${TOGGLE_CLASS} ${VIDEO_SEL} {
      position: absolute !important;
      inset: 0 !important;
      width: 100% !important;
      height: 100% !important;
      object-fit: cover !important;
      object-position: center center !important;
      display: block !important;
      background: #000 !important;
      border: 0 !important;
      box-shadow: none !important;
      transform: none !important;
    }

    /* Hide overlays that might intrude (adjust as needed) */
    html.${TOGGLE_CLASS} ${ROOT_SEL} .name,
    html.${TOGGLE_CLASS} ${ROOT_SEL} .pin-button,
    html.${TOGGLE_CLASS} ${ROOT_SEL} .more-button {
      display: none !important;
      visibility: hidden !important;
      pointer-events: none !important;
    }

    /* On-screen toggle button */
    .tm-fb-btn {
      position: fixed !important;
      right: 14px;
      bottom: 14px;
      z-index: 2147483647 !important;
      font: 600 12px/1 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
      padding: 10px 12px;
      border-radius: 999px;
      background: rgba(0,0,0,0.6);
      color: #fff;
      border: 1px solid rgba(255,255,255,0.2);
      cursor: pointer;
      user-select: none;
      backdrop-filter: blur(4px);
    }
    .tm-fb-btn:hover { background: rgba(0,0,0,0.75); }
  `);

  // ---------- toggle logic ----------
  const setActive = (on) => {
    const rootList = [document.documentElement, document.body].filter(Boolean);
    rootList.forEach(el => el.classList.toggle(TOGGLE_CLASS, on));
    if (btn) btn.textContent = on ? 'Exit Full-Bleed' : 'Full-Bleed';
  };

  const isActive = () =>
    document.documentElement.classList.contains(TOGGLE_CLASS) ||
    document.body.classList.contains(TOGGLE_CLASS);

  const toggle = () => setActive(!isActive());

  // ---------- button ----------
  let btn;
  const mountButton = () => {
    if (btn && document.body.contains(btn)) return btn;
    btn = document.createElement('button');
    btn.className = 'tm-fb-btn';
    btn.type = 'button';
    btn.textContent = isActive() ? 'Exit Full-Bleed (f)' : 'Full-Bleed (f)';
    btn.addEventListener('click', toggle);
    document.body.appendChild(btn);
    return btn;
  };

  // ---------- event bindings ----------
  const bindKeys = () => {
    window.addEventListener('keydown', (e) => {
      if (e.key === 'f' || e.key === 'F') {
        e.preventDefault();
        toggle();
      } else if (e.key === 'Escape' && isActive()) {
        e.preventDefault();
        setActive(false);
      }
    }, { capture: true });
  };

  const bindVideoDoubleClick = (video) => {
    if (!video || video.dataset.tmFbBound) return;
    video.addEventListener('dblclick', (e) => {
      e.preventDefault();
      toggle();
    }, { capture: true });
    video.dataset.tmFbBound = '1';
  };

  // ---------- observer to reapply on dynamic pages ----------
  const observe = () => {
    const mo = new MutationObserver(() => {
      const video = q(VIDEO_SEL);
      if (video) bindVideoDoubleClick(video);
      if (!btn && document.body) mountButton();
    });
    mo.observe(document.documentElement, { childList: true, subtree: true, attributes: false });
  };

  // ---------- bootstrap ----------
  (async () => {
    await waitFor(() => document.body);
    bindKeys();
    observe();

    const video = await waitFor(VIDEO_SEL, 120, 150);
    if (video) bindVideoDoubleClick(video);

    mountButton();

    // Optional: expose console controls
    window.tmFullBleed = { toggle, on: () => setActive(true), off: () => setActive(false) };
    console.log('[tmFullBleed] Ready on SimplePractice. Use tmFullBleed.toggle(), press F, double-click video, or use the button.');
  })();

})();

See my creation conversation with ChatGPT here:

https://chatgpt.com/share/68c89780-f700-8007-916b-2d707b1970a6

Fix for audio cutting out for 1-3 seconds randomly with MSI B650-S Wifi Motherboard

If your sound keeps cutting out seemingly randomly for your wirelessly headset with this motherboard then try plugging it into the other USB slots (not the blue ones, the red ones). It seems to have fixed my issue. Seems the other ones may not have enough power to support the power draw.

Found the fix here: Audio issues MSI Pro B650-S Wifi | MSI Global English Forum

Have you use another headphone to check for the symptom?

I think I found the solution to my issue :

The USB 3.2 Gen 1 ports connect to something called “Hub-1074” and I guess those aren’t good enough for USB headsets. I at first thought that it was an issue with the board because the chipset driver updates reduced the problem, but didn’t fix it. I then tried connecting with a different headset that uses 3.5mm jacks and it worked without issues, so now I have connected my USB headset to one of the USB 3.2 Gen 2 ports that connect to the CPU, and so far I haven’t had any audio issues.

My Favourite Podcasts!

Podcasts are one of my favourite ways to learn and entertain myself. Here are some of my faves from over the years.

  • Planet Money / The Indicator
    • Fairly light podcast on economics, very engaging hosts, interesting topics.
  • This American Life
    • A classic, beautiful stories on being human.
  • 99% Invisible
    • Learn awesome things about design and little details in life you wouldn’t have otherwise discovered.
  • Rumble Strip
    • Very direct, down to earth stories about everyday Vermonters. Especially love the ones with her farmer friend or town hall meetings. Note, I hate about 25% of the episodes as she’ll have random guest episodes and they can be awful.
  • What Roman Mars Can Learn About Con Law
    • Get into the minutia of constitutional law with Roman Mars and his law professor friend.
  • Twenty Thousand Hertz
    • The most beautifully podcast I’ve ever listened to. Amazing stories about audio in many different ways and a surprisingly genuine and warm. host.
  • The Big Dig
    • Ever wanted to find out exactly what went wrong in so many ways with a mega project in the United States? This podcast goes into the details on the many trials and tribulations of the Big Dig project in Boston. I learned a lot and it was super interesting!
  • Acquired
    • Listen to this if you’re interested in going in deep on companies. The hosts go on multi hour deep dives into the histories of the companies they cover. I find it to be super helpful for understanding how companies came to be but also what differentiates them from other companies.
  • Search Engine
    • In depth episodes on a very wide range of topics (the secret pool in Buckingham Palace, people who buy luggage at the airport, jawmaxxing) which I find very interesting and delightful in their unnecessary depth.
  • The Sporkful
    • Nice stories about food. The host’s speaking style isn’t my favourite and some of the episodes are a little meh but there are enough good ones in there to make it worthwhile.
  • Startup
    • Front row seats to creation and development over the years of Gimlet. Really fascinating glimpse into the birth of a company (and all the various things that come with it).
  • Reply All
    • One of Gimlet’s first successful podcasts, now retired due to a messy series of events but lots of fun and interesting episodes before that happened.
  • Heavyweight
    • Heartwarming and humanistic stories about the host trying to help a given guest try and resolve a deep seated relationship issue. Personally, I find the non-mainline episodes annoying but the standard episodes are great!
  • More Perfect
    • An awesomely in depth podcast series on how the Supreme Court works and some of their historic decisions. If you have any interest in the way the American legal system functions I wholeheartedly recommend this podcast.
  • The Disconnect
    • Want to spend 10+ hours learning about how Texas’s energy grid functions and why it failed? This is the podcast for you, :).
  • The End of the World (with Josh Clark)
    • Quite different from most podcasts out there. This one touches on a number of far far future scenarios and feels very sci fi at times. Excellent production and you’ll probably learn a few new concepts.
  • The Last Cup
    • Detailed storytelling surrounding Messi’s final world cup run and all the drama leading up to it. As a fan of Messi this was great to listen to plus it has a very satisfying end.
  • Gamecraft
    • One of Benchmark’s legendary GPs breaks down the economics and structure of the gaming industry piece by piece. Incredible knowledge contained within. Great if you’re interested in the business of gaming.
  • The Secret Life of Canada
    • Stories told about the often untold side of things from a indigenous perspective in Canada. A mix of humour and learning about horrific events you’ll likely learn a lot about things you didn’t know happened. The hosts have excellent chemistry with each other and do a fantastic job of covering the subject matter in a way that doesn’t shy away from the facts but also doesn’t leave you feeling irredeemably depressed.
  • Lenny’s Podcast
    • The best product podcast out there. Learn about a whole range of product related topics and from some of the strongest operators out there. When I feel like learning more about my craft I listen to this, :).
  • Containers
    • In depth coverage of the systems that power our ports (and how they’ve changed over time). The storytelling here is excellent and you’ll learn a lot about how goods move around the world.

How to get Tablepress tables to show in full width

  1. Go to Appearance –> Customize –> Additional CSS
    (If you’re in the right place, this should show after your site URL /wp-admin/customize.php)

2. Add in this CSS block modification

.content-area__wrapper {
–go–max-width: 100rem;
}

3. Now your tables will show at full width on your site!

If you want it to only apply to a specific page, publish your page, go to that page, right click to inspect and find out the page ID (it shows at the very top), then target your change to only apply to that page in your Additional CSS settings.

In my case, my page ID is 534 so I did the following

.page-id-534 .content-area__wrapper {
–go–max-width: 100rem;
}

Make sure you include a space between .page-id-534 and .content-area__wrapper

Here’s a screenshot of what my inspect view looked like.

How to build & check to see if your ads.txt file is correct for Google Ads (Adsense)

Your ads.txt file is composed of the following parts

For example: google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0

Where:

  • “google.com” is the advertising system domain
  • “pub-XXXXXXXXXXXXXXXX” is your unique publisher ID
  • “DIRECT” indicates a direct business relationship
  • “f08c47fec0942fa0” is Google’s AdSystem ID

So to create your own ads.txt, all you need to do is go to your account information page (Account information – Settings – Account – Google AdSense) and pull your publisher ID and slot it in.

Once you’ve got your ads.txt file set up on your website (I used Ads.txt Manager – WordPress plugin | WordPress.org for this).

When its added in, you can check to see if its showing up properly by visiting website.com/ads.txt (in my case pezant.ca/ads.txt)

Or if you want to, you can also use this tool which works well!

ads.txt Validator – Free ads.txt Validation Tool by ads.txt Guru (adstxt.guru)

Updating your wordpress PW without being able to password reset via email (GCP hosted WordPress instance)

Steps:

  1. Get to your VM instance page (https://console.cloud.google.com/compute/instances)
  2. Click on the SSH dropdown
  3. Enter the following commands

~$ cd /var/www/html
/var/www/html$ wp user update [username] –user_pass=[new_password]

Here’s an example if your user name was “admin” and you wanted the password to be “new_pass” you would enter the following after navigating to the right directory.

wp user update admin –user_pass=new_pass

Here’s the SSH button if you don’t know where to find it

Getting iframes to appear centered for WordPress pages/posts

I recently set up a few iframes and they were stubbornly left aligned despite all the other content being center aligned. Here’s how I was able to fix the issue.

  1. Go to Appearance –> Customize –> Additional CSS (/wp-admin/customize.php)
  2. Add this value to your custom CSS

iframe {
display: block;
margin: 0 auto;
}

3. Hit publish and you should be good!

Getting podcasts onto your Garmin Watch

Let’s say you have a podcast you like listening to and you want to listen to it off of your Garmin watch (and not your phone). Here’s how you can set that up!

Block any ads from showing on your computer with AdGuard DNS

Connect to public AdGuard DNS server (adguard-dns.io)

Set up your network settings to use AdGuard DNS and they’ll block any ad server requests for you. A good option if you don’t want to block ads across your entire network with something like a PiHole. Downside is that you’re sending all your traffic to a DNS server that you don’t control.

DNS addresses

Default servers
AdGuard DNS will block ads and trackers.
94.140.14.14
94.140.15.15

Non-filtering servers
AdGuard DNS will not block ads, trackers, or any other DNS requests.
94.140.14.140
94.140.14.141

Family protection servers
AdGuard DNS will block ads, trackers, adult content, and enable Safe Search and Safe Mode, where possible.
94.140.14.15
94.140.15.16

Examples of blocked ads

Privacy Policy for URL Transformer

Privacy Policy for URL Transformer

Effective Date: January 30, 2024

This Privacy Policy document outlines the types of information that is not collected, gathered, or processed by URL Transformer

Since we do not collect any personal data, we have no information to share or use in any way. Our commitment is to ensure your privacy and data protection rights at all times.

1. No Data Collection

URL Transformer does not collect, store, or transmit any personal information. We do not track user activity, browsing history, or any other information related to the user’s online or offline activities.

2. No Third-Party Data Sharing

Since no data is collected, there is no sharing of personal information with any third parties.

3. No User Authentication

Our extension does not require any form of user registration or authentication. Users can utilize the functionalities of URL Transformer without an account, which ensures that their identity remains anonymous.

4. No Cookies

URL Transformer does not use cookies or any similar tracking technologies.

5. Extension Permissions

The extension may require certain permissions to function correctly (such as activeTab for interacting with web content). However, these permissions do not allow the extension to access personal data. They are solely for providing the features and functionalities as intended by URL Transformer .

6. Changes to This Privacy Policy

We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page. You are advised to review this Privacy Policy periodically for any changes.

7. Contact Us

If you have any questions about this Privacy Policy, please contact us at nick@pezant.ca.

This policy is effective as of January 30, 2024.

Disabling Speak to Chat on Sony XM4

I was frustrated at my Sony XM4 headphones cutting out and playing a sidetone instead every time you start talking during a meeting. Since I was using it on my Mac I couldn’t turn off the Speak to Chat functionality through the app settings. Here’s the fix I found!

SOLUTION:

To turn off this annoying feature, hold two fingers down flat on your right earbud until you hear a “Speak to Chat deactivated” message. You might need to try a few different finger placements (just holding for a few seconds should do)

Credit to hiiamhere1 for this solution (Wh-1000XM4 CUTS OUT WHEN TALKING? : sony (reddit.com))!