srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/styles.css
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-12-14 09:24:00 +0200
committersrdusr <[email protected]>2025-12-14 09:24:00 +0200
commit3dbebfbc9345d2603908f32c0dabebc0ff21feb3 (patch)
tree61a4f60187046a087424845eb8e22239ccd7d6ee /web/src/styles.css
parent8fc4440150ce84c9cc3f5ec13d50beb58a5b65bc (diff)
downloadtyperpunk-3dbebfbc9345d2603908f32c0dabebc0ff21feb3.tar.gz
typerpunk-3dbebfbc9345d2603908f32c0dabebc0ff21feb3.zip
Add community text submissions, and make custom text usable for study
Submissions - POST /api/texts proposes a passage; nothing reaches players until a moderator approves it. GET /api/texts serves the approved set, which the client merges on top of its bundled packs at startup. - Validation the server enforces rather than trusts: category from a fixed list, 40 to 600 characters, no control characters (a newline makes a passage untypeable in a single-line input), attribution length, and a unique index on md5(lower(btrim(content))) so the same passage cannot be submitted twice under different whitespace or casing. - Moderation is a flag on users. The queue and the review endpoint both refuse a non-moderator, and reviewing an already-reviewed submission is a 404 rather than a silent second write. - Submissions are rate limited per user: enough for a real contributor, not enough to fill the queue from a script. - A Contribute screen carries the form, your own submissions with their status, and - for moderators only - the review queue. This is the half of TypeRacer's model the packs could not reach by authoring: their corpus is roughly twelve thousand passages, grown by submission. Custom text as a study tool - Imported documents are kept between visits, with how far through each one you are. Custom text lived only in memory, so importing a set of notes and reloading the page lost them - fine for pasting a paragraph to race, useless for working through a file over several sittings. - Position is recorded when a segment is finished, not when the next is started, so closing the tab after a segment does not lose it. - Markdown is chunked as markdown: fenced code blocks are kept whole and typed line by line, and the decoration - hashes, asterisks, backticks, link brackets, table pipes - is stripped so what you retype is the material rather than the punctuation around it. - Everything stays on the device. Notes are not uploaded anywhere. Fixed while doing it: a chunk could contain a newline, which cannot be typed in a single-line input at all. Any paragraph with a line break inside it -- ordinary in notes and in wrapped prose - produced an unfinishable segment. Whitespace inside a chunk is now flattened.
Diffstat (limited to 'web/src/styles.css')
-rw-r--r--web/src/styles.css129
1 files changed, 129 insertions, 0 deletions
diff --git a/web/src/styles.css b/web/src/styles.css
index 6a04db2..bb92393 100644
--- a/web/src/styles.css
+++ b/web/src/styles.css
@@ -2920,3 +2920,132 @@ body::-webkit-scrollbar, .app::-webkit-scrollbar, #root::-webkit-scrollbar {
color: var(--primary-color);
border-color: var(--primary-color);
}
+
+/* Community submissions: the form, your own history, and the review queue. */
+.contribute-form { width: 100%; max-width: 34rem; }
+
+.contribute-count {
+ align-self: flex-end;
+ font-size: 0.7rem;
+ color: var(--sub-color);
+ font-family: 'JetBrains Mono', monospace;
+}
+
+.contribute-len.bad { color: var(--error-color); }
+
+.submission-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+ width: 100%;
+ max-width: 40rem;
+}
+
+.submission-row {
+ border: 1px solid var(--neutral-color);
+ border-radius: 8px;
+ padding: 0.75rem 0.9rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+ text-align: left;
+}
+
+.submission-head {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ font-size: 0.7rem;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: var(--sub-color);
+}
+
+.submission-status { margin-left: auto; }
+.submission-status.status-approved { color: var(--primary-color); }
+.submission-status.status-rejected { color: var(--error-color); }
+.submission-status.status-pending { color: var(--warning-color); }
+
+.submission-content {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.85rem;
+ color: var(--text-color);
+ line-height: 1.5;
+}
+
+.submission-attribution,
+.submission-reason {
+ font-size: 0.75rem;
+ color: var(--sub-color);
+}
+
+.submission-reason { color: var(--error-color); }
+
+.submission-actions { display: flex; gap: 0.5rem; }
+
+/* Imported documents, kept between visits. Notes are worked through over
+ several sittings, so the list shows how far into each one you are. */
+.custom-library {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ width: 100%;
+ margin-bottom: 0.5rem;
+}
+
+.custom-doc {
+ display: flex;
+ align-items: stretch;
+ gap: 0.35rem;
+}
+
+.custom-doc-open {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.15rem;
+ background: none;
+ border: 1px solid var(--neutral-color);
+ border-radius: 6px;
+ padding: 0.5rem 0.7rem;
+ color: var(--text-color);
+ font-family: inherit;
+ cursor: pointer;
+ text-align: left;
+ min-width: 0;
+}
+
+.custom-doc-open:hover {
+ border-color: var(--primary-color);
+}
+
+.custom-doc-name {
+ font-size: 0.85rem;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.custom-doc-progress {
+ font-size: 0.7rem;
+ color: var(--sub-color);
+ font-family: 'JetBrains Mono', monospace;
+}
+
+.custom-doc-remove {
+ flex: 0 0 auto;
+ width: 2rem;
+ background: none;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ color: var(--sub-color);
+ font-size: 1.1rem;
+ cursor: pointer;
+}
+
+.custom-doc-remove:hover {
+ color: var(--error-color);
+ border-color: color-mix(in srgb, var(--error-color) 45%, transparent);
+}