/* ---- themes ---------------------------------------------------------------- Every colour is light-dark(LIGHT, DARK); the chosen appearance sets `color-scheme` on (light / dark / "light dark" = follow the OS), so a single set of tokens drives both modes. Themes are selected with a data-theme attribute on . The app sets it to "classic" by default (see DEFAULT_THEME in ui.js); the bare :root values below double as the Lit Room theme and the no-JS fallback. Structural tokens (radius, fonts, shadow, pills, status colours) live here once; theme blocks only override what differs. Order matters: themes come after this block so they win on equal specificity. */ :root, [data-theme="litroom"] { --radius: 18px; /* Message + composer text size. The desktop default is a touch over Mac iMessage's (we used to inherit the browser's 16px); the coarse-pointer block further down bumps it for touch and layers Dynamic Type on top. A member's picked size (theme sheet slider, lchat_text_size) is applied by shell.js as an inline override on — desktop only; touch follows the system. */ --msg-font-size: 15px; --font-display: ui-rounded, "SF Pro Rounded", "Avenir Next", -apple-system, system-ui, sans-serif; /* depth + status colours are theme-agnostic */ --shadow: light-dark(rgba(20, 30, 80, 0.10), rgba(0, 0, 0, 0.55)); --danger: light-dark(#c2410c, #f0936b); --warn: light-dark(#b4530a, #e29457); --pill-bg: light-dark(#23252e, #eef0f6); --pill-ink: light-dark(#ffffff, #1c2333); /* selected-chat row background; defaults to the hover grey, themes may override */ --row-active: var(--bubble-them); /* Lifted-panel fill shared by text inputs (chat composer, App Studio, …) — see the .eleven-input class. Derived from --panel via color-mix, so it adapts to every theme; light lifts a touch above the panel, dark blends into the band. */ --input-bg: light-dark(color-mix(in srgb, var(--panel) 50%, white 50%), transparent); /* Lit Room — warm dusk, honey glow */ --bg: light-dark(#efe3d6, #1b1418); --panel: light-dark(#fff8f0, #241a1e); --sidebar: light-dark(#fbf1e6, #1f161a); --ink: light-dark(#2a211b, #f4e9dc); --muted: light-dark(#736657, #b09a89); --border: light-dark(#ece2d4, rgba(255, 255, 255, 0.10)); --accent: light-dark(#f0a93c, #ffb23e); --accent-ink: light-dark(#3a2412, #2a1808); --link: light-dark(#956925, #ffb23e); --bubble-me: light-dark(#ffb23e, #ffb23e); --bubble-me-ink: light-dark(#3a2412, #2a1808); --bubble-them: light-dark(#f1ece4, #2f2329); } /* Classic — the original lchat blue (now with a dark mode) */ [data-theme="classic"] { --font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; --bg: light-dark(#f6f7fb, #15171c); --panel: light-dark(#ffffff, #1d2026); --sidebar: light-dark(#f8f9fc, #1a1d22); --ink: light-dark(#1c2333, #e7e9f0); --muted: light-dark(#5a6170, #9aa1b3); --border: light-dark(#e7e9f0, rgba(255, 255, 255, 0.10)); --accent: light-dark(#5666f0, #7d8bff); --accent-ink: light-dark(#ffffff, #0a0e2a); --link: light-dark(#5666f0, #7d8bff); --bubble-me: light-dark(#5666f0, #5666f0); --bubble-me-ink: light-dark(#ffffff, #ffffff); --bubble-them: light-dark(#eef0f6, #2a2e38); --row-active: light-dark(#d9dfec, #2a2e38); } /* Lagoon — cool, fresh; a coral accent that pops */ [data-theme="lagoon"] { --bg: light-dark(#d7ecea, #06201f); --panel: light-dark(#f3fbfa, #0c2a29); --sidebar: light-dark(#e7f6f4, #082523); --ink: light-dark(#123a3a, #d8efec); --muted: light-dark(#4c7271, #6fa6a3); --border: light-dark(#d2e8e6, rgba(255, 255, 255, 0.10)); --accent: light-dark(#b85840, #ff8a6a); --accent-ink: light-dark(#ffffff, #2a0f08); --link: light-dark(#b2553e, #ff8a6a); --bubble-me: light-dark(#0e827e, #16b3ad); --bubble-me-ink: light-dark(#ffffff, #042220); --bubble-them: light-dark(#ffffff, #123634); } /* Sunny — bright-side daytime */ [data-theme="sunny"] { --bg: light-dark(#ffeecb, #1c1710); --panel: light-dark(#fffaf0, #241d12); --sidebar: light-dark(#fff4df, #1f190f); --ink: light-dark(#3a2f1c, #f6ecd6); --muted: light-dark(#796d57, #b6a079); --border: light-dark(#f0e6d0, rgba(255, 255, 255, 0.10)); --accent: light-dark(#ffc62e, #ffc62e); --accent-ink: light-dark(#3a2f1c, #2a2106); --link: light-dark(#8a6b19, #ffc62e); --bubble-me: light-dark(#2c7ab0, #3aa0e8); --bubble-me-ink: light-dark(#ffffff, #04243a); --bubble-them: light-dark(#ffffff, #2e2616); } /* Linen — quiet, grown-up; warmth through restraint */ [data-theme="linen"] { --font-display: "Avenir Next", "Helvetica Neue", -apple-system, system-ui, sans-serif; --bg: light-dark(#e7e1d6, #181714); --panel: light-dark(#f5f1ea, #211f1b); --sidebar: light-dark(#ece6dc, #1c1b17); --ink: light-dark(#33302a, #ece7dd); --muted: light-dark(#69655c, #9c958a); --border: light-dark(#e2dccf, rgba(255, 255, 255, 0.10)); --accent: light-dark(#647686, #93a8ba); --accent-ink: light-dark(#ffffff, #0c1217); --link: light-dark(#5c6e7c, #93a8ba); --bubble-me: light-dark(#647686, #7d94a8); --bubble-me-ink: light-dark(#ffffff, #0c1217); --bubble-them: light-dark(#fbf9f4, #2a2823); } /* Plum — soft grape; playful but not childish, light or deep-aubergine dark */ [data-theme="plum"] { --bg: light-dark(#ece2f0, #1a1320); --panel: light-dark(#fdf8fd, #221829); --sidebar: light-dark(#f4ecf6, #1e1525); --ink: light-dark(#2f2436, #f0e6f4); --muted: light-dark(#6f6578, #ab9bb5); --border: light-dark(#e7dced, rgba(255, 255, 255, 0.10)); --accent: light-dark(#9456c9, #b57ae8); --accent-ink: light-dark(#ffffff, #20102e); --link: light-dark(#9456c9, #b57ae8); --bubble-me: light-dark(#9456c9, #b57ae8); --bubble-me-ink: light-dark(#ffffff, #20102e); --bubble-them: light-dark(#f1e8f5, #2e2238); } /* Default appearance: follow the OS. The app overrides this on per the saved preference (light / dark / system). */ html { color-scheme: light dark; } * { box-sizing: border-box; } html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; } /* The app shell must fill the viewport so the flex height chain (.tt → .thread-view → .feed flex:1) resolves and the composer pins to the bottom. Without an explicit height here, .tt's height:100% resolves against an auto-height parent, the layout collapses to content height, and the composer floats with blank space below it. */ #app { height: 100%; } /* Full-screen loading cover (see index.html) — matches the pre-painted theme bg, fades out once the app's first real paint is ready. */ .app-loading { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; align-content: center; gap: 20px; background: var(--bg); transition: opacity 0.25s ease; } .app-loading.hidden { opacity: 0; pointer-events: none; } /* The cover's EXIT class (hideLoading): opacity-only, unlike the global `.hidden` utility whose display:none !important would snap the cover off with no transition. The boot scene's exit is two quick fades — the scene bows to the bare cover (bootscene.js), then this fades the cover to the chat (.scene-out tunes the second fade's length). */ .app-loading.cover-out { opacity: 0; pointer-events: none; } .app-loading.scene-out { transition-duration: 0.35s; } /* The join page's "Plugging in…" flash between claiming and the app. */ .plugging-in { min-height: 22px; color: var(--muted); font-size: 15px; text-align: center; opacity: 0; transform: translateY(6px); transition: opacity 0.3s ease, transform 0.3s ease; } .plugging-in.show { opacity: 1; transform: none; } /* The boot scene (bootscene.js): its elements start invisible and are posed per-frame from JS (ease-in at start, the bow at the end) — so the embed and signed-out paths, which never start the scene, only ever show the plain theme bg. (Scoped to the scene's own classes: join.html shares .app-loading for its plain spinner.) The rules here are layout + type only. */ .boot-canvas, .boot-brand, .boot-spin, .boot-quote, .boot-goesto { opacity: 0; } .boot-canvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; } /* Whose space this is — the same photo + name lockup as the join card. */ .boot-brand { position: absolute; left: 0; right: 0; top: 18%; text-align: center; pointer-events: none; } .boot-avatar { position: relative; width: 88px; height: 88px; margin: 0 auto 16px; border-radius: 22px; overflow: hidden; background: linear-gradient(160deg, #6b6cf0, #4a48c8); box-shadow: 0 10px 32px rgba(0, 0, 0, 0.3); } .boot-avatar-letter { display: block; color: #fff; font-size: 40px; font-weight: 600; line-height: 88px; } .boot-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; } .boot-name { font-size: 17px; font-weight: 600; letter-spacing: 0.01em; color: var(--ink); } /* A quiet spinner dead centre — the app's own, at a zen 2.4s/rev. */ .boot-spin { position: absolute; left: 50%; top: 50%; width: 26px; height: 26px; margin: -13px 0 0 -13px; border-radius: 50%; border: 3px solid color-mix(in srgb, var(--ink) 14%, transparent); border-top-color: var(--accent); animation: spin 2.4s linear infinite; transition: opacity 0.3s ease; pointer-events: none; } @media (prefers-reduced-motion: reduce) { .boot-spin { animation: none; } } /* The quotes: what a member actually reads while the green drifts. A quiet serif, so they read as words from somewhere else, not UI. */ .boot-quote { position: absolute; left: 0; right: 0; top: 66%; padding: 0 28px; text-align: center; pointer-events: none; } .boot-quote-text { font-family: Georgia, "Iowan Old Style", "Times New Roman", serif; font-style: italic; font-size: 21px; line-height: 1.55; color: var(--ink); max-width: 38ch; margin: 0 auto 12px; text-wrap: balance; } .boot-quote-attr { font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--muted); } /* The one clickable thing on the cover — where all this came from. */ .boot-goesto { position: absolute; left: 0; right: 0; bottom: 16px; text-align: center; font-size: 12px; letter-spacing: 0.04em; color: var(--muted); text-decoration: none; } .boot-goesto b { font-weight: 600; color: var(--accent); } .boot-goesto:hover { color: var(--ink); } /* The join page's card yields entirely to the full-screen loader while a claim is in flight (see showLoader/showCard in join.js). */ .card.hidden { display: none; } .spinner { width: 32px; height: 32px; border-radius: 50%; border: 3px solid color-mix(in srgb, var(--ink) 15%, transparent); border-top-color: var(--accent); animation: spin 0.7s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .spinner { animation: none; opacity: 0.5; } } button { font: inherit; cursor: var(--ui-cursor, pointer); border: none; border-radius: 999px; padding: 10px 18px; background: var(--accent); color: var(--accent-ink); font-weight: 600; white-space: nowrap; line-height: 1.2; } button.secondary { background: var(--bubble-them); color: var(--ink); } button:disabled { opacity: 0.5; cursor: default; } input, textarea { font: inherit; border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; background: var(--panel); color: var(--ink); width: 100%; caret-color: var(--accent); /* the typing cursor picks up the accent (à la iMessage) */ } input:focus, textarea:focus { outline: 2px solid var(--accent); border-color: transparent; } /* Plain