From 59ed6a5951f78d56183ee7c9e14300666704370a Mon Sep 17 00:00:00 2001 From: tonym Date: Mon, 19 Oct 2026 00:21:58 -0500 Subject: [PATCH] feat(otm): shared OTM login look, ported verbatim (0.4.2) Port OTM_LOGIN_CSS + the otm-login markup structure from @otm/account-panel otm-login-core.ts 0.44.0 verbatim into login_page() (brand "13ft" / "Read the article without the cruft.", accent --otm-accent:#b5562a copper, otherwise the module's neutral ink defaults). Still OTM-only (no "Other ways to sign in"), fail-closed 503 copy + reasons + tides beacon unchanged. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_014LCQmFvKshyAJsE8C3B5ET --- CHANGELOG.md | 4 ++++ app/otm.py | 63 ++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 184f4a6..e65ffac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog — 13ft (OTM fork) +## 0.4.2 — 2026-10-19 + +- Sign-in page now wears the shared otm-login module's actual CSS/markup (ported verbatim from `@otm/account-panel` otm-login-core.ts 0.44.0), not a one-off lookalike — same brand/card/button structure every other OTM app uses, accent set to copper (`--otm-accent:#b5562a`) like the platform. Same OTM-only behavior: no "Other ways to sign in", fail-closed "isn't wired to OTM yet" copy unchanged. + ## 0.4.1 — 2026-10-18 - Sign-in page restyled onto the platform's shared OTM sign-in standard: brand (name + tagline) above one centered card, the "Sign in with OTM" button full-width and primary, a muted Face ID/Touch ID/passkey line underneath. 13ft has no local accounts, so there's no "Other ways to sign in" step — the fail-closed "isn't wired to OTM yet" copy still shows when unconfigured. diff --git a/app/otm.py b/app/otm.py index 8f02d85..e38c70a 100644 --- a/app/otm.py +++ b/app/otm.py @@ -32,7 +32,7 @@ from typing import Any, Optional import requests -APP_VERSION = "0.4.1" +APP_VERSION = "0.4.2" OTM_AUDIENCE = "app:13ft" OTM_BASE = os.environ.get("OTM_SSO_AUTHORIZE_BASE", "https://admin.poweredbyotm.com") SESSION_COOKIE = "thirteen_session" @@ -243,34 +243,59 @@ SSO_REASONS = { } +# Ported from @otm/account-panel otm-login-core.ts 0.44.0 (OTM_LOGIN_CSS) — +# keep in sync, verbatim. 13ft is OTM-only (no local accounts), so only the +# .otm-login rules are needed — no .otm-menu/.otm-badge here. +OTM_LOGIN_CSS = """ +.otm-login{--otm-accent:#b5562a;--otm-accent-ink:#fff;--otm-bg:#faf8f5;--otm-card:#fff;--otm-fg:#1f1b18;--otm-muted:#6b6560;--otm-line:rgba(31,27,24,.12);--otm-radius:20px;--otm-font-display:inherit; + min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px 16px;background:var(--otm-bg);color:var(--otm-fg);box-sizing:border-box} +@media (prefers-color-scheme:dark){.otm-login{--otm-bg:#161412;--otm-card:#211e1b;--otm-fg:#f3efe9;--otm-muted:#a39b92;--otm-line:rgba(243,239,233,.12)}} +.otm-login *{box-sizing:border-box} +.otm-login__col{width:100%;max-width:26rem} +.otm-login__brand{text-align:center;margin-bottom:24px} +.otm-login__mark{width:56px;height:56px;border-radius:999px;background:var(--otm-accent);color:var(--otm-accent-ink);display:inline-flex;align-items:center;justify-content:center;font-weight:700;font-size:20px;letter-spacing:.02em;margin-bottom:12px} +.otm-login__mark svg,.otm-login__mark img{width:28px;height:28px} +.otm-login__name{font-family:var(--otm-font-display);font-size:30px;line-height:1.1;letter-spacing:-.01em;font-weight:600;margin:0} +.otm-login__tagline{color:var(--otm-muted);font-size:14px;margin:6px 0 0} +.otm-login__card{background:var(--otm-card);border:1px solid var(--otm-line);border-radius:var(--otm-radius);padding:24px;box-shadow:0 4px 24px rgba(0,0,0,.06)} +.otm-login__btn{display:flex;align-items:center;justify-content:center;width:100%;height:44px;border-radius:12px;border:0;background:var(--otm-accent);color:var(--otm-accent-ink);font:inherit;font-weight:600;font-size:15px;text-decoration:none;cursor:pointer} +.otm-login__btn:hover{filter:brightness(1.06)} +.otm-login__btn--outline{background:transparent;color:var(--otm-fg);border:1px solid var(--otm-line)} +.otm-login__btn--outline:hover{background:rgba(127,127,127,.06);filter:none} +.otm-login__btn[disabled]{opacity:.55;cursor:default} +.otm-login__helper{color:var(--otm-muted);font-size:14px;text-align:center;margin:12px 0 0;line-height:1.45} +.otm-login__error{color:#b3556b;font-size:14px;margin:0 0 12px;text-align:center} +.otm-login__link{display:inline-block;background:none;border:0;padding:0;font:inherit;font-size:14px;color:var(--otm-muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer} +.otm-login__link:hover{color:var(--otm-fg)} +.otm-login__foot{text-align:center;margin-top:16px} +.otm-login__stack>*+*{margin-top:10px} +""" + + def login_page(next_path: str, reason: Optional[str], configured: bool) -> str: msg = "" if reason: - msg = f'

{html_mod.escape(SSO_REASONS.get(reason, "Sign-in failed."))}

' + msg = f'' if not configured: - msg = '

This 13ft isn\'t wired to OTM yet, so nobody can sign in. (Fails closed on purpose — it\'s a fetch proxy.)

' - # OTM-only app (no local accounts) — step 2's primary button is the whole - # card, with the passkey helper line under it. There is no step 3 ("Other - # ways to sign in"): there is no other way. + msg = '' + # OTM-only app (no local accounts) — the "Sign in with OTM" button is the + # whole card, with the passkey helper line under it. There is no "Other + # ways to sign in": there is no other way. card = msg + ( - f'Sign in with OTM' - '

' - 'Your OTM account — Face ID, Touch ID or a passkey works there.

' + f'' + '' if configured else "" ) return ( '' - '13ft — sign in' + f'13ft — sign in' '' - '

13ft

Read the article without the cruft.

' - f'
{card}
' + '' f'{tides_beacon()}' )