<ol class="container">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
  <li>9</li>
</ol>

<div class="controls">
  <div class="formula">
    <code>
      <pre><span id="factor-limit">3</span> * <span id="--si2">0</span> + <span id="--si1">0</span></pre>
    </code>

    <code class="result">
      <span id="current">0</span>
      <span id="max">(8 max)</span>
    </code>
  </div>

  <form class="sliders">
    <label for="elements">
      Elements: <span class="value">9</span>
    </label>
    <input type="range" id="elements" min="2" max="49" value="9">

    <label for="factor">
      Factor: <span class="value">3</span>
    </label>
    <input type="range" id="factor" min="2" max="7" value="3">
  </form>

  <main class="snippets">

    <div class="sibling-index" id="snippets-si1">
      <h2>--si1:</h2>
      <code>
        <pre data-sibling-index="1" data-value="1">li:nth-child(3n + 1){ --si1: 1;}</pre>
      </code>
      <code>
        <pre data-sibling-index="1" data-value="2">li:nth-child(3n + 2){ --si1: 2;}</pre>
      </code>
    </div>

    <div class="sibling-index" id="snippets-si2">
      <h2>--si2:</h2>

      <code>
        <pre data-sibling-index="2" data-value="1">li:nth-child(n + 3 ):nth-child(-n + 5){--si2: 1;}</pre>
      </code>
      <code>
        <pre data-sibling-index="2" data-value="2">li:nth-child(n + 6 ):nth-child(-n + 8){--si2: 2;}</pre>
      </code>
    </div>

  </main>
</div>
/* Aesthetic Styles */

:root {
  --text-color: #eae2b7;
  --spacing: 25px;
  color-scheme: dark;
}

* {
  padding: 0px;
  margin: 0px;
  box-sizing: border-box;
}

html {
  font-family: monospace;
  font-size: clamp(10px, 1vw, 16px);
}

body {
  display: flex;
  justify-content: space-around;

  min-height: 100vh;

  background: repeating-linear-gradient(
    -45deg,
    #111,
    #111 30px,
    #222 30px,
    #222 60px
  );
}

.container {
  flex: 1 1;

  display: flex;
  flex-flow: row wrap;
  align-content: flex-start;
  justify-content: flex-start;
  gap: 15px;

  padding: var(--spacing);
  container-type: inline-size;

  li {
    display: grid;
    place-items: center;

    border-radius: 15px;
    padding: 10px;

    width: 60px;
    aspect-ratio: 1;

    list-style: none;

    font-size: 1.5rem;
    font-weight: 600;
    color: var(--text-color);
    background-color: black;

    transition: all 100ms ease-in;
  }

  &:has(> :nth-child(30)) li {
    width: 50px;
  }

  .selected {
    transform: scale(110%);
    background-color: orange;
  }

  .active {
    background-color: purple;
  }
}

.controls {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 3fr;
  grid-template-areas:
    "formula sliders"
    "snippets snippets";
  gap: var(--spacing);

  height: 100vh;
  scrollbar-gutter: stable both-edges;

  padding: var(--spacing);
}

.snippets {
  grid-area: snippets;

  display: flex;
  flex-flow: column;

  gap: var(--spacing);

  padding: var(--spacing);
  border-radius: 20px;

  overflow-y: auto;
  scrollbar-gutter: stable both-edges;

  background-color: #111;
  color: var(--text-color);

  box-shadow: 4px 4px 4px 4px #000;

  .sibling-index {
    display: flex;
    flex-flow: column;
    gap: var(--spacing);
  }

  pre {
    margin: 0px 10px;
    padding: 10px 40px 10px 20px;
    border-radius: 10px;

    width: 90%;

    background-color: #171717;
    transition: all 100ms ease-in;
    cursor: pointer;

    &:hover {
      transform: scale(105%);
    }
  }

  .active {
    background-color: #000;
  }
}

.formula {
  grid-area: formula;

  display: flex;
  flex-flow: column;
  align-items: center;
  justify-content: center;
  gap: 20px;

  padding: var(--spacing);
  border-radius: 20px;

  background-color: #111;
  color: var(--text-color);
  font-size: 1rem;

  box-shadow: 4px 4px 4px 4px #000;

  .result {
    display: flex;
    flex-flow: column;
    align-items: center;
  }

  #max {
    color: #999;
  }

  #current {
    font-size: 4rem;
  }
}

