aboutsummaryrefslogtreecommitdiff
path: root/build/posts/dynamic-checkboxes
diff options
context:
space:
mode:
authorBradley Taunt <bt@btxx.org>2024-06-10 09:41:25 -0400
committerBradley Taunt <bt@btxx.org>2024-06-10 09:41:25 -0400
commit07e4a2dafe248280b5610f8c7d09b0f30b530f54 (patch)
tree8a145d1d4d07e1278a837ff15dadccc322d27515 /build/posts/dynamic-checkboxes
parent16d28628aca9b2d356de31c319f5e7bc0f5b2b02 (diff)
Initial modifications to rebuilt only changed files based on mod date, performance updates
Diffstat (limited to 'build/posts/dynamic-checkboxes')
-rw-r--r--build/posts/dynamic-checkboxes/index.html412
1 files changed, 412 insertions, 0 deletions
diff --git a/build/posts/dynamic-checkboxes/index.html b/build/posts/dynamic-checkboxes/index.html
new file mode 100644
index 0000000..218d783
--- /dev/null
+++ b/build/posts/dynamic-checkboxes/index.html
@@ -0,0 +1,412 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="color-scheme" content="dark light">
+ <link rel="icon" href="data:,">
+ <title>Dynamic Checkboxes</title>
+ <link href="/atom.xml" type="application/atom+xml" rel="alternate" title="Atom feed for blog posts" />
+ <link href="/rss.xml" type="application/rss+xml" rel="alternate" title="RSS feed for blog posts" />
+<style>*{box-sizing:border-box;}body{font-family:sans-serif;line-height:1.33;margin:0 auto;max-width:650px;padding:1rem;}blockquote{background:rgba(0,0,0,0.1);border-left:4px solid;padding-left:5px;}img{max-width:100%;}pre{border:1px solid;overflow:auto;padding:5px;}table{text-align:left;width:100%;}.footnotes{font-size:90%;}</style>
+</head>
+
+<nav>
+ <a href="#menu">Menu &darr;</a>
+</nav>
+
+<main>
+<h1 id="dynamic-checkboxes">Dynamic Checkboxes</h1>
+<p>2019-07-30</p>
+<p>Checkboxes are used quite frequently on forms across the web. Whether you&#8217;re selecting a pricing plan during a site&#8217;s sign-up process or just simply selecting to opt-out from a newsletter, you have most likely interacted with some form of checkbox element.</p>
+<p>What if we could make everyday checkboxes more beautiful <em>and</em> more intuitive? <em>It&#8217;s easier than you think</em>. We only need a small amount of CSS and JavaScript to make considerable improvements to your average checkbox UX.</p>
+<p>Let&#8217;s get into it.</p>
+<h2 id="what-we-are-building">What we are building</h2>
+<p>Take a look and play around with the CodePen below to get an idea of what we are going to build. The premise is a simple add-on pricing form which calculates the additional monthly total to the user in real-time.</p>
+<p><a href="https://codepen.io/bradleytaunt/pen/rXWEpy/">Live CodePen Example</a></p>
+<h2 id="the-structure-html">The Structure (HTML)</h2>
+<p>As always, we will start by breaking down the &#8220;bones&#8221; of the HTML structure for this checkbox form. Let&#8217;s take a look at the HTML in it&#8217;s entirety (don&#8217;t worry, it is a lot more simple than it seems at first glance):</p>
+<pre><code>&#60;h2&#62;Add-ons&#60;&#47;h2&#62;
+
+&#60;input class="checkbox-btn" name="checkbox-collection" id="checkbox-1" type="checkbox" value="49"&#62;
+&#60;label class="checkbox-label" for="checkbox-1"&#62;
+ &#60;span&#62;
+ White-labeled Domain
+ &#60;em&#62;Use your own custom domain with SSL security included.&#60;&#47;em&#62;
+ &#60;&#47;span&#62;
+ &#60;span&#62;$49&#47;mo&#60;&#47;span&#62;
+&#60;&#47;label&#62;
+
+&#60;input class="checkbox-btn" name="checkbox-collection" id="checkbox-2" type="checkbox" value="49"&#62;
+&#60;label class="checkbox-label" for="checkbox-2"&#62;
+ &#60;span&#62;
+ API Access
+ &#60;em&#62;Make API calls to perform custom serving and account actions.&#60;&#47;em&#62;
+ &#60;&#47;span&#62;
+ &#60;span&#62;$49&#47;mo&#60;&#47;span&#62;
+&#60;&#47;label&#62;
+
+&#60;input class="checkbox-btn" name="checkbox-collection" id="checkbox-3" type="checkbox" value="349"&#62;
+&#60;label class="checkbox-label" for="checkbox-3"&#62;
+ &#60;span&#62;
+ Priority Support
+ &#60;em&#62;A dedicated account manager to assist your team with ongoing deployments.&#60;&#47;em&#62;
+ &#60;&#47;span&#62;
+ &#60;span&#62;$349&#47;mo&#60;&#47;span&#62;
+&#60;&#47;label&#62;
+
+&#60;div class="total-cost"&#62;
+ &#60;h2&#62;Your Plan&#60;&#47;h2&#62;
+ &#60;div&#62;
+ &#60;span&#62;$&#60;&#47;span&#62;
+ &#60;span id="total-cost-inner"&#62;0&#60;&#47;span&#62;
+ &#60;input id="output" type="text" value="0" disabled&#47;&#62;
+ &#60;span&#62;&#47;mo&#60;&#47;span&#62;
+ &#60;&#47;div&#62;
+&#60;&#47;div&#62;
+</code></pre>
+<h3 id="the-checkbox-inputs-labels">The checkbox inputs &#38; labels</h3>
+<pre><code>&#60;!-- #1 --&#62;
+&#60;input class="checkbox-btn" name="checkbox-collection" id="checkbox-1" type="checkbox" value="49"&#62;
+
+&#60;!-- #2 --&#62;
+&#60;label class="checkbox-label" for="checkbox-1"&#62;
+
+ &#60;!-- #2i --&#62;
+ &#60;span&#62;
+ White-labeled Domain
+ &#60;em&#62;Use your own custom domain with SSL security included.&#60;&#47;em&#62;
+ &#60;&#47;span&#62;
+
+ &#60;!-- #2ii --&#62;
+ &#60;span&#62;$49&#47;mo&#60;&#47;span&#62;
+
+&#60;&#47;label&#62;
+</code></pre>
+<ol>
+<li><p>This input will be hidden via <code>position:absolute</code> by default. All checkbox inputs need to share the same <code>name</code> value and all checkboxes require their our custom <code>id</code> that will link with the corresponding <code>for</code> value on the label.</p></li>
+<li><p>This label needs it&#8217;s <code>for</code> value to correspond with it&#8217;s partnered checkbox.</p>
+<ul>
+<li>i) The first span holds the title and description information of the add-on</li>
+<li>ii) The last span holds the cost associated with the current add-on</li>
+</ul></li>
+</ol>
+<h3 id="the-total-cost-container-output">The total cost container output</h3>
+<pre><code>&#60;!-- #1 --&#62;
+&#60;div class="total-cost"&#62;
+
+ &#60;h2&#62;Your Plan&#60;&#47;h2&#62;
+
+ &#60;!-- #2 --&#62;
+ &#60;div&#62;
+
+ &#60;!-- #2i --&#62;
+ &#60;span&#62;$&#60;&#47;span&#62;
+
+ &#60;!-- #2ii --&#62;
+ &#60;span id="total-cost-inner"&#62;0&#60;&#47;span&#62;
+
+ &#60;!-- #2iii --&#62;
+ &#60;input id="output" type="text" value="0" disabled&#47;&#62;
+
+ &#60;!-- #2iv --&#62;
+ &#60;span&#62;&#47;mo&#60;&#47;span&#62;
+
+ &#60;&#47;div&#62;
+
+&#60;&#47;div&#62;
+</code></pre>
+<ol>
+<li><p>A simple <code>div</code> with a class we can easily target later</p></li>
+<li><p>A <code>div</code> parent container is needed to house all the total <code>spans</code> together (more on this when we get into the CSS)</p>
+<ul>
+<li>i) The first <code>span</code> holds the static currency symbol</li>
+<li>ii) The second <code>span</code> is where our updated cost will be injected</li>
+<li>iii) This input field is required for us to take-in the <code>value</code> of the associated <code>:checked</code> inputs and add them together. This current value is then used for the injection into the second <code>span</code></li>
+<li>iv) The final <code>span</code> simply holds the static monthly duration content</li>
+</ul></li>
+</ol>
+<p>All that&#8217;s all we need for the HTML!</p>
+<h2 id="the-visuals-css">The Visuals (CSS)</h2>
+<p>Again, lets take a look at the entire file before we break it down step-by-step:</p>
+<pre><code>.checkbox-label {
+ align-items: center;
+ background-color: none;
+ border: 1px solid lightgrey;
+ border-radius: 5px;
+ cursor: pointer;
+ display: flex;
+ font-weight: 600;
+ justify-content: space-between;
+ margin: 0 auto 10px;
+ padding: 20px 20px 20px 70px;
+ position: relative;
+ transition: .3s ease all;
+ width: 100%;
+}
+.checkbox-label span:last-child {
+ padding: 0 0 0 20px;
+}
+.checkbox-label:hover {
+ background-color: rgba(255,255,255,0.2);
+}
+.checkbox-label:before {
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 15px;
+ border: 1px solid lightgrey;
+ border-radius: 50%;
+ content:&#39;&#39;;
+ height: 30px;
+ left: 20px;
+ position: absolute;
+ top: calc(50% - 15px);
+ transition: .3s ease background-color;
+ width: 30px;
+}
+.checkbox-label:hover:before {
+ background-image:
+ url(&#39;data:image&#47;svg+xml;utf8,&#60;svg xmlns="http:&#47;&#47;www.w3.org&#47;2000&#47;svg" width="23.571429" height="23.571429" viewBox="0.000000 -47.142857 23.571429 23.571429"&#62;&#60;path fill="lightgrey" d="M23.571429 -36.964286L23.571429 -33.750000C23.571429 -33.303571 23.415179 -32.924107 23.102679 -32.611607C22.790179 -32.299107 22.410714 -32.142857 21.964286 -32.142857L15.000000 -32.142857L15.000000 -25.178571C15.000000 -24.732143 14.843750 -24.352679 14.531250 -24.040179C14.218750 -23.727679 13.839286 -23.571429 13.392857 -23.571429L10.178571 -23.571429C9.732143 -23.571429 9.352679 -23.727679 9.040179 -24.040179C8.727679 -24.352679 8.571429 -24.732143 8.571429 -25.178571L8.571429 -32.142857L1.607143 -32.142857C1.160714 -32.142857 0.781250 -32.299107 0.468750 -32.611607C0.156250 -32.924107 0.000000 -33.303571 0.000000 -33.750000L0.000000 -36.964286C0.000000 -37.410714 0.156250 -37.790179 0.468750 -38.102679C0.781250 -38.415179 1.160714 -38.571429 1.607143 -38.571429L8.571429 -38.571429L8.571429 -45.535714C8.571429 -45.982143 8.727679 -46.361607 9.040179 -46.674107C9.352679 -46.986607 9.732143 -47.142857 10.178571 -47.142857L13.392857 -47.142857C13.839286 -47.142857 14.218750 -46.986607 14.531250 -46.674107C14.843750 -46.361607 15.000000 -45.982143 15.000000 -45.535714L15.000000 -38.571429L21.964286 -38.571429C22.410714 -38.571429 22.790179 -38.415179 23.102679 -38.102679C23.415179 -37.790179 23.571429 -37.410714 23.571429 -36.964286ZM23.571429 -36.964286"&#62;&#60;&#47;path&#62;&#60;&#47;svg&#62;&#39;);
+}
+.checkbox-label span {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+.checkbox-label span em {
+ display: block;
+ font-size: 80%;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 1.2;
+}
+.checkbox-btn {
+ position: absolute;
+ visibility: hidden;
+}
+.checkbox-btn:checked + .checkbox-label {
+ background-color: white;
+ border-color: mediumpurple;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+}
+.checkbox-btn:checked + .checkbox-label:before {
+ background-color: mediumpurple;
+ background-image:
+ url(&#39;data:image&#47;svg+xml;utf8,&#60;svg xmlns="http:&#47;&#47;www.w3.org&#47;2000&#47;svg" width="25.948661" height="19.888393" viewBox="2.025670 -40.011161 25.948661 19.888393"&#62;&#60;path fill="white" d="M27.974330 -36.127232C27.974330 -35.680804 27.818080 -35.301339 27.505580 -34.988839L15.385045 -22.868304L13.108259 -20.591518C12.795759 -20.279018 12.416295 -20.122768 11.969866 -20.122768C11.523438 -20.122768 11.143973 -20.279018 10.831473 -20.591518L8.554688 -22.868304L2.494420 -28.928571C2.181920 -29.241071 2.025670 -29.620536 2.025670 -30.066964C2.025670 -30.513393 2.181920 -30.892857 2.494420 -31.205357L4.771205 -33.482143C5.083705 -33.794643 5.463170 -33.950893 5.909598 -33.950893C6.356027 -33.950893 6.735491 -33.794643 7.047991 -33.482143L11.969866 -28.543527L22.952009 -39.542411C23.264509 -39.854911 23.643973 -40.011161 24.090402 -40.011161C24.536830 -40.011161 24.916295 -39.854911 25.228795 -39.542411L27.505580 -37.265625C27.818080 -36.953125 27.974330 -36.573661 27.974330 -36.127232ZM27.974330 -36.127232"&#62;&#60;&#47;path&#62;&#60;&#47;svg&#62;&#39;);
+ border-color: mediumpurple;
+}
+
+.total-cost {
+ align-items: baseline;
+ border-top: 1px solid lightgrey;
+ display: flex;
+ justify-content: space-between;
+ margin-top: 40px;
+ padding: 40px 20px 0;
+}
+.total-cost div {
+ align-items: baseline;
+ display: flex;
+}
+.total-cost span:nth-child(1) {
+ align-self: flex-start;
+ padding-top: 5px;
+}
+.total-cost span:nth-child(2) {
+ font-size: 32px;
+ font-weight: bold;
+}
+.total-cost input {
+ display: none;
+}
+
+@media(max-width:480px) {
+ .checkbox-label {
+ align-items: flex-start;
+ flex-direction: column;
+ flex-wrap: wrap;
+ }
+ .checkbox-label span:last-child {
+ padding: 10px 0 0 0;
+ }
+}
+</code></pre>
+<h3 id="the-checkbox-label">The checkbox label</h3>
+<pre><code>&#47;*
+This is the main element for each checkbox "container".
+Inside it houses the title, description and price.
+*&#47;
+.checkbox-label {
+ align-items: center;
+ background-color: none;
+ border: 1px solid lightgrey;
+ border-radius: 5px;
+ cursor: pointer;
+ display: flex;
+ font-weight: 600;
+ justify-content: space-between;
+ margin: 0 auto 10px;
+ padding: 20px 20px 20px 70px;
+ position: relative;
+ transition: .3s ease all;
+ width: 100%;
+}
+.checkbox-label:hover {
+ background-color: rgba(255,255,255,0.2);
+}
+
+&#47;* Update the label styling when the input is :checked *&#47;
+.checkbox-btn:checked + .checkbox-label {
+ background-color: white;
+ border-color: mediumpurple;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+}
+</code></pre>
+<h3 id="the-custom-checkbox-input">The custom checkbox input</h3>
+<p>We need to hide the browser&#8217;s default checkbox input and replace it with our own using pseudo selectors.</p>
+<pre><code>&#47;* Hide browser default input *&#47;
+.checkbox-btn {
+ position: absolute;
+ visibility: hidden;
+}
+
+&#47;* Our custom input checkbox *&#47;
+.checkbox-label:before {
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 15px;
+ border: 1px solid lightgrey;
+ border-radius: 50%;
+ content:&#39;&#39;;
+ height: 30px;
+ left: 20px;
+ position: absolute;
+ top: calc(50% - 15px);
+ transition: .3s ease background-color;
+ width: 30px;
+}
+
+&#47;*
+Here we add a simple &#39;+&#39; icon on hover
+to our custom pseudo element.
+Adding it as an inline SVG gives us the
+ability to fully customize it&#39;s styling
+*&#47;
+.checkbox-label:hover:before {
+ background-image:
+ url(&#39;data:image&#47;svg+xml;utf8,&#60;svg xmlns="http:&#47;&#47;www.w3.org&#47;2000&#47;svg" width="23.571429" height="23.571429" viewBox="0.000000 -47.142857 23.571429 23.571429"&#62;&#60;path fill="lightgrey" d="M23.571429 -36.964286L23.571429 -33.750000C23.571429 -33.303571 23.415179 -32.924107 23.102679 -32.611607C22.790179 -32.299107 22.410714 -32.142857 21.964286 -32.142857L15.000000 -32.142857L15.000000 -25.178571C15.000000 -24.732143 14.843750 -24.352679 14.531250 -24.040179C14.218750 -23.727679 13.839286 -23.571429 13.392857 -23.571429L10.178571 -23.571429C9.732143 -23.571429 9.352679 -23.727679 9.040179 -24.040179C8.727679 -24.352679 8.571429 -24.732143 8.571429 -25.178571L8.571429 -32.142857L1.607143 -32.142857C1.160714 -32.142857 0.781250 -32.299107 0.468750 -32.611607C0.156250 -32.924107 0.000000 -33.303571 0.000000 -33.750000L0.000000 -36.964286C0.000000 -37.410714 0.156250 -37.790179 0.468750 -38.102679C0.781250 -38.415179 1.160714 -38.571429 1.607143 -38.571429L8.571429 -38.571429L8.571429 -45.535714C8.571429 -45.982143 8.727679 -46.361607 9.040179 -46.674107C9.352679 -46.986607 9.732143 -47.142857 10.178571 -47.142857L13.392857 -47.142857C13.839286 -47.142857 14.218750 -46.986607 14.531250 -46.674107C14.843750 -46.361607 15.000000 -45.982143 15.000000 -45.535714L15.000000 -38.571429L21.964286 -38.571429C22.410714 -38.571429 22.790179 -38.415179 23.102679 -38.102679C23.415179 -37.790179 23.571429 -37.410714 23.571429 -36.964286ZM23.571429 -36.964286"&#62;&#60;&#47;path&#62;&#60;&#47;svg&#62;&#39;);
+}
+
+&#47;*
+When the checkbox input is :checked we need to
+update the inline SVG to use a checkmark symbol
+*&#47;
+.checkbox-btn:checked + .checkbox-label:before {
+ background-color: mediumpurple;
+ background-image:
+ url(&#39;data:image&#47;svg+xml;utf8,&#60;svg xmlns="http:&#47;&#47;www.w3.org&#47;2000&#47;svg" width="25.948661" height="19.888393" viewBox="2.025670 -40.011161 25.948661 19.888393"&#62;&#60;path fill="white" d="M27.974330 -36.127232C27.974330 -35.680804 27.818080 -35.301339 27.505580 -34.988839L15.385045 -22.868304L13.108259 -20.591518C12.795759 -20.279018 12.416295 -20.122768 11.969866 -20.122768C11.523438 -20.122768 11.143973 -20.279018 10.831473 -20.591518L8.554688 -22.868304L2.494420 -28.928571C2.181920 -29.241071 2.025670 -29.620536 2.025670 -30.066964C2.025670 -30.513393 2.181920 -30.892857 2.494420 -31.205357L4.771205 -33.482143C5.083705 -33.794643 5.463170 -33.950893 5.909598 -33.950893C6.356027 -33.950893 6.735491 -33.794643 7.047991 -33.482143L11.969866 -28.543527L22.952009 -39.542411C23.264509 -39.854911 23.643973 -40.011161 24.090402 -40.011161C24.536830 -40.011161 24.916295 -39.854911 25.228795 -39.542411L27.505580 -37.265625C27.818080 -36.953125 27.974330 -36.573661 27.974330 -36.127232ZM27.974330 -36.127232"&#62;&#60;&#47;path&#62;&#60;&#47;svg&#62;&#39;);
+ border-color: mediumpurple;
+}
+</code></pre>
+<h3 id="the-total-cost-container">The total cost container</h3>
+<p>We only need some very basic flexbox styling for our bottom &#8220;total&#8221; container:</p>
+<pre><code>.total-cost {
+ align-items: baseline;
+ border-top: 1px solid lightgrey;
+ display: flex;
+ justify-content: space-between;
+ margin-top: 40px;
+ padding: 40px 20px 0;
+}
+.total-cost div {
+ align-items: baseline;
+ display: flex;
+}
+.total-cost span:nth-child(1) {
+ align-self: flex-start;
+ padding-top: 5px;
+}
+.total-cost span:nth-child(2) {
+ font-size: 32px;
+ font-weight: bold;
+}
+
+&#47;*
+This input is used in our JavaScript - look at the
+function part of this post to understand why
+*&#47;
+.total-cost input {
+ display: none;
+}
+</code></pre>
+<h3 id="last-but-not-least---mobile">Last but not least - mobile</h3>
+<p>Now we just ensure that on smaller devices our checkbox labels render nicely:</p>
+<pre><code>@media(max-width:480px) {
+ &#47;*
+ Avoids the inner label content from squishing together
+ and becoming unreadable
+ *&#47;
+ .checkbox-label {
+ align-items: flex-start;
+ flex-direction: column;
+ flex-wrap: wrap;
+ }
+ .checkbox-label span:last-child {
+ padding: 10px 0 0 0;
+ }
+}
+</code></pre>
+<p>That&#8217;s it for the styling!</p>
+<h2 id="the-function-js">The Function (JS)</h2>
+<p>As you can see below, we only need a very minor amount of JavaScript to accomplish our total cost &#8220;injection&#8221;.</p>
+<pre><code>window.onload=function(){
+
+&#47;&#47; Place the default browser checkbox inputs into a variable
+var inputs = document.getElementsByClassName(&#39;checkbox-btn&#39;)
+
+&#47;&#47; Now we loop through the inputs and check if they are
+&#47;&#47; greater than zero. If so, we run our function.
+for (var i=0; i &#60; inputs.length; i++) {
+
+ inputs[i].onchange = function() {
+
+ &#47;&#47; Create `add` variable which takes the :checked input value
+ var add = this.value * (this.checked ? 1 : -1);
+
+ &#47;&#47; We grab the current total value on our hidden input field and return it
+ &#47;&#47; as a floating point number
+ &#47;&#47; (since in this use case it will be a price number based on currency)
+ var new_total = parseFloat(document.getElementById(&#39;output&#39;).value);
+
+ &#47;&#47; Now we simply add the existing total value with the newly ":checked" input value
+ var updated_total = document.getElementById(&#39;output&#39;).value=new_total + add
+
+ &#47;&#47; Place the new updated total directly inside the `total-cost-inner` span element
+ document.getElementById(&#39;total-cost-inner&#39;).innerHTML = updated_total;
+ }
+
+}
+}
+</code></pre>
+<p>That&#8217;s it! Feel free to play with the demo some more at the top of the post, or check out the <a href="https://codepen.io/bradleytaunt/pen/rXWEpy">CodePen source directly</a>.</p>
+<footer role="contentinfo">
+ <h2>Menu Navigation</h2>
+ <ul id="menu">
+ <li><a href="/">Home</a></li>
+ <li><a href="/projects">Projects</a></li>
+ <li><a href="/uses">Uses</a></li>
+ <li><a href="/wiki">Wiki</a></li>
+ <li><a href="/resume">Resume</a></li>
+ <li><a href="/colophon">Colophon</a></li>
+ <li><a href="/now">Now</a></li>
+ <li><a href="/donate">Donate</a></li>
+ <li><a href="/atom.xml">RSS</a></li>
+ <li><a href="#top">&uarr; Top of the page</a></li>
+ </ul>
+ <small>
+ Built with <a href="https://git.sr.ht/~bt/barf">barf</a>. <br>
+ Maintained with ♥ for the web. <br>
+ Proud supporter of <a href="https://usefathom.com/ref/DKHJVX">Fathom</a> &amp; <a href="https://nextdns.io/?from=74d3p3h8">NextDNS</a>. <br>
+ The content for this site is <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>.<br> The <a href="https://git.sr.ht/~bt/bt.ht">code for this site</a> is <a href="https://git.sr.ht/~bt/bt.ht/tree/master/item/LICENSE">MIT</a>.
+ </small>
+</footer> \ No newline at end of file