Compare commits

..

No commits in common. "main" and "v3.3.10" have entirely different histories.

11 changed files with 41 additions and 136 deletions

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{ {
"name": "@fwdekker/template", "name": "@fwdekker/template",
"version": "3.6.5", "version": "3.3.10",
"description": "The base template for pages on fwdekker.com.", "description": "The base template for pages on fwdekker.com.",
"author": "Florine W. Dekker", "author": "Florine W. Dekker",
"license": "MIT", "license": "MIT",
@ -24,20 +24,20 @@
"deploy": "grunt deploy" "deploy": "grunt deploy"
}, },
"dependencies": { "dependencies": {
"@picocss/pico": "^1.5.10" "@picocss/pico": "^1.5.6"
}, },
"devDependencies": { "devDependencies": {
"grunt": "^1.6.1", "grunt": "^1.5.3",
"grunt-cli": "^1.4.3", "grunt-cli": "^1.4.3",
"grunt-contrib-clean": "^2.0.1", "grunt-contrib-clean": "^2.0.1",
"grunt-contrib-cssmin": "^5.0.0", "grunt-contrib-cssmin": "^4.0.0",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"grunt-focus": "^1.0.0", "grunt-focus": "^1.0.0",
"grunt-webpack": "^6.0.0", "grunt-webpack": "^5.0.0",
"ts-loader": "^9.5.1", "ts-loader": "^9.4.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.3.2", "typescript": "^4.9.3",
"webpack": "^5.89.0", "webpack": "^5.75.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.0.0"
} }
} }

View File

@ -4,5 +4,5 @@
@import "snippets/colors.css"; @import "snippets/colors.css";
@import "snippets/common.css"; @import "snippets/common.css";
@import "snippets/forms.css";
@import "snippets/nav.css"; @import "snippets/nav.css";
@import "snippets/validation.css";

View File