.sliders {
  grid-area: sliders;

  display: flex;
  flex-flow: column;
  align-items: center;
  justify-content: center;
  gap: 10px;

  padding: var(--spacing);
  border-radius: 20px;

  background-color: #111;
  color: var(--text-color);
  font-size: 1rem;

  box-shadow: 4px 4px 4px 4px #000;

  input {
    width: 100%;
  }

  .value {
    display: inline-block;
    width: 2rem;
  }
}

@media (width < 800px) {
  body {
    flex-flow: column;
    justify-content: space-between;
  }

  .container li {
    width: 5rem;
  }

  .controls {
    grid-template-rows: 150px 160px;
    height: auto;
  }

  .snippets {
    justify-content: space-between;
    width: 100%;
    overflow: hidden;

    .sibling-index {
      align-items: center;
      flex-flow: row;
      gap: 20px;
    }

    pre {
      visibility: hidden;
      width: 0px;
      height: 3rem;
      padding: 0px;
    }

    pre::before {
      content: attr(data-value);

      display: grid;
      place-items: center;

      visibility: visible;
      overflow: visible;

      width: 3rem;
      height: 3rem;
      border-radius: 10px;

      font-size: 1.3rem;

      background-color: #171717;
      transition: all 100ms ease-in;
      cursor: pointer;

      &:hover {
        transform: scale(105%);
      }
    }

    .active::before {
      background-color: #000;
    }
  }
}
/* Selecting Elements */

const selectElements = (factorLimit) => {
  let allElements = [];

  for (let i = 1; i < factorLimit; i++) {
    elementsSiblingIndex1 = document.querySelectorAll(
      `li:nth-child(${factorLimit}n + ${i})`
    );

    allElements.push(elementsSiblingIndex1);
  }

  for (let i = 1; i < factorLimit; i++) {
    elementsSiblingIndex2 = document.querySelectorAll(
      `li:nth-child(n + ${factorLimit * i}):nth-child(-n + ${
        factorLimit * (i + 1) - 1
      })`
    );

    allElements.push(elementsSiblingIndex2);
  }

  return allElements;
};

const toggleSelectedElements = (elements) => {
  elements.forEach((element) => {
    element.classList.toggle("selected");
  });
};

const removeAllActive = (elements) => {
  elements.forEach((element) => {
    element.classList.remove("active");
  });
};

const changeActive = (elements, element) => {
  if (!element) {
    //If element is out of reach, simply remove all active
    removeAllActive(elements);
    return;
  }

  if (!element.classList.contains("active")) {
    removeAllActive(elements);
  }

  element.classList.toggle("active");
};

const factorLimitVar = document.querySelector("#factor-limit");
const siblingIndex1Var = document.querySelector("#--si1");
const siblingIndex2Var = document.querySelector("#--si2");
const currentIndex = document.querySelector("#current");

const calculateSiblingIndex = () => {
  const calculationResult =
    parseInt(factorLimitVar.innerText) * parseInt(siblingIndex2Var.innerText) +
    parseInt(siblingIndex1Var.innerText);

  const elements = document.querySelectorAll(".container li");

  if (calculationResult === 0) {
    removeAllActive(elements);
    currentIndex.innerText = 0;
    return;
  }

  changeActive(elements, elements[calculationResult - 1]);

  currentIndex.innerText = calculationResult;
};

