# iOS Safari Userscripts — cs.rin.ru [English](#english) | [Deutsch](#deutsch) --- ## English A single userscript that rebuilds the phpBB2 pages of `cs.rin.ru` into a mobile layout for Safari on iOS. It replaces the original table layout entirely: the existing page content is hidden and a freshly built DOM is appended to ``. `rin-mobile.user.js` matches `*://cs.rin.ru/forum/*` and picks its view from the page type: | Page | View | |---|---| | `viewforum.php`, `search.php`, `index.php`, forum root | Topic and forum list as cards | | `viewtopic.php` | Thread as a post stream with a fixed bottom navigation bar | | `ucp.php?mode=login` | Login form, restyled in place; on the same URL after a successful submit, the resulting information page instead | | anything else | Untouched — no styles are injected | A `viewforum.php` page is not necessarily a list of topics: a category page lists forums instead, and a forum page can carry both. Each table row is therefore classified by its first usable link. Forum rows become cards with name, description, subforum links and counts; topic rows keep their existing card; category headers become section headings. Rows are recognised by phpBB's own `forumlink` / `forumdesc` classes, and failing that by their shape — two purely numeric columns are only ever a forum's topic and post count. Forums, global announcements, announcements, stickies and topics all end up in the same stream of cards, which loses the separation the original page draws between them. A header strip naming the section and its card count is therefore inserted wherever the kind changes, and forum cards additionally carry a coloured left edge. The label is taken from the board's own separator row (`Global Announcements`, `Stickies`, `Topics`), so sections this script does not know are named correctly too. Which kind a topic row is comes from the icon in its first column, not from its title. The strip is left out directly below a category heading, which already introduces the forums under it. Tapping a header collapses its section. Global announcements, announcements and stickies start collapsed: they precede the topics on *every* page of a forum and otherwise fill the first screen. The state is stored per kind in `localStorage`, so collapsing stickies once applies to every forum. `localStorage` is used rather than `GM_setValue` because it needs no `@grant` and behaves the same in Tampermonkey and in the iOS app; the trade-off is that Safari may clear script-writable storage after long inactivity, which resets the sections to their default. Both views share the same fixed navigation bar at the bottom: first / previous / next / last and a page indicator. Tapping the indicator opens a sheet with a number field and a grid of all pages. Targets are computed as `start = (page - 1) × per_page` rather than taken from the rendered page links, because phpBB only ever shows a handful of them. `per_page` is read from the `jumpto()` function the forum embeds in the page; without it, only first page and the original previous/next links remain available. In the list the bar is built only where there is more than one page — forum pages run to hundreds of them, category pages to one. Both views carry a fixed breadcrumb bar at the top, taken from the forum's own `Board index » … » Main Forum` trail. Without it there is no way back out of a thread, because the original page is hidden and phpBB links the parent forum nowhere else. The bar scrolls horizontally if the trail is too long for the screen, and is left out entirely on pages that do not carry a trail. The breadcrumb bar also carries a login pill, built on every page the script touches, not only the login form itself. Session state is read from the forum's own menu bar: a `mode=login` link means logged out, a `mode=logout` link means logged in, and the pill switches between a "Login" link and the account name accordingly. The name is parsed from the link text with a `Logout [ Name ]` pattern, confirmed against a real logged-in session's markup; if it ever fails to match — a different language, a different bracket style — the pill still shows "Logout", just without a name, rather than breaking. Once a session exists, the login pill also carries a magnifier button directly to its left, so it does not compete with the breadcrumb trail for space and only shows up where cs.rin.ru's own search is actually reachable. Outside a thread it links straight to `search.php`; inside one it instead reveals a second bar under the crumbs with a search field, reusing the original "Search this topic…" form in place rather than rebuilding it, so the hidden topic-id and scope fields stay intact. The login form itself is not rebuilt from scratch: the original `
` element is moved into the new layout and restyled, table rows collapsed to a vertical stack. Reusing the element rather than reconstructing it keeps the hidden `sid` and `redirect` fields, the `autologin` / `viewonline` checkboxes and the plain server-side POST intact — the script never talks to the forum's session handling directly, it only changes how the existing form looks. Submitting the form re-renders the same URL with a generic "Information" template instead of the form — a short confirmation ("You have been successfully logged in.") before phpBB's own `` moves on to the forum index. The script recognises this template by its shape (a single-cell header followed by a message cell), not by its wording, and restyles it the same way as the form: the original cell — message and link included — is reused in place rather than rebuilt. `search.php` gets its own treatment when it shows the query form rather than results. It is recognised by its forum picker — a `select[name="fid[]"]` multi-select that only exists on this form, not on the small "Search this forum…" / "Search this topic…" widgets embedded elsewhere — and then moved into the new layout the same way the login form is: table rows collapsed to a vertical stack, radio groups and selects restyled for touch, and the quick links above it ("View new posts", "View your posts", …) turned into a row of chips. "Search in forums" defaults to Main Forum and "Search subforums" defaults to No, overriding the server's own blank/Yes defaults, since that is the scope most searches from the mobile view actually want. A submitted search lands back on `search.php`, now with results — which, in the "Posts" display mode the in-thread search bar produces, look nothing like a topic list: each hit is a post excerpt with its own author, timestamp, and forum/topic context. Recognised by a `p.topictitle` marker unique to this mode, these get their own cards, built before the regular list view's row scanning would otherwise misread the topic and profile links inside them. The "Topics" display mode is not handled specially — no fixture shows what it looks like, so it still falls through to the regular list view, which may or may not render it sensibly. The script runs at `document-start`, injects a mobile viewport meta tag, and carries a `DEBUG` flag. While `DEBUG` is `true`, a green diagnostic overlay is prepended to the page showing which view was chosen and how many rows were converted. Set it to `false` before daily use. ### Installation on iOS 1. Install a userscript manager for Safari on iOS (for example *Userscripts*) and enable it under *Settings → Safari → Extensions*. 2. Add `rin-mobile.user.js` to the manager's script directory. 3. Grant the extension permission for `cs.rin.ru`. --- ### Development The script is developed on Windows in Chrome or Edge with Tampermonkey. `dev/rin-mobile.dev.user.js` is a loader: it contains nothing but a metadata block that pulls in the real script from disk via `@require`. Editing `rin-mobile.user.js` and reloading the page is enough — no reinstall in Tampermonkey. #### ⚠️ Path adjustment required The loader in `dev/` references the script by **absolute path**: ```js // @require file:///C:/Projekte/ios_safari_userscripts/rin-mobile.user.js ``` This path is hardcoded and only valid on the machine it was written for. After cloning the repository or moving the project folder, adjust the `@require` line in `dev/rin-mobile.dev.user.js` to your own location. Use forward slashes and the `file:///` prefix, including the drive letter — `file:///D:/projects/ios_safari_userscripts/rin-mobile.user.js`. A `@require` that cannot be resolved causes Tampermonkey to skip the script **entirely and without an error message on the page**, which looks exactly like the script not being installed at all. #### Tampermonkey setup Three settings are required; missing the first one is the most common cause of "nothing happens": 1. `chrome://extensions` → Tampermonkey → enable **Allow user scripts**. On older Chrome versions, enable **Developer mode** at the top right instead. Without this, Manifest V3 blocks all injection. 2. Same page → enable **Allow access to file URLs**, so `@require file://` can be resolved. 3. Tampermonkey dashboard → *Settings* → *Externals* → set the update interval to **always**, otherwise the loader keeps serving a cached copy of the script. Then open the loader once via `file:///.../dev/rin-mobile.dev.user.js` — Tampermonkey intercepts `.user.js` URLs and offers to install it. Do not run the loader and the plain script side by side. If both are enabled in Tampermonkey, the code executes twice on the same page, producing duplicate cards and two debug overlays. #### Test fixtures `cs.rin.ru` requires a login, so save the pages you need once and iterate against local copies: 1. Open the page while logged in, then in DevTools right-click `` → *Copy outerHTML*. 2. Save it as `dev/viewtopic.php.html` or `dev/viewforum.php.html`. 3. Serve the folder: `python -m http.server 8777 --directory .` The file name matters twice over. The `.html` extension makes the server send the file as HTML instead of offering it as a download, and the retained `viewtopic.php` / `viewforum.php` part is what the loader's `@include` rules and the page-type check in `rin-mobile.user.js` match against. Forum images and stylesheets are missing from such a snapshot. That is fine for layout work, because the script rebuilds the DOM anyway — only avatars stay empty, since their `src` is carried over unchanged. Note that a snapshot saved while logged out contains `span.link_removed` placeholders instead of links, and may use different markup for author names than the logged-in view. #### Device emulation DevTools → *Device Toolbar* (`Ctrl+Shift+M`) → *Edit…* → add a custom device with **430 × 932** and DPR **3** for an iPhone 15 Pro Max, device type *Mobile*, and an iOS Safari user agent string. #### What desktop emulation cannot show Chrome uses Blink, iOS Safari uses WebKit. These points have to be verified on the device: - `env(safe-area-inset-bottom)` in `.rm-nav` — always `0` in the emulator, so the clearance for the home indicator is untested. - A bottom `position: fixed` bar together with Safari's collapsing toolbar. - `width: 100vw` — on the desktop the scrollbar counts towards it and creates phantom overflow, so the debug overlay reports different numbers than iOS. - `-webkit-text-size-adjust` and Safari's automatic font scaling. - `-webkit-backdrop-filter` renders in Chrome, but looks different. For higher fidelity without an iPhone, Playwright ships a real WebKit build for Windows (`npx playwright install webkit`). #### Troubleshooting Install `dev/tm-test.user.js` — it injects a red bar and nothing else. If the bar does not appear, the problem is Tampermonkey (see setup above), not the script code. If it does appear, check the loader's *Externals* tab in the Tampermonkey dashboard: an empty or failed entry means the `@require` path is wrong. If Tampermonkey keeps serving a stale copy despite the update interval, replace the `file://` require with a local HTTP server, which does not cache: ```js // @require http://localhost:8777/rin-mobile.user.js ``` ### Repository layout ``` rin-mobile.user.js topic list → cards, thread → post stream dev/ rin-mobile.dev.user.js loader, @require with absolute path ``` --- ## Deutsch Ein Userscript, das die phpBB2-Seiten von `cs.rin.ru` für Safari auf iOS in eine mobile Ansicht umbaut. Es ersetzt das ursprüngliche Tabellenlayout vollständig: der vorhandene Seiteninhalt wird ausgeblendet und ein neu gebautes DOM an `` gehängt. `rin-mobile.user.js` greift auf `*://cs.rin.ru/forum/*` und wählt die Ansicht anhand des Seitentyps: | Seite | Ansicht | |---|---| | `viewforum.php`, `search.php`, `index.php`, Forumwurzel | Themen- und Forenliste als Karten | | `viewtopic.php` | Thread als Beitragsstrom mit fixierter Navigationsleiste | | `ucp.php?mode=login` | Login-Formular, an Ort und Stelle umgestylt; auf derselben URL nach erfolgreichem Absenden stattdessen die Informationsseite | | alles andere | Unverändert — es werden keine Stile injiziert | Eine `viewforum.php`-Seite ist nicht zwangsläufig eine Themenliste: eine Kategorieseite listet Foren auf, eine Forenseite kann beides enthalten. Jede Tabellenzeile wird deshalb anhand ihres ersten verwertbaren Links eingeordnet. Forenzeilen werden zu Karten mit Name, Beschreibung, Unterforen und Zahlen, Themenzeilen behalten ihre bisherige Karte, Kategoriezeilen werden zu Zwischenüberschriften. Erkannt werden die Zeilen an phpBBs eigenen Klassen `forumlink` / `forumdesc` und ersatzweise an ihrer Form — zwei rein numerische Spalten hat nur eine Forenzeile mit Themen- und Beitragszahl. Foren, globale Ankündigungen, Ankündigungen, Stickies und Themen landen alle im selben Kartenstrom, womit die Trennung verlorengeht, die die Originalseite zwischen ihnen zieht. Bei jedem Wechsel wird deshalb ein Gruppenkopf mit dem Namen des Abschnitts und der Zahl seiner Karten eingezogen; Forenkarten tragen zusätzlich eine farbige linke Kante. Die Beschriftung stammt aus der Trennzeile der Seite selbst (`Global Announcements`, `Stickies`, `Topics`), damit auch Abschnitte richtig heißen, die dieses Script nicht kennt. Welcher Art eine Themenzeile ist, verrät das Symbol ihrer ersten Spalte, nicht der Titel. Direkt unter einer Kategorieüberschrift entfällt der Gruppenkopf, weil die Überschrift die Foren darunter schon einleitet. Ein Tipp auf den Kopf klappt seinen Abschnitt zu. Globale Ankündigungen, Ankündigungen und Stickies starten zugeklappt: sie stehen auf *jeder* Seite eines Forums vor den Themen und füllen sonst den ersten Bildschirm. Der Zustand liegt je Art in `localStorage`, einmal zugeklappte Stickies gelten also in jedem Forum. `localStorage` statt `GM_setValue`, weil es ohne `@grant` auskommt und sich in Tampermonkey wie in der iOS-App gleich verhält; dafür kann Safari schreibbaren Speicher nach längerer Nichtnutzung räumen, dann stehen die Abschnitte wieder auf ihrer Voreinstellung. Beide Ansichten teilen sich dieselbe fixierte Navigationsleiste am unteren Rand: erste / vorherige / nächste / letzte Seite und eine Positionsanzeige. Ein Tipp darauf öffnet ein Sheet mit Zahlenfeld und einem Raster aller Seiten. Die Ziele werden als `start = (Seite - 1) × per_page` berechnet und nicht aus den gerenderten Seitenlinks übernommen, weil phpBB davon immer nur eine Handvoll anzeigt. `per_page` stammt aus der `jumpto()`-Funktion, die das Forum in die Seite einbettet; fehlt sie, bleiben nur die erste Seite und die ursprünglichen Zurück-/Weiter-Links nutzbar. In der Liste entsteht die Leiste nur, wo es mehr als eine Seite gibt — Forenseiten haben hunderte, Kategorieseiten eine. Beide Ansichten haben oben eine fixierte Brotkrumenleiste, übernommen aus dem `Board index » … » Main Forum`-Pfad des Forums. Ohne sie führt aus einem Thread kein Weg zurück, weil die Originalseite ausgeblendet ist und phpBB das Elternforum nirgends sonst verlinkt. Die Leiste lässt sich waagerecht schieben, wenn der Pfad breiter ist als der Bildschirm, und entfällt auf Seiten ohne Pfadangabe. Die Brotkrumenleiste trägt außerdem eine Login-Pille, gebaut auf jeder Seite, die das Script anfasst — nicht nur auf dem Login-Formular selbst. Der Sitzungszustand wird aus dem Menübalken der Originalseite gelesen: ein `mode=login`-Link bedeutet ausgeloggt, ein `mode=logout`-Link eingeloggt, die Pille wechselt entsprechend zwischen einem „Login"-Link und dem Kontonamen. Der Name wird über das Muster `Logout [ Name ]` aus dem Linktext gelesen, das gegen das Markup einer echten eingeloggten Sitzung bestätigt ist; greift es einmal nicht — andere Sprache, andere Klammerform — bleibt die Pille bei „Logout" stehen, nur ohne Namen, statt zu reißen. Besteht eine Sitzung, trägt die Login-Pille zusätzlich einen Lupen-Button direkt links neben sich, damit er nicht mit der Brotkrumenleiste um Platz konkurriert, und erscheint nur dort, wo cs.rins eigene Suche überhaupt erreichbar ist. Außerhalb eines Threads führt er direkt zu `search.php`; innerhalb eines Threads blendet er stattdessen eine zweite Leiste unter den Brotkrumen mit einem Suchfeld ein — das ist das Original-Formular „Search this topic…", unverändert an Ort und Stelle wiederverwendet statt neu gebaut, damit die versteckten Felder für Themen-Id und Suchbereich erhalten bleiben. Das Login-Formular selbst wird nicht neu gebaut: das Original-``-Element wandert unverändert in das neue Layout und wird umgestylt, Tabellenzeilen werden zu einer senkrechten Abfolge zusammengeklappt. Die Wiederverwendung statt Neubau erhält die versteckten Felder `sid` und `redirect`, die Checkboxen `autologin` / `viewonline` und den schlichten serverseitigen POST — das Script spricht nie selbst mit der Sitzungsverwaltung des Forums, es ändert nur, wie das vorhandene Formular aussieht. Nach dem Absenden liefert dieselbe URL statt des Formulars eine allgemeine „Information"-Vorlage aus — eine kurze Bestätigung („You have been successfully logged in."), bevor phpBBs eigenes `` zum Forenindex weiterleitet. Das Script erkennt diese Vorlage an ihrer Form, nicht am Wortlaut (eine Kopfzeile mit einer einzelnen Zelle, gefolgt von einer Nachrichtenzelle), und stylt sie wie das Formular um: die Original-Zelle — Nachricht und Link inklusive — wandert unverändert an ihren Platz statt neu gebaut zu werden. `search.php` bekommt eine eigene Behandlung, wenn es das Suchformular statt Treffern zeigt. Erkannt wird das am Forenwähler — einem Mehrfachauswahlfeld `select[name="fid[]"]`, das es nur in diesem Formular gibt, nicht in den kleinen „Search this forum…" / „Search this topic…"-Widgets an anderer Stelle — und dann wie das Login-Formular in das neue Layout verschoben: Tabellenzeilen zu einer senkrechten Abfolge zusammengeklappt, Radio-Gruppen und Auswahlfelder für Touch umgestylt, die Schnellzugriffe darüber („View new posts", „View your posts", …) zu einer Reihe aus Chips. „Search in forums" ist dabei standardmäßig auf Main Forum vorausgewählt, „Search subforums" auf No — statt der leeren/„Yes"-Vorgabe des Servers, weil das dem Suchumfang entspricht, der von der mobilen Ansicht aus meistens gewollt ist. Eine abgeschickte Suche landet wieder auf `search.php`, diesmal mit Treffern — die im Anzeigemodus „Posts", den die Suchleiste im Thread erzeugt, aber überhaupt nicht wie eine Themenliste aussehen: jeder Treffer ist ein Beitragsauszug mit eigenem Autor, Zeitstempel und Forum-/Thema-Kontext. Erkannt an einem Merkmal (`p.topictitle`), das es nur in diesem Modus gibt, bekommen sie eigene Karten, gebaut bevor das Zeileneinsammeln der gewöhnlichen Listenansicht die enthaltenen Themen- und Profillinks falsch deuten würde. Der Anzeigemodus „Topics" bekommt keine eigene Behandlung — keine Fixture zeigt, wie er aussieht, er läuft also weiterhin durch die gewöhnliche Listenansicht, was passend sein kann oder auch nicht. Das Script läuft bei `document-start`, setzt ein mobiles Viewport-Meta-Tag und hat ein `DEBUG`-Flag. Solange `DEBUG` auf `true` steht, wird ein grünes Diagnose-Overlay über die Seite gelegt, das zeigt, welche Ansicht gewählt wurde und wie viele Zeilen umgewandelt wurden. Für den täglichen Gebrauch auf `false` setzen. ### Installation unter iOS 1. Einen Userscript-Manager für Safari unter iOS installieren (zum Beispiel *Userscripts*) und unter *Einstellungen → Safari → Erweiterungen* aktivieren. 2. `rin-mobile.user.js` in das Script-Verzeichnis des Managers legen. 3. Der Erweiterung die Berechtigung für `cs.rin.ru` erteilen. --- ### Entwicklung Entwickelt wird unter Windows in Chrome oder Edge mit Tampermonkey. `dev/rin-mobile.dev.user.js` ist ein Loader: er enthält nur einen Metadatenblock, der das eigentliche Script per `@require` von der Platte einbindet. Es genügt, `rin-mobile.user.js` zu bearbeiten und die Seite neu zu laden — keine Neuinstallation in Tampermonkey nötig. #### ⚠️ Pfadanpassung erforderlich Der Loader in `dev/` verweist mit einem **absoluten Pfad** auf das Script: ```js // @require file:///C:/Projekte/ios_safari_userscripts/rin-mobile.user.js ``` Dieser Pfad ist fest eingetragen und gilt nur auf dem Rechner, für den er geschrieben wurde. Nach dem Klonen des Repositories oder dem Verschieben des Projektordners muss die `@require`-Zeile in `dev/rin-mobile.dev.user.js` auf den eigenen Ablageort angepasst werden. Dabei Schrägstriche und das Präfix `file:///` inklusive Laufwerksbuchstaben verwenden — `file:///D:/projekte/ios_safari_userscripts/rin-mobile.user.js`. Ein `@require`, das nicht aufgelöst werden kann, führt dazu, dass Tampermonkey das Script **komplett und ohne Fehlermeldung auf der Seite** überspringt. Das sieht exakt so aus, als wäre gar kein Script installiert. #### Tampermonkey einrichten Drei Einstellungen sind nötig; die erste ist die mit Abstand häufigste Ursache für „es passiert überhaupt nichts": 1. `chrome://extensions` → Tampermonkey → **Nutzerskripte zulassen** aktivieren. Bei älteren Chrome-Versionen stattdessen den **Entwicklermodus** oben rechts einschalten. Ohne das blockiert Manifest V3 jede Injektion. 2. Auf derselben Seite → **Zugriff auf Datei-URLs zulassen** aktivieren, damit `@require file://` aufgelöst werden kann. 3. Tampermonkey-Dashboard → *Einstellungen* → *Externals* → Update-Intervall auf **immer** stellen, sonst liefert der Loader weiterhin eine zwischen­ gespeicherte Fassung des Scripts aus. Danach den Loader einmal über `file:///.../dev/rin-mobile.dev.user.js` öffnen — Tampermonkey fängt `.user.js`-URLs ab und bietet die Installation an. Den Loader nicht parallel zum unveränderten Script betreiben. Sind beide in Tampermonkey aktiv, läuft der Code doppelt auf derselben Seite, mit doppelten Karten und zwei Debug-Overlays als Folge. #### Geräte-Emulation DevTools → *Device Toolbar* (`Strg+Umschalt+M`) → *Edit…* → eigenes Gerät mit **430 × 932** und DPR **3** für ein iPhone 15 Pro Max anlegen, Gerätetyp *Mobile*, dazu einen iOS-Safari-User-Agent eintragen. #### Was die Desktop-Emulation nicht zeigt Chrome verwendet Blink, iOS Safari verwendet WebKit. Diese Punkte müssen am Gerät geprüft werden: - `env(safe-area-inset-bottom)` in `.rm-nav` — im Emulator immer `0`, die Freihaltung für den Home-Indicator bleibt also ungetestet. - Eine unten fixierte Leiste zusammen mit Safaris ein- und ausfahrender Toolbar. - `width: 100vw` — auf dem Desktop zählt die Scrollbar mit und erzeugt Phantom-Overflow, das Debug-Overlay meldet dort andere Zahlen als unter iOS. - `-webkit-text-size-adjust` und Safaris automatische Schriftvergrößerung. - `-webkit-backdrop-filter` rendert in Chrome, sieht aber anders aus. Für höhere Treue ohne iPhone bringt Playwright einen echten WebKit-Build für Windows mit (`npx playwright install webkit`). #### Fehlersuche `dev/tm-test.user.js` installieren — das Script blendet nur einen roten Balken ein, sonst nichts. Erscheint der Balken nicht, liegt das Problem bei Tampermonkey (siehe Einrichtung oben) und nicht am Script-Code. Erscheint er, im Tampermonkey-Dashboard den Tab *Externals* des Loaders prüfen: ein leerer oder fehlgeschlagener Eintrag bedeutet, dass der `@require`-Pfad falsch ist. Liefert Tampermonkey trotz Update-Intervall weiterhin einen veralteten Stand aus, das `file://`-Require durch einen lokalen HTTP-Server ersetzen, der nicht zwischenspeichert: ```js // @require http://localhost:8777/rin-mobile.user.js ``` ### Aufbau des Repositories ``` rin-mobile.user.js Themenliste → Karten, Thread → Beitragsstrom dev/ rin-mobile.dev.user.js Loader, @require mit absolutem Pfad ```