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 > 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.

What classes can I use when writing custom CSS?

While we are careful not to change the skeleton of our markup, it is possible that it will evolve in the future. If you see a class that begins with the prefix .site-, you can be confident that we will be taking special care to avoid changing that class (and we’ll be sure to let you know if we do change it).

Here is a non-exhaustive list of classes that we are currently providing:

Class Name Element Description
.site-header header Site header
.site-nav nav Site nav
.site-nav-link a Every link in the site nav
.site-nav-link--active a The navigation link if it is the page currently being viewed
.site-meta footer Any meta footer, such as at the end of a post or a list of posts
.site-blog-index body The blog page at /posts
.site-post-body body A post
.site-post-[id] body A specific post, with [id] being a unique number representing the post
.site-post article The post content container
.site-post-tag-[id] article A post carrying a specific tag, with [id] being a unique number representing the tag
.site-home-page body Home page
.site-page-body body A page
.site-page-[id] body A specific page, with [id] being a unique number representing the page
.site-page div The page content container
.site-tag body Tag listing page
.site-tag-[id] body A specific tag, with [id] being a unique number representing the tag
.site-guestbook body Guestbook page
.site-new-guestbook-entry body Guestbook signing page

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>