srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/adSlot.js
AgeCommit message (Collapse)AuthorFilesLines
2026-03-05Put the header back in its corners, and give sound a controlsrdusr1-2/+6
The banner. It was a full-width bar with its own background and border, which read as a second layer of chrome stretching past the content on both sides, and because it reserved height at the top of the page it pushed the wordmark out of the top-left corner and the icon rail down out of the top-right. It is just the unit now, centred, sharing that line with both of them. It is also thinner: a 468x60 full banner rather than a 728x90 leaderboard, and 320x50 rather than 320x100 on a phone. Both are standard units. At 90px the banner was deeper than the icon rail beside it, so it decided how far down the whole page began; at 60px it is shorter than the rail and the content offset is the rail's, not the banner's. The page below is unaffected by whether an advert is there at all. Below 980px the wordmark, the unit and the rail no longer fit on one line, so the banner drops beneath them and the content clears both. The mark. It was five shapes in a 32 pixel square: a chevron, an offset ghost copy of that chevron, a full-width baseline, and a large filled block. That is a drawing, not a mark, and the parts crowded each other so none of them read. It is two shapes now, a chevron and the underscore cursor after it, which is a shell prompt and the gesture anyone who has used a terminal recognises. The cursor blink faded to 0.15 opacity over an uneven 45/55 split, so it ghosted rather than blinked. A terminal cursor is a hard square wave: fully on, fully off, 530ms each way. Only the menu's title blinks now, because something blinking in the corner of every screen is a distraction. The menu title sat 16px right of the buttons underneath it. Centring the mark and the name together as one box put the name off-centre, and the absolutely-positioned mark overflowed the menu's padding, which moved the box itself. It is a three column grid now, the third empty and the same width as the first, so the wordmark is in the exact centre by construction with the mark to its left. Measured 0px offset at six widths. The corner wordmark is back on the menu. Leaving it off to avoid showing the name twice made it look like it had gone missing, and it is the way back to the menu from everywhere else, so it should be in the same place on every screen. Sound. The engine worked: one oscillator per keystroke, verified by counting them. It was off by default and its only control was a line inside the settings dialog, so the usual way to find it was to be told it existed. It is a speaker in the top rail now, cycling off, click and mechanical, and it defaults to mechanical. A browser will not start an audio context before a user gesture and the first keystroke is one, so the first tone plays with the first character: confirmed with a fresh profile under the default autoplay policy, context running, six tones for six keystrokes. The copies in the two settings panels are gone. One setting, one control. 3 browser tests pass, all 21 screen and viewport combinations clean, and the margin rails still appear only above 1424px.
2026-02-28Sell merchandise, put the sprites on the track, and add the page marginssrdusr1-0/+30
Merchandise. Shirts, mugs and deskmats, sold through the same hosted checkout. Physical goods differ from cosmetics in three ways the schema had to carry: they have a size to choose, they need an address, and payment unlocks nothing. What a paid order produces is an obligation to pack and post something. The address is collected by Stripe on its own page and arrives here on the webhook, so no postal detail is ever entered on this site. Postage is a separate line item rather than folded into the price. A size is checked against the sizes the item actually comes in, on the server as well as in the browser, so a request naming anything else is refused rather than quietly posted as a medium. /api/admin/orders lists what has been paid for and not yet sent, and the Contribute screen shows an administrator the same list with the address and a button to mark each one posted. Without that the list of what is owed lives only in the processor's dashboard. Shipping is limited to 21 countries, which is a list of places somebody is willing to post to, not a technical limit. The race view. The sprite sat still at the left of a 6px bar, which made the one thing a player owns and can see the least visible part of the race. It rides the track now, moving with progress, on a dashed line that reads as road ahead with the trail behind it. The field is 1040px wide rather than 680, the sprites are 30px rather than 18, and each row carries its own percentage as well as its speed. Advertising. Two 160x600 rails in the page margins, shown only above 1424px, which is the width at which they fit beside the widest content column without crowding it. Below that they do not exist. They follow the same rules as the banner: never while typing, never for a supporter. --border-color was used in five places and defined in none. An undefined custom property invalidates the whole declaration at computed-value time, so every one of those borders fell back to currentColor: bundle cards, merch cards, the size buttons and the settings checkbox all had borders that were either invisible or faint text-coloured lines. It is defined now, derived from each theme's neutral so it tracks the palette. .settings-hint caps itself at 340px, which is right under a heading and wrong for a line introducing a full-width grid. Verified against a real database: the catalogue endpoint, a shirt refused without a size, a shirt refused with a size it does not come in, a valid size and a no-variant mug both reaching checkout, 404 on an unknown item, 401 unauthenticated, and 401 on the admin orders list without the role. 24 Rust tests, 3 browser tests, and all 21 screen and viewport combinations clean.
2026-01-14Make every passage long enough to be worth timing, and credit all of themsrdusr1-2/+51
The dataset's median passage was 69 characters, about twelve words, which is over in twelve seconds at an ordinary speed. Some packs were worse: shell had a median of 46 and a shortest entry of 17. That is the reason packs felt small. It was not the number of entries, which was 15 to 37 per pack, but the length of each one. Multiplayer had already hit this and worked around it: load_race_texts filters to 120 characters or more. That filter left only 61 of 350 passages eligible, 38 of them from two packs, so races repeated constantly and four packs could never come up at all. Both halves are fixed. Content: 104 longer passages added across every pack. The command packs get whole pipelines rather than single flags, which is how the tools are actually used and what the explanations were always for. Prose packs get passages that run 150 to 320 characters. Selection: a floor of 120 characters. Prose packs draw from their long entries where a pack holds at least five, so no pack is reduced to the same few passages. The command packs chain consecutive entries into one drill, which is the natural shape for them, and the explanations are collected so each line is still described. Measured over 400 draws per pack, every category now runs a median of 146 to 218 characters with a shortest draw of 120. The race pool went from 61 of 350 to 130 of 454. Attribution: 24 passages had none and displayed nothing at all under the text. They now say Unknown, which is the honest answer for a fact written for the pack, and the pangram is credited as one. The results and typing screens both fall back to Unknown rather than rendering an empty line, and the pack name is shown beside the source. Multiplayer results, three fixes: - PB never appeared. A race has no mode of its own, so modeKey was undefined, so recordResult never ran. Races share one key, because the passage is whatever the server dealt and a per-passage best would never be beaten. - Play Again started a solo test. A race carries standings and a solo run does not, so the results screen can tell them apart and now queues for another race. The end screen's own cleanup leaves the old room first. - CONSISTENCY was the longest label on the screen and made the accuracy column wider than the WPM column opposite it. It reads CON. RaceText carries the pack name so the results can show it for a race the same way single player does. 24 Rust tests and 3 browser tests pass.
2025-12-25Unify the control styling, add racer sprites, fill the store, reserve ad spacesrdusr1-0/+60
Design tokens - The stylesheet had five corner radii in use with no rule for which applied where, so a button was square while the input beside it was rounded and the icon button next to that was something else again. Two tokens now: --radius for controls, --radius-panel for the surfaces they sit on. - One border weight. Buttons were 2px and inputs 1px, which made a field and its own button read as different weights of the same idea. The two start buttons keep 2px, where it is doing work. - Letter-spacing was 1px, 2px, 3px and 4px with no rule behind it. Now 1px for small caps labels and 2px for button text. - Removed the .ghost modifier. After the hierarchy pass it rendered identically to .quiet, so the two were one control under two names. Its uses were toggles, which .quiet plus .active already expresses. Racer sprites - Six sprites in the same angular language as the rest of the icon set, one per racer in the lobby and the race. A race reads as characters moving rather than as coloured lines with names attached. Assigned from the server's player order, so every client draws the same person as the same character without needing to agree on anything. Store - Six items to twenty-six, across three slots. Race sprites join carets and flair as a third slot, with the equipped sprite stored per user. Nothing is required to race: an unequipped player keeps the sprite their position in the room assigns. Advertising space - Reserved, empty, and loading nothing. The slot exists so that adding a network later cannot push the page around when the tag loads. Shown to signed-out visitors and to accounts without the supporter flag, on the end screen only: interrupting somebody mid-test is the one placement that would cost more than it earns. - No ad code is included, and none can be added without three deliberate changes recorded in adSlot.js: the CSP forbids third-party script, frame and image sources; the privacy page states there is no advertising and no third-party script; and an ad network brings consent requirements that this app has no mechanism for. Verified after this change that the only external request the page makes is still fonts.googleapis.com.