aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--http/public/do/bcrypt.php57
-rw-r--r--http/public/scripts/feed-condense-content.js67
-rw-r--r--http/src/css/base.uppcss18
-rw-r--r--http/src/css/feed.uppcss38
4 files changed, 95 insertions, 85 deletions
diff --git a/http/public/do/bcrypt.php b/http/public/do/bcrypt.php
new file mode 100644
index 0000000..a61dd9e
--- /dev/null
+++ b/http/public/do/bcrypt.php
@@ -0,0 +1,57 @@
+<?php
+
+const BCRYPT_COST = 12;
+
+$user = $_SERVER['PHP_AUTH_USER'] ?? null;
+$pass = $_SERVER['PHP_AUTH_PW'] ?? null;
+
+// Fallback for CGI / FastCGI SAPIs that don't populate PHP_AUTH_*.
+// Apache: add "CGIPassAuth On" (2.4.13+) or a RewriteRule to expose
+// HTTP_AUTHORIZATION; nginx+php-fpm passes it through by default.
+if ($user === null || $pass === null) {
+ $header = $_SERVER['HTTP_AUTHORIZATION']
+ ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION']
+ ?? null;
+
+ if (is_string($header) && preg_match('/^Basic\s+(.+)$/i', $header, $m) === 1) {
+ $decoded = base64_decode($m[1], true);
+
+ // Split on the FIRST colon only: passwords may contain colons.
+ if ($decoded !== false && str_contains($decoded, ':')) {
+ [$user, $pass] = explode(':', $decoded, 2);
+ }
+ }
+}
+
+if (!is_string($user) || $user === '' || !is_string($pass) || $pass === '') {
+ header('WWW-Authenticate: Basic realm="Restricted", charset="UTF-8"');
+ http_response_code(401);
+ header('Content-Type: text/plain; charset=utf-8');
+ header('Cache-Control: no-store');
+ echo 'authentication required';
+ exit;
+}
+
+if (strlen($pass) > 72) {
+ http_response_code(422);
+ header('Content-Type: text/plain; charset=utf-8');
+ header('Cache-Control: no-store');
+ echo 'password exceeds the 72-byte bcrypt limit';
+ exit;
+}
+
+$hash = password_hash($pass, PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]);
+
+if ($hash === false) {
+ http_response_code(500);
+ header('Content-Type: text/plain; charset=utf-8');
+ header('Cache-Control: no-store');
+ echo 'hashing failed';
+ exit;
+}
+
+header('Content-Type: text/plain; charset=utf-8');
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Pragma: no-cache');
+
+echo $user . ':' . $hash;
diff --git a/http/public/scripts/feed-condense-content.js b/http/public/scripts/feed-condense-content.js
deleted file mode 100644
index 984dbce..0000000
--- a/http/public/scripts/feed-condense-content.js
+++ /dev/null
@@ -1,67 +0,0 @@
-const expandedClass = "e-content--expanded";
-
-export function toggleContentExpanded(id) {
- const content = document.querySelector(`.e-content[data-content-id="${id}"]`);
- const buttonBefore = document.querySelector(
- `button:has(+ .e-content[data-content-id="${id}"])`,
- );
- const buttonAfter = document.querySelector(
- `.e-content[data-content-id="${id}"] + button`,
- );
-
- if (!content) {
- throw new Error(`No content found with ID: ${id}`);
- }
- const classes = content.getAttribute("class").split(" ");
- const wasExpanded = classes.includes(expandedClass);
- if (wasExpanded) {
- const newClasses = classes.filter((c) => c != expandedClass);
- content.setAttribute("class", newClasses.join(" "));
- } else {
- content.setAttribute("class", [...classes, expandedClass].join(" "));
- }
-
- if (wasExpanded) {
- if (!buttonBefore) {
- console.warn("Tried to remove 'Read less' button but could not find it");
- } else {
- buttonBefore.remove();
- }
- if (!buttonAfter) {
- console.warn(
- "Tried to toggle 'Read less' button to say 'Read more' but could not find it",
- );
- } else {
- buttonAfter.textContent = "Read more";
- }
- } else {
- if (!buttonAfter) {
- console.warn(
- "Tried to toggle 'Read more' button to say 'Read less' but could not find it",
- );
- } else {
- buttonAfter.textContent = "Read less";
- }
- if (buttonBefore) {
- buttonBefore.remove();
- }
- const newButtonBefore = createExpandCondenseButton(id, "Read less");
- content.insertAdjacentElement("beforebegin", newButtonBefore);
- }
-}
-
-export function addExpandItemButtons() {
- for (const content of document.querySelectorAll(".e-content")) {
- const contentId = crypto.randomUUID();
- content.setAttribute("data-content-id", contentId);
- const button = createExpandCondenseButton(contentId, "Read more");
- content.insertAdjacentElement("afterend", button);
- }
-}
-
-function createExpandCondenseButton(contentId, initialText) {
- const button = document.createElement("button");
- button.textContent = initialText;
- button.addEventListener("click", () => toggleContentExpanded(contentId));
- return button;
-}
diff --git a/http/src/css/base.uppcss b/http/src/css/base.uppcss
index e67e5c0..b7a0e83 100644
--- a/http/src/css/base.uppcss
+++ b/http/src/css/base.uppcss
@@ -73,9 +73,9 @@
--font-size-sm: 1rem;
--font-size-base: 1.125rem;
- --font-size-md: 1.5rem;
- --font-size-lg: 2rem;
- --font-size-xl: 3rem;
+ --font-size-md: 1.3rem;
+ --font-size-lg: 1.6rem;
+ --font-size-xl: 2rem;
--spacing-block-xs: 0.5rem;
--spacing-block-sm: 1.75rem;
@@ -89,6 +89,16 @@
--spacing-inline-xl: 6rem;
}
+@media (min-width: 60rem) {
+ :root {
+ --font-size-sm: 1rem;
+ --font-size-base: 1.25rem;
+ --font-size-md: 1.55rem;
+ --font-size-lg: 1.9rem;
+ --font-size-xl: 2.3rem;
+ }
+}
+
/** Light theme */
@media (prefers-color-scheme: light) {
:root {
@@ -204,7 +214,7 @@ img {
[margin-end grid-end];
--grid-total-width: 80rem;
- --grid-max-content-width: 40rem;
+ --grid-max-content-width: 50rem;
}
}
diff --git a/http/src/css/feed.uppcss b/http/src/css/feed.uppcss
index ae7c05d..5db8431 100644
--- a/http/src/css/feed.uppcss
+++ b/http/src/css/feed.uppcss
@@ -15,6 +15,25 @@ ul:has(> .h-entry) {
.h-feed .h-entry {
display: flex;
flex-direction: column;
+
+ &:has(details) {
+ margin-inline: calc(-1 * var(--spacing-inline-lg));
+ border: 2px solid;
+ padding-inline: var(--spacing-inline-lg);
+ padding-block: var(--spacing-block-sm);
+ background: var(--colour-primary-bg-accent);
+ cursor: pointer;
+ }
+
+ > details > summary {
+ margin-inline-start: -1rem;
+ &::marker {
+ font-size: 1rem;
+ }
+ :is(h2, h3, h4, h5, h6) {
+ display: inline;
+ }
+ }
}
.h-feed .h-entry + .h-entry {
@@ -26,6 +45,10 @@ ul:has(> .h-entry) {
margin: 0;
}
+.h-feed .h-entry summary :is(h2, h3, h4, h5, h6) {
+ margin: 0;
+}
+
.h-feed .h-entry .entry-meta {
order: 0;
font-size: var(--font-size-sm);
@@ -38,6 +61,7 @@ ul:has(> .h-entry) {
> * + *::before {
content: '•';
padding-inline: var(--spacing-inline-sm);
+ }
}
.h-feed .h-entry .p-name {
@@ -52,17 +76,3 @@ ul:has(> .h-entry) {
.h-feed :is(a.full-feed-link, .full-feed-link a)::after {
content: ' >';
}
-
-.h-feed .e-content {
- border-bottom: solid 1px;
- &:not(.e-content--expanded) {
- height: 10rem;
- overflow: clip;
- }
-}
-
-.h-feed .h-entry > button {
- width: max-content;
- align-self: center;
- margin-block-start: var(--spacing-block-xs);
-}