Back to Manual

Custom Code

For advanced customization, Pika lets you add your own code to your public site. Style your site with custom CSS, or inject your own markup into your site’s <head> and <body>. You can manage all of these from Settings → Custom Code.

Contents


Custom CSS

For advanced customization, add your own CSS to style your site. This only affects your public site, not your dashboard. Use !important; to ensure your styles take precedence, and remember to support both light and dark modes.


Custom <head> code Pro

Add your own markup to the <head> of every page on your public site. This is a good place for verification tags, custom fonts, meta tags, and third-party analytics or embed snippets. Your code is added at the bottom of the <head>, just before the closing </head> tag.

Examples

Below are some examples of code snippets you might add to your custom <head> code. Have a script to share? Let us know.

Show an estimated reading time under your post title

This script calculates a naive read time, and it’s probably only applicable to western languages. It adds the read time just below your post title. You will most certainly want to add your own custom CSS so it displays to your liking. This script only calculates read time for posts with titles.

<script>
  (function () {
    "use strict";

    document.addEventListener("DOMContentLoaded", function () {
      if (!document.body.classList.contains("site-post-body")) return;

      const header = document.querySelector(".site-post-header");
      const content = document.querySelector(".site-post .trix-content");
      if (!header || !content) return;

      const wordCount = (content.textContent.match(/\S+/g) || []).length;
      const minutes = Math.max(1, Math.ceil(wordCount / 200));

      const readingTime = document.createElement("small");
      readingTime.textContent = minutes + " min read";
      readingTime.style.cssText = "display:block;margin-top:-1rem;margin-bottom:2rem;color:var(--color-txt-light)";

      header.appendChild(readingTime);
    });
  })();
</script>

Turn footnotes into margin notes on wide screens

On a post or page, this script moves each footnote into the right margin as a sidenote. On narrower screens it leaves the normal footnotes list in place.

<script>
  (function () {
    "use strict";

    const MIN_WIDTH = 1200;
    const GAP = 16; // vertical space between stacked notes, in px

    document.addEventListener("DOMContentLoaded", function () {
      if (!document.body.matches(".site-post-body, .site-page-body")) return;

      const content = document.querySelector(".site-post .trix-content, .site-page .trix-content");
      if (!content) return;

      const list = content.querySelector("ol.footnotes");
      if (!list) return;

      const pairs = [];
      content.querySelectorAll("a.footnote-ref").forEach(function (ref) {
        const li = list.querySelector('li[data-id="' + ref.dataset.id + '"]');
        if (!li) return;

        const number = document.createElement("span");
        number.className = "sidenote-number";
        number.textContent = ref.textContent + ".";

        const text = document.createElement("div");
        text.className = "sidenote-text";
        text.innerHTML = li.innerHTML;

        const note = document.createElement("div");
        note.className = "sidenote";
        note.append(number, text);
        content.appendChild(note);

        pairs.push({ ref: ref, note: note });
      });

      // Position each note beside its reference, stacking to avoid overlap.
      const wide = window.matchMedia("(min-width:" + MIN_WIDTH + "px)");
      function layout() {
        if (!wide.matches) return;
        const contentTop = content.getBoundingClientRect().top + window.scrollY;
        let cursor = 0;
        pairs.forEach(function (pair) {
          const refTop = pair.ref.getBoundingClientRect().top + window.scrollY - contentTop;
          const top = Math.max(refTop, cursor);
          pair.note.style.top = top + "px";
          cursor = top + pair.note.offsetHeight + GAP;
        });
      }

      window.addEventListener("load", layout);
      window.addEventListener("resize", layout);
      wide.addEventListener("change", layout);

      const style = document.createElement("style");
      style.textContent =
        ".trix-content .sidenote{display:none}" +
        "@media (min-width:" + MIN_WIDTH + "px){" +
          ".trix-content{position:relative}" +
          ".trix-content .sidenote{display:flex;gap:0.4em;position:absolute;left:100%;" +
            "width:min(15rem, calc((100vw - var(--width-M)) / 2 - 3rem));margin-left:2rem;" +
            "padding-left:1em;border-left:2px solid var(--color-border);" +
            "font-size:var(--font-S);line-height:1.45;color:var(--color-txt-light)}" +
          ".trix-content .sidenote-number{flex:none}" +
          ".trix-content .sidenote-text{flex:1}" +
          ".trix-content .sidenote-text p{margin:0}" +
          ".trix-content ol.footnotes{display:none}" +
        "}";
      document.head.appendChild(style);

      layout();
    });
  })();
</script>

Custom <body> code Pro

Add your own markup just before the closing </body> tag on every page of your public site. This is a good place for chat widgets, scripts, and other tools that should load at the end of the page.

Examples

Below are some examples of code snippets you might add to your custom <body> code. Have a script to share? Let us know.

Show a reading progress bar

This adds a thin bar across the top of the window that fills as you scroll through a post, showing how far along you are.

<script>
  (function () {
    "use strict";

    if (!document.body.classList.contains("site-post-body")) return;

    const article = document.querySelector(".site-post");
    if (!article) return;

    const bar = document.createElement("div");
    bar.className = "reading-progress";
    document.body.appendChild(bar);

    const style = document.createElement("style");
    style.textContent =
      ".reading-progress{position:fixed;top:0;left:0;width:100%;height:5px;" +
      "background:var(--color-primary);transform:scaleX(0);transform-origin:left;" +
      "will-change:transform;z-index:10}";
    document.head.appendChild(style);

    // Progress = how far the article has scrolled past the top of the window,
    // from its top reaching the top to its bottom reaching the bottom.
    function update() {
      const rect = article.getBoundingClientRect();
      const scrollable = rect.height - window.innerHeight;
      const progress = scrollable > 0 ? -rect.top / scrollable : 0;
      bar.style.transform = "scaleX(" + Math.min(1, Math.max(0, progress)) + ")";
    }

    // Throttle scroll work to one update per animation frame.
    let ticking = false;
    function onScroll() {
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(function () { update(); ticking = false; });
    }

    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", update);
    update();
  })();
</script>