Multi-filter Rec Templates

About

These are templates for rec pages on an independent website where you can create .html and .css files (at minimum), applying filters with Javascript.

The templates have been pre-formatted for media and fanfiction recommendations in mind, but you can use them for whatever you want.

Both templates include the following features:

  • Three (3) category filters.
  • Any (inclusive)/All (exclusive) options for users to choose how each category filter works.
  • Clear Filters to reset all selected filters. This will also toggle all filter modes back to "Any".
  • Results will automatically update based on the selected tags and filter mode, and expand/shrink per number of results.
  • Warnings show on click, but not by default.
  • A formatted field for personal thoughts on each rec.
  • Responsive styling for tablets and mobile devices.

Primary differences are additions made to the fanfiction template, which are:

  • The Medium category has been changed to a Fandom category.
  • Additional metadata for: Link, Author, Word Count, and Ships
  • Rec titles use <h3> instead of <h2> (for media recs), without a border bottom.
  • Rec titles include a link to the work, opening in a new tab.

Instructions for use and customization are within the code of each webpage template, in addition to the documentation below.

Installation
  1. Go to the template for media recs or fanfiction recs, depending on which template you would like to use. You may use both.
  2. On the template webpage, right click and select View Page Source.
  3. Copy the entire page/file of what comes up. Save it in a .html file. This may be a local file or a file on your Neocities/Nekoweb site. Name it whatever you want.
  4. Open the .html file in a text editor (or your host's in-browser text editor), and modify it to your heart's content with your own text.
  5. Manually add and order labels for your rec filters in each category, under <div class="filters">. See Basic Customizations for more detail.
    <label><input type="checkbox" name="category" value="Tag"> Tag (display text)</label>
  6. I advise you to not remove or modify the rating tags, as they are pretty standard. For media recs, you may also only want to do minimal edits to medium depending on what you are recommending.
  7. In the code, find and follow /** Below are my recs. Delete them and replace with your own. (You can delete this line as well) **/
  8. See Basic Customizations below to add your own recs. Once you've added them, you're mostly done with the .html file. (Some exceptions may apply.)
  9. Now, go to the rec templates' style.css and copy/paste everything into your own style.css file. Save it in the same folder as your .html file.
    • Note: Both the media and the fanfiction templates use the same style.css, so you only need one if you are using both templates and want them to feature the same colors/styling.
    • If you would like them to be different, see Advanced Customizations.
  10. In style.css, modify the variables under :root{} to reflect colors, fonts, and other styles you prefer. This is not necessary if you would like your page to be styled like the preview.

You can also download the files from Github.

<spoiler> is supported as well, and will black out text when used like this (highlight to view).

Basic Customizations

This will cover:

Customizing Filter Tags (step 5)

This is for customizing the filterable tags inside <div class="filters">

The standard format for each tag label is as such:

<label><input type="checkbox" name="category" value="Tag"> Tag (display text)</label>
  • Capitalization matters for all text.
  • name="" is the name of the filter category, such as fandom, medium, trope, and rating. By default, they are in lowercase. I suggest keeping them that way for code readability.
  • Keep value="" and the display text identical or similar for ease of use.
  • Make sure there is a space between the closing angle bracket > and the display text:
    <input ... value="Tag"> Tag (display text)

For example:

<label><input type="checkbox" name="fandom" value="Brooklyn 99"> Brooklyn 99</label>
<label><input type="checkbox" name="trope" value="Fake Dating"> Fake Dating</label>

However, for linguistic purposes you may want the display text to be pluralized, such as "TV Shows" instead of "TV Show".

<label><input type="checkbox" name="medium" value="TV Show"> TV Shows</label>

What is important here is the value of TV Show. When listing a rec for this filter, the tag must be identical to the value, not the display text ("TV Shows"). The label display text is what is seen for visitor readability, but does not affect the filters or rec metadata in any way.

{
title: "White Collar",
mediums: ["TV Show"],
...

Recs (steps 7/8)

Recs are added as cards through Javascript. The code for the metadata is in the following formats:

Media:

{
title: "Title",
mediums: ["Medium"],
tropes: ["Trope 1", "Trope 2"],
ratings: ["Rating"],
warnings: "Warnings here.",
summary: "<p>Summary here. If your summary contains double quotes, place a backslash \ before it, like \"this\".</p>",
thoughts: "Write your thoughts about the rec here." 
},

Fanfiction:

{
title: "Work Title",
link: "https://work.link",
author: "Author",
wordCount: "Word count, any format",
fandoms: ["Fandom Name"],
ships: "Ship, any format",
tropes: ["Trope 1", "Trope 2"],
ratings: ["Rating"],
warnings: "",
summary: "<p>Summary here. If your summary contains double quotes, place a backslash \ before it, like \"this\".</p>",
thoughts: "Write your thoughts about the rec here." 
},

Keep in mind:

  • The title, link, author, wordCount, ships, warnings, summary, and thoughts fields must be in double quotes "". These fields support basic HTML, like <p>, <em>, <a> and <strong>.
  • For HTML tags that may contain quotes, such as <a href="...">, use single or no quotes at all and the HTML will still work, e.g. <a href=https://archiveofourown.org>link</a> or <a href='https://archiveofourown.org'>link</a>.
  • Fanfiction rec titles are automatically linked to the URL provided in the link field, so do not add any additional <a> tags in the title or link field. The link must be in double quotes "" here as well.
  • Fields for each filterable category (medium, fandoms, tropes, ratings) must match the exact value of the corresponding filter tag, including spaces, punctuation, and capitalization.
  • The exact field value for each filterable category of an item will display, which is why it is easier to make the value and the display text for the tag identical in the filter navigation.
  • Fields for each filterable category (see above) must be in double quotes "", with the entire field in brackets [].
  • Each field must end with a comma , outside of all brackets and quotes.
  • If there are no warnings, leave the field empty "" and "None" will be displayed automatically.
  • thoughts is optional and can be omitted if not needed. You may also keep the field but leave it empty "". It will not display either way.
  • The entire rec must be inside curly brackets {}, and end with a comma , outside of the closing curly bracket.

The list of recs stops with the in-line comment /** END OF RECS **/.

Unless you are doing Advanced Customizations, do NOT delete or modify anything below this comment.

Advanced Customizations

This will cover:

For best results, pay attention to the text colors across the examples below.

Customizing Filter Categories

The filter navigation section begins with <div id="filters"> and ends with the </div> right before <div id="results">.

Each filter category box is contained in code that looks like the following:

<fieldset>
        <legend>Medium</legend>
        <div class="filter-header">
            <span class="legend">Medium</span>
          ...
        </div>
        <div class="filter-options">
          <label><input type="checkbox" name="medium" value="Animanga"> Anime/Manga</label>
          <label><input type="checkbox" name="medium" value="Book"> Books</label>
          <label><input type="checkbox" name="medium" value="Comics"> Comics</label>
          <label><input type="checkbox" name="medium" value="Movie"> Movies</label>
          <label><input type="checkbox" name="medium" value="TV"> TV Shows</label>
          <label><input type="checkbox" name="medium" value="Video Games"> Video Games</label>
        </div>
</fieldset>

The above code is the example for the Medium category, on the media recs template.

If there is a category you do not need, you may delete everything within (and including) the <fieldset> for the corresponding category. The remaining categories will expand to fill out the space. However, I heavily advise having at minimum two categories and no more than four, for best results.

The default template filter categories are Medium (for media), Fandom (for fanfiction), Tropes/Genres (labeled as "tropes"), and Ratings.

You may choose to change these categories based on personal use cases for your recs and organization. For example, if you are recommending fic for only one fandom, you may want to remove the Fandom filter category, and/or change it into a different category, such as Ships.

In order to do this, you will need to add or change:

  • The legend display text (<legend> does not display, but is for accessibility purposes and best kept consistent so you don't get confused):
    <legend>Ships</legend>
    
            <div class="filter-header">
              <span class="legend">Ships</span>
  • The name attribute of each filter label input, as well as their corresponding value and display text:
    <label><input type="checkbox" name="ship" value="Oliver/Barry"> Oliver/Barry</label>
    <label><input type="checkbox" name="ship" value="Cisco/Hartley"> Cisco/Hartley</label>
  • The name of the filter-mode, if the category is to have any/all filter options:
    <label><input type="radio" name="shipMode" value="inclusive" checked> Any</label>
    This is the example for only the "Any" option - you will need to make the same changes for the "All" option as well.

The result may look something like this:

<fieldset>
        <legend>Ships</legend>
        <div class="filter-header">
            <span class="legend">Ships</span>
            <span class="filter-mode">
                <label><input type="radio" name="shipMode" value="inclusive" checked> Any</label>
                <label><input type="radio" name="shipMode" value="exclusive"> All</label>
            </span>
        </div>
        <div class="filter-options">
          <label><input type="checkbox" name="ship" value="Oliver/Barry"> Oliver/Barry</label>
          <label><input type="checkbox" name="ship" value="Cisco/Hartley"> Cisco/Hartley</label>
        </div>
</fieldset>

Additionally, in each rec card, the new or changed field will need to be formatted as a filterable tag with brackets [], with each item in double quotes "" and separated by commas ,:

{...
ships: ["Oliver/Barry", "Cisco/Hartley"],
...}
                    

You will also need to make the following modifications in the Javascript:

  1. After </main>, find this line. You will be working within/beneath it:
  2. <script>
  3. Locate const filters = {. Beneath it, in the curly brackets {}, add (or edit the category you're replacing) the following line:
    ship: new Set(),
  4. If the new/changed field is to have any/all filter options:
    1. Locate modes: {. Within the curly bracket {}, add or edit to:
      ship:"inclusive",
    2. Down a few lines, locate resetBtn.addEventListener("click", () => {. Within the curly bracket {}, add or edit to:
      filters.ship.clear();
    3. In the same curly brackets, add or edit to:
      filters.modes.ship = "inclusive";
    4. Further down, locate:
      function applyFilters() {
            const filteredItems = items.filter(item =>
      After item =>, add or edit to:
      matchesFilter(filters.ship, item.ships, filters.modes.ship) &&
      If this code is not at the end of this list, make sure && is at the end of the line. If it is at the end of this list, then leave it off.

With Ships added as a filter (and none removed), your code should now look something like this:

const filters = {
    fandom: new Set(),
    trope: new Set(),
    rating: new Set(),
    ship: new Set(),
      modes: {
        fandom: "inclusive",
        trope: "inclusive",
        rating: "inclusive",
        ship: "inclusive"
      }
    };

    const resetBtn = document.getElementById("resetFilters");

    resetBtn.addEventListener("click", () => {
      // 1. Clear JS filter state
      filters.fandom.clear();
      filters.trope.clear();
      filters.rating.clear();
      filters.ship.clear();

      filters.modes.fandom = "inclusive";
      filters.modes.trope = "inclusive";
      filters.modes.rating = "inclusive";
      filters.modes.ship = "inclusive";
      
      ...
      ...
      ...
    });

    function applyFilters() {
      const filteredItems = items.filter(item =>
        matchesFilter(filters.fandom, item.fandoms, filters.modes.fandom) &&
        matchesFilter(filters.trope, item.tropes, filters.modes.trope) &&
        matchesFilter(filters.rating, item.ratings, filters.modes.rating) &&
        matchesFilter(filters.ship, item.ships, filters.modes.ship)
      );

      ...

item.ships (above) corresponds to the label within the category in the rec card:

{...
ships: ["Oliver/Barry", "Cisco/Hartley"],
...}

The singular ship corresponds to the category filter, while the plural ships corresponds to the labels within the rec data.

The rec formatting instructions below show how they are linked.

Customizing Rec Formatting

Within the Javascript, rec metadata can be listed in any order. This will not affect the frontend display.

If you would like to change the frontend display formatting, order, or text, find card.innerHTML = ` at the bottom of the Javascript code. Reorganize or change the HTML to your liking.

card.innerHTML = `
    <h3><a href="${item.link}" target="_blank">${item.title}</a></h3>
    <div class="meta"><strong>Author:</strong> ${item.author}
    <br /><strong>Link:</strong> <a href="${item.link}" target="_blank">${item.link}</a>
    <br /><strong>Events:</strong> ${item.events || "None"}
    <br /><strong>Fandom:</strong> ${item.fandoms.join(", ")}
    <br /><strong>Ships:</strong> ${item.ships.join(", ")}
    <br /><strong>Rating:</strong> ${item.ratings.join(", ")}
    <br /><strong>Word Count:</strong> ${item.wordCount || "N/A"}
    <br /><details><summary>Warnings</summary> ${item.warnings || "None"}</details>
    </div>
    <div class="summary"><strong>Summary:</strong> ${item.summary}</div>
    ${item.thoughts ? `<div class="thoughts"><strong>My thoughts and feelings:</strong> ${item.thoughts}</div>` : ''}
`;

Above is an example of a modified (not the default) rec format that:

  • Displays the link as a separate field in addition to being linked in the title.
  • There is a new non-filterable field for Events.
  • Filters by ships.
  • No longer lists tropes.
  • Empty word count has been changed to return N/A.
  • Word count is now placed right above the warnings.
  • Summary is now prepended with a label of Summary:
  • The thoughts label has been changed to now display My thoughts and feelings:.

Other things to keep in mind:

  • If a new filter category was changed or added, the display for the field must be ${item.category.join(", ")}, such as the Ships field above.
  • If you have added a new field but not as a filter category, the display for the field must be ${item.category}, such as ${item.events} or ${item.author} field above.
  • If display text for an empty field is not defined, it will display as undefined when empty, unless a default value is provided using the || operator, such as ${item.events || "None"}.
  • In the Javascript code for each rec metadata, technically no field is required. However, deleting or leaving certain fields empty may output unexpected results if the HTML has not also been modified in some way as well.

Here is a modified rec metadata example, as well as its sample output using the modified HTML format above:

{
  title: "Throw All Your Walls (Into the Fire)",
  link: "https://archiveofourown.org/works/10680495",
  author: "TornThorn",
  events: "Event Name",
  wordCount: "2546",
  fandoms: ["The Flash", "Arrow (TV)"],
  ships: ["Oliver/Barry"],
  ratings: ["General"],
  warnings: "",
  summary: "Taking a sip of the new beer the bartender handed him, Barry decided he would finish the bottle, try to find Thea and give her a quick hug, then head home for the night.",
}

Throw All Your Walls (Into the Fire)

Author: TornThorn
Link: https://archiveofourown.org/works/10680495
Events: Event Name
Fandom: The Flash, Arrow (TV)
Ships: Oliver/Barry
Rating: General
Word Count: 2546
Warnings None
Summary: Taking a sip of the new beer the bartender handed him, Barry decided he would finish the bottle, try to find Thea and give her a quick hug, then head home for the night.

Note that events in the rec metadata code matches ${item.events || "None"} in the earlier modified HTML.

If it was a filterable field, it would match a corresponding

matchesFilter(filters.event, item.events, filters.modes.event)
where the label for the event within the filters would look like:

<label><input type="checkbox" name="event" value="Event Name"> Event Name</label>

However, non-filtered fields such as title or author only have to be defined in:

  • The HTML template for displaying the recs
    card.innerHTML = `
    <h3><a href="${item.link}" target="_blank">${item.title}</a></h3>
    <div class="meta"><strong>Author:</strong> ${item.author}
    ...`
  • The metadata for the rec card
    { 
    title: "Throw All Your Walls (Into the Fire)", 
    link: "https://archiveofourown.org/works/10680495",
    author: "TornThorn",
    ...},

Remove or allow category filter modes (any/all logic options)

In each <fieldset> <div class="filter-header">, the HTML code to allow "Any"/"All" options for users looks like this:

    <fieldset>
        <legend>Category</legend>

        <div class="filter-header">
          <span class="legend">Category</span>
          <span class="filter-mode">
            <label><input type="radio" name="categoryMode" value="inclusive" checked> Any</label>
            <label><input type="radio" name="categoryMode" value="exclusive"> All</label>
          </span>
        </div>
        <div class="filter-options">
            <label><input type="checkbox" name="category" value="Tag"> Tag</label>
            ...
        </div>
    </fieldset>

The entire <span class="filter-mode"> must be inside <div class="filter-header"> and after <span class="legend"> </span> to display properly.

To remove it, simply delete <span class="filter-mode"> to the end of its </span>. This is everything in green bold text above.

If you are adding the option to a new or custom category:

  1. Complete all of the steps for the corresponding category following the "Customizing Filter Categories" section above.
  2. Then, inside <span class="filter-mode"></span>, change categoryMode to the name of your new or custom category.
  3. The resulting code will look something like this:
  4. <fieldset>
            <legend>Ships</legend>
    
            <div class="filter-header">
              <span class="legend">Ships</span>
              <span class="filter-mode">
                <label><input type="radio" name="shipMode" value="inclusive" checked> Any</label>
                <label><input type="radio" name="shipMode" value="exclusive"> All</label>
              </span>
            </div>
            <div class="filter-options">
                <label><input type="checkbox" name="ship" value="Oliver/Barry"> Oliver/Barry</label>
                ...
            </div>
        </fieldset>

Note that Ratings have <span class="filter-mode"> commented out in order not to display the any/all option, due to the nature of ratings. However, if you want to allow this option, making filter-mode visible will still functionally work.

If you choose to replace Ratings with another category instead, make sure that all rating(s) under <script> are updated for your replacement category as well.

Using both templates with different styling

There are many ways you could use both templates with different styling, so I will not cover them all in detail here.

Some options are:

  • Use different folders for each rec page. This will require you to download style.css twice. You can then put a style.css in each respective folder, and the webpage will call for the style.css within its respective folder.
  • Name each .css file something different. This will require you to download style.css twice. You will also have to modify <link rel="stylesheet" href="style.css" /> at the top of each rec page, based on what you named the CSS file:
    <link rel="stylesheet" href="fic-rec-style.css" />
  • Download style.css once, but add root definitions in the .html file of each rec page. You may not need to touch the style.css if you use this solution.

    In order to do this:

    1. In the .html file of each rec page, right below <link rel="stylesheet" href="style.css" />, add:
      <style type="text/css">
      </style>
    2. This must be called after <link rel="stylesheet" href="style.css" /> or else it won't work.
    3. Within <style type="text/css"> </style>, copy and paste all of the root definitions from your style.css file (everything under :root{}), though you can omit the calculations.
    4. Customize the colors, fonts, sizes, etc. for the rec page in this block.
    5. Repeat steps 1-4 for each individual rec page.
    6. Any styling rules not defined on the webpage will inherit the values from style.css, so if there are styles you want to keep across all rec pages, you can omit the lines from the webpage <style> block.
    7. Define shared rules in style.css if they need to be changed from the default.
      • For example, if you want to keep the default white body background color, you can delete --body-background: #fff; from the <style> block, since it is already defined in style.css.
    ...
                        
    <link rel="stylesheet" href="style.css" />
      
    <style type="text/css">
        :root {    
        --body-background: #282828;
        --main-background: #000000; 
        --card-background: #2b0300; 
        --legend-background: var(--card-background); 
        --thoughts-background: #000000; 
        --text: #ffffff;
        --meta-color: #e5e5e5; 
        --link-hover: var(--text); 
        --accent: #b81616; 
        --accent-dark: #974444; 
        --accent-light: #fae9e9; 
        --border-color: var(--accent); 
        --highlight-color: #ecaeae; 
        
        --main-font: 'Georgia';
        --h1-font: 'Georgia';
        --legend-font: monospace;
        --button-font: 'Georgia'; 
        --rec-header-font: monospace; 
        
        --main-border-style: dotted;
        --link-border-style: wavy;
        
        --line-height: 160%;
        --main-width: 80em; 
        --card-width: 15em; 
        --border-size: 1px;
        --h1-size: 2.2em;
        --rec-header-size: 1.5em;
        --legend-font-size: 1.2em;
        --button-text-size: 0.8em; 
        --max-legend-size: 25em; 
        --label-size: 0.8em;
      }
    </style>
    
    ...

Below is a preview of the customized styles defined above applied to the fanfiction rec template:

Preview of customized fanfiction rec template

Further Notes

All webpage templates and the stylesheet, as well as the previews, are available on Github.

You might be interested in the masterlist template I made. The masterlist template only allows one filter at a time instead of multiple.

If you need support for this code, please feel free to contact me.

This site is part of magpies, my index of fannish webpage resources.