@ -1,39 +1,12 @@
/* pico.css overrides, based on https://picocss.com/docs/customization.html */ /* pico.css overrides, based on https://picocss.com/docs/customization.html */
:root {
/* Light (default) */
.fwd-nav,
[data-theme="light"],
:root:not([data-theme="dark"]) {
--primary: rgb(0, 51, 204) !important; --primary: rgb(0, 51, 204) !important;
--primary-hover: rgb(0, 61, 245) !important; --primary-hover: rgb(0, 61, 245) !important;
--primary-focus: rgba(0, 41, 163, 0.125) !important; --primary-focus: rgba(0, 41, 163, 0.125) !important;
--primary-focus-opaque: rgb(0, 41, 163) !important; --primary-focus-opaque: rgb(0, 41, 163) !important;
--primary-focus-dark: rgb(0, 29, 114) !important; --primary-focus-dark: rgb(0, 29, 114) !important;
--primary-inverse: white !important; --primary-inverse: white !important;
}
/* Dark (auto) */
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme="light"]):not(.fwd-nav) {
--primary: #1e88e5 !important;
--primary-hover: #2196f3 !important;
--primary-focus: rgba(30, 136, 229, 0.25) !important;
--primary-focus-opaque: rgb(30, 136, 229) !important;
--primary-inverse: white !important;
}
}
/* Dark (forced) */
:root[data-theme="dark"]:not(.fwd-nav) {
--primary: #1e88e5 !important;
--primary-hover: #2196f3 !important;
--primary-focus: rgba(30, 136, 229, 0.25) !important;
--primary-focus-opaque: rgb(30, 136, 229) !important;
--primary-inverse: white !important;
}
/* Common */
:root {
--form-element-active-border-color: var(--primary) !important; --form-element-active-border-color: var(--primary) !important;
--form-element-focus-color: var(--primary-focus) !important; --form-element-focus-color: var(--primary-focus) !important;
--switch-color: var(--primary-inverse) !important; --switch-color: var(--primary-inverse) !important;

View File

@ -53,11 +53,6 @@ a[target="_blank"]::after {
.grid-with-sidebar aside { .grid-with-sidebar aside {
max-width: var(--aside-width); max-width: var(--aside-width);
} }
.grid-with-sidebar aside .sticky {
position: sticky;
top: var(--block-spacing-vertical);
}
} }
@media (max-width: 992px) { @media (max-width: 992px) {
@ -80,7 +75,7 @@ noscript.fwd-js-notice p {
/* Header */ /* Header */
header.fwd-header a[href="."] { header.fwd-header a[href="."] {
color: unset; color: black;
} }

View File

@ -1,25 +1,14 @@
/* pico.css: Improved text contrast (see also picocss/pico#276) */ /* pico.css: Improved text contrast (see also picocss/pico#276) */
:root { :root {
--code-color: var(--color) !important; --code-color: var(--color) !important;
}
:root[data-theme="light"] {
--muted-color: hsl(205deg, 15%, 41%) !important; --muted-color: hsl(205deg, 15%, 41%) !important;
} }
:root[data-theme="dark"] { /* pico.css: Bold labels, except for checkbox/radio labels */
--muted-color: hsl(205deg, 12%, 59%) !important;
}
/* pico.css: Bold <label> and <th>, except for checkbox/radio labels */
:root { :root {
--form-label-font-weight: bold; --form-label-font-weight: bold;
} }
tr th {
font-weight: bold;
}
input:where([type="checkbox"], [type="radio"]) + label { input:where([type="checkbox"], [type="radio"]) + label {
font-weight: normal; font-weight: normal;
} }
@ -69,12 +58,8 @@ article > header {
} }
article > footer { article > footer {
margin-top: calc(var(--block-spacing-vertical) / 2);
margin-bottom: calc(var(--block-spacing-vertical) / -2); margin-bottom: calc(var(--block-spacing-vertical) / -2);
} margin-top: calc(var(--block-spacing-vertical) / 2);
article > footer > form > article {
margin-top: 0;
} }
article > header > hgroup, article > header > hgroup,

View File

@ -1,21 +1,14 @@
/* Status card */
.status-card { .status-card {
font-weight: bold; font-weight: bold;
position: relative;
}
.status-card output {
display: block;
margin-right: var(--block-spacing-horizontal);
} }
.status-card .close { .status-card .close {
position: absolute; float: right;
right: var(--block-spacing-horizontal); display: block;
top: calc(var(--block-spacing-vertical) / 2);
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
margin: 0 0 calc(var(--block-spacing-vertical) / 2) calc(var(--block-spacing-vertical) / 2);
background-image: var(--icon-close); background-image: var(--icon-close);
background-position: center; background-position: center;
@ -53,7 +46,6 @@
} }
/* Input validation */
label.invalid, label.invalid,
*[data-label-for].invalid, *[data-label-for].invalid,
input.invalid, input.invalid,
@ -67,21 +59,3 @@ input.valid,
*[data-hint-for].valid { *[data-hint-for].valid {
color: var(--form-element-valid-border-color) !important; color: var(--form-element-valid-border-color) !important;
} }
/* Enable hint-like styling on any element */
.input-hint {
display: block;
/*noinspection CssUnresolvedCustomProperty*/
margin-top: calc(var(--spacing) * -.75);
}
/* Custom components */
.inline-button {
display: inline-block;
width: unset;
margin: 0;
padding: 0.3em;
}

View File

@ -20,6 +20,7 @@ export interface Storage {
* *
* @param name the name of the array to store * @param name the name of the array to store
* @param value the array to store under the given name * @param value the array to store under the given name
* @protected
*/ */
setArray(name: string, value: any[]): void; setArray(name: string, value: any[]): void;
@ -28,6 +29,7 @@ export interface Storage {
* *
* @param name the name of the boolean to retrieve * @param name the name of the boolean to retrieve
* @param def the value to return if no boolean is stored with the given name * @param def the value to return if no boolean is stored with the given name
* @protected
*/ */
getBoolean(name: string, def: boolean): boolean; getBoolean(name: string, def: boolean): boolean;
@ -36,6 +38,7 @@ export interface Storage {
* *
* @param name the name of the boolean to store * @param name the name of the boolean to store
* @param value the boolean to store under the given name * @param value the boolean to store under the given name
* @protected
*/ */
setBoolean(name: string, value: boolean): void; setBoolean(name: string, value: boolean): void;
@ -44,6 +47,7 @@ export interface Storage {
* *
* @param name the name of the number to retrieve * @param name the name of the number to retrieve
* @param def the value to return if no number is stored with the given name * @param def the value to return if no number is stored with the given name
* @protected
*/ */
getNumber(name: string, def: number): number; getNumber(name: string, def: number): number;
@ -52,24 +56,9 @@ export interface Storage {
* *
* @param name the name of the number to store * @param name the name of the number to store
* @param value the number to store under the given name * @param value the number to store under the given name
* @protected
*/ */
setNumber(name: string, value: number): void; setNumber(name: string, value: number): void;
/**
* Retrieves a string from storage.
*
* @param name the name of the string to retrieve
* @param def the value to return if no string is stored with the given name
*/
getString(name: string, def: string): string;
/**
* Stores a string.
*
* @param name the name of the string to store
* @param value the number to store under the given name
*/
setString(name: string, value: string): void;
} }
/** /**
@ -126,32 +115,28 @@ export class LocalStorage implements Storage {
} }
setArray(name: string, value: any[]): void { setArray(name: string, value: any[]): void {
this.setString(name, JSON.stringify(value)); const item = this.read();
item[name] = JSON.stringify(value);
this.write(item);
} }
getBoolean(name: string, def: boolean = false): boolean { getBoolean(name: string, def: boolean = false): boolean {
return this.getString(name, def ? "true" : "false") === "true"; return (this.read()[name] ?? `${def}`) === "true";
} }
setBoolean(name: string, value: boolean): void { setBoolean(name: string, value: boolean): void {
this.setString(name, "" + value); const item = this.read();
item[name] = "" + value;
this.write(item);
} }
getNumber(name: string, def: number = 0): number { getNumber(name: string, def: number = 0): number {
return +this.getString(name, "" + def); return +(this.read()[name] ?? def);
} }
setNumber(name: string, value: number): void { setNumber(name: string, value: number): void {
this.setString(name, "" + value);
}
getString(name: string, def: string = ""): string {
return this.read()[name] ?? def;
}
setString(name: string, value: string): void {
const item = this.read(); const item = this.read();
item[name] = value; item[name] = "" + value;
this.write(item); this.write(item);
} }
} }
@ -190,12 +175,4 @@ export class MemoryStorage implements Storage {
getNumber(name: string, def: number): number { getNumber(name: string, def: number): number {
return this.storage[name] ?? def; return this.storage[name] ?? def;
} }
setString(name: string, value: string): void {
this.storage[name] = value;
}
getString(name: string, def: string): string {
return this.storage[name] ?? def;
}
} }

View File

@ -199,7 +199,7 @@ function footer(
if (author === undefined) author = "Florine&nbsp;W.&nbsp;Dekker"; if (author === undefined) author = "Florine&nbsp;W.&nbsp;Dekker";
if (authorURL === undefined) authorURL = "https://fwdekker.com/"; if (authorURL === undefined) authorURL = "https://fwdekker.com/";
if (license === undefined) license = "MIT"; if (license === undefined) license = "MIT";
if (licenseURL === undefined && vcsURL !== undefined) licenseURL = `${vcsURL}src/branch/main/LICENSE`; if (licenseURL === undefined && vcsURL !== undefined) licenseURL = `${vcsURL}src/branch/master/LICENSE`;
if (vcs === undefined && vcsURL !== undefined) vcs = "git"; if (vcs === undefined && vcsURL !== undefined) vcs = "git";
if (privacyPolicyURL === undefined) privacyPolicyURL = "https://fwdekker.com/privacy/"; if (privacyPolicyURL === undefined) privacyPolicyURL = "https://fwdekker.com/privacy/";
@ -243,6 +243,9 @@ function footerLink(prefix: string, text: string | null | undefined, url: string
* not a valid value as a parameter for that function, its value is considered `undefined`. * not a valid value as a parameter for that function, its value is considered `undefined`.
*/ */
doAfterLoad(() => { doAfterLoad(() => {
const html = $("html")!;
if (html.dataset["theme"] == null) html.dataset["theme"] = "light";
const navTarget = $(getMetaProperty("fwd:nav:target")); const navTarget = $(getMetaProperty("fwd:nav:target"));
if (navTarget != null) { if (navTarget != null) {
navTarget.parentElement?.replaceChild( navTarget.parentElement?.replaceChild(

View File

@ -27,23 +27,21 @@ export function showMessageType(card: HTMLElement | HTMLFormElement,
message?: string, message?: string,
type?: "busy" | "error" | "info" | "success" | "warning"): void { type?: "busy" | "error" | "info" | "success" | "warning"): void {
if (card instanceof HTMLFormElement) { if (card instanceof HTMLFormElement) {
if (card.dataset.statusCard == null) return; const formCard = $(`article[data-status-for="${card.id}"]`);
const formCard = $(`#${card.dataset.statusCard}`);
if (formCard == null) return; if (formCard == null) return;
card = formCard; card = formCard;
} }
const output = $("output", card)!; const output = $("output", card)!;
output.removeAttribute("aria-busy"); card.removeAttribute("aria-busy");
card.classList.remove("hidden", "error", "info", "success", "warning"); card.classList.remove("hidden", "error", "info", "success", "warning");
if (message == null || type == null) { if (message == null || type == null) {
card.classList.add("hidden"); card.classList.add("hidden");
output.innerHTML = ""; output.innerHTML = "";
} else { } else {
if (type === "busy") output.setAttribute("aria-busy", "true"); if (type === "busy") card.setAttribute("aria-busy", "true");
else card.classList.add(type); else card.classList.add(type);
output.innerHTML = message; output.innerHTML = message;
@ -202,7 +200,7 @@ doAfterLoad(() => {
}); });
}); });
$a("small[data-hint]").forEach((hint: Element) => { $a("input + small[data-hint]").forEach((hint: Element) => {
if (!(hint instanceof HTMLElement)) return; if (!(hint instanceof HTMLElement)) return;
hint.innerHTML = hint.dataset["hint"] ?? ""; hint.innerHTML = hint.dataset["hint"] ?? "";

View File

@ -61,8 +61,8 @@
<h3>Already have an account? Welcome back!</h3> <h3>Already have an account? Welcome back!</h3>
</hgroup> </hgroup>
</header> </header>
<form id="test-form" data-status-card="test-status-card" novalidate> <form id="test-form" novalidate>
<article id="test-status-card" class="status-card hidden"> <article class="status-card hidden" data-status-for="test-form">
<output>Congrats!</output> <output>Congrats!</output>
<a class="close" href="#" aria-label="Close"></a> <a class="close" href="#" aria-label="Close"></a>
</article> </article>