initial public commit
This commit is contained in:
6
public/demo/add-default-route.js
Normal file
6
public/demo/add-default-route.js
Normal file
@@ -0,0 +1,6 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// add default #overview route
|
||||
if (!window.location.hash) {
|
||||
window.location.hash = "overview";
|
||||
}
|
||||
});
|
||||
12
public/demo/external-links.js
Normal file
12
public/demo/external-links.js
Normal file
@@ -0,0 +1,12 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const currentHost = window.location.hostname;
|
||||
|
||||
const links = document.querySelectorAll("a");
|
||||
|
||||
links.forEach((link) => {
|
||||
if (link.hostname && link.hostname !== currentHost) {
|
||||
link.setAttribute("target", "_blank");
|
||||
link.setAttribute("rel", "noopener noreferrer");
|
||||
}
|
||||
});
|
||||
});
|
||||
44
public/demo/format-source.js
Normal file
44
public/demo/format-source.js
Normal file
@@ -0,0 +1,44 @@
|
||||
export default function formatSource(source, panelId) {
|
||||
source = (source ?? "").trim();
|
||||
|
||||
if (!source || !panelId) return;
|
||||
|
||||
const sourceSplit = source.split(/\r?\n/);
|
||||
const spaceCount = sourceSplit[sourceSplit.length - 1].search(/\S/);
|
||||
if (spaceCount <= 0) return; // empty string
|
||||
|
||||
const trimRE = new RegExp(`^\\s{${spaceCount}}`);
|
||||
|
||||
const preDom = document.createElement("pre");
|
||||
preDom.append(sourceSplit.map((val) => val.replace(trimRE, "")).join("\n"));
|
||||
|
||||
preDom.innerHTML = preDom.innerHTML
|
||||
// strRegEx must be applied first to prevent false positives
|
||||
.replace(/\"[^"]+\"/g, (val) =>
|
||||
val !== '"module"' ? `<span class="code-str">${val}</span>` : val,
|
||||
)
|
||||
.replace(/\/\/.*/g, (val) => `<span class="code-cmnt">${val}</span>`)
|
||||
.replace(/\`[^`]+\`/g, (val) => `<span class="code-str">${val}</span>`)
|
||||
.replace(/\.\w+/g, (val) =>
|
||||
val !== ".js" ? `<span class="code-func">${val}</span>` : val,
|
||||
)
|
||||
.replace(
|
||||
/<\/?script[^&]*>/g,
|
||||
(val) => `<span class="code-cmd">${val}</span>`,
|
||||
);
|
||||
|
||||
const sourceHTML = `
|
||||
<details class='js-sourceDetails' open="true">
|
||||
<summary>Source</summary>
|
||||
<div>
|
||||
<figure>
|
||||
${preDom.outerHTML}
|
||||
</figure>
|
||||
</div>
|
||||
</details>
|
||||
`.trim();
|
||||
|
||||
document
|
||||
.getElementById(panelId)
|
||||
.insertAdjacentHTML("beforeend", sourceHTML);
|
||||
}
|
||||
63
public/demo/hashTwt-example-result.js
Normal file
63
public/demo/hashTwt-example-result.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { hashTwt } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
let wasHashTwtResultAppended = false;
|
||||
|
||||
const formHash = document.forms["formHash"];
|
||||
formHash.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const content = formHash.elements["content"].value;
|
||||
const created = formHash.elements["created"].value;
|
||||
const url = formHash.elements["url"].value;
|
||||
const hash = hashTwt({
|
||||
content,
|
||||
created,
|
||||
url,
|
||||
});
|
||||
|
||||
const result = [
|
||||
`content: ${content}`,
|
||||
`created: ${created}`,
|
||||
`url: ${url}`,
|
||||
`hash: ${hash}`,
|
||||
].join("\n");
|
||||
|
||||
console.log((wasHashTwtResultAppended ? "\n" : "") + result);
|
||||
|
||||
const resultHTML = result
|
||||
.split("\n")
|
||||
.map((line) =>
|
||||
line.replace(
|
||||
// to color properties
|
||||
/^\w+:/,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
),
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
if (wasHashTwtResultAppended) {
|
||||
document
|
||||
.getElementById("preHashTwtResult")
|
||||
.insertAdjacentHTML("afterbegin", resultHTML + "<br />\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsHTML = `
|
||||
<details open="">
|
||||
<summary>Results</summary>
|
||||
<figure>
|
||||
<pre id="preHashTwtResult">${resultHTML}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`.trim();
|
||||
|
||||
document
|
||||
.getElementById("tabHashTwt-panel")
|
||||
.insertAdjacentHTML("beforeend", resultsHTML);
|
||||
|
||||
document
|
||||
.querySelector("#tabHashTwt-panel .js-sourceDetails")
|
||||
?.removeAttribute("open");
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
wasHashTwtResultAppended = true;
|
||||
});
|
||||
40
public/demo/hashTwt-example-source.js
Normal file
40
public/demo/hashTwt-example-source.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { hashTwt } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
let wasHashTwtResultAppended = false;
|
||||
|
||||
const formHash = document.forms["formHash"];
|
||||
formHash.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const content = formHash.elements["content"].value;
|
||||
const created = formHash.elements["created"].value;
|
||||
const url = formHash.elements["url"].value;
|
||||
|
||||
const hash = hashTwt({
|
||||
content,
|
||||
created,
|
||||
url,
|
||||
});
|
||||
|
||||
const result = [
|
||||
\`content: \${content}\`,
|
||||
\`created: \${created}\`,
|
||||
\`url: \${url}\`,
|
||||
\`hash: \${hash}\`,
|
||||
].join("\\n");
|
||||
|
||||
console.log(
|
||||
(wasHashTwtResultAppended ? "\\n" : "") + result,
|
||||
);
|
||||
|
||||
wasHashTwtResultAppended = true;
|
||||
});
|
||||
<\/script>
|
||||
`,
|
||||
"tabHashTwt-panel",
|
||||
);
|
||||
79
public/demo/loadAndParseTwtxtFile-example-result.js
Normal file
79
public/demo/loadAndParseTwtxtFile-example-result.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { loadAndParseTwtxtFile } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
const tabLoadAndParsePanel = document.getElementById("tabLoadAndParse-panel");
|
||||
let wasLoadAndParseResultAppended = false;
|
||||
|
||||
document
|
||||
.getElementById("formLoadAndParse")
|
||||
.addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
tabLoadAndParsePanel.classList.add("isLoading");
|
||||
|
||||
const loadAndParseURL = document.getElementById("loadAndParseURL");
|
||||
|
||||
const url =
|
||||
loadAndParseURL?.value || "/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const parsedFile = await loadAndParseTwtxtFile(url);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
tabLoadAndParsePanel.classList.remove("isLoading");
|
||||
|
||||
if (wasLoadAndParseResultAppended) {
|
||||
document.getElementById("preLoadAndParseResult").outerHTML = `
|
||||
<pre id="preLoadAndParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsHTML = `
|
||||
<details open="true">
|
||||
<summary>Results</summary>
|
||||
<figure>
|
||||
<pre id="preLoadAndParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`;
|
||||
|
||||
tabLoadAndParsePanel.insertAdjacentHTML("beforeend", resultsHTML);
|
||||
|
||||
document
|
||||
.querySelector("#tabLoadAndParse-panel .js-sourceDetails")
|
||||
?.removeAttribute("open");
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
|
||||
wasLoadAndParseResultAppended = true;
|
||||
});
|
||||
|
||||
const loadAndParseClickHandler = (ev) => {
|
||||
ev?.preventDefault();
|
||||
loadAndParseURL.value = ev.target.dataset.url;
|
||||
};
|
||||
|
||||
[
|
||||
"loadAndParseHipsterButton",
|
||||
"loadAndParsePirateButton",
|
||||
"loadAndParseSaganButton",
|
||||
].forEach((curr) => {
|
||||
document
|
||||
.getElementById(curr)
|
||||
.addEventListener("click", loadAndParseClickHandler);
|
||||
});
|
||||
25
public/demo/loadAndParseTwtxtFile-example-source.js
Normal file
25
public/demo/loadAndParseTwtxtFile-example-source.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { loadAndParseTwtxtFile } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
document
|
||||
.getElementById("formLoadAndParse")
|
||||
.addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
|
||||
const url =
|
||||
document.getElementById("loadAndParseURL")?.value ??
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const parsedFile = await loadAndParseTwtxtFile(url);
|
||||
|
||||
console.log(parsedFile);
|
||||
});
|
||||
|
||||
<\/script>
|
||||
`,
|
||||
"tabLoadAndParse-panel",
|
||||
);
|
||||
36
public/demo/overview-example-result.js
Normal file
36
public/demo/overview-example-result.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { loadAndParseTwtxtFile } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
// run in an IIFE (or event listener) to avoid issues with top-level await
|
||||
(async () => {
|
||||
try {
|
||||
const parsedFile = await loadAndParseTwtxtFile(
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt",
|
||||
);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
document.getElementById("tabOverview-example")?.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`
|
||||
<details open="true">
|
||||
<summary>Result</summary>
|
||||
<figure>
|
||||
<pre id="preResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`,
|
||||
);
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
24
public/demo/overview-example-source.js
Normal file
24
public/demo/overview-example-source.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { loadAndParseTwtxtFile } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
// run in an IIFE (or event listener) to avoid issues with top-level await
|
||||
(async () => {
|
||||
try {
|
||||
const parsedFile = await loadAndParseTwtxtFile(
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt",
|
||||
);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
<\/script>
|
||||
`,
|
||||
"tabOverview-example",
|
||||
);
|
||||
76
public/demo/parseTwtxt-example-result.js
Normal file
76
public/demo/parseTwtxt-example-result.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { parseTwtxt } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
const tabParsePanel = document.getElementById("tabParse-panel");
|
||||
let wasParseResultAppended = false;
|
||||
|
||||
document.getElementById("formParse").addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
tabParsePanel.classList.add("isLoading");
|
||||
|
||||
const parseURL = document.getElementById("parseURL");
|
||||
|
||||
const url = parseURL?.value ?? "/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const response = await fetch(url);
|
||||
const twtxtFile = await response.text();
|
||||
const parsedFile = parseTwtxt(twtxtFile);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
tabParsePanel.classList.remove("isLoading");
|
||||
|
||||
if (wasParseResultAppended) {
|
||||
document.getElementById("preParseResult").outerHTML = `
|
||||
<pre id="preParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsHTML = `
|
||||
<details open="true">
|
||||
<summary>Results</summary>
|
||||
<figure>
|
||||
<pre id="preParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`;
|
||||
|
||||
tabParsePanel.insertAdjacentHTML("beforeend", resultsHTML);
|
||||
|
||||
document
|
||||
.querySelector("#tabParse-panel .js-sourceDetails")
|
||||
?.removeAttribute("open");
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
|
||||
wasParseResultAppended = true;
|
||||
});
|
||||
|
||||
const parseClickHandler = (ev) => {
|
||||
ev?.preventDefault();
|
||||
parseURL.value = ev.target.dataset.url;
|
||||
};
|
||||
|
||||
["parseHipsterButton", "parsePirateButton", "parseSaganButton"].forEach(
|
||||
(curr) => {
|
||||
document
|
||||
.getElementById(curr)
|
||||
.addEventListener("click", parseClickHandler);
|
||||
},
|
||||
);
|
||||
27
public/demo/parseTwtxt-example-source.js
Normal file
27
public/demo/parseTwtxt-example-source.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { parseTwtxt } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
document
|
||||
.getElementById("formParse")
|
||||
.addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
|
||||
const url =
|
||||
document.getElementById("parseURL")?.value ??
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const response = await fetch(url);
|
||||
const twtxtFile = await response.text();
|
||||
const parsedFile = parseTwtxt(twtxtFile);
|
||||
|
||||
console.log(parsedFile);
|
||||
});
|
||||
|
||||
<\/script>
|
||||
`,
|
||||
"tabParse-panel",
|
||||
);
|
||||
471
public/demo/styles.css
Normal file
471
public/demo/styles.css
Normal file
@@ -0,0 +1,471 @@
|
||||
:root {
|
||||
--fg-main: #DBDFAC;
|
||||
--bg-main: #3B1F2B;
|
||||
|
||||
--fg-light: #edefd5;
|
||||
--bg-dark: rgba(10, 10, 10, .5);
|
||||
--bg-light: rgba(245, 245, 245, .6);
|
||||
|
||||
--main-link: #8598AD;
|
||||
--link-active: #ccc;
|
||||
--link-active: lch(from var(--main-link) calc(l + 20) c h);
|
||||
|
||||
--gray-light: #5F758E;
|
||||
}
|
||||
|
||||
@keyframes riseInDetails {
|
||||
0% {opacity: 0; margin-top: 2rem}
|
||||
100% {opacity: 1; margin-top: 0rem}
|
||||
}
|
||||
|
||||
@keyframes riseInTab {
|
||||
0% {opacity: 0; margin-top: -2rem}
|
||||
100% {opacity: 1; margin-top: -4rem}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-main);
|
||||
color: var(--fg-main);
|
||||
margin: 0;
|
||||
transition:
|
||||
background-color .5s,
|
||||
border-color .5s,
|
||||
color .5s;
|
||||
}
|
||||
|
||||
a {
|
||||
border: 1px solid transparent;
|
||||
border-bottom-color: var(--main-link);
|
||||
border-radius: 0;
|
||||
color: var(--main-link);
|
||||
padding: 0 .5rem ;
|
||||
text-decoration: none;
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
a:active {
|
||||
background-color: var(--fg-main);
|
||||
border-color: var(--bg-main);
|
||||
color: var(--link-active);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
border-color: var(--link-active);
|
||||
border-radius: .5rem;
|
||||
color: var(--link-active);
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--link-active);
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
max-width: 25rem;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: var(--fg-main);
|
||||
}
|
||||
|
||||
input[type="reset"], input[type="submit"] {
|
||||
background-color: var(--link-active);
|
||||
}
|
||||
|
||||
input[type="text"], input[type="url"] {
|
||||
background-color: var(--fg-light);
|
||||
font-size: 1rem;
|
||||
max-width: 25rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--bg-dark);
|
||||
border: 1px solid var(--fg-main);
|
||||
color: var(--fg-main);
|
||||
font-size: smaller;
|
||||
padding: 1rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background-color: var(--fg-light);
|
||||
font-size: 1rem;
|
||||
max-width: 30rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
details figure {
|
||||
margin: 1rem .5rem;
|
||||
}
|
||||
|
||||
details figure pre {
|
||||
overflow: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
details[open] summary ~ * {
|
||||
animation: riseInDetails .5s ease-in-out;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-cmd {
|
||||
color: var(--gray-light);
|
||||
}
|
||||
|
||||
.code-cmnt {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.code-str {
|
||||
color: var(--gray-light);
|
||||
}
|
||||
|
||||
.copyright {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.flexCol {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
max-width: 25rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.flexRow {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 2px solid transparent;
|
||||
border-top: 2px solid var(--fg-main);
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab-link {
|
||||
background-color: var(--bg-main);
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
border-bottom: 0;
|
||||
color: var(--main-link);
|
||||
display: block;
|
||||
padding: 1rem 2rem;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tab-panel {
|
||||
bottom: 0;
|
||||
display: none;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
padding: 0 1rem 1rem;
|
||||
right: 0;
|
||||
top: 4.25rem;
|
||||
width: 100%;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
border: 2px solid var(--fg-main);
|
||||
border-top-color: transparent;
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab:target .tab-panel {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body:not(:has(:target)) #tabOverview-panel {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab:target .tab-link {
|
||||
background: linear-gradient(var(--bg-dark), var(--bg-main));
|
||||
}
|
||||
|
||||
.tab:target + .tab {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
body:not(:has(:target)) #tabOverview-link {
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--fg-main);
|
||||
}
|
||||
|
||||
.themeToggle-button {
|
||||
background-color: var(--fg-main);
|
||||
border: 1px solid var(--gray-light);
|
||||
border-radius: 1rem;
|
||||
bottom: .5rem;
|
||||
color: var(--gray-light);
|
||||
display: flex;
|
||||
font-size: 1.5rem;
|
||||
padding: .5rem;
|
||||
position: fixed;
|
||||
right: .5rem;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.themeToggle-svgIndicator {
|
||||
rotate: 180deg;
|
||||
transition: rotate .5s;
|
||||
}
|
||||
|
||||
/** Loader from https://www.cssportal.com/css-loader-generator/ */
|
||||
|
||||
.dotLoader {
|
||||
animation: dotLoaderFrames 1s infinite steps(6);
|
||||
background:
|
||||
linear-gradient(var(--bg-main) 0 0) left -25% top 0 /20% 100% no-repeat var(--fg-main);
|
||||
display: block;
|
||||
height: 20px;
|
||||
margin-top: 1rem;
|
||||
mask: linear-gradient(90deg,var(--bg-main) 70%,#0000 0) left/20% 100%;
|
||||
-webkit-mask: linear-gradient(90deg,var(--bg-main) 70%,#0000 0) left/20% 100%;
|
||||
transition: opacity .5s;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
@keyframes dotLoaderFrames {
|
||||
100% {background-position: right -25% top 0}
|
||||
}
|
||||
|
||||
/** ID Overrides */
|
||||
|
||||
#formHash .flexCol {
|
||||
min-width: 15rem;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#formHash input[type="submit"] {
|
||||
margin-top: 1rem;
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
#formHash textarea {
|
||||
min-height: 6rem;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
#formLoadAndParse .flexCol {
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
#formLoadAndParse label {
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
/** Media-Query Overrides */
|
||||
@media (min-width: 900px) {
|
||||
.flexRow {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border-color: transparent;
|
||||
display: inline-block;
|
||||
position: static;
|
||||
}
|
||||
|
||||
.tab-link {
|
||||
background-color: transparent;;
|
||||
border: 2px solid var(--fg-main);
|
||||
border-bottom-color: transparent;
|
||||
margin-top: -4.5rem;
|
||||
}
|
||||
|
||||
.tab-link:hover {
|
||||
background-color: var(--link-active);
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
color: var(--bg-main);
|
||||
font-size: 1.2rem;
|
||||
margin-top: -4.65rem;
|
||||
}
|
||||
|
||||
|
||||
.tab-panel {
|
||||
animation: riseInTab .5s ease-in-out;
|
||||
margin-top: -4rem;
|
||||
position: relative;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
|
||||
.tabs {
|
||||
border-top: 2px solid var(--fg-main);
|
||||
bottom: 0;
|
||||
flex-direction: row;
|
||||
left: 0;
|
||||
margin-top: 4.5rem;
|
||||
min-height: auto;
|
||||
padding: 1rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tab:target + .tab {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tab:target .tab-link {
|
||||
border-bottom: 0;
|
||||
font-size: 1.2rem;
|
||||
margin-top: -4.55rem;
|
||||
}
|
||||
|
||||
.tab:target .tab-link:hover {
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--main-link);
|
||||
font-size: 1.2rem;
|
||||
margin-top: -4.55rem;
|
||||
}
|
||||
|
||||
|
||||
.tab:target .tab-panel {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body:not(:has(:target)) #tabOverview-panel {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.themeToggle-button {
|
||||
bottom: auto;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/** State-Based Overrides */
|
||||
|
||||
body.invertedTheme {
|
||||
background-color: var(--fg-main);
|
||||
color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme a:hover {
|
||||
background-color: var(--main-link);
|
||||
border-color: var(--bg-dark);
|
||||
color: var(--fg-main);
|
||||
}
|
||||
|
||||
|
||||
body.invertedTheme figcaption {
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme figure {
|
||||
background-color: var(--gray-light);
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme input,
|
||||
body.invertedTheme textarea {
|
||||
background-color: var(--fg-light);
|
||||
}
|
||||
|
||||
body.invertedTheme pre {
|
||||
background-color: var(--bg-light);
|
||||
border-color: var(--bg-main);
|
||||
color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .code-cmd {
|
||||
color: var(--bg-dark);
|
||||
}
|
||||
|
||||
body.invertedTheme .code-str {
|
||||
color: var(--bg-dark);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab-link {
|
||||
color: var(--bg-dark);
|
||||
background-color: var(--fg-main);
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab:target .tab-link {
|
||||
background: linear-gradient(var(--link-active), var(--fg-main));
|
||||
}
|
||||
|
||||
body.invertedTheme .tab-link:hover {
|
||||
background-color: var(--main-link);
|
||||
border-color: var(--bg-dark);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab:target .tab-link {
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab-panel {
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .tabs {
|
||||
border: 2px solid var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .themeToggle-button {
|
||||
background-color: var(--bg-main);
|
||||
color: var(--main-link);
|
||||
}
|
||||
|
||||
body.invertedTheme .themeToggle-svgIndicator {
|
||||
rotate: 0deg;
|
||||
}
|
||||
|
||||
.isLoaded .dotLoader {
|
||||
margin: 0;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.isLoading .dotLoader {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
a[href^='http']::after {
|
||||
content: '\2197'; /* Code for ↗ */
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
29
public/demo/theme-toggle.js
Normal file
29
public/demo/theme-toggle.js
Normal file
@@ -0,0 +1,29 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const toggle = document.createElement("button");
|
||||
|
||||
toggle.classList.add("themeToggle-button");
|
||||
toggle.setAttribute("id", "themeToggle-button");
|
||||
|
||||
toggle.addEventListener("click", () => {
|
||||
document.body.classList.toggle("invertedTheme");
|
||||
});
|
||||
|
||||
toggle.innerHTML = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
width="1em"
|
||||
height="1em"
|
||||
class="themeToggle-svgIndicator"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M16 .5C7.4.5.5 7.4.5 16S7.4 31.5 16 31.5 31.5 24.6 31.5 16 24.6.5
|
||||
16 .5zm0 28.1V3.4C23 3.4 28.6 9 28.6 16S23 28.6 16 28.6z"
|
||||
/>
|
||||
</svg>
|
||||
`.trim();
|
||||
|
||||
document.body.appendChild(toggle);
|
||||
});
|
||||
Reference in New Issue
Block a user