const setClickListeners = () => {
  const allSnippetsSiblingIndex1 = document.querySelectorAll(
    "#snippets-si1 pre"
  );
  const allSnippetsSiblingIndex2 = document.querySelectorAll(
    "#snippets-si2 pre"
  );
  const allSnippets = document.querySelectorAll(".sibling-index pre");

  for (let i = 0; i < allSnippets.length / 2; i++) {
    allSnippetsSiblingIndex1[i].addEventListener("click", (event) => {
      let snippetSiblingIndexValue = parseInt(event.target.attributes[1].value);

      siblingIndex1Var.innerText = allSnippetsSiblingIndex1[
        i
      ].classList.contains("active")
        ? 0
        : snippetSiblingIndexValue;

      changeActive(allSnippetsSiblingIndex1, allSnippetsSiblingIndex1[i]);
      calculateSiblingIndex();
    });

    allSnippetsSiblingIndex2[i].addEventListener("click", (event) => {
      let snippetSiblingIndexValue = parseInt(event.target.attributes[1].value);
      siblingIndex2Var.innerText = allSnippetsSiblingIndex2[
        i
      ].classList.contains("active")
        ? 0
        : snippetSiblingIndexValue;

      changeActive(allSnippetsSiblingIndex2, allSnippetsSiblingIndex2[i]);
      calculateSiblingIndex();
    });
  }
};

const setListenerForSnippets = () => {
  const factorLimit = parseInt(factorLimitVar.innerText);

  let allSnippets = document.querySelectorAll(".sibling-index pre");
  let allElements = selectElements(factorLimit);

  for (let i = 0; i < allSnippets.length; i++) {
    allSnippets[i].addEventListener("mouseover", (event) => {
      toggleSelectedElements(allElements[i]);
    });

    allSnippets[i].addEventListener("mouseout", (event) => {
      toggleSelectedElements(allElements[i]);
    });
  }
};

setClickListeners();
setListenerForSnippets();

/* Adding and Removing Elements */

const elements = document.querySelector(".container");
const elementsInput = document.querySelector("#elements");
const elementsLabelValue = document.querySelector(
  "label[for='elements'] .value"
);

const createLiElement = (index) => {
  const newElement = document.createElement("li");
  newElement.innerText = index;

  return newElement;
};

elementsInput.addEventListener("input", (event) => {
  const targetElementsNumber = parseInt(event.target.value);

  elementsLabelValue.innerHTML = targetElementsNumber;
  elements.innerHTML = "";

  for (let i = 1; i <= targetElementsNumber; i++) {
    const newElement = createLiElement(i);
    elements.appendChild(newElement);
  }

  setListenerForSnippets();
  calculateSiblingIndex();
});

/* Changing Factor and Snippets */

const createSnippetElement = (siblingIndex, value) => {
  const newCodeElement = document.createElement("code");
  const newPreElement = document.createElement("pre");

  newPreElement.setAttribute("data-sibling-index", siblingIndex);
  newPreElement.setAttribute("data-value", value);

  const factorLimit = parseInt(factorLimitVar.innerText);

  if (siblingIndex === 1) {
    newPreElement.innerText = `li:nth-child(${factorLimit}n + ${value}){--si1: ${value};}`;
  }

  if (siblingIndex === 2) {
    newPreElement.innerText = `li:nth-child(n + ${
      factorLimit * value
    }):nth-child(-n + ${factorLimit * (value + 1) - 1}){--si2: ${value};}`;
  }

  newCodeElement.appendChild(newPreElement);

  return newCodeElement;
};

const snippetsSiblingIndex1 = document.querySelector("#snippets-si1");
const snippetsSiblingIndex2 = document.querySelector("#snippets-si2");

const factorInput = document.querySelector("#factor");
const factorLabelValue = document.querySelector("label[for='factor'] .value");

const currentMax = document.querySelector("#max");

factorInput.addEventListener("input", (event) => {
  const targetFactor = parseInt(event.target.value);
  factorLabelValue.innerText = targetFactor;
  factorLimitVar.innerText = targetFactor;

  snippetsSiblingIndex1.innerHTML = "<h2>--si1:</h2>";
  snippetsSiblingIndex2.innerHTML = "<h2>--si2:</h2>";

  const maxIndexPossible = targetFactor ** 2 - 1;
  currentMax.innerText = `(${maxIndexPossible} max)`;

  for (let i = 1; i < targetFactor; i++) {
    const newSnippetSiblingIndex1 = createSnippetElement(1, i);

    const newSnippetSiblingIndex2 = createSnippetElement(2, i);

    snippetsSiblingIndex1.appendChild(newSnippetSiblingIndex1);
    snippetsSiblingIndex2.appendChild(newSnippetSiblingIndex2);
  }

  setClickListeners();
  setListenerForSnippets();
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.