commit 05914a9a77ae363925c4db6eb44ded266b1c437e
Author: JayVii <jayvii[AT]posteo[DOT]de>
Date: Sat, 6 Jul 2024 13:41:55 +0200
feat: initial version of traserci
Diffstat:
7 files changed, 1090 insertions(+), 0 deletions(-)
diff --git a/assets/css/simple.css b/assets/css/simple.css
@@ -0,0 +1,711 @@
+/* Global variables. */
+:root,
+::backdrop {
+ /* Set sans-serif & mono fonts */
+ --sans-font: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir,
+ "Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica,
+ "Helvetica Neue", sans-serif;
+ --mono-font: Consolas, Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
+ --standard-border-radius: 5px;
+
+ /* Default (light) theme */
+ --bg: #fff;
+ --accent-bg: #f5f7ff;
+ --text: #212121;
+ --text-light: #585858;
+ --border: #898EA4;
+ --accent: #0d47a1;
+ --accent-hover: #1266e2;
+ --accent-text: var(--bg);
+ --code: #d81b60;
+ --preformatted: #444;
+ --marked: #ffdd33;
+ --disabled: #efefef;
+}
+
+/* Dark theme */
+@media (prefers-color-scheme: dark) {
+ :root,
+ ::backdrop {
+ color-scheme: dark;
+ --bg: #212121;
+ --accent-bg: #2b2b2b;
+ --text: #dcdcdc;
+ --text-light: #ababab;
+ --accent: #ffb300;
+ --accent-hover: #ffe099;
+ --accent-text: var(--bg);
+ --code: #f06292;
+ --preformatted: #ccc;
+ --disabled: #111;
+ }
+ /* Add a bit of transparency so light media isn't so glaring in dark mode */
+ img,
+ video {
+ opacity: 0.8;
+ }
+}
+
+/* Reset box-sizing */
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+/* Reset default appearance */
+textarea,
+select,
+input,
+progress {
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+}
+
+html {
+ /* Set the font globally */
+ font-family: var(--sans-font);
+ scroll-behavior: smooth;
+}
+
+/* Make the body a nice central block */
+body {
+ color: var(--text);
+ background-color: var(--bg);
+ font-size: 1.15rem;
+ line-height: 1.5;
+ display: grid;
+ grid-template-columns: 1fr min(45rem, 90%) 1fr;
+ margin: 0;
+}
+body > * {
+ grid-column: 2;
+}
+
+/* Make the header bg full width, but the content inline with body */
+body > header {
+ background-color: var(--accent-bg);
+ border-bottom: 1px solid var(--border);
+ text-align: center;
+ padding: 0 0.5rem 2rem 0.5rem;
+ grid-column: 1 / -1;
+}
+
+body > header > *:only-child {
+ margin-block-start: 2rem;
+}
+
+body > header h1 {
+ max-width: 1200px;
+ margin: 1rem auto;
+}
+
+body > header p {
+ max-width: 40rem;
+ margin: 1rem auto;
+}
+
+/* Add a little padding to ensure spacing is correct between content and header > nav */
+main {
+ padding-top: 1.5rem;
+}
+
+body > footer {
+ margin-top: 4rem;
+ padding: 2rem 1rem 1.5rem 1rem;
+ color: var(--text-light);
+ font-size: 0.9rem;
+ text-align: center;
+ border-top: 1px solid var(--border);
+}
+
+/* Format headers */
+h1 {
+ font-size: 3rem;
+}
+
+h2 {
+ font-size: 2.6rem;
+ margin-top: 3rem;
+}
+
+h3 {
+ font-size: 2rem;
+ margin-top: 3rem;
+}
+
+h4 {
+ font-size: 1.44rem;
+}
+
+h5 {
+ font-size: 1.15rem;
+}
+
+h6 {
+ font-size: 0.96rem;
+}
+
+p {
+ margin: 1.5rem 0;
+}
+
+/* Prevent long strings from overflowing container */
+p, h1, h2, h3, h4, h5, h6 {
+ overflow-wrap: break-word;
+}
+
+/* Fix line height when title wraps */
+h1,
+h2,
+h3 {
+ line-height: 1.1;
+}
+
+/* Reduce header size on mobile */
+@media only screen and (max-width: 720px) {
+ h1 {
+ font-size: 2.5rem;
+ }
+
+ h2 {
+ font-size: 2.1rem;
+ }
+
+ h3 {
+ font-size: 1.75rem;
+ }
+
+ h4 {
+ font-size: 1.25rem;
+ }
+}
+
+/* Format links & buttons */
+a,
+a:visited {
+ color: var(--accent);
+}
+
+a:hover {
+ text-decoration: none;
+}
+
+button,
+.button,
+a.button, /* extra specificity to override a */
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+label[type="button"] {
+ border: 1px solid var(--accent);
+ background-color: var(--accent);
+ color: var(--accent-text);
+ padding: 0.5rem 0.9rem;
+ text-decoration: none;
+ line-height: normal;
+}
+
+.button[aria-disabled="true"],
+input:disabled,
+textarea:disabled,
+select:disabled,
+button[disabled] {
+ cursor: not-allowed;
+ background-color: var(--disabled);
+ border-color: var(--disabled);
+ color: var(--text-light);
+}
+
+input[type="range"] {
+ padding: 0;
+}
+
+/* Set the cursor to '?' on an abbreviation and style the abbreviation to show that there is more information underneath */
+abbr[title] {
+ cursor: help;
+ text-decoration-line: underline;
+ text-decoration-style: dotted;
+}
+
+button:enabled:hover,
+.button:not([aria-disabled="true"]):hover,
+input[type="submit"]:enabled:hover,
+input[type="reset"]:enabled:hover,
+input[type="button"]:enabled:hover,
+label[type="button"]:hover {
+ background-color: var(--accent-hover);
+ border-color: var(--accent-hover);
+ cursor: pointer;
+}
+
+.button:focus-visible,
+button:focus-visible:where(:enabled),
+input:enabled:focus-visible:where(
+ [type="submit"],
+ [type="reset"],
+ [type="button"]
+) {
+ outline: 2px solid var(--accent);
+ outline-offset: 1px;
+}
+
+/* Format navigation */
+header > nav {
+ font-size: 1rem;
+ line-height: 2;
+ padding: 1rem 0 0 0;
+}
+
+/* Use flexbox to allow items to wrap, as needed */
+header > nav ul,
+header > nav ol {
+ align-content: space-around;
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+/* List items are inline elements, make them behave more like blocks */
+header > nav ul li,
+header > nav ol li {
+ display: inline-block;
+}
+
+header > nav a,
+header > nav a:visited {
+ margin: 0 0.5rem 1rem 0.5rem;
+ border: 1px solid var(--border);
+ border-radius: var(--standard-border-radius);
+ color: var(--text);
+ display: inline-block;
+ padding: 0.1rem 1rem;
+ text-decoration: none;
+}
+
+header > nav a:hover,
+header > nav a.current,
+header > nav a[aria-current="page"] {
+ border-color: var(--accent);
+ color: var(--accent);
+ cursor: pointer;
+}
+
+/* Reduce nav side on mobile */
+@media only screen and (max-width: 720px) {
+ header > nav a {
+ border: none;
+ padding: 0;
+ text-decoration: underline;
+ line-height: 1;
+ }
+}
+
+/* Consolidate box styling */
+aside, details, pre, progress {
+ background-color: var(--accent-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--standard-border-radius);
+ margin-bottom: 1rem;
+}
+
+aside {
+ font-size: 1rem;
+ width: 30%;
+ padding: 0 15px;
+ margin-inline-start: 15px;
+ float: right;
+}
+*[dir="rtl"] aside {
+ float: left;
+}
+
+/* Make aside full-width on mobile */
+@media only screen and (max-width: 720px) {
+ aside {
+ width: 100%;
+ float: none;
+ margin-inline-start: 0;
+ }
+}
+
+article, fieldset, dialog {
+ border: 1px solid var(--border);
+ padding: 1rem;
+ border-radius: var(--standard-border-radius);
+ margin-bottom: 1rem;
+}
+
+article h2:first-child,
+section h2:first-child,
+article h3:first-child,
+section h3:first-child {
+ margin-top: 1rem;
+}
+
+section {
+ border-top: 1px solid var(--border);
+ border-bottom: 1px solid var(--border);
+ padding: 2rem 1rem;
+ margin: 3rem 0;
+}
+
+/* Don't double separators when chaining sections */
+section + section,
+section:first-child {
+ border-top: 0;
+ padding-top: 0;
+}
+
+section + section {
+ margin-top: 0;
+}
+
+section:last-child {
+ border-bottom: 0;
+ padding-bottom: 0;
+}
+
+details {
+ padding: 0.7rem 1rem;
+}
+
+summary {
+ cursor: pointer;
+ font-weight: bold;
+ padding: 0.7rem 1rem;
+ margin: -0.7rem -1rem;
+ word-break: break-all;
+}
+
+details[open] > summary + * {
+ margin-top: 0;
+}
+
+details[open] > summary {
+ margin-bottom: 0.5rem;
+}
+
+details[open] > :last-child {
+ margin-bottom: 0;
+}
+
+/* Format tables */
+table {
+ border-collapse: collapse;
+ margin: 1.5rem 0;
+}
+
+figure > table {
+ width: max-content;
+ margin: 0;
+}
+
+td,
+th {
+ border: 1px solid var(--border);
+ text-align: start;
+ padding: 0.5rem;
+}
+
+th {
+ background-color: var(--accent-bg);
+ font-weight: bold;
+}
+
+tr:nth-child(even) {
+ /* Set every other cell slightly darker. Improves readability. */
+ background-color: var(--accent-bg);
+}
+
+table caption {
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+}
+
+/* Format forms */
+textarea,
+select,
+input,
+button,
+.button {
+ font-size: inherit;
+ font-family: inherit;
+ padding: 0.5rem;
+ margin-bottom: 0.5rem;
+ border-radius: var(--standard-border-radius);
+ box-shadow: none;
+ max-width: 100%;
+ display: inline-block;
+}
+textarea,
+select,
+input {
+ color: var(--text);
+ background-color: var(--bg);
+ border: 1px solid var(--border);
+}
+label {
+ display: block;
+}
+textarea:not([cols]) {
+ width: 100%;
+}
+
+/* Add arrow to drop-down */
+select:not([multiple]) {
+ background-image: linear-gradient(45deg, transparent 49%, var(--text) 51%),
+ linear-gradient(135deg, var(--text) 51%, transparent 49%);
+ background-position: calc(100% - 15px), calc(100% - 10px);
+ background-size: 5px 5px, 5px 5px;
+ background-repeat: no-repeat;
+ padding-inline-end: 25px;
+}
+*[dir="rtl"] select:not([multiple]) {
+ background-position: 10px, 15px;
+}
+
+/* checkbox and radio button style */
+input[type="checkbox"],
+input[type="radio"] {
+ vertical-align: middle;
+ position: relative;
+ width: min-content;
+}
+
+input[type="checkbox"] + label,
+input[type="radio"] + label {
+ display: inline-block;
+}
+
+input[type="radio"] {
+ border-radius: 100%;
+}
+
+input[type="checkbox"]:checked,
+input[type="radio"]:checked {
+ background-color: var(--accent);
+}
+
+input[type="checkbox"]:checked::after {
+ /* Creates a rectangle with colored right and bottom borders which is rotated to look like a check mark */
+ content: " ";
+ width: 0.18em;
+ height: 0.32em;
+ border-radius: 0;
+ position: absolute;
+ top: 0.05em;
+ left: 0.17em;
+ background-color: transparent;
+ border-right: solid var(--bg) 0.08em;
+ border-bottom: solid var(--bg) 0.08em;
+ font-size: 1.8em;
+ transform: rotate(45deg);
+}
+input[type="radio"]:checked::after {
+ /* creates a colored circle for the checked radio button */
+ content: " ";
+ width: 0.25em;
+ height: 0.25em;
+ border-radius: 100%;
+ position: absolute;
+ top: 0.125em;
+ background-color: var(--bg);
+ left: 0.125em;
+ font-size: 32px;
+}
+
+/* Makes input fields wider on smaller screens */
+@media only screen and (max-width: 720px) {
+ textarea,
+ select,
+ input {
+ width: 100%;
+ }
+}
+
+/* Set a height for color input */
+input[type="color"] {
+ height: 2.5rem;
+ padding: 0.2rem;
+}
+
+/* do not show border around file selector button */
+input[type="file"] {
+ border: 0;
+}
+
+/* Misc body elements */
+hr {
+ border: none;
+ height: 1px;
+ background: var(--border);
+ margin: 1rem auto;
+}
+
+mark {
+ padding: 2px 5px;
+ border-radius: var(--standard-border-radius);
+ background-color: var(--marked);
+ color: black;
+}
+
+mark a {
+ color: #0d47a1;
+}
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+ border-radius: var(--standard-border-radius);
+}
+
+figure {
+ margin: 0;
+ display: block;
+ overflow-x: auto;
+}
+
+figure > img,
+figure > picture > img {
+ display: block;
+ margin-inline: auto;
+}
+
+figcaption {
+ text-align: center;
+ font-size: 0.9rem;
+ color: var(--text-light);
+ margin-block: 1rem;
+}
+
+blockquote {
+ margin-inline-start: 2rem;
+ margin-inline-end: 0;
+ margin-block: 2rem;
+ padding: 0.4rem 0.8rem;
+ border-inline-start: 0.35rem solid var(--accent);
+ color: var(--text-light);
+ font-style: italic;
+}
+
+cite {
+ font-size: 0.9rem;
+ color: var(--text-light);
+ font-style: normal;
+}
+
+dt {
+ color: var(--text-light);
+}
+
+/* Use mono font for code elements */
+code,
+pre,
+pre span,
+kbd,
+samp {
+ font-family: var(--mono-font);
+ color: var(--code);
+}
+
+kbd {
+ color: var(--preformatted);
+ border: 1px solid var(--preformatted);
+ border-bottom: 3px solid var(--preformatted);
+ border-radius: var(--standard-border-radius);
+ padding: 0.1rem 0.4rem;
+}
+
+pre {
+ padding: 1rem 1.4rem;
+ max-width: 100%;
+ overflow: auto;
+ color: var(--preformatted);
+}
+
+/* Fix embedded code within pre */
+pre code {
+ color: var(--preformatted);
+ background: none;
+ margin: 0;
+ padding: 0;
+}
+
+/* Progress bars */
+/* Declarations are repeated because you */
+/* cannot combine vendor-specific selectors */
+progress {
+ width: 100%;
+}
+
+progress:indeterminate {
+ background-color: var(--accent-bg);
+}
+
+progress::-webkit-progress-bar {
+ border-radius: var(--standard-border-radius);
+ background-color: var(--accent-bg);
+}
+
+progress::-webkit-progress-value {
+ border-radius: var(--standard-border-radius);
+ background-color: var(--accent);
+}
+
+progress::-moz-progress-bar {
+ border-radius: var(--standard-border-radius);
+ background-color: var(--accent);
+ transition-property: width;
+ transition-duration: 0.3s;
+}
+
+progress:indeterminate::-moz-progress-bar {
+ background-color: var(--accent-bg);
+}
+
+dialog {
+ max-width: 40rem;
+ margin: auto;
+}
+
+dialog::backdrop {
+ background-color: var(--bg);
+ opacity: 0.8;
+}
+
+@media only screen and (max-width: 720px) {
+ dialog {
+ max-width: 100%;
+ margin: auto 1em;
+ }
+}
+
+/* Superscript & Subscript */
+/* Prevent scripts from affecting line-height. */
+sup, sub {
+ vertical-align: baseline;
+ position: relative;
+}
+
+sup {
+ top: -0.4em;
+}
+
+sub {
+ top: 0.3em;
+}
+
+/* Classes for notices */
+.notice {
+ background: var(--accent-bg);
+ border: 2px solid var(--border);
+ border-radius: var(--standard-border-radius);
+ padding: 1.5rem;
+ margin: 2rem 0;
+}
diff --git a/assets/css/simple.min.css b/assets/css/simple.min.css
@@ -0,0 +1 @@
+:root,::backdrop{--sans-font:-apple-system,BlinkMacSystemFont,"Avenir Next",Avenir,"Nimbus Sans L",Roboto,"Noto Sans","Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif;--mono-font:Consolas,Menlo,Monaco,"Andale Mono","Ubuntu Mono",monospace;--standard-border-radius:5px;--bg:#fff;--accent-bg:#f5f7ff;--text:#212121;--text-light:#585858;--border:#898ea4;--accent:#0d47a1;--accent-hover:#1266e2;--accent-text:var(--bg);--code:#d81b60;--preformatted:#444;--marked:#fd3;--disabled:#efefef}@media (prefers-color-scheme:dark){:root,::backdrop{color-scheme:dark;--bg:#212121;--accent-bg:#2b2b2b;--text:#dcdcdc;--text-light:#ababab;--accent:#ffb300;--accent-hover:#ffe099;--accent-text:var(--bg);--code:#f06292;--preformatted:#ccc;--disabled:#111}img,video{opacity:.8}}*,:before,:after{box-sizing:border-box}textarea,select,input,progress{-webkit-appearance:none;-moz-appearance:none;appearance:none}html{font-family:var(--sans-font);scroll-behavior:smooth}body{color:var(--text);background-color:var(--bg);grid-template-columns:1fr min(45rem,90%) 1fr;margin:0;font-size:1.15rem;line-height:1.5;display:grid}body>*{grid-column:2}body>header{background-color:var(--accent-bg);border-bottom:1px solid var(--border);text-align:center;grid-column:1/-1;padding:0 .5rem 2rem}body>header>:only-child{margin-block-start:2rem}body>header h1{max-width:1200px;margin:1rem auto}body>header p{max-width:40rem;margin:1rem auto}main{padding-top:1.5rem}body>footer{color:var(--text-light);text-align:center;border-top:1px solid var(--border);margin-top:4rem;padding:2rem 1rem 1.5rem;font-size:.9rem}h1{font-size:3rem}h2{margin-top:3rem;font-size:2.6rem}h3{margin-top:3rem;font-size:2rem}h4{font-size:1.44rem}h5{font-size:1.15rem}h6{font-size:.96rem}p{margin:1.5rem 0}p,h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}h1,h2,h3{line-height:1.1}@media only screen and (width<=720px){h1{font-size:2.5rem}h2{font-size:2.1rem}h3{font-size:1.75rem}h4{font-size:1.25rem}}a,a:visited{color:var(--accent)}a:hover{text-decoration:none}button,.button,a.button,input[type=submit],input[type=reset],input[type=button],label[type=button]{border:1px solid var(--accent);background-color:var(--accent);color:var(--accent-text);padding:.5rem .9rem;line-height:normal;text-decoration:none}.button[aria-disabled=true],input:disabled,textarea:disabled,select:disabled,button[disabled]{cursor:not-allowed;background-color:var(--disabled);border-color:var(--disabled);color:var(--text-light)}input[type=range]{padding:0}abbr[title]{cursor:help;text-decoration-line:underline;text-decoration-style:dotted}button:enabled:hover,.button:not([aria-disabled=true]):hover,input[type=submit]:enabled:hover,input[type=reset]:enabled:hover,input[type=button]:enabled:hover,label[type=button]:hover{background-color:var(--accent-hover);border-color:var(--accent-hover);cursor:pointer}.button:focus-visible,button:focus-visible:where(:enabled),input:enabled:focus-visible:where([type=submit],[type=reset],[type=button]){outline:2px solid var(--accent);outline-offset:1px}header>nav{padding:1rem 0 0;font-size:1rem;line-height:2}header>nav ul,header>nav ol{flex-flow:wrap;place-content:space-around center;align-items:center;margin:0;padding:0;list-style-type:none;display:flex}header>nav ul li,header>nav ol li{display:inline-block}header>nav a,header>nav a:visited{border:1px solid var(--border);border-radius:var(--standard-border-radius);color:var(--text);margin:0 .5rem 1rem;padding:.1rem 1rem;text-decoration:none;display:inline-block}header>nav a:hover,header>nav a.current,header>nav a[aria-current=page]{border-color:var(--accent);color:var(--accent);cursor:pointer}@media only screen and (width<=720px){header>nav a{border:none;padding:0;line-height:1;text-decoration:underline}}aside,details,pre,progress{background-color:var(--accent-bg);border:1px solid var(--border);border-radius:var(--standard-border-radius);margin-bottom:1rem}aside{float:right;width:30%;margin-inline-start:15px;padding:0 15px;font-size:1rem}[dir=rtl] aside{float:left}@media only screen and (width<=720px){aside{float:none;width:100%;margin-inline-start:0}}article,fieldset,dialog{border:1px solid var(--border);border-radius:var(--standard-border-radius);margin-bottom:1rem;padding:1rem}article h2:first-child,section h2:first-child,article h3:first-child,section h3:first-child{margin-top:1rem}section{border-top:1px solid var(--border);border-bottom:1px solid var(--border);margin:3rem 0;padding:2rem 1rem}section+section,section:first-child{border-top:0;padding-top:0}section+section{margin-top:0}section:last-child{border-bottom:0;padding-bottom:0}details{padding:.7rem 1rem}summary{cursor:pointer;word-break:break-all;margin:-.7rem -1rem;padding:.7rem 1rem;font-weight:700}details[open]>summary+*{margin-top:0}details[open]>summary{margin-bottom:.5rem}details[open]>:last-child{margin-bottom:0}table{border-collapse:collapse;margin:1.5rem 0}figure>table{width:max-content;margin:0}td,th{border:1px solid var(--border);text-align:start;padding:.5rem}th{background-color:var(--accent-bg);font-weight:700}tr:nth-child(2n){background-color:var(--accent-bg)}table caption{margin-bottom:.5rem;font-weight:700}textarea,select,input,button,.button{font-size:inherit;border-radius:var(--standard-border-radius);box-shadow:none;max-width:100%;margin-bottom:.5rem;padding:.5rem;font-family:inherit;display:inline-block}textarea,select,input{color:var(--text);background-color:var(--bg);border:1px solid var(--border)}label{display:block}textarea:not([cols]){width:100%}select:not([multiple]){background-image:linear-gradient(45deg,transparent 49%,var(--text)51%),linear-gradient(135deg,var(--text)51%,transparent 49%);background-position:calc(100% - 15px),calc(100% - 10px);background-repeat:no-repeat;background-size:5px 5px,5px 5px;padding-inline-end:25px}[dir=rtl] select:not([multiple]){background-position:10px,15px}input[type=checkbox],input[type=radio]{vertical-align:middle;width:min-content;position:relative}input[type=checkbox]+label,input[type=radio]+label{display:inline-block}input[type=radio]{border-radius:100%}input[type=checkbox]:checked,input[type=radio]:checked{background-color:var(--accent)}input[type=checkbox]:checked:after{content:" ";border-right:solid var(--bg).08em;border-bottom:solid var(--bg).08em;background-color:#0000;border-radius:0;width:.18em;height:.32em;font-size:1.8em;position:absolute;top:.05em;left:.17em;transform:rotate(45deg)}input[type=radio]:checked:after{content:" ";background-color:var(--bg);border-radius:100%;width:.25em;height:.25em;font-size:32px;position:absolute;top:.125em;left:.125em}@media only screen and (width<=720px){textarea,select,input{width:100%}}input[type=color]{height:2.5rem;padding:.2rem}input[type=file]{border:0}hr{background:var(--border);border:none;height:1px;margin:1rem auto}mark{border-radius:var(--standard-border-radius);background-color:var(--marked);color:#000;padding:2px 5px}mark a{color:#0d47a1}img,video{border-radius:var(--standard-border-radius);max-width:100%;height:auto}figure{margin:0;display:block;overflow-x:auto}figure>img,figure>picture>img{margin-inline:auto;display:block}figcaption{text-align:center;color:var(--text-light);margin-block:1rem;font-size:.9rem}blockquote{border-inline-start:.35rem solid var(--accent);color:var(--text-light);margin-block:2rem;margin-inline:2rem 0;padding:.4rem .8rem;font-style:italic}cite{color:var(--text-light);font-size:.9rem;font-style:normal}dt{color:var(--text-light)}code,pre,pre span,kbd,samp{font-family:var(--mono-font);color:var(--code)}kbd{color:var(--preformatted);border:1px solid var(--preformatted);border-bottom:3px solid var(--preformatted);border-radius:var(--standard-border-radius);padding:.1rem .4rem}pre{color:var(--preformatted);max-width:100%;padding:1rem 1.4rem;overflow:auto}pre code{color:var(--preformatted);background:0 0;margin:0;padding:0}progress{width:100%}progress:indeterminate{background-color:var(--accent-bg)}progress::-webkit-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent-bg)}progress::-webkit-progress-value{border-radius:var(--standard-border-radius);background-color:var(--accent)}progress::-moz-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent);transition-property:width;transition-duration:.3s}progress:indeterminate::-moz-progress-bar{background-color:var(--accent-bg)}dialog{max-width:40rem;margin:auto}dialog::backdrop{background-color:var(--bg);opacity:.8}@media only screen and (width<=720px){dialog{max-width:100%;margin:auto 1em}}sup,sub{vertical-align:baseline;position:relative}sup{top:-.4em}sub{top:.3em}.notice{background:var(--accent-bg);border:2px solid var(--border);border-radius:var(--standard-border-radius);margin:2rem 0;padding:1.5rem}
+\ No newline at end of file
diff --git a/assets/img/favicon.ico b/assets/img/favicon.ico
Binary files differ.
diff --git a/assets/img/favicon.png b/assets/img/favicon.png
Binary files differ.
diff --git a/config/config.php b/config/config.php
@@ -0,0 +1,221 @@
+<?php
+
+/* Define Search Engines
+* Insert "%s" for the search-query part
+* Bangs are defined here as well. They may ONLY contain [A-Za-z0-9_\-\.].
+*/
+
+$searches = array(
+ /* MetaGer */
+ "metager_web" => array(
+ "name" => "MetaGer Web",
+ "website" => "https://metager.de/?focus=web",
+ "query" => "https://metager.de/meta/meta.ger3?focus=web&eingabe=%s",
+ "bangs" => array("metager_web", "metager", "meta", "mg")
+ ),
+ "metager_img" => array(
+ "name" => "MetaGer Images",
+ "website" => "https://metager.de/focus=bilder",
+ "query" => "https://metager.de/meta/meta.ger3?focus=bilder&eingabe=%s",
+ "bangs" => array("metager_img")
+ ),
+ "metager_shop" => array(
+ "name" => "MetaGer Products",
+ "website" => "https://metager.de/focus=produkte",
+ "query" => "https://metager.de/meta/meta.ger3?focus=produkte&eingabe=%s",
+ "bangs" => array("metager_shop")
+ ),
+ "metager_news" => array(
+ "name" => "MetaGer Nachrichten",
+ "website" => "https://metager.de/?fokus=nachrichten",
+ "query" => "https://metager.de/meta/meta.ger3?focus=nachrichten&eingabe=%s",
+ "bangs" => array("metager_news")
+ ),
+ "metager_sci" => array(
+ "name" => "MetaGer Nachrichten",
+ "website" => "https://metager.de/?fokus=science",
+ "query" => "https://metager.de/meta/meta.ger3?focus=science&eingabe=%s",
+ "bangs" => array("metager_sci")
+ ),
+ "metager_map" => array(
+ "name" => "MetaGer Maps",
+ "website" => "https://metager.de/?fokus=maps",
+ "query" => "https://maps.metager.de/en/%s/guess",
+ "bangs" => array("metager_map")
+ ),
+ /* Qwant */
+ "qwant_web" => array(
+ "name" => "Qwant Web",
+ "website" => "https://qwant.com/?t=web",
+ "query" => "https://www.qwant.com/?t=web&q=%s",
+ "bangs" => array("qwant_web", "qwant", "qw")
+ ),
+ "qwant_img" => array(
+ "name" => "Qwant Images",
+ "website" => "https://qwant.com/?t=images",
+ "query" => "https://www.qwant.com/?t=images&q=%s",
+ "bangs" => array("qwant_img", "img")
+ ),
+ "qwant_video" => array(
+ "name" => "Qwant Videos",
+ "website" => "https://qwant.com/?t=videos",
+ "query" => "https://www.qwant.com/?t=videos&q=%s",
+ "bangs" => array("qwant_video")
+ ),
+ "qwant_shop" => array(
+ "name" => "Qwant Shopping",
+ "website" => "https://qwant.com/?t=shopping",
+ "query" => "https://www.qwant.com/?t=shopping&q=%s",
+ "bangs" => array("qwant_shop")
+ ),
+ "qwant_news" => array(
+ "name" => "Qwant News",
+ "website" => "https://qwant.com/?t=news",
+ "query" => "https://www.qwant.com/?t=news&q=%s",
+ "bangs" => array("qwant_news")
+ ),
+ /* Mojeek */
+ "mojeek_web" => array(
+ "name" => "Mojeek Web",
+ "website" => "https://mojeek.com",
+ "query" => "https://www.mojeek.com/search?q=%s",
+ "bangs" => array("mojeek_web", "mojeek", "mj")
+ ),
+ "mojeek_img" => array(
+ "name" => "Mojeek Images",
+ "website" => "https://mojeek.com/images",
+ "query" => "https://www.mojeek.com/search?fmt=images&q=%s",
+ "bangs" => array("mojeek_img")
+
+ ),
+ "mojeek_news" => array(
+ "name" => "Mojeek News",
+ "website" => "https://mojeek.com/news",
+ "query" => "https://www.mojeek.com/search?fmt=news&q=%s",
+ "bangs" => array("mojeek_news")
+ ),
+ "mojeek_summary" => array(
+ "name" => "Mojeek Summary",
+ "website" => "https://mojeek.com/?fmt=summary",
+ "query" => "https://www.mojeek.com/search?fmt=summary&q=%s",
+ "bangs" => array("mojeek_summary", "summary")
+ ),
+ /* Translations */
+ "leo" => array(
+ "name" => "LEO (en-de)",
+ "website" => "https://leo.org",
+ "query" => "https://dict.leo.org/englisch-deutsch/%s",
+ "bangs" => array("leo", "translate", "tl")
+ ),
+ "dictcc" => array(
+ "name" => "dict.cc",
+ "website" => "https://dict.cc",
+ "query" => "https://www.dict.cc/?s=%s",
+ "bangs" => array("dictcc", "dict")
+ ),
+ "deepl" => array(
+ "name" => "DeepL (en-de)",
+ "website" => "https://deepl.com",
+ "query" => "https://www.deepl.com/translator#de/en/%s",
+ "bangs" => array("deepl")
+ ),
+ /* Videos */
+ "youtube" => array(
+ "name" => "YouTube",
+ "website" => "https://www.youtube.com",
+ "query" => "https://www.youtube.com/results?search_query=%s",
+ "bangs" => array("youtube", "yt")
+ ),
+ /* Maps */
+ "openstreetmap" => array(
+ "name" => "OpenStreetMap",
+ "website" => "https://www.openstreetmap.org",
+ "query" => "https://www.openstreetmap.org/search?query=%s",
+ "bangs" => array("openstreetmap", "osm", "map", "maps")
+ ),
+ /* Science */
+ "base" => array(
+ "name" => "BASE",
+ "website" => "https://www.base-search.net",
+ "query" => "https://www.base-search.net/Search/Results?lookfor=%s",
+ "bangs" => array("base", "basesearch", "science")
+ ),
+ /* Wikis */
+ "wikipedia" => array(
+ "name" => "Wikipedia",
+ "website" => "https://wikipedia.org",
+ "query" => "https://wikipedia.org/wiki/%s",
+ "bangs" => array("wikipedia", "wiki", "wp")
+ ),
+ "archwiki" => array(
+ "name" => "Arch Wiki",
+ "website" => "https://wiki.archlinux.org",
+ "query" => "https://wiki.archlinux.org/index.php?search=%s",
+ "bangs" => array("archwiki", "arch", "aw")
+ ),
+ "debianwiki" => array(
+ "name" => "Debian Wiki",
+ "website" => "https://wiki.debian.org",
+ "query" => "https://wiki.debian.org/?action=fullsearch&value=%s",
+ "bangs" => array("debianwiki", "debian", "dw")
+ ),
+ /* Stackoverflow */
+ "stackoverflow" => array(
+ "name" => "Stackoverflow",
+ "website" => "https://stackoverflow.com",
+ "query" => "https://stackoverflow.com/search?q=%s",
+ "bangs" => array("stackoverflow", "so", "code")
+ ),
+ /* Code Repositories */
+ "github" => array(
+ "name" => "GitHub",
+ "website" => "https://github.com",
+ "query" => "https://github.com/search?q=%s",
+ "bangs" => array("github", "gh")
+ ),
+ "codeberg" => array(
+ "name" => "Codeberg",
+ "website" => "https://codeberg.org",
+ "query" => "https://codeberg.org/explore/repos?only_show_relevant=true&q=%s",
+ "bangs" => array("codeberg", "cb")
+ ),
+ "sourcehut" => array(
+ "name" => "Sourcehut",
+ "website" => "https://sr.ht",
+ "query" => "https://sr.ht/projects?search=%s",
+ "bangs" => array("sourcehut", "srht", "sir")
+ ),
+ /* Software Repositories */
+ "dockerhub" => array(
+ "name" => "dockerhub",
+ "website" => "https://hub.docker.com",
+ "query" => "https://hub.docker.com/search?q=%s",
+ "bangs" => array("dockerhub", "docker", "dh")
+ ),
+ "flathub" => array(
+ "name" => "Flathub",
+ "website" => "https://flathub.org",
+ "query" => "https://flathub.org/apps/search?q=%s",
+ "bangs" => array("flathub", "flatpak", "fh", "fp")
+ ),
+ "fdroid" => array(
+ "name" => "F-Droid",
+ "website" => "https://f-droid.org",
+ "query" => "https://search.f-droid.org/?q=%s",
+ "bangs" => array("fdroid", "f-droid", "fd", "android")
+ ),
+ /* Misc */
+ "symbl" => array(
+ "name" => "SYMBL",
+ "website" => "https://symbl.cc",
+ "query" => "https://symbl.cc/search/?q=%s",
+ "bangs" => array("symbl", "symbol", "unicode")
+ )
+);
+
+/* Default Search Engine
+* Search Engine to use when no (known) bang is given
+*/
+$default_search = "metager_web";
+
+?>
diff --git a/index.php b/index.php
@@ -0,0 +1,148 @@
+<!doctype html>
+<html>
+
+<!-- Process input (if given) -->
+<?php
+
+/* Load Configuration */
+include("config/config.php");
+
+/* Fetch and sanitize search query via GET request */
+$_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+$query = rawurldecode($_GET["query"]);
+
+if (strlen($query) > 0) {
+
+ /* Find bang (only first one is considered) */
+ $bang = preg_replace(
+ '/^.*?\!([A-Za-z0-9_\-\.]+).*$/',
+ '${1}',
+ $query
+ );
+
+ /* Based on given bang, choose a search engine */
+ $search = null;
+ foreach (array_keys($searches) as $key) {
+ if (array_search($bang, $searches[$key]["bangs"]) != "") {
+ /* If bang has been found, assign search engine and end the loop */
+ $search = $key;
+ break;
+ }
+ }
+
+ /* Fallback search engine, if bang wasn't found */
+ if (is_null($search)) {
+ $search = $default_search;
+ }
+
+ /* Strip bang from search term */
+ $search_term = preg_replace(
+ '/\![A-Za-z0-9_\-\.]+/',
+ '',
+ $query
+ );
+
+ /* Remove leading or trailing spaces */
+ $search_term = preg_replace(
+ '/\ /',
+ '',
+ $search_term
+ );
+
+ /* Construct search query */
+ $target = str_replace(
+ '%s',
+ rawurlencode($search_term),
+ $searches[$search]["query"]
+ );
+
+ /* Redirect to target search engine: 307 - Temporary Redirect */
+ header("Location: " . $target, true, 307);
+ die();
+}
+
+?>
+
+
+ <!-- Head -->
+ <head>
+ <meta charset=utf-8>
+ <meta name=viewport content="width=device-width,initial-scale=1">
+ <link rel=icon href=/assets/img/favicon.png type=image/png>
+ <link rel=icon href=/assets/img/favicon.ico type=x-image/ico>
+ <link rel=stylesheet href=/assets/css/simple.min.css media=all>
+ <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="traserĉi - search with !bangs">
+ <title>traserĉi - search with !bangs</title>
+ <style>
+ #searchbar {
+ width: 100%;
+ }
+ </style>
+ </head>
+
+ <!-- Body -->
+ <body>
+
+ <!-- Search Bar -->
+ <h1>Search with !bangs</h1>
+ <form action="/" method="get">
+ <input
+ id="searchbar"
+ name="query"
+ type="text"
+ autofocus
+ placeholder="🔎 Search the web"
+ alt="Search the web"
+ onkeydown="if(event.keyCode===13)return this.form.submit(),!1"
+ >
+ </form>
+
+ <!-- FAQs -->
+ <h3>FAQs</h3>
+ <ul>
+ <li><a href="https://translate.google.com/?sl=eo&text=traser%C4%89i&op=translate" target="_blank">
+ What does <em>traserĉi</em> mean?
+ </a></li>
+ <li><a href="https://duckduckgo.com/bangs" target="_blank">
+ What are bangs?
+ </a></li>
+ <li><a href="https://src.jayvii.de/pub/traserci" target="_blank">
+ Help with development!
+ </a></li>
+ </ul>
+
+ <!-- Search Engine Table -->
+ <h3>Search Engines</h3>
+ <table>
+ <tr>
+ <th>Engine</th>
+ <th>Bangs</th>
+ <tr>
+
+<?php
+/* Print one table row for each configured search engine */
+$engine_table = "";
+foreach (array_keys($searches) as $key) {
+ $engine_table .= "<tr><td>" .
+ "<a href=\"" . $searches[$key]["website"] . "\" " .
+ "target=\"_blank\">" .
+ $searches[$key]["name"];
+
+ if ($key == $default_search) {
+ $engine_table .= " (default)";
+ }
+
+ $engine_table .= "</a>" .
+ "</td><td>" .
+ "<code>!" . implode("</code>, <code>!", $searches[$key]["bangs"]) .
+ "</code>" .
+ "</td></tr>";
+}
+echo $engine_table;
+?>
+
+ </table>
+ </body>
+
+</html>
+
diff --git a/opensearch.xml b/opensearch.xml
@@ -0,0 +1,8 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>traserĉi</ShortName>
+ <Description>Search with !bangs</Description>
+ <Url type="text/html" method="get" template="%%URL%%/?query={searchTerms}"/>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKBgUACgYFBQwHB0wIBQWTCAUEnAgFBJwIBQScCAUEnAgFBJwIBQScCAUEnAgFBJwIBQWcBgQDnAAAAIMAAAA01nt2AK9lYE2fW1fWkFNP9o9ST/iPUk/4j1JP+I9ST/iPUk/4j1JP+I9ST/iOUk74jVFN+IhOS/g8IiHnAAAAg7FmYQXDcWyuxnJt/8dzbv/Hc27/x3Nu/8dzbv/Hc27/x3Nu/8dzbv/Hcm3/t2pl/5tZVf+1Z2P/ilBM+AcEBJy3aWUHw3FstcRxbP/EcWz/xHFs/8RxbP/EcWz/xHFs/8RxbP/EcWz/tmtm/3tdW/9ZREP/nltX/41RTvgIBQWct2llB8NxbLXEcWz/xHFs/8RxbP/DcGv/wG5p/75taP/Bb2r/uWtn/4RiYP9hYGD/f11b/7lrZv+OUk74CAUEnLdpZQfDcWy1xHFs/8RxbP++bWj/q2Rh/5RbWf+IUU7/kVJP/4ReXP9kYWD/hmJg/7hrZ//Hcm3/j1JP+AgFBJy3aWUHw3FstcRxbP+/b2r/sXx0/7CTff+wmoD/mot+/4Nzcf9ybGv/hV9c/7psZ//EcWz/x3Nu/49ST/gIBQSct2llB8NxbLXDcGv/uoN8/8epf//Ur3D/17J1/97Fmf/Lv6r/gnBv/5RUUP/Bb2r/xHFs/8dzbv+PUk/4CAUEnLdpZQfEcGu1wnRw/7WWhP/PrW//061s/9Stbf/Vr3D/2LiC/5iIff+MUk//v25p/8RxbP/Hc27/j1JP+AgFBJy3aWUHxHBrtcF4dP+tloP/2cGT/9a4f//RrWz/061s/9WvcP+nkn7/mlxZ/8Fvav/EcWz/x3Nu/49ST/gIBQSct2llB8Nwa7XFd3L/rZWM/9TGrf/r3sP/2b6L/9Ota//SsHX/qYd5/7JmYv/EcWz/xHFs/8dzbv+PUk/4CAUEnLdpZQfDcWy1xXFs/76Jhv+rn5T/08q4/9W9kv/HqG//v6GD/7Rzb//Bb2r/xHFs/8RxbP/Hc27/j1JP+AgFBJy4amYHxHFstcRxbP/Fc27/vYWC/6iLhP+qj4L/s46D/7x6df/Cb2r/xHFs/8RxbP/EcWz/x3Nu/5BTT/YIBQWTwG9qBcRxbLDEcWz/xHFs/8Rwa//Ec2//w3Vx/8Nybf/EcGv/xHFs/8RxbP/EcWz/xHFs/8Zybf+eW1fVCwYGTMRxbADEcWxNxHFsscRxbLXDcWy1w3FstcRwa7XEcWy1w3FstcNxbLXDcWy1w3FstcNxbLXDcWyvr2VgTQAAAAXEcWwAxXFsAcBvaga4amYHt2llB7dpZQe3aWUHt2llB7dpZQe3aWUHt2llB7dpZQe3aWUHtmlkBbBlYQCvZWAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgAEAAA==</Image> <InputEncoding>UTF-8</InputEncoding>
+ <moz:SearchForm>%%URL%%</moz:SearchForm>
+</OpenSearchDescription>